use super::CanonicalPolicy;
use crate::contract::capability::{
EnvEntry, EnvPolicy, FdPolicy, NetDest, NetPolicy, SecretRef, SpawnPolicy,
};
fn elit(name: &str, value: &str) -> EnvEntry {
EnvEntry::literal(name, value)
}
fn elease(name: &str, reference: &str) -> EnvEntry {
EnvEntry::lease(name, SecretRef::new(reference))
}
fn distinct_policies() -> Vec<CanonicalPolicy> {
vec![
CanonicalPolicy::of_fd(&FdPolicy::None),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![])), CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1])),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![3])),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1, 3])),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1, 2, 3])),
CanonicalPolicy::of_spawn(&SpawnPolicy::DenyNewTasks),
CanonicalPolicy::of_spawn(&SpawnPolicy::AllowThreadsWithinBoundary),
CanonicalPolicy::of_spawn(&SpawnPolicy::AllowDescendantsWithinBoundary),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elit("PATH", "/usr/bin")])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elit("PATH", "/bin")])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elit("HOME", "/usr/bin")])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elit("TOKEN", "x")])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elease("TOKEN", "x")])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![
elit("HOME", "/srv/u"),
elit("PATH", "/usr/bin"),
])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elit("AB", "C")])),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![elit("A", "BC")])),
CanonicalPolicy::of_net(&NetPolicy::DenyAll),
CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![])), CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![dest("example.com", 443)])),
CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![dest("example.com", 80)])), CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![dest("other.com", 443)])), CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![
dest("example.com", 443),
dest("other.com", 80),
])),
]
}
fn dest(host: &str, port: u16) -> NetDest {
NetDest {
host: host.to_string(),
port,
}
}
#[test]
fn distinct_semantics_imply_distinct_bytes() {
let policies = distinct_policies();
let mut collisions = Vec::new();
for (i, a) in policies.iter().enumerate() {
for (j, b) in policies.iter().enumerate() {
if i < j && a.as_bytes() == b.as_bytes() {
collisions.push((i, j, a.as_bytes().to_vec()));
}
}
}
assert!(
collisions.is_empty(),
"distinct policies collided to identical canonical bytes: {collisions:?}"
);
}
#[test]
fn normalization_is_deterministic() {
let mut failures = Vec::new();
for p in [
FdPolicy::None,
FdPolicy::Only(vec![1, 3]),
FdPolicy::Only(vec![]),
] {
if CanonicalPolicy::of_fd(&p).as_bytes() != CanonicalPolicy::of_fd(&p).as_bytes() {
failures.push(format!("fd {p:?}"));
}
}
for p in [
SpawnPolicy::DenyNewTasks,
SpawnPolicy::AllowThreadsWithinBoundary,
SpawnPolicy::AllowDescendantsWithinBoundary,
] {
if CanonicalPolicy::of_spawn(&p).as_bytes() != CanonicalPolicy::of_spawn(&p).as_bytes() {
failures.push(format!("spawn {p:?}"));
}
}
for p in [
EnvPolicy::Exact(vec![]),
EnvPolicy::Exact(vec![elit("PATH", "/usr/bin"), elease("TOKEN", "lease://t")]),
] {
if CanonicalPolicy::of_env(&p).as_bytes() != CanonicalPolicy::of_env(&p).as_bytes() {
failures.push(format!("env {p:?}"));
}
}
for p in [
NetPolicy::DenyAll,
NetPolicy::AllowList(vec![dest("example.com", 443)]),
] {
if CanonicalPolicy::of_net(&p).as_bytes() != CanonicalPolicy::of_net(&p).as_bytes() {
failures.push(format!("net {p:?}"));
}
}
assert!(
failures.is_empty(),
"normalization was non-deterministic for: {failures:?}"
);
}
#[test]
fn syntactic_aliases_collapse_to_the_same_bytes() {
let mut mismatches = Vec::new();
let fd_canon = CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1, 3]));
for alias in [
FdPolicy::Only(vec![3, 1]),
FdPolicy::Only(vec![3, 1, 3, 1]),
FdPolicy::Only(vec![1, 3, 3]),
] {
if CanonicalPolicy::of_fd(&alias).as_bytes() != fd_canon.as_bytes() {
mismatches.push(format!("fd alias {alias:?}"));
}
}
let env_canon = CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![
elit("HOME", "/srv/u"),
elit("PATH", "/usr/bin"),
]));
for alias in [EnvPolicy::Exact(vec![
elit("PATH", "/usr/bin"),
elit("HOME", "/srv/u"),
])] {
if CanonicalPolicy::of_env(&alias).as_bytes() != env_canon.as_bytes() {
mismatches.push(format!("env alias {alias:?}"));
}
}
let net_canon = CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![
dest("example.com", 443),
dest("other.com", 80),
]));
for alias in [
NetPolicy::AllowList(vec![dest("other.com", 80), dest("example.com", 443)]),
NetPolicy::AllowList(vec![
dest("other.com", 80),
dest("example.com", 443),
dest("other.com", 80),
]),
] {
if CanonicalPolicy::of_net(&alias).as_bytes() != net_canon.as_bytes() {
mismatches.push(format!("net alias {alias:?}"));
}
}
assert!(
mismatches.is_empty(),
"syntactic aliases failed to collapse to the canonical bytes: {mismatches:?}"
);
}
#[test]
fn normalization_is_idempotent_on_canonical_payloads() {
let mut failures = Vec::new();
let raw = CanonicalPolicy::of_fd(&FdPolicy::Only(vec![3, 1, 3]));
let already = CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1, 3]));
if raw.as_bytes() != already.as_bytes() {
failures.push("fd not idempotent".to_string());
}
let raw_env = CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![
elit("PATH", "/usr/bin"),
elit("HOME", "/srv/u"),
]));
let already_env = CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![
elit("HOME", "/srv/u"),
elit("PATH", "/usr/bin"),
]));
if raw_env.as_bytes() != already_env.as_bytes() {
failures.push("env not idempotent".to_string());
}
let raw_net = CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![
dest("other.com", 80),
dest("example.com", 443),
dest("other.com", 80),
]));
let already_net = CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![
dest("example.com", 443),
dest("other.com", 80),
]));
if raw_net.as_bytes() != already_net.as_bytes() {
failures.push("net not idempotent".to_string());
}
assert!(
failures.is_empty(),
"normalization was not idempotent for: {failures:?}"
);
}