provide_telemetry/
classification.rs1use std::sync::{Mutex, OnceLock};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub enum DataClass {
10 Public,
11 Internal,
12 Pii,
13 Phi,
14 Pci,
15 Secret,
16}
17
18impl DataClass {
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 Self::Public => "PUBLIC",
22 Self::Internal => "INTERNAL",
23 Self::Pii => "PII",
24 Self::Phi => "PHI",
25 Self::Pci => "PCI",
26 Self::Secret => "SECRET", }
28 }
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct ClassificationRule {
33 pub pattern: String,
34 pub classification: DataClass,
35}
36
37impl ClassificationRule {
38 pub fn new(pattern: impl Into<String>, classification: DataClass) -> Self {
39 Self {
40 pattern: pattern.into(),
41 classification,
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct ClassificationPolicy {
48 pub public: String,
49 pub internal: String,
50 pub pii: String,
51 pub phi: String,
52 pub pci: String,
53 pub secret: String,
54}
55
56impl Default for ClassificationPolicy {
57 fn default() -> Self {
58 Self {
59 public: "pass".to_string(),
60 internal: "pass".to_string(),
61 pii: "redact".to_string(),
62 phi: "drop".to_string(),
63 pci: "hash".to_string(),
64 secret: "drop".to_string(), }
66 }
67}
68
69static POLICY: OnceLock<Mutex<ClassificationPolicy>> = OnceLock::new();
70
71fn policy() -> &'static Mutex<ClassificationPolicy> {
72 POLICY.get_or_init(|| Mutex::new(ClassificationPolicy::default()))
73}
74
75pub fn set_classification_policy(p: ClassificationPolicy) {
76 *policy()
77 .lock()
78 .expect("classification policy lock poisoned") = p;
79}
80
81pub fn get_classification_policy() -> ClassificationPolicy {
82 policy()
83 .lock()
84 .expect("classification policy lock poisoned")
85 .clone()
86}
87
88static RULES: OnceLock<Mutex<Vec<ClassificationRule>>> = OnceLock::new();
89
90fn rules() -> &'static Mutex<Vec<ClassificationRule>> {
91 RULES.get_or_init(|| Mutex::new(Vec::new()))
92}
93
94fn match_glob(pattern: &str, key: &str) -> bool {
95 if let Some((prefix, "")) = pattern.split_once('*') {
96 return key.starts_with(prefix);
97 }
98 pattern == key
99}
100
101pub fn register_classification_rule(rule: ClassificationRule) {
102 rules()
103 .lock()
104 .expect("classification lock poisoned")
105 .push(rule);
106}
107
108pub fn register_classification_rules(next: Vec<ClassificationRule>) {
109 rules()
110 .lock()
111 .expect("classification lock poisoned")
112 .extend(next);
113}
114
115pub fn clear_classification_rules() {
116 rules()
117 .lock()
118 .expect("classification lock poisoned")
119 .clear();
120}
121
122pub fn classify_key(key: &str) -> Option<String> {
123 rules()
124 .lock()
125 .expect("classification lock poisoned")
126 .iter()
127 .find(|rule| match_glob(&rule.pattern, key))
128 .map(|rule| rule.classification.as_str().to_string())
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::testing::acquire_test_state_lock;
135
136 #[test]
137 fn classification_test_clear_rules_removes_registered_matches() {
138 let _guard = acquire_test_state_lock();
139 clear_classification_rules();
140 register_classification_rule(ClassificationRule::new("email*", DataClass::Pii));
141 assert_eq!(classify_key("email_address").as_deref(), Some("PII"));
142
143 clear_classification_rules();
144
145 assert_eq!(classify_key("email_address"), None);
146 }
147}