Skip to main content

zlayer_types/api/
secrets.rs

1//! Secrets management API DTOs.
2//!
3//! Wire types for the secrets endpoints. Secret values are never exposed
4//! through the API except via an explicit admin-only `?reveal=true` request —
5//! only metadata is returned for listing and retrieval.
6
7use serde::{Deserialize, Serialize};
8use utoipa::ToSchema;
9
10/// Request to create or update a secret.
11///
12/// `scope` is optional and only honored on the legacy code path — when set
13/// alongside `?environment=`, the request is rejected.
14#[derive(Debug, Deserialize, ToSchema)]
15pub struct CreateSecretRequest {
16    /// The name of the secret.
17    pub name: String,
18    /// The secret value (will be encrypted at rest).
19    pub value: String,
20    /// Optional explicit scope (legacy form). Mutually exclusive with the
21    /// `?environment=` query parameter.
22    #[serde(default)]
23    pub scope: Option<String>,
24    /// Optional per-secret node affinity. `None` (default) = any node may
25    /// host the decryptable form. When set, only matching nodes receive a
26    /// wrap of the DEK material for this row, and the API gate filters
27    /// reads accordingly. Ignored on standalone (non-clustered) daemons.
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    pub node_affinity: Option<crate::storage::NodeAffinity>,
30}
31
32/// Response containing secret metadata. Never includes the value unless
33/// the caller is on the explicit `?reveal=true` admin path, in which case
34/// `value` is populated.
35#[derive(Debug, Serialize, Deserialize, ToSchema)]
36pub struct SecretMetadataResponse {
37    /// The name/identifier of the secret.
38    pub name: String,
39    /// Unix timestamp when the secret was created.
40    pub created_at: i64,
41    /// Unix timestamp when the secret was last updated.
42    pub updated_at: i64,
43    /// Version number of the secret (incremented on each update).
44    pub version: u32,
45    /// Plaintext value — populated only on `?reveal=true` admin reads.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub value: Option<String>,
48}
49
50/// Request body for secret rotation.
51#[derive(Debug, Deserialize, ToSchema)]
52pub struct RotateSecretRequest {
53    /// The new secret value (will be encrypted at rest).
54    pub value: String,
55    /// Optional per-secret node affinity update. `None` here means "leave
56    /// existing affinity unchanged"; to clear affinity explicitly, pass an
57    /// empty selector via a separate update endpoint (Phase 2).
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub node_affinity: Option<crate::storage::NodeAffinity>,
60}
61
62/// Response returned by the rotate endpoint.
63#[derive(Debug, Serialize, Deserialize, ToSchema)]
64pub struct RotateSecretResponse {
65    /// The secret name.
66    pub name: String,
67    /// Version prior to rotation. `None` if the secret did not exist (won't
68    /// happen today — rotate rejects missing secrets — but preserved for
69    /// forward compatibility).
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub previous_version: Option<u32>,
72    /// Version after rotation.
73    pub new_version: u32,
74}
75
76/// Response for the batch reveal endpoint — returns every secret in an env as plaintext.
77/// Admin-only for now (Phase 3 will gate this on per-env Read permission instead).
78#[derive(Debug, Serialize, Deserialize, ToSchema)]
79pub struct RevealAllSecretsResponse {
80    /// The environment id the secrets were revealed from.
81    pub environment: String,
82    /// Name → plaintext value map. Includes every secret in the scope.
83    pub secrets: std::collections::HashMap<String, String>,
84}
85
86/// Result body for `POST /api/v1/secrets/bulk-import`.
87#[derive(Debug, Serialize, Deserialize, ToSchema)]
88pub struct BulkImportResponse {
89    /// Number of new secrets created.
90    pub created: usize,
91    /// Number of existing secrets updated.
92    pub updated: usize,
93    /// Per-line errors. Empty when every line parsed and stored cleanly.
94    pub errors: Vec<String>,
95}
96
97/// Query for create / list / get / delete endpoints.
98#[derive(Debug, Default, Deserialize)]
99pub struct SecretsScopeQuery {
100    /// Environment id whose namespace to operate in. Mutually exclusive
101    /// with `scope`.
102    #[serde(default)]
103    pub environment: Option<String>,
104    /// Explicit scope string (legacy). Mutually exclusive with `environment`.
105    #[serde(default)]
106    pub scope: Option<String>,
107}
108
109/// Query for `GET /api/v1/secrets/{name}` — extends the scope query with a
110/// `reveal` flag for admin-only plaintext reads.
111#[derive(Debug, Default, Deserialize)]
112pub struct GetSecretQuery {
113    /// Environment id whose namespace to read from. Mutually exclusive with `scope`.
114    #[serde(default)]
115    pub environment: Option<String>,
116    /// Explicit scope string (legacy). Mutually exclusive with `environment`.
117    #[serde(default)]
118    pub scope: Option<String>,
119    /// When true, include the plaintext value. Admin only.
120    #[serde(default)]
121    pub reveal: bool,
122}
123
124/// Query for `POST /api/v1/secrets/bulk-import` — `environment` is required.
125#[derive(Debug, Deserialize)]
126pub struct BulkImportQuery {
127    /// Environment id to import the secrets into.
128    pub environment: String,
129}