Skip to main content

vti_secrets/
config.rs

1//! Secret-store configuration.
2//!
3//! `SecretsConfig` selects which [`SeedStore`](crate::SeedStore) backend
4//! [`create_seed_store`](crate::create_seed_store) builds, plus the
5//! per-backend connection parameters. It is deserialised from the
6//! `[secrets]` table of a service's config file; `vta-service` re-exports
7//! this type so its `AppConfig` keeps a `secrets: SecretsConfig` field.
8
9use serde::{Deserialize, Serialize};
10
11/// Which seed-store backend to build, stated explicitly.
12///
13/// Mirrors `vtc_service::config::SecretBackend` and the wizard's
14/// `SecretsBackendInput` discriminator, so operators meet one vocabulary
15/// across the setup TOML, the generated `config.toml`, and the VTC.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
17#[serde(rename_all = "snake_case")]
18pub enum SecretBackend {
19    /// OS keyring (libsecret / Keychain / Credential Manager).
20    Keyring,
21    /// Hex-encoded seed inlined in `[secrets] seed` (config-seed feature).
22    ConfigSeed,
23    /// AWS Secrets Manager.
24    Aws,
25    /// GCP Secret Manager.
26    Gcp,
27    /// Azure Key Vault.
28    Azure,
29    /// HashiCorp Vault (KV v2).
30    Vault,
31    /// Kubernetes `Secret`.
32    Kubernetes,
33    /// Plaintext file under the data dir — NOT secure, dev/test only.
34    Plaintext,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
38pub struct SecretsConfig {
39    /// Explicit backend selector. When set it wins outright:
40    /// [`create_seed_store`](crate::create_seed_store) builds exactly this
41    /// backend and validates its required fields, rather than inferring the
42    /// backend from whichever selector field happens to be populated.
43    ///
44    /// Omit to keep the legacy implicit priority chain. Setting it is the
45    /// only way to reach a backend that sits *below* a compiled-in one in
46    /// that chain — `plaintext` in particular is unreachable implicitly on
47    /// any build with `keyring` compiled in (the default), because the
48    /// keyring arm matches unconditionally.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub backend: Option<SecretBackend>,
51    /// Hex-encoded BIP-32 seed (config-seed feature)
52    pub seed: Option<String>,
53    /// AWS Secrets Manager secret name (aws-secrets feature)
54    pub aws_secret_name: Option<String>,
55    /// AWS region override (aws-secrets feature)
56    pub aws_region: Option<String>,
57    /// GCP project ID (gcp-secrets feature)
58    pub gcp_project: Option<String>,
59    /// GCP secret name (gcp-secrets feature)
60    pub gcp_secret_name: Option<String>,
61    /// Azure Key Vault URL (azure-secrets feature)
62    pub azure_vault_url: Option<String>,
63    /// Azure Key Vault secret name (azure-secrets feature)
64    pub azure_secret_name: Option<String>,
65    /// OS keyring service name (keyring feature).
66    /// Change this to run multiple VTA instances on the same machine.
67    #[serde(default = "default_keyring_service")]
68    pub keyring_service: String,
69    /// HashiCorp Vault server URL (vault-secrets feature). Setting this
70    /// activates the Vault backend.
71    pub vault_addr: Option<String>,
72    /// KV v2 mount path (vault-secrets feature). Default `secret`.
73    #[serde(default = "default_vault_kv_mount")]
74    pub vault_kv_mount: String,
75    /// KV v2 secret path under the mount, e.g. `vta/master-seed`
76    /// (vault-secrets feature).
77    pub vault_secret_path: Option<String>,
78    /// Field name within the KV v2 secret that holds the hex-encoded
79    /// seed (vault-secrets feature). Default `seed`.
80    #[serde(default = "default_vault_secret_key")]
81    pub vault_secret_key: String,
82    /// Vault Enterprise namespace, if any (vault-secrets feature).
83    pub vault_namespace: Option<String>,
84    /// Auth method: `kubernetes` (default), `token`, or `approle`
85    /// (vault-secrets feature).
86    #[serde(default = "default_vault_auth_method")]
87    pub vault_auth_method: String,
88    /// Kubernetes auth role name (vault-secrets feature, kubernetes
89    /// auth method).
90    pub vault_k8s_role: Option<String>,
91    /// Kubernetes auth mount path (vault-secrets feature). Default
92    /// `kubernetes`.
93    #[serde(default = "default_vault_k8s_mount")]
94    pub vault_k8s_mount: String,
95    /// File holding the ServiceAccount JWT presented to Vault
96    /// (vault-secrets feature, kubernetes auth method). Default is the
97    /// kubelet-mounted projected volume path.
98    #[serde(default = "default_vault_k8s_jwt_path")]
99    pub vault_k8s_jwt_path: String,
100    /// Static token (vault-secrets feature, token auth method). Prefer
101    /// the `VAULT_TOKEN` env var over hard-coding here.
102    pub vault_token: Option<String>,
103    /// AppRole role_id (vault-secrets feature, approle auth method).
104    pub vault_approle_role_id: Option<String>,
105    /// AppRole secret_id (vault-secrets feature, approle auth method).
106    pub vault_approle_secret_id: Option<String>,
107    /// AppRole mount path (vault-secrets feature). Default `approle`.
108    #[serde(default = "default_vault_approle_mount")]
109    pub vault_approle_mount: String,
110    /// Skip TLS certificate verification — dev/test only
111    /// (vault-secrets feature).
112    #[serde(default)]
113    pub vault_skip_verify: bool,
114    /// Kubernetes `Secret` name holding the hex-encoded seed
115    /// (k8s-secrets feature). Setting this activates the Kubernetes
116    /// backend.
117    pub k8s_secret_name: Option<String>,
118    /// Kubernetes namespace the `Secret` lives in (k8s-secrets feature).
119    /// When unset, the in-cluster ServiceAccount namespace (or the
120    /// kubeconfig context namespace) is used, falling back to `default`.
121    pub k8s_namespace: Option<String>,
122    /// Key within the `Secret`'s `data` map that holds the hex-encoded
123    /// seed (k8s-secrets feature). Default `seed`.
124    #[serde(default = "default_k8s_secret_key")]
125    pub k8s_secret_key: String,
126    /// Opt in to the **plaintext file** seed-store fallback. Off by
127    /// default: when no secure backend (keyring / cloud / Vault /
128    /// config-seed) is compiled-in *and* configured, `create_seed_store`
129    /// errors rather than silently writing the BIP-32 master seed to a
130    /// file in clear. Set `true` only for dev/test where that is
131    /// acceptable. (P0.9 — closes the "one wrong TOML key → master seed
132    /// on disk in cleartext" footgun.)
133    #[serde(default)]
134    pub allow_plaintext: bool,
135}
136
137fn default_keyring_service() -> String {
138    "vta".to_string()
139}
140
141fn default_vault_kv_mount() -> String {
142    "secret".to_string()
143}
144
145fn default_vault_secret_key() -> String {
146    "seed".to_string()
147}
148
149fn default_vault_auth_method() -> String {
150    "kubernetes".to_string()
151}
152
153fn default_vault_k8s_mount() -> String {
154    "kubernetes".to_string()
155}
156
157fn default_vault_k8s_jwt_path() -> String {
158    "/var/run/secrets/kubernetes.io/serviceaccount/token".to_string()
159}
160
161fn default_k8s_secret_key() -> String {
162    "seed".to_string()
163}
164
165fn default_vault_approle_mount() -> String {
166    "approle".to_string()
167}
168
169impl Default for SecretsConfig {
170    fn default() -> Self {
171        Self {
172            backend: None,
173            seed: None,
174            aws_secret_name: None,
175            aws_region: None,
176            gcp_project: None,
177            gcp_secret_name: None,
178            azure_vault_url: None,
179            azure_secret_name: None,
180            keyring_service: default_keyring_service(),
181            vault_addr: None,
182            vault_kv_mount: default_vault_kv_mount(),
183            vault_secret_path: None,
184            vault_secret_key: default_vault_secret_key(),
185            vault_namespace: None,
186            vault_auth_method: default_vault_auth_method(),
187            vault_k8s_role: None,
188            vault_k8s_mount: default_vault_k8s_mount(),
189            vault_k8s_jwt_path: default_vault_k8s_jwt_path(),
190            vault_token: None,
191            vault_approle_role_id: None,
192            vault_approle_secret_id: None,
193            vault_approle_mount: default_vault_approle_mount(),
194            vault_skip_verify: false,
195            k8s_secret_name: None,
196            k8s_namespace: None,
197            k8s_secret_key: default_k8s_secret_key(),
198            allow_plaintext: false,
199        }
200    }
201}