Skip to main content

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}