kipuka/config/est.rs
1//! EST protocol configuration.
2//!
3//! The `[est]` section controls which EST operations are enabled globally,
4//! and `[[est.label]]` entries define per-label enrollment profiles with
5//! CA routing and authentication policies.
6//!
7//! # EST labels (RFC 7030 §3.2.2)
8//!
9//! EST labels provide a namespace mechanism for multiple enrollment profiles
10//! under the same server. Each label maps to a URL path segment:
11//!
12//! ```text
13//! https://est.example.com/.well-known/est/{label}/simpleenroll
14//! ```
15//!
16//! When no label is specified in the URL, the default label configuration
17//! applies.
18
19use serde::Deserialize;
20
21/// Authentication method for EST enrollment requests.
22///
23/// RFC 7030 §3.2.3 defines several client authentication mechanisms.
24/// Each EST label can require a specific method or accept multiple.
25#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
26#[serde(rename_all = "lowercase")]
27pub enum EstAuthMethod {
28 /// mTLS client certificate authentication (RFC 7030 §3.3.2).
29 Mtls,
30 /// HTTP Basic authentication with OTP (RHELBU-3536 R7).
31 Otp,
32 /// HTTP Basic authentication with static credentials.
33 Basic,
34 /// Certificate-based re-enrollment (existing certificate proves identity).
35 Certificate,
36}
37
38/// `[est]` section — global EST protocol settings.
39#[derive(Debug, Clone, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct EstConfig {
42 /// Enable the `/simpleenroll` endpoint (RFC 7030 §4.2).
43 #[serde(default = "bool_true")]
44 pub simpleenroll: bool,
45
46 /// Enable the `/simplereenroll` endpoint (RFC 7030 §4.2.2).
47 #[serde(default = "bool_true")]
48 pub simplereenroll: bool,
49
50 /// Enable the `/fullcmc` endpoint (RFC 7030 §4.3).
51 ///
52 /// Full CMC is rarely needed; disabled by default.
53 #[serde(default)]
54 pub fullcmc: bool,
55
56 /// Enable the `/serverkeygen` endpoint (RFC 7030 §4.4).
57 ///
58 /// Server-side key generation requires HSM integration.
59 /// Disabled by default.
60 #[serde(default)]
61 pub serverkeygen: bool,
62
63 /// Enable the `/csrattrs` endpoint (RFC 7030 §4.5).
64 #[serde(default = "bool_true")]
65 pub csrattrs: bool,
66
67 /// Default enrollment profile applied when no label is specified.
68 ///
69 /// When absent, enrollment requests without a label use the default
70 /// CA and authentication policy.
71 #[serde(default)]
72 pub default_profile: Option<String>,
73
74 /// CSR attribute hints returned by `/csrattrs`.
75 ///
76 /// Each entry is an OID string (e.g., `"1.2.840.113549.1.9.14"` for
77 /// the Certificate Extensions Request attribute).
78 #[serde(default)]
79 pub csr_attributes: Vec<String>,
80
81 /// Per-label enrollment configurations.
82 #[serde(default, rename = "label")]
83 pub labels: Vec<EstLabelConfig>,
84
85 /// Disconnected mode: accept enrollment requests without upstream
86 /// CA connectivity (RHELBU-3536 R7-Disconnected).
87 ///
88 /// When `true`, the server queues CSRs for deferred signing and
89 /// returns `202 Accepted` with a `Retry-After` header instead of
90 /// the signed certificate.
91 #[serde(default)]
92 pub disconnected: bool,
93
94 /// Retry-After value (seconds) returned in disconnected mode.
95 /// Default: 300 (5 minutes).
96 #[serde(default = "default_retry_after_secs")]
97 pub disconnected_retry_after_secs: u64,
98}
99
100/// `[[est.label]]` — per-label enrollment profile.
101///
102/// Each label provides an independent enrollment namespace with its own
103/// CA routing, authentication requirements, and CSR attribute set.
104///
105/// ```toml
106/// [[est.label]]
107/// name = "devices"
108/// ca_id = "device-ca"
109/// auth_methods = ["mtls", "otp"]
110/// require_cn_match = true
111/// ```
112#[derive(Debug, Clone, Deserialize)]
113#[serde(deny_unknown_fields)]
114pub struct EstLabelConfig {
115 /// Label name used in the URL path.
116 ///
117 /// Must be a non-empty string matching `^[a-z0-9][a-z0-9_-]*$`.
118 pub name: String,
119
120 /// CA identifier to use for enrollments under this label.
121 ///
122 /// Must reference a `[[ca]]` entry by its `id` field.
123 /// When absent, the default CA is used.
124 pub ca_id: Option<String>,
125
126 /// Allowed authentication methods for this label.
127 ///
128 /// When empty, all globally-enabled auth methods are accepted.
129 #[serde(default)]
130 pub auth_methods: Vec<EstAuthMethod>,
131
132 /// Per-label CSR attribute hints (overrides global `csr_attributes`
133 /// for this label).
134 #[serde(default)]
135 pub csr_attributes: Vec<String>,
136
137 /// Require that the CSR Common Name matches the authenticated identity.
138 ///
139 /// When `true`, the server rejects CSRs where the CN does not match
140 /// the client's authenticated principal name.
141 #[serde(default)]
142 pub require_cn_match: bool,
143
144 /// Maximum validity period (days) for certificates issued under this label.
145 ///
146 /// Overrides the CA's default `validity_days` for this label.
147 pub max_validity_days: Option<u32>,
148
149 /// Enable disconnected mode for this specific label.
150 /// Overrides the global `[est].disconnected` setting.
151 pub disconnected: Option<bool>,
152}
153
154fn bool_true() -> bool {
155 true
156}
157
158fn default_retry_after_secs() -> u64 {
159 300
160}
161
162impl Default for EstConfig {
163 fn default() -> Self {
164 Self {
165 simpleenroll: true,
166 simplereenroll: true,
167 fullcmc: false,
168 serverkeygen: false,
169 csrattrs: true,
170 default_profile: None,
171 csr_attributes: Vec::new(),
172 labels: Vec::new(),
173 disconnected: false,
174 disconnected_retry_after_secs: default_retry_after_secs(),
175 }
176 }
177}