switchyard/logging/
redact.rs1use crate::types::plan::ApplyMode;
2use serde_json::Value;
3use time::format_description::well_known::Rfc3339;
4use time::OffsetDateTime;
5
6pub const TS_ZERO: &str = "1970-01-01T00:00:00Z";
7
8#[must_use]
9pub fn now_iso() -> String {
10 OffsetDateTime::now_utc()
11 .format(&Rfc3339)
12 .unwrap_or_else(|_| TS_ZERO.to_string())
13}
14
15#[must_use]
19pub fn ts_for_mode(mode: &ApplyMode) -> String {
20 match mode {
21 ApplyMode::DryRun => TS_ZERO.to_string(),
22 ApplyMode::Commit => now_iso(),
23 }
24}
25
26#[must_use]
30pub fn redact_event(mut v: Value) -> Value {
31 if let Some(obj) = v.as_object_mut() {
32 obj.insert("ts".into(), Value::String(TS_ZERO.to_string()));
33 obj.remove("duration_ms");
35 if obj.contains_key("fsync_ms") {
37 obj.insert("fsync_ms".into(), Value::from(0));
38 }
39 obj.remove("severity");
41 obj.remove("before_hash");
44 obj.remove("after_hash");
45 obj.remove("hash_alg");
46 if let Some(p) = obj.get_mut("provenance") {
49 if let Some(pobj) = p.as_object_mut() {
50 if pobj.contains_key("helper") {
51 pobj.insert("helper".into(), Value::String("***".into()));
52 }
53 }
54 }
55 if let Some(att) = obj.get_mut("attestation") {
57 if let Some(aobj) = att.as_object_mut() {
58 if aobj.contains_key("bundle_hash") {
59 aobj.insert("bundle_hash".into(), Value::String("***".into()));
60 }
61 if aobj.contains_key("public_key_id") {
62 aobj.insert("public_key_id".into(), Value::String("***".into()));
63 }
64 if aobj.contains_key("signature") {
65 aobj.insert("signature".into(), Value::String("***".into()));
66 }
67 }
68 }
69 }
70 v
71}
72
73#[cfg(test)]
74#[allow(clippy::panic)]
75mod tests {
76 use super::*;
77 use serde_json::json;
78
79 #[test]
80 fn redact_masks_and_removes_expected_fields() {
81 let input = json!({
82 "ts": "2025-01-01T12:00:00Z",
83 "duration_ms": 123,
84 "lock_wait_ms": 45,
85 "severity": "warn",
86 "degraded": true,
87 "before_hash": "abc",
88 "after_hash": "def",
89 "hash_alg": "sha256",
90 "provenance": {"helper":"paru", "uid": 0, "gid": 0, "pkg": "coreutils"},
91 "attestation": {"signature":"sig","bundle_hash":"bh","public_key_id":"pk"}
92 });
93 let out = redact_event(input);
94 assert_eq!(out.get("ts").and_then(|v| v.as_str()), Some(TS_ZERO));
95 assert!(out.get("duration_ms").is_none());
96 assert_eq!(out.get("lock_wait_ms").and_then(Value::as_i64), Some(45));
98 assert!(out.get("severity").is_none());
99 assert_eq!(out.get("degraded").and_then(Value::as_bool), Some(true));
100 assert!(out.get("before_hash").is_none());
101 assert!(out.get("after_hash").is_none());
102 assert!(out.get("hash_alg").is_none());
103 let prov = out
104 .get("provenance")
105 .and_then(|v| v.as_object())
106 .unwrap_or_else(|| panic!("provenance should be an object"));
107 assert_eq!(prov.get("helper").and_then(|v| v.as_str()), Some("***"));
108 let att = out
109 .get("attestation")
110 .and_then(|v| v.as_object())
111 .unwrap_or_else(|| panic!("attestation should be an object"));
112 assert_eq!(att.get("signature").and_then(|v| v.as_str()), Some("***"));
113 assert_eq!(att.get("bundle_hash").and_then(|v| v.as_str()), Some("***"));
114 assert_eq!(
115 att.get("public_key_id").and_then(|v| v.as_str()),
116 Some("***")
117 );
118 }
119}