kipuka/config/audit.rs
1//! Audit configuration.
2//!
3//! The `[audit]` section controls the audit trail that satisfies NIAP CA PP
4//! FAU family requirements:
5//!
6//! - **FAU_GEN.1** — the server generates audit records for all security-relevant
7//! events (enrollment, authentication, key operations, admin actions).
8//! - **FAU_STG.1** — the audit trail is append-only at the application level.
9//! - **FAU_STG.4** — when `overflow_policy = "halt"`, EST operations are rejected
10//! if the audit trail storage is exhausted.
11//! - **FAU_ARP.1** — when the alarm threshold is reached, the configured alarm
12//! action is triggered.
13
14use serde::Deserialize;
15
16/// Audit log rotation policy.
17#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "lowercase")]
19#[derive(Default)]
20pub enum RotationPolicy {
21 /// Rotate based on file size.
22 Size,
23 /// Rotate daily.
24 #[default]
25 Daily,
26 /// Rotate weekly.
27 Weekly,
28 /// Never rotate (rely on external log management).
29 Never,
30}
31
32/// What to do when audit storage is exhausted (FAU_STG.4).
33#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
34#[serde(rename_all = "snake_case")]
35#[derive(Default)]
36pub enum OverflowPolicy {
37 /// Drop the oldest audit records to make room.
38 #[default]
39 DropOldest,
40 /// Halt EST operations until audit storage is cleared.
41 ///
42 /// NIAP CA PP FAU_STG.4: "The TSF shall prevent audited events,
43 /// except those taken by the authorised administrator, if the
44 /// audit trail is full."
45 Halt,
46}
47
48/// `[audit]` section — audit trail configuration.
49///
50/// ```toml
51/// [audit]
52/// enabled = true
53/// log_path = "/var/log/kipuka/audit.log"
54/// signed = true
55/// rotation_policy = "daily"
56/// overflow_policy = "halt"
57/// max_rows = 1000000
58/// ```
59#[derive(Debug, Clone, Deserialize)]
60#[serde(deny_unknown_fields)]
61pub struct AuditConfig {
62 /// Enable audit logging. Default: `true`.
63 #[serde(default = "bool_true")]
64 pub enabled: bool,
65
66 /// Path to the audit log file.
67 ///
68 /// When using database-backed audit (`log_to_db = true`), this path
69 /// is used for the file-based backup copy.
70 #[serde(default = "default_log_path")]
71 pub log_path: String,
72
73 /// Enable cryptographic signing of audit log entries.
74 ///
75 /// When `true`, each audit entry includes an RFC 3161-style timestamp
76 /// signature chain for tamper detection.
77 #[serde(default)]
78 pub signed: bool,
79
80 /// Log rotation policy.
81 #[serde(default)]
82 pub rotation_policy: RotationPolicy,
83
84 /// Maximum rotation file size in bytes (when `rotation_policy = "size"`).
85 /// Default: 100 MiB.
86 #[serde(default = "default_max_file_size")]
87 pub max_file_size: u64,
88
89 /// Number of rotated log files to retain.
90 /// Default: 10.
91 #[serde(default = "default_retention_count")]
92 pub retention_count: u32,
93
94 /// Store audit events in the database in addition to the log file.
95 #[serde(default = "bool_true")]
96 pub log_to_db: bool,
97
98 /// Overflow policy when audit storage is full (FAU_STG.4).
99 #[serde(default)]
100 pub overflow_policy: OverflowPolicy,
101
102 /// Maximum number of audit rows in the database.
103 ///
104 /// When this limit is reached, the `overflow_policy` determines
105 /// whether old rows are dropped or EST operations are halted.
106 /// `None` means no limit (rely on disk space monitoring).
107 pub max_rows: Option<u64>,
108
109 /// Number of consecutive security violations before the alarm
110 /// action fires (FAU_ARP.1).
111 ///
112 /// Default: 10.
113 #[serde(default = "default_alarm_threshold")]
114 pub alarm_threshold: u32,
115
116 /// Action taken when the alarm threshold is reached.
117 ///
118 /// - `"syslog"` — emit a syslog alert.
119 /// - `"halt"` — halt EST operations.
120 ///
121 /// Default: `"syslog"`.
122 #[serde(default = "default_alarm_action")]
123 pub alarm_action: String,
124
125 /// NIAP CA PP FAU_GEN.1: list of auditable event types.
126 ///
127 /// When non-empty, only these event types are recorded.
128 /// When empty (default), all events are audited.
129 #[serde(default)]
130 pub auditable_events: Vec<String>,
131}
132
133fn bool_true() -> bool {
134 true
135}
136
137fn default_log_path() -> String {
138 "/var/log/kipuka/audit.log".to_string()
139}
140
141fn default_max_file_size() -> u64 {
142 100 * 1024 * 1024 // 100 MiB
143}
144
145fn default_retention_count() -> u32 {
146 10
147}
148
149fn default_alarm_threshold() -> u32 {
150 10
151}
152
153fn default_alarm_action() -> String {
154 "syslog".to_string()
155}
156
157impl Default for AuditConfig {
158 fn default() -> Self {
159 Self {
160 enabled: true,
161 log_path: default_log_path(),
162 signed: false,
163 rotation_policy: RotationPolicy::default(),
164 max_file_size: default_max_file_size(),
165 retention_count: default_retention_count(),
166 log_to_db: true,
167 overflow_policy: OverflowPolicy::default(),
168 max_rows: None,
169 alarm_threshold: default_alarm_threshold(),
170 alarm_action: default_alarm_action(),
171 auditable_events: Vec::new(),
172 }
173 }
174}
175
176impl AuditConfig {
177 /// Validate audit configuration constraints.
178 pub fn validate(&self) -> std::result::Result<(), String> {
179 if !self.enabled {
180 return Ok(());
181 }
182
183 match self.alarm_action.as_str() {
184 "syslog" | "halt" => {}
185 other => {
186 return Err(format!(
187 "[audit].alarm_action must be \"syslog\" or \"halt\", got {other:?}"
188 ));
189 }
190 }
191
192 if self.alarm_threshold == 0 {
193 return Err("[audit].alarm_threshold must be at least 1".into());
194 }
195
196 if self.rotation_policy == RotationPolicy::Size && self.max_file_size == 0 {
197 return Err(
198 "[audit].max_file_size must be at least 1 when rotation_policy = \"size\"".into(),
199 );
200 }
201
202 Ok(())
203 }
204}