1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::fs;
12use std::path::Path;
13
14use crate::SPEC_VERSION;
15
16#[derive(Debug)]
17pub enum PackError {
18 Io(String),
19 Json(String),
20 MissingField(String),
21 SpecMismatch { got: String, want: &'static str },
22 ExpiresAtUnparseable(String),
23 Expired(String),
24 SignatureAlgUnsupported(String),
25}
26
27impl std::fmt::Display for PackError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 PackError::Io(s) => write!(f, "pack_load_failed:{s}"),
31 PackError::Json(s) => write!(f, "pack_load_failed:json:{s}"),
32 PackError::MissingField(s) => write!(f, "pack_missing_fields: {s}"),
33 PackError::SpecMismatch { got, want } => {
34 write!(f, "pack_spec_version_mismatch: got {got:?}, daemon supports {want:?}")
35 }
36 PackError::ExpiresAtUnparseable(s) => write!(f, "pack_expires_at_unparseable:{s}"),
37 PackError::Expired(s) => write!(f, "pack_expired: {s}"),
38 PackError::SignatureAlgUnsupported(s) => {
39 write!(f, "pack_signature_alg_unsupported: {s:?}")
40 }
41 }
42 }
43}
44
45impl std::error::Error for PackError {}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct PolicyPack {
49 pub spec_version: String,
50 pub pack_id: String,
51 pub pack_name: String,
52 pub issuer: String,
53 pub issued_at: String,
54 pub expires_at: String,
55 pub jurisdictions: Vec<String>,
56 pub rules: Vec<Rule>,
57 pub default_decision: String,
58 pub signature: Signature,
59 #[serde(flatten)]
61 pub extra: serde_json::Map<String, Value>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct Rule {
66 pub rule_id: String,
67 pub r#match: Match,
68 pub policy: Policy,
69 #[serde(default)]
70 pub deny_reason: Option<String>,
71 #[serde(default)]
72 pub layer: Option<String>,
73 #[serde(default)]
74 pub warn_only: bool,
75 #[serde(flatten)]
76 pub extra: serde_json::Map<String, Value>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct Match {
81 #[serde(default)]
84 pub action: Value,
85 #[serde(default)]
86 pub fields: Option<Vec<String>>,
87 #[serde(flatten)]
88 pub extra: serde_json::Map<String, Value>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct Policy {
93 pub language: String,
94 pub module: String,
95 #[serde(flatten)]
96 pub extra: serde_json::Map<String, Value>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct Signature {
101 pub alg: String,
102 #[serde(default)]
103 pub kid: Option<String>,
104 #[serde(default)]
105 pub value: Option<String>,
106}
107
108pub fn load_pack(path: &Path) -> Result<PolicyPack, PackError> {
118 let bytes = fs::read(path).map_err(|e| PackError::Io(format!("{e}")))?;
119 let raw: Value =
123 serde_json::from_slice(&bytes).map_err(|e| PackError::Json(format!("{e}")))?;
124 let obj = raw
125 .as_object()
126 .ok_or_else(|| PackError::Json("top-level not an object".into()))?;
127
128 const REQUIRED: &[&str] = &[
129 "spec_version",
130 "pack_id",
131 "pack_name",
132 "issuer",
133 "issued_at",
134 "expires_at",
135 "jurisdictions",
136 "rules",
137 "default_decision",
138 "signature",
139 ];
140 let mut missing: Vec<&str> = REQUIRED.iter().copied().filter(|k| !obj.contains_key(*k)).collect();
141 if !missing.is_empty() {
142 missing.sort();
143 let formatted = format!(
144 "[{}]",
145 missing.iter().map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", ")
146 );
147 return Err(PackError::MissingField(formatted));
148 }
149
150 let pack: PolicyPack = serde_json::from_value(raw).map_err(|e| PackError::Json(format!("{e}")))?;
151
152 if pack.spec_version != SPEC_VERSION {
153 return Err(PackError::SpecMismatch {
154 got: pack.spec_version.clone(),
155 want: SPEC_VERSION,
156 });
157 }
158
159 let expires_normalized = pack.expires_at.replace('Z', "+00:00");
161 let exp: DateTime<Utc> = DateTime::parse_from_rfc3339(&expires_normalized)
162 .map_err(|e| PackError::ExpiresAtUnparseable(format!("{e}")))?
163 .with_timezone(&Utc);
164 if exp < Utc::now() {
165 return Err(PackError::Expired(pack.expires_at.clone()));
166 }
167
168 if pack.signature.alg != "EdDSA" {
169 return Err(PackError::SignatureAlgUnsupported(pack.signature.alg.clone()));
170 }
171 Ok(pack)
175}
176