rust_supervisor/config/ipc_security.rs
1//! IPC security configuration model.
2//!
3//! Defines the IPC control point configuration structs with serde
4//! deserialization support and secure-by-default values. C7 audit persistence
5//! uses the root [`crate::config::audit::AuditConfig`] instead of a nested IPC
6//! copy.
7
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11// ---------------------------------------------------------------------------
12// Top-level config aggregator
13// ---------------------------------------------------------------------------
14
15/// Aggregated IPC security configuration loaded from YAML.
16///
17/// Holds IPC control-point sub-configs except C7 audit persistence, which is
18/// configured once at the supervisor root.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
20pub struct IpcSecurityConfig {
21 /// C1-C2: Peer identity verification settings.
22 #[serde(default)]
23 pub peer_identity: PeerIdentityConfig,
24
25 /// C3: Command authorization matrix.
26 #[serde(default)]
27 pub authorization: AuthorizationConfig,
28
29 /// C4: Replay protection settings.
30 #[serde(default)]
31 pub replay_protection: ReplayProtectionConfig,
32
33 /// C5: Request size limit.
34 #[serde(default)]
35 pub request_size_limit: RequestSizeLimitConfig,
36
37 /// C6: Rate limiting settings.
38 #[serde(default)]
39 pub rate_limit: RateLimitConfig,
40
41 /// C8: Command idempotency settings.
42 #[serde(default)]
43 pub idempotency: IdempotencyConfig,
44
45 /// C9: External command allowlist.
46 #[serde(default)]
47 pub allowlist: AllowlistConfig,
48}
49
50impl Default for IpcSecurityConfig {
51 /// Returns the default IPC security configuration with all control
52 /// points enabled and set to secure-by-default values.
53 fn default() -> Self {
54 Self {
55 peer_identity: PeerIdentityConfig::default(),
56 authorization: AuthorizationConfig::default(),
57 replay_protection: ReplayProtectionConfig::default(),
58 request_size_limit: RequestSizeLimitConfig::default(),
59 rate_limit: RateLimitConfig::default(),
60 idempotency: IdempotencyConfig::default(),
61 allowlist: AllowlistConfig::default(),
62 }
63 }
64}
65
66// ---------------------------------------------------------------------------
67// C1-C2: Peer identity verification
68// ---------------------------------------------------------------------------
69
70/// Peer identity verification configuration.
71///
72/// C1: socket owner verification — the process that bound the socket
73/// (this process) is the only allowed owner by definition.
74/// C2: peer credentials verification — connecting process must match
75/// configured identity expectations.
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
77pub struct PeerIdentityConfig {
78 /// Whether peer credential checks are enabled. Default: true.
79 #[serde(default = "default_true")]
80 pub enabled: bool,
81
82 /// Require peer uid to match this process uid. Default: true.
83 #[serde(default = "default_true")]
84 pub require_uid_match: bool,
85
86 /// Allowed gid list. Empty means gid check is disabled.
87 /// Default: empty.
88 #[serde(default)]
89 pub allowed_gids: Vec<u32>,
90
91 /// Allowed pid list. Empty means pid check is disabled.
92 /// Pid checks are inherently racy and only useful in container
93 /// environments with deterministic pids. Default: empty.
94 #[serde(default)]
95 pub allowed_pids: Vec<u32>,
96}
97
98impl Default for PeerIdentityConfig {
99 /// Returns secure-by-default peer identity config: uid match required,
100 /// gid and pid checks disabled.
101 fn default() -> Self {
102 Self {
103 enabled: true,
104 require_uid_match: true,
105 allowed_gids: Vec::new(),
106 allowed_pids: Vec::new(),
107 }
108 }
109}
110
111// ---------------------------------------------------------------------------
112// C3: Command authorization
113// ---------------------------------------------------------------------------
114
115/// Command authorization matrix.
116///
117/// Maps each risk category to an allowed identity set.
118/// Write commands (restart, shutdown, etc.) require authorized peer identity.
119/// Read commands (hello, state) are always allowed when peer identity passes
120/// C1-C2.
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
122pub struct AuthorizationConfig {
123 /// Whether authorization checks are enabled. Default: true.
124 #[serde(default = "default_true")]
125 pub enabled: bool,
126
127 /// Commands classified as high-risk that require explicit authorization.
128 /// Default: all write/destructive commands.
129 #[serde(default = "default_high_risk_commands")]
130 pub high_risk_commands: Vec<String>,
131
132 /// Allowed peer uids for high-risk commands. Empty means deny all.
133 /// Default: [0] (root only).
134 #[serde(default = "default_root_only")]
135 pub allowed_uids: Vec<u32>,
136}
137
138impl Default for AuthorizationConfig {
139 /// Returns default authorization config: enabled, root-only uid whitelist.
140 fn default() -> Self {
141 Self {
142 enabled: true,
143 high_risk_commands: default_high_risk_commands(),
144 allowed_uids: default_root_only(),
145 }
146 }
147}
148
149/// Returns the default high-risk command list: all write/destructive
150/// IPC methods that require explicit authorization.
151fn default_high_risk_commands() -> Vec<String> {
152 vec![
153 "command.restart_child".into(),
154 "command.pause_child".into(),
155 "command.resume_child".into(),
156 "command.quarantine_child".into(),
157 "command.remove_child".into(),
158 "command.add_child".into(),
159 "command.shutdown_tree".into(),
160 ]
161}
162
163/// Returns the default allowed uid list: root only ([0]).
164fn default_root_only() -> Vec<u32> {
165 vec![0]
166}
167
168// ---------------------------------------------------------------------------
169// C4: Replay protection
170// ---------------------------------------------------------------------------
171
172/// Replay protection configuration.
173///
174/// Uses a sliding window of seen request identifiers with a TTL (time-to-live).
175/// A request_id appearing twice within the window is rejected.
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
177pub struct ReplayProtectionConfig {
178 /// Whether replay protection is enabled. Default: true.
179 #[serde(default = "default_true")]
180 pub enabled: bool,
181
182 /// Sliding window size in number of request_ids. Default: 1024.
183 #[serde(default = "default_1024")]
184 pub window_size: usize,
185
186 /// Time-to-live for each entry in seconds. Entry removed after TTL.
187 /// Default: 60.
188 #[serde(default = "default_60")]
189 pub ttl_seconds: u64,
190}
191
192impl Default for ReplayProtectionConfig {
193 /// Returns default replay protection: window size 1024, TTL 60s.
194 fn default() -> Self {
195 Self {
196 enabled: true,
197 window_size: 1024,
198 ttl_seconds: 60,
199 }
200 }
201}
202
203/// Serde default helper: returns 1024.
204fn default_1024() -> usize {
205 1024
206}
207/// Serde default helper: returns 60.
208fn default_60() -> u64 {
209 60
210}
211
212// ---------------------------------------------------------------------------
213// C5: Request size limit
214// ---------------------------------------------------------------------------
215
216/// Request size limit configuration.
217///
218/// Rejects requests whose body byte length exceeds the configured maximum.
219/// Checked before JSON deserialization to prevent memory-bomb attacks.
220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
221pub struct RequestSizeLimitConfig {
222 /// Whether size limit is enforced. Default: true.
223 #[serde(default = "default_true")]
224 pub enabled: bool,
225
226 /// Maximum request body size in bytes. Default: 65536 (64 KiB).
227 #[serde(default = "default_65536")]
228 pub max_bytes: usize,
229}
230
231impl Default for RequestSizeLimitConfig {
232 /// Returns default size limit: 64 KiB (65536 bytes).
233 fn default() -> Self {
234 Self {
235 enabled: true,
236 max_bytes: 65536,
237 }
238 }
239}
240
241/// Serde default helper: returns 65536 (64 KiB).
242fn default_65536() -> usize {
243 65536
244}
245
246// ---------------------------------------------------------------------------
247// C6: Rate limit
248// ---------------------------------------------------------------------------
249
250/// Rate limit configuration.
251///
252/// Uses token bucket algorithm per connection.
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
254pub struct RateLimitConfig {
255 /// Whether rate limiting is enabled. Default: true.
256 #[serde(default = "default_true")]
257 pub enabled: bool,
258
259 /// Token refill rate per second. Default: 100.0.
260 #[serde(default = "default_100_0")]
261 pub refill_rate: f64,
262
263 /// Maximum burst capacity. Default: 20.
264 #[serde(default = "default_20")]
265 pub burst_capacity: u32,
266}
267
268impl Default for RateLimitConfig {
269 /// Returns default rate limit: 100 req/s, burst capacity 20.
270 fn default() -> Self {
271 Self {
272 enabled: true,
273 refill_rate: 100.0,
274 burst_capacity: 20,
275 }
276 }
277}
278/// Serde default helper: returns 100.0.
279fn default_100_0() -> f64 {
280 100.0
281}
282/// Serde default helper: returns 20.
283fn default_20() -> u32 {
284 20
285}
286
287// ---------------------------------------------------------------------------
288// C8: Command idempotency
289// ---------------------------------------------------------------------------
290
291/// Command idempotency configuration.
292///
293/// Uses the same request_id from C4 replay protection.
294/// If a command with a seen request_id is replayed, return the cached
295/// result instead of re-executing.
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
297pub struct IdempotencyConfig {
298 /// Whether idempotency is enforced. Default: true.
299 #[serde(default = "default_true")]
300 pub enabled: bool,
301
302 /// TTL for cached command results in seconds. Default: 60.
303 #[serde(default = "default_60")]
304 pub result_cache_ttl_seconds: u64,
305
306 /// Maximum cached results. Default: 1024.
307 #[serde(default = "default_1024")]
308 pub max_cached_results: usize,
309}
310
311impl Default for IdempotencyConfig {
312 /// Returns default idempotency: TTL 60s, max 1024 cached results.
313 fn default() -> Self {
314 Self {
315 enabled: true,
316 result_cache_ttl_seconds: 60,
317 max_cached_results: 1024,
318 }
319 }
320}
321
322// ---------------------------------------------------------------------------
323// C9: External command allowlist
324// ---------------------------------------------------------------------------
325
326/// External command allowlist configuration.
327///
328/// Only absolute paths listed here are eligible for execution via
329/// control-plane extension points. Default: empty (deny all).
330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
331pub struct AllowlistConfig {
332 /// Whether allowlist enforcement is enabled. Default: true.
333 #[serde(default = "default_true")]
334 pub enabled: bool,
335
336 /// Allowed absolute executable paths. Default: empty array.
337 /// Returns default allowlist: empty (deny all external commands).
338 #[serde(default)]
339 pub allowed_paths: Vec<String>,
340}
341
342impl Default for AllowlistConfig {
343 /// Returns default allowlist: empty (deny all external commands).
344 fn default() -> Self {
345 Self {
346 enabled: true,
347 allowed_paths: Vec::new(),
348 }
349 }
350}
351
352// ---------------------------------------------------------------------------
353// Shared default helpers
354// ---------------------------------------------------------------------------
355
356/// Serde default helper: returns true.
357fn default_true() -> bool {
358 true
359}