1use globset::Glob;
2
3use crate::spec::form::SecretsPolicy;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SecretAction {
8 Read,
9 Write,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SecretAccessResult {
15 Allowed,
16 Denied(&'static str),
17 HostUnavailable,
18}
19
20fn matches_pattern(pattern: &str, key: &str) -> bool {
21 match Glob::new(pattern) {
22 Ok(glob) => glob.compile_matcher().is_match(key),
23 Err(_) => false,
24 }
25}
26
27fn matches_any(patterns: &[String], key: &str) -> bool {
28 patterns.iter().any(|pattern| matches_pattern(pattern, key))
29}
30
31pub fn evaluate(
32 policy: Option<&SecretsPolicy>,
33 key: &str,
34 action: SecretAction,
35 host_available: bool,
36) -> SecretAccessResult {
37 let policy = match policy {
38 Some(policy) if policy.enabled => policy,
39 _ => return SecretAccessResult::Denied("secret_access_denied"),
40 };
41
42 let enabled = match action {
43 SecretAction::Read => policy.read_enabled,
44 SecretAction::Write => policy.write_enabled,
45 };
46
47 if !enabled {
48 return SecretAccessResult::Denied("secret_access_denied");
49 }
50
51 if matches_any(&policy.deny, key) {
52 return SecretAccessResult::Denied("secret_access_denied");
53 }
54
55 if policy.allow.is_empty() || !matches_any(&policy.allow, key) {
56 return SecretAccessResult::Denied("secret_access_denied");
57 }
58
59 if !host_available {
60 return SecretAccessResult::HostUnavailable;
61 }
62
63 SecretAccessResult::Allowed
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::spec::form::SecretsPolicy;
70
71 fn policy() -> SecretsPolicy {
72 SecretsPolicy {
73 enabled: true,
74 read_enabled: true,
75 write_enabled: true,
76 allow: vec!["aws/*".into()],
77 deny: vec!["aws/secret-deny".into()],
78 }
79 }
80
81 #[test]
82 fn allowed_key_using_pattern() {
83 assert_eq!(
84 evaluate(Some(&policy()), "aws/key", SecretAction::Read, true),
85 SecretAccessResult::Allowed
86 );
87 }
88
89 #[test]
90 fn denied_key_due_to_deny_list() {
91 assert_eq!(
92 evaluate(Some(&policy()), "aws/secret-deny", SecretAction::Read, true),
93 SecretAccessResult::Denied("secret_access_denied")
94 );
95 }
96
97 #[test]
98 fn host_unavailable_when_disabled() {
99 assert_eq!(
100 evaluate(Some(&policy()), "aws/key", SecretAction::Read, false),
101 SecretAccessResult::HostUnavailable
102 );
103 }
104}