New crates: stratum-orchestrator (Cedar authz, Vault secrets, Nu/agent executors, saga runner), stratum-graph (petgraph DAG + SurrealDB repo), stratum-state (SurrealDB tracker), platform-nats (NKey auth client), ncl-import-resolver. Updates: stratum-embeddings (SurrealDB store + persistent cache), stratum-llm circuit breaker. Adds Nickel action-nodes, schemas, config, Nushell scripts, docker-compose dev stack, and ADR-003.
147 lines
3.9 KiB
Rust
147 lines
3.9 KiB
Rust
/// Integration test: Cedar authorization — multi-policy permit/deny scenarios.
|
|
use std::io::Write;
|
|
|
|
use stratum_orchestrator::auth::CedarAuthorizer;
|
|
use tempfile::TempDir;
|
|
|
|
fn write_policy(dir: &TempDir, name: &str, content: &str) {
|
|
let path = dir.path().join(name);
|
|
let mut f = std::fs::File::create(path).unwrap();
|
|
f.write_all(content.as_bytes()).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_permit_orchestrator_executes_allowed_node() {
|
|
let dir = TempDir::new().unwrap();
|
|
write_policy(
|
|
&dir,
|
|
"allow.cedar",
|
|
r#"permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"lint"
|
|
);"#,
|
|
);
|
|
|
|
let authz = CedarAuthorizer::load_from_dir(dir.path()).unwrap();
|
|
authz
|
|
.authorize("orchestrator", "execute", "Node::\"lint\"")
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_deny_unknown_principal() {
|
|
let dir = TempDir::new().unwrap();
|
|
write_policy(
|
|
&dir,
|
|
"allow.cedar",
|
|
r#"permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"lint"
|
|
);"#,
|
|
);
|
|
|
|
let authz = CedarAuthorizer::load_from_dir(dir.path()).unwrap();
|
|
// Unknown principal — no matching permit → implicit deny
|
|
let err = authz
|
|
.authorize("rogue-agent", "execute", "Node::\"lint\"")
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("Cedar denied"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_deny_wrong_resource() {
|
|
let dir = TempDir::new().unwrap();
|
|
write_policy(
|
|
&dir,
|
|
"allow.cedar",
|
|
r#"permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"lint"
|
|
);"#,
|
|
);
|
|
|
|
let authz = CedarAuthorizer::load_from_dir(dir.path()).unwrap();
|
|
let err = authz
|
|
.authorize("orchestrator", "execute", "Node::\"build\"")
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("Cedar denied"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_explicit_forbid_overrides_permit() {
|
|
let dir = TempDir::new().unwrap();
|
|
write_policy(
|
|
&dir,
|
|
"00_permit.cedar",
|
|
r#"permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"lint"
|
|
);"#,
|
|
);
|
|
write_policy(
|
|
&dir,
|
|
"01_forbid.cedar",
|
|
r#"forbid(principal, action, resource);"#,
|
|
);
|
|
|
|
let authz = CedarAuthorizer::load_from_dir(dir.path()).unwrap();
|
|
// Cedar: forbid overrides permit
|
|
let err = authz
|
|
.authorize("orchestrator", "execute", "Node::\"lint\"")
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("Cedar denied"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_policy_dir_returns_error() {
|
|
let dir = TempDir::new().unwrap();
|
|
let err = match CedarAuthorizer::load_from_dir(dir.path()) {
|
|
Err(e) => e,
|
|
Ok(_) => panic!("expected load_from_dir to fail on empty directory"),
|
|
};
|
|
assert!(
|
|
err.to_string().contains("no .cedar policy files"),
|
|
"expected 'no .cedar policy files' error, got: {err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_permits_any_matching_allows() {
|
|
let dir = TempDir::new().unwrap();
|
|
write_policy(
|
|
&dir,
|
|
"nodes.cedar",
|
|
r#"permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"lint"
|
|
);
|
|
permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"build"
|
|
);
|
|
permit(
|
|
principal == User::"orchestrator",
|
|
action == Action::"execute",
|
|
resource == Node::"install"
|
|
);"#,
|
|
);
|
|
|
|
let authz = CedarAuthorizer::load_from_dir(dir.path()).unwrap();
|
|
for node in ["lint", "build", "install"] {
|
|
authz
|
|
.authorize("orchestrator", "execute", &format!("Node::\"{node}\""))
|
|
.unwrap_or_else(|e| panic!("node '{node}' should be permitted: {e}"));
|
|
}
|
|
|
|
// Node not in the permit list → deny
|
|
authz
|
|
.authorize("orchestrator", "execute", "Node::\"notify\"")
|
|
.unwrap_err();
|
|
}
|