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}