Skip to main content

microsandbox_network/secrets/
config.rs

1//! Secret injection configuration types.
2//!
3//! The data types ([`SecretsConfig`], [`SecretEntry`], [`HostPattern`],
4//! [`SecretInjection`], [`ViolationAction`]) and their validation live in the
5//! shared `microsandbox-types` crate so the cloud control plane, the SDKs, and
6//! this engine all speak one contract. This module re-exports them and adds the
7//! engine-internal query helpers used by the proxy.
8
9pub use microsandbox_types::{
10    HostPattern, MAX_SECRET_PLACEHOLDER_BYTES, SecretConfigError, SecretEntry, SecretInjection,
11    SecretSource, SecretsConfig, ViolationAction,
12};
13
14//--------------------------------------------------------------------------------------------------
15// Traits
16//--------------------------------------------------------------------------------------------------
17
18/// Engine-internal queries over a [`SecretsConfig`] that decide whether the
19/// proxy's plain-HTTP header peek is worth its latency.
20pub(crate) trait SecretsConfigExt {
21    /// Whether any secret can be substituted over plain HTTP.
22    ///
23    /// True only when at least one secret has opted out of TLS identity
24    /// (`require_tls_identity == false`) and has an enabled injection scope.
25    fn has_plain_http_candidates(&self) -> bool;
26
27    /// Whether any secret restricts itself to specific hosts (a non-`Any` host
28    /// pattern). Such a secret's plain-HTTP eligibility — substitute, forward
29    /// the placeholder unchanged, or block as a violation — depends on the
30    /// request `Host`, so the peek must read the full header block before the
31    /// handler is built, even for secrets that will never be substituted.
32    fn has_host_scoped_secrets(&self) -> bool;
33}
34
35impl SecretsConfigExt for SecretsConfig {
36    fn has_plain_http_candidates(&self) -> bool {
37        self.secrets.iter().any(|secret| {
38            !secret.require_tls_identity
39                && (secret.injection.headers
40                    || secret.injection.basic_auth
41                    || secret.injection.query_params
42                    || secret.injection.body)
43        })
44    }
45
46    fn has_host_scoped_secrets(&self) -> bool {
47        self.secrets
48            .iter()
49            .any(|secret| secret.allowed_hosts.iter().any(|h| *h != HostPattern::Any))
50    }
51}
52
53//--------------------------------------------------------------------------------------------------
54// Tests
55//--------------------------------------------------------------------------------------------------
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    fn secret(require_tls_identity: bool, hosts: Vec<HostPattern>) -> SecretEntry {
62        SecretEntry {
63            env_var: "API_KEY".into(),
64            value: zeroize::Zeroizing::new("secret".into()),
65            source: None,
66            placeholder: "$MSB_API_KEY".into(),
67            allowed_hosts: hosts,
68            injection: SecretInjection::default(),
69            on_violation: None,
70            require_tls_identity,
71        }
72    }
73
74    #[test]
75    fn plain_http_candidates_require_tls_opt_out() {
76        let tls_only = SecretsConfig {
77            secrets: vec![secret(true, vec![HostPattern::Any])],
78            on_violation: ViolationAction::default(),
79        };
80        assert!(!tls_only.has_plain_http_candidates());
81
82        let plain = SecretsConfig {
83            secrets: vec![secret(false, vec![HostPattern::Any])],
84            on_violation: ViolationAction::default(),
85        };
86        assert!(plain.has_plain_http_candidates());
87    }
88
89    #[test]
90    fn host_scoped_detects_non_any_pattern() {
91        let any = SecretsConfig {
92            secrets: vec![secret(true, vec![HostPattern::Any])],
93            on_violation: ViolationAction::default(),
94        };
95        assert!(!any.has_host_scoped_secrets());
96
97        let scoped = SecretsConfig {
98            secrets: vec![secret(
99                true,
100                vec![HostPattern::Exact("api.example.com".into())],
101            )],
102            on_violation: ViolationAction::default(),
103        };
104        assert!(scoped.has_host_scoped_secrets());
105    }
106}