assay/lua/policy/
schema.rs1use std::collections::BTreeMap;
2
3use serde::Deserialize;
4
5pub const SUPPORTED_VERSION: u32 = 1;
6
7#[derive(Debug, Clone, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct PolicyFile {
10 pub version: u32,
11 #[serde(default)]
12 pub modules: Option<ModuleSection>,
13 #[serde(default)]
14 pub env: Option<EnvSection>,
15 #[serde(default)]
16 pub http: Option<HttpSection>,
17 #[serde(default)]
18 pub credentials: BTreeMap<String, BTreeMap<String, String>>,
19 #[serde(default)]
20 pub globals: Option<GlobalsSection>,
21}
22
23#[derive(Debug, Clone, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct GlobalsSection {
26 #[serde(default)]
29 pub block: Vec<String>,
30}
31
32#[derive(Debug, Clone, Deserialize)]
33#[serde(deny_unknown_fields)]
34pub struct ModuleSection {
35 #[serde(default)]
36 pub allow: Option<Vec<String>>,
37}
38
39#[derive(Debug, Clone, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct EnvSection {
42 #[serde(default)]
43 pub allow: Vec<String>,
44}
45
46#[derive(Debug, Clone, Deserialize)]
47#[serde(deny_unknown_fields)]
48pub struct HttpSection {
49 #[serde(default)]
50 pub max_response_bytes: Option<usize>,
51 #[serde(default)]
52 pub redact: Vec<String>,
53 #[serde(default)]
54 pub rules: Option<Vec<HttpRule>>,
55}
56
57#[derive(Debug, Clone, Deserialize)]
58#[serde(deny_unknown_fields)]
59pub struct HttpRule {
60 pub hosts: Vec<String>,
61 #[serde(default)]
62 pub methods: Vec<String>,
63 #[serde(default)]
64 pub paths: Vec<String>,
65 #[serde(default)]
66 pub classify: Option<Classify>,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
70#[serde(rename_all = "lowercase")]
71pub enum Classify {
72 Read,
73 Write,
74}
75
76impl PolicyFile {
77 pub fn parse(source: &str) -> Result<Self, String> {
78 let file: PolicyFile =
79 serde_yml::from_str(source).map_err(|e| format!("policy: invalid YAML: {e}"))?;
80 if file.version != SUPPORTED_VERSION {
81 return Err(format!(
82 "policy: unsupported version {} (this build understands {SUPPORTED_VERSION})",
83 file.version
84 ));
85 }
86 file.validate()?;
87 Ok(file)
88 }
89
90 fn validate(&self) -> Result<(), String> {
91 let Some(http) = &self.http else {
92 return Ok(());
93 };
94 for (i, rule) in http.rules.iter().flatten().enumerate() {
95 if rule.hosts.is_empty() {
96 return Err(format!("policy: http.rules[{i}] needs at least one host"));
97 }
98 for method in &rule.methods {
99 if !is_known_method(method) {
100 return Err(format!(
101 "policy: http.rules[{i}] has unknown method '{method}'"
102 ));
103 }
104 }
105 }
106 Ok(())
107 }
108}
109
110fn is_known_method(method: &str) -> bool {
111 matches!(
112 method.trim().to_ascii_uppercase().as_str(),
113 "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"
114 )
115}