Skip to main content

aa_security/policy/
error.rs

1//! Errors raised while parsing the canonical policy AST.
2
3use std::fmt;
4
5/// Errors that can occur while parsing a [`PolicyDocument`](super::PolicyDocument).
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum PolicyParseError {
8    /// The YAML could not be deserialized.
9    Yaml(String),
10    /// A capability token was not recognised.
11    InvalidCapability {
12        /// The offending raw token.
13        raw: String,
14        /// Why it was rejected.
15        reason: String,
16    },
17    /// A syscall name in the `syscalls.allow` list was not recognised.
18    InvalidSyscall {
19        /// The offending raw token.
20        raw: String,
21        /// Why it was rejected.
22        reason: String,
23    },
24    /// A structural key was not part of the known policy schema.
25    ///
26    /// Raised when a security-relevant section or field is misspelled (e.g.
27    /// `dney:` for `deny:`, `allow_list:` for `allowlist:`). Such typos would
28    /// otherwise be silently dropped by `#[serde(flatten)]` — yielding an empty,
29    /// permissive policy that parses successfully. Surfacing them keeps policy
30    /// parsing fail-closed (AAASM-3874).
31    UnknownKey {
32        /// Dotted path to the mapping that contained the unknown key.
33        path: String,
34        /// The unrecognised key.
35        key: String,
36    },
37    /// The document was empty or null.
38    ///
39    /// An empty / null / `{}` policy deserializes to an all-`None` document,
40    /// which is fully permissive (no capability denials, no allowlist, no tool
41    /// gating). A policy must positively declare its posture, so a blank
42    /// document is rejected rather than silently defaulting open (AAASM-3997).
43    EmptyDocument,
44    /// The document parsed but declared no enforcement dimension.
45    ///
46    /// A metadata-only document (e.g. just `apiVersion`/`kind`/`metadata`, with
47    /// no `network`, `capabilities`, `tools`, or `syscalls`) deserializes to a
48    /// fully-permissive policy exactly like an empty one. It is rejected so a
49    /// policy cannot become open by omission rather than by declaration
50    /// (AAASM-4020, extending the AAASM-3997 empty/null floor).
51    NoEnforcementSection,
52}
53
54impl fmt::Display for PolicyParseError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::Yaml(msg) => write!(f, "policy YAML parse error: {msg}"),
58            Self::InvalidCapability { raw, reason } => {
59                write!(f, "invalid capability {raw:?}: {reason}")
60            }
61            Self::InvalidSyscall { raw, reason } => {
62                write!(f, "invalid syscall {raw:?}: {reason}")
63            }
64            Self::UnknownKey { path, key } => {
65                write!(f, "unknown policy key {key:?} under {path}")
66            }
67            Self::EmptyDocument => {
68                write!(
69                    f,
70                    "empty or null policy document is not permitted (would be fully permissive)"
71                )
72            }
73            Self::NoEnforcementSection => {
74                write!(
75                    f,
76                    "policy declares no enforcement section (network/capabilities/tools/syscalls); \
77                     it would be fully permissive"
78                )
79            }
80        }
81    }
82}
83
84impl std::error::Error for PolicyParseError {}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn display_yaml_error() {
92        let err = PolicyParseError::Yaml("bad".to_string());
93        assert_eq!(err.to_string(), "policy YAML parse error: bad");
94    }
95
96    #[test]
97    fn display_invalid_capability() {
98        let err = PolicyParseError::InvalidCapability {
99            raw: "teleport".to_string(),
100            reason: "unknown capability: 'teleport'".to_string(),
101        };
102        assert!(err.to_string().contains("teleport"));
103    }
104
105    #[test]
106    fn display_unknown_key() {
107        let err = PolicyParseError::UnknownKey {
108            path: "capabilities".to_string(),
109            key: "dney".to_string(),
110        };
111        assert_eq!(err.to_string(), "unknown policy key \"dney\" under capabilities");
112    }
113}