scratchstack_aspen/condition/
arn.rs

1use {
2    super::variant::Variant,
3    crate::{eval::regex_from_glob, serutil::StringLikeList, AspenError, Context, PolicyVersion},
4    log::trace,
5    scratchstack_arn::Arn,
6    scratchstack_aws_principal::SessionValue,
7    std::str::FromStr,
8};
9
10#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11#[repr(u8)]
12pub enum ArnCmp {
13    Equals = 0,
14    Like = 4,
15}
16
17impl ArnCmp {
18    pub(super) fn display_name(&self, variant: &Variant) -> &'static str {
19        ARN_DISPLAY_NAMES[*self as usize | variant.as_usize()]
20    }
21}
22
23// The order is important here. For a given operation, the if-exists variant must follow, then the negated variant,
24// then the negated if-exists variant.
25
26/// ARN operation names.
27const ARN_DISPLAY_NAMES: [&str; 8] = [
28    "ArnEquals",
29    "ArnEqualsIfExists",
30    "ArnNotEquals",
31    "ArnNotEqualsIfExists",
32    "ArnLike",
33    "ArnLikeIfExists",
34    "ArnNotLike",
35    "ArnNotLikeIfExists",
36];
37
38pub(super) fn arn_match(
39    context: &Context,
40    pv: PolicyVersion,
41    allowed: &StringLikeList<String>,
42    value: &SessionValue,
43    _cmp: ArnCmp, // not used; ArnLike and ArnEquals are equivalent.
44    variant: Variant,
45) -> Result<bool, AspenError> {
46    match value {
47        SessionValue::Null => Ok(variant.if_exists()),
48        SessionValue::String(value) => {
49            match Arn::from_str(value) {
50                Err(_) => {
51                    // Failed to convert, so this won't match anything. If not-equals (negated), return true;
52                    // otherwise, false.
53                    Ok(variant.negated())
54                }
55                Ok(value) => {
56                    for el in allowed.iter() {
57                        let parts = el.splitn(6, ':').collect::<Vec<&str>>();
58                        if parts.len() != 6 || parts[0] != "arn" {
59                            continue;
60                        }
61
62                        let partition = regex_from_glob(parts[1], false);
63                        let service = regex_from_glob(parts[2], false);
64                        let region = regex_from_glob(parts[3], false);
65                        let account_id = regex_from_glob(parts[4], false);
66                        let resource = context.matcher(parts[5], pv, false)?;
67
68                        trace!(
69                            "partition={} service={} region={} account_id={} resource={}",
70                            partition,
71                            service,
72                            region,
73                            account_id,
74                            resource
75                        );
76                        trace!("value={}", value);
77
78                        let is_match = partition.is_match(value.partition())
79                            && service.is_match(value.service())
80                            && region.is_match(value.region())
81                            && account_id.is_match(value.account_id())
82                            && resource.is_match(value.resource());
83                        if is_match != variant.negated() {
84                            return Ok(true);
85                        }
86                    }
87
88                    Ok(false)
89                }
90            }
91        }
92        _ => Ok(false),
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use {super::ArnCmp, pretty_assertions::assert_eq};
99
100    #[test_log::test]
101    fn test_clone() {
102        assert_eq!(ArnCmp::Equals.clone(), ArnCmp::Equals);
103        assert_eq!(ArnCmp::Like.clone(), ArnCmp::Like);
104    }
105}