use std::sync::OnceLock;
use chrono::{DateTime, TimeZone, Utc};
use serde::Deserialize;
use crate::state::{IamPolicy, PolicyVersion};
#[derive(Debug, Clone, Deserialize)]
pub struct ManagedPolicy {
pub name: String,
pub arn: String,
pub path: String,
#[serde(rename = "defaultVersionId")]
pub default_version_id: String,
#[serde(rename = "createDate")]
pub create_date: String,
pub description: String,
pub document: String,
}
#[derive(Deserialize)]
struct CatalogFile {
policies: Vec<ManagedPolicy>,
}
const CATALOG_GZ: &[u8] = include_bytes!("managed_policies/catalog.json.gz");
fn catalog() -> &'static [ManagedPolicy] {
static CATALOG: OnceLock<Vec<ManagedPolicy>> = OnceLock::new();
CATALOG
.get_or_init(|| {
use std::io::Read;
let mut json = String::new();
flate2::read::GzDecoder::new(CATALOG_GZ)
.read_to_string(&mut json)
.expect("embedded AWS-managed policy catalog must gunzip");
let parsed: CatalogFile = serde_json::from_str(&json)
.expect("embedded AWS-managed policy catalog must be valid JSON");
parsed.policies
})
.as_slice()
}
pub fn is_aws_managed_arn(arn: &str) -> bool {
arn.contains(":aws:policy/")
}
pub fn all() -> &'static [ManagedPolicy] {
catalog()
}
pub fn lookup(arn: &str) -> Option<&'static ManagedPolicy> {
catalog().iter().find(|p| p.arn == arn)
}
pub fn default_document(arn: &str) -> Option<&'static str> {
lookup(arn).map(|p| p.document.as_str())
}
impl ManagedPolicy {
pub fn created_at(&self) -> DateTime<Utc> {
DateTime::parse_from_rfc3339(&self.create_date)
.map(|d| d.with_timezone(&Utc))
.unwrap_or_else(|_| Utc.timestamp_opt(0, 0).single().unwrap_or_default())
}
pub fn policy_id(&self) -> String {
deterministic_policy_id(&self.name)
}
fn next_version_num(&self) -> u32 {
self.default_version_id
.trim_start_matches('v')
.parse::<u32>()
.unwrap_or(1)
.saturating_add(1)
}
pub fn to_iam_policy(&self, attachment_count: u32) -> IamPolicy {
let created_at = self.created_at();
IamPolicy {
policy_name: self.name.clone(),
policy_id: self.policy_id(),
arn: self.arn.clone(),
path: self.path.clone(),
description: self.description.clone(),
created_at,
tags: Vec::new(),
default_version_id: self.default_version_id.clone(),
versions: vec![PolicyVersion {
version_id: self.default_version_id.clone(),
document: self.document.clone(),
is_default: true,
created_at,
}],
next_version_num: self.next_version_num(),
attachment_count,
}
}
}
fn deterministic_policy_id(name: &str) -> String {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let mut h1: u64 = 0xcbf2_9ce4_8422_2325;
for b in name.bytes() {
h1 ^= b as u64;
h1 = h1.wrapping_mul(0x0000_0100_0000_01b3);
}
let mut h2: u64 = 0x8422_2325_cbf2_9ce4;
for b in name.bytes().rev() {
h2 ^= b as u64;
h2 = h2.wrapping_mul(0x0000_0100_0000_01b3);
}
let mut bits = ((h1 as u128) << 64) | h2 as u128;
let mut out = String::with_capacity(21);
out.push_str("ANPA");
for _ in 0..17 {
out.push(ALPHABET[(bits & 0x1f) as usize] as char);
bits >>= 5;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_parses_and_is_nonempty() {
assert!(!all().is_empty(), "catalog should seed at least one policy");
}
#[test]
fn every_entry_is_managed_with_valid_document() {
for p in all() {
assert!(
is_aws_managed_arn(&p.arn),
"catalog ARN must be AWS-managed: {}",
p.arn
);
assert!(
p.arn.ends_with(&p.name),
"ARN should end with the policy name: {} / {}",
p.arn,
p.name
);
let v: serde_json::Value =
serde_json::from_str(&p.document).expect("policy document must be valid JSON");
assert!(
v.get("Statement").is_some(),
"document for {} must have a Statement",
p.name
);
DateTime::parse_from_rfc3339(&p.create_date)
.unwrap_or_else(|_| panic!("createDate must be RFC3339 for {}", p.name));
if p.arn.contains(":aws:policy/service-role/") {
assert_eq!(p.path, "/service-role/", "path mismatch for {}", p.name);
}
}
}
#[test]
fn catalog_is_comprehensive() {
assert!(
all().len() > 1000,
"expected the full AWS managed-policy catalog, got {}",
all().len()
);
for arn in [
"arn:aws:iam::aws:policy/AdministratorAccess",
"arn:aws:iam::aws:policy/AmazonRoute53FullAccess",
"arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole",
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
] {
let p = lookup(arn).unwrap_or_else(|| panic!("{arn} must be seeded"));
let v: serde_json::Value = serde_json::from_str(&p.document).unwrap();
assert!(
v["Statement"][0]["Effect"].is_string(),
"{arn} has no grant"
);
}
}
#[test]
fn arns_are_unique() {
let mut seen = std::collections::HashSet::new();
for p in all() {
assert!(seen.insert(&p.arn), "duplicate ARN in catalog: {}", p.arn);
}
}
#[test]
fn lookup_and_default_document_resolve_known_policy() {
let arn = "arn:aws:iam::aws:policy/AdministratorAccess";
let p = lookup(arn).expect("AdministratorAccess must be seeded");
assert_eq!(p.name, "AdministratorAccess");
let doc = default_document(arn).expect("must have a default document");
let v: serde_json::Value = serde_json::from_str(doc).expect("document is valid JSON");
let stmt = &v["Statement"][0];
assert_eq!(stmt["Effect"], "Allow");
assert!(stmt["Action"] == "*" || stmt["Action"][0] == "*");
assert!(stmt["Resource"] == "*" || stmt["Resource"][0] == "*");
assert!(lookup("arn:aws:iam::aws:policy/DoesNotExist").is_none());
}
#[test]
fn customer_arn_is_not_managed() {
assert!(!is_aws_managed_arn(
"arn:aws:iam::123456789012:policy/MyPolicy"
));
assert!(is_aws_managed_arn(
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
));
}
#[test]
fn policy_id_is_stable_and_well_formed() {
let a = deterministic_policy_id("AdministratorAccess");
let b = deterministic_policy_id("AdministratorAccess");
assert_eq!(a, b, "policy id must be deterministic");
assert!(a.starts_with("ANPA"));
assert_eq!(a.len(), 21);
assert!(a
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()));
assert_ne!(
deterministic_policy_id("AdministratorAccess"),
deterministic_policy_id("PowerUserAccess"),
);
}
#[test]
fn to_iam_policy_carries_default_version() {
let p = lookup("arn:aws:iam::aws:policy/AmazonS3FullAccess").unwrap();
let ip = p.to_iam_policy(3);
assert_eq!(ip.attachment_count, 3);
assert_eq!(ip.default_version_id, "v2");
assert_eq!(ip.versions.len(), 1);
assert!(ip.versions[0].is_default);
assert_eq!(ip.next_version_num, 3);
}
}