Skip to main content

systemprompt_models/profile/
vault.rs

1//! Profile `secrets.vault:` block — Vault / `OpenBao` KV v2 access.
2//!
3//! There is deliberately no TLS-verification escape hatch:
4//! `deny_unknown_fields` turns a `skip_verify:` key into a parse error rather
5//! than a silently downgraded connection. A private CA is configured through
6//! `ca_cert_path`.
7//!
8//! The document at `mount`/`path` holds the whole `secrets.json` shape; `keys`
9//! redirects individual entries at a different KV path or field, for identity
10//! material shared across instances.
11//!
12//! Copyright (c) systemprompt.io — Business Source License 1.1.
13//! See <https://systemprompt.io> for licensing details.
14
15use std::collections::BTreeMap;
16
17use serde::{Deserialize, Serialize};
18
19pub const DEFAULT_VAULT_TIMEOUT_SECS: u64 = 10;
20pub const DEFAULT_VAULT_RETRIES: u8 = 3;
21pub const MAX_VAULT_TIMEOUT_SECS: u64 = 120;
22pub const MAX_VAULT_RETRIES: u8 = 10;
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
25#[serde(deny_unknown_fields)]
26pub struct VaultSecretsConfig {
27    pub address: String,
28
29    #[serde(default = "default_mount")]
30    pub mount: String,
31
32    pub path: String,
33
34    #[serde(default)]
35    pub namespace: Option<String>,
36
37    pub auth: VaultAuth,
38
39    #[serde(default)]
40    pub keys: BTreeMap<String, VaultKeyRef>,
41
42    #[serde(default)]
43    pub ca_cert_path: Option<String>,
44
45    #[serde(default = "default_timeout_secs")]
46    pub timeout_secs: u64,
47
48    #[serde(default = "default_retries")]
49    pub retries: u8,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
53#[serde(deny_unknown_fields)]
54pub struct VaultKeyRef {
55    pub path: String,
56
57    pub field: String,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
61#[serde(tag = "method", rename_all = "lowercase", deny_unknown_fields)]
62pub enum VaultAuth {
63    Token {
64        #[serde(default = "default_token_env")]
65        token_env: String,
66
67        #[serde(default)]
68        token_file: Option<String>,
69    },
70
71    AppRole {
72        #[serde(default = "default_role_id_env")]
73        role_id_env: String,
74
75        #[serde(default = "default_secret_id_env")]
76        secret_id_env: String,
77
78        #[serde(default = "default_approle_mount")]
79        mount: String,
80    },
81
82    Kubernetes {
83        role: String,
84
85        #[serde(default = "default_jwt_path")]
86        jwt_path: String,
87
88        #[serde(default = "default_kubernetes_mount")]
89        mount: String,
90    },
91}
92
93impl VaultAuth {
94    #[must_use]
95    pub const fn method_name(&self) -> &'static str {
96        match self {
97            Self::Token { .. } => "token",
98            Self::AppRole { .. } => "approle",
99            Self::Kubernetes { .. } => "kubernetes",
100        }
101    }
102}
103
104fn default_mount() -> String {
105    "secret".to_owned()
106}
107
108const fn default_timeout_secs() -> u64 {
109    DEFAULT_VAULT_TIMEOUT_SECS
110}
111
112const fn default_retries() -> u8 {
113    DEFAULT_VAULT_RETRIES
114}
115
116fn default_token_env() -> String {
117    "VAULT_TOKEN".to_owned()
118}
119
120fn default_role_id_env() -> String {
121    "VAULT_ROLE_ID".to_owned()
122}
123
124fn default_secret_id_env() -> String {
125    "VAULT_SECRET_ID".to_owned()
126}
127
128fn default_approle_mount() -> String {
129    "approle".to_owned()
130}
131
132fn default_kubernetes_mount() -> String {
133    "kubernetes".to_owned()
134}
135
136fn default_jwt_path() -> String {
137    "/var/run/secrets/kubernetes.io/serviceaccount/token".to_owned()
138}