Skip to main content

systemprompt_config/bootstrap/secrets/
error.rs

1//! Errors raised while resolving, loading, validating or reloading secrets.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use systemprompt_manifest::profile::ProfileError;
7use systemprompt_models::errors::SecretsError;
8
9use super::vault::VaultError;
10use crate::bootstrap::key_material::KeyMaterialError;
11
12#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14pub enum SecretsBootstrapError {
15    #[error(
16        "Secrets not initialized. Call SecretsBootstrap::init() after ProfileBootstrap::init()"
17    )]
18    NotInitialized,
19
20    #[error("Secrets already initialized")]
21    AlreadyInitialized,
22
23    #[error("Profile not initialized. Call ProfileBootstrap::init() first")]
24    ProfileNotInitialized,
25
26    #[error("Secrets file not found: {path}")]
27    FileNotFound { path: String },
28
29    #[error("Invalid secrets file: {0}")]
30    InvalidSecretsFile(#[source] SecretsError),
31
32    #[error("Secrets document could not be encoded: {0}")]
33    DocumentEncode(#[source] serde_json::Error),
34
35    #[error("No secrets configured. Create a secrets.json file.")]
36    NoSecretsConfigured,
37
38    #[error("Invalid secrets configuration in profile: {0}")]
39    SecretsConfigInvalid(#[source] ProfileError),
40
41    #[error(
42        "secrets.source is 'vault' but the profile has no secrets.vault block. Add one or switch \
43         secrets.source to 'file' or 'env'."
44    )]
45    VaultBlockMissing,
46
47    #[error(transparent)]
48    Vault(#[from] VaultError),
49
50    #[error(
51        "OAuth at-rest pepper is required. Add 'oauth_at_rest_pepper' (>= 32 chars) to your \
52         secrets file or set OAUTH_AT_REST_PEPPER environment variable."
53    )]
54    OauthAtRestPepperRequired,
55
56    #[error(
57        "Database URL is required. Add 'database_url' to your secrets.json or set DATABASE_URL \
58         environment variable."
59    )]
60    DatabaseUrlRequired,
61
62    #[error(
63        "manifest_signing_secret_seed is required: every replica must share one seed, so it is \
64         never generated at boot. Run `systemprompt admin identity generate --json` once and \
65         distribute the value (secrets file or MANIFEST_SIGNING_SECRET_SEED)."
66    )]
67    ManifestSeedRequired,
68
69    #[error(
70        "signing_key_pem is required on cloud and deployment-host boots: every replica must \
71         sign with one key, so it is never read from a file beside the binary there. Run \
72         `systemprompt admin identity generate --json` once and distribute the value (secrets \
73         file or SIGNING_KEY_PEM)."
74    )]
75    SigningKeyPemRequired,
76
77    #[error("manifest_signing_secret_seed is invalid: {0}")]
78    ManifestSeedInvalid(#[source] KeyMaterialError),
79
80    #[error(
81        "encryption_master_key is required: it seals at-rest secrets and the gateway accounting \
82         journal. Add 64 hex characters (`openssl rand -hex 32`) as 'encryption_master_key' in \
83         secrets.json, or take the value from `systemprompt admin identity generate`. With \
84         `secrets.source: env`, also list it in SYSTEMPROMPT_CUSTOM_SECRETS."
85    )]
86    EncryptionMasterKeyRequired,
87
88    #[error(
89        "encryption_master_key is invalid ({0}): it must be 32 bytes as 64 hex characters \
90         (`openssl rand -hex 32`)"
91    )]
92    EncryptionMasterKeyInvalid(#[source] KeyMaterialError),
93
94    #[error("signing_key_pem secret is invalid: {0}")]
95    SigningKeyPemInvalid(#[source] KeyMaterialError),
96
97    #[error(
98        "secrets.source is 'external' but the profile has no secrets.external block. Add one or \
99         switch secrets.source to 'file', 'env' or 'vault'."
100    )]
101    ExternalBlockMissing,
102
103    #[error(
104        "secrets.external.kind '{kind}' is not registered in this binary (registered: {known:?})"
105    )]
106    UnknownExternalKind { kind: String, known: Vec<String> },
107
108    #[error("external secrets source '{kind}' failed: {source}")]
109    External {
110        kind: String,
111        #[source]
112        source: Box<dyn std::error::Error + Send + Sync>,
113    },
114
115    #[error(
116        "secrets reload rejected: '{key}' changed, and it is pinned for the life of the process. \
117         Restart to adopt a new value."
118    )]
119    ReloadRejected { key: String },
120}