use std::sync::Arc;
use astrid_core::dirs::AstridHome;
use astrid_core::principal::PrincipalId;
use astrid_core::profile::PrincipalProfile;
use astrid_events::kernel_api::{AdminRequestKind, AdminResponseBody};
use tempfile::TempDir;
use super::handlers;
use crate::Kernel;
async fn fixture() -> (TempDir, Arc<Kernel>) {
let dir = tempfile::tempdir().expect("tempdir");
let home = AstridHome::from_path(dir.path());
let kernel = crate::test_kernel_with_home(home).await;
(dir, kernel)
}
fn pid(name: &str) -> PrincipalId {
PrincipalId::new(name).unwrap()
}
fn assert_success(res: &AdminResponseBody) {
match res {
AdminResponseBody::Success(_)
| AdminResponseBody::Quotas(_)
| AdminResponseBody::Usage(_)
| AdminResponseBody::AgentList(_)
| AdminResponseBody::GroupList(_)
| AdminResponseBody::Invite(_)
| AdminResponseBody::InviteRedeemed(_)
| AdminResponseBody::InviteList(_)
| AdminResponseBody::PairToken(_)
| AdminResponseBody::PairTokenRedeemed(_) => {},
AdminResponseBody::Error(msg) => panic!("expected success, got Error: {msg}"),
}
}
fn assert_error_contains(res: &AdminResponseBody, needle: &str) {
match res {
AdminResponseBody::Error(msg) => {
assert!(
msg.contains(needle),
"expected error to contain {needle:?}, got: {msg}"
);
},
other => panic!("expected Error, got: {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_grant_appends_and_invalidates_cache() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "grace".into(),
groups: vec!["restricted".into()],
grants: Vec::new(),
},
)
.await;
use astrid_capabilities::CapabilityCheck;
{
let profile = kernel.profile_cache.resolve(&pid("grace")).unwrap();
let groups = kernel.groups.load_full();
let check = CapabilityCheck::new(profile.as_ref(), groups.as_ref(), pid("grace"));
assert!(check.require("capsule:install").is_err());
}
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("grace"),
capabilities: vec!["capsule:install".into()],
unsafe_admin: false,
},
)
.await;
let profile = kernel.profile_cache.resolve(&pid("grace")).unwrap();
let groups = kernel.groups.load_full();
let check = CapabilityCheck::new(profile.as_ref(), groups.as_ref(), pid("grace"));
check.require("capsule:install").unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_grant_does_not_clear_matching_revoke() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "henry".into(),
groups: vec!["admin".into()],
grants: Vec::new(),
},
)
.await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsRevoke {
principal: pid("henry"),
capabilities: vec!["self:*".into()],
},
)
.await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("henry"),
capabilities: vec!["self:capsule:install".into()],
unsafe_admin: false,
},
)
.await;
use astrid_capabilities::{CapabilityCheck, PermissionError};
let profile = kernel.profile_cache.resolve(&pid("henry")).unwrap();
let groups = kernel.groups.load_full();
let check = CapabilityCheck::new(profile.as_ref(), groups.as_ref(), pid("henry"));
let err = check.require("self:capsule:install").unwrap_err();
assert!(
matches!(err, PermissionError::RevokedCapability { .. }),
"grant must not clear revoke: {err:?}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_revoke_of_unheld_capability_appends_preemptive() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "ivy".into(),
groups: vec!["restricted".into()],
grants: Vec::new(),
},
)
.await;
let res = handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsRevoke {
principal: pid("ivy"),
capabilities: vec!["capsule:install".into()],
},
)
.await;
assert_success(&res);
let profile = kernel.profile_cache.resolve(&pid("ivy")).unwrap();
assert!(profile.revokes.iter().any(|r| r == "capsule:install"));
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_grant_is_idempotent_no_disk_growth_on_repeat() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "indy".into(),
groups: vec!["restricted".into()],
grants: Vec::new(),
},
)
.await;
for _ in 0..5 {
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("indy"),
capabilities: vec!["capsule:install".into(), "capsule:remove".into()],
unsafe_admin: false,
},
)
.await;
}
let profile = kernel.profile_cache.resolve(&pid("indy")).unwrap();
let install_count = profile
.grants
.iter()
.filter(|c| *c == "capsule:install")
.count();
let remove_count = profile
.grants
.iter()
.filter(|c| *c == "capsule:remove")
.count();
assert_eq!(install_count, 1, "duplicate grant: {:?}", profile.grants);
assert_eq!(remove_count, 1, "duplicate grant: {:?}", profile.grants);
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_revoke_is_idempotent_no_disk_growth_on_repeat() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "isaac".into(),
groups: vec!["admin".into()],
grants: Vec::new(),
},
)
.await;
for _ in 0..3 {
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsRevoke {
principal: pid("isaac"),
capabilities: vec!["self:*".into()],
},
)
.await;
}
let profile = kernel.profile_cache.resolve(&pid("isaac")).unwrap();
let count = profile.revokes.iter().filter(|c| *c == "self:*").count();
assert_eq!(count, 1, "duplicate revoke: {:?}", profile.revokes);
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_grant_rejects_invalid_capability_grammar() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "julia".into(),
groups: Vec::new(),
grants: Vec::new(),
},
)
.await;
let res = handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("julia"),
capabilities: vec!["system:shut down".into()], unsafe_admin: false,
},
)
.await;
assert_error_contains(&res, "rejected");
}
#[tokio::test(flavor = "multi_thread")]
async fn caps_grant_universal_requires_unsafe_admin_acknowledgement() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "luke".into(),
groups: Vec::new(),
grants: Vec::new(),
},
)
.await;
let rejected = handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("luke"),
capabilities: vec!["*".into()],
unsafe_admin: false,
},
)
.await;
assert_error_contains(&rejected, "universal admin");
assert_error_contains(&rejected, "unsafe_admin");
let path = PrincipalProfile::path_for(&kernel.astrid_home, &pid("luke"));
let profile = PrincipalProfile::load_from_path(&path).unwrap();
assert!(profile.grants.is_empty(), "rejected grant must not persist");
let scoped = handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("luke"),
capabilities: vec!["network:egress:*".into()],
unsafe_admin: false,
},
)
.await;
assert_success(&scoped);
let accepted = handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("luke"),
capabilities: vec!["*".into()],
unsafe_admin: true,
},
)
.await;
assert_success(&accepted);
let profile = PrincipalProfile::load_from_path(&path).unwrap();
assert!(profile.grants.contains(&"*".to_string()));
assert!(profile.grants.contains(&"network:egress:*".to_string()));
}
#[tokio::test(flavor = "multi_thread")]
async fn concurrent_caps_grants_serialized_by_admin_write_lock() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
&astrid_core::PrincipalId::default(),
AdminRequestKind::AgentCreate {
name: "kate".into(),
groups: vec!["restricted".into()],
grants: Vec::new(),
},
)
.await;
let k1 = Arc::clone(&kernel);
let k2 = Arc::clone(&kernel);
let t1 = tokio::spawn(async move {
handlers::dispatch(
&k1,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("kate"),
capabilities: vec!["capsule:install".into()],
unsafe_admin: false,
},
)
.await
});
let t2 = tokio::spawn(async move {
handlers::dispatch(
&k2,
&astrid_core::PrincipalId::default(),
AdminRequestKind::CapsGrant {
principal: pid("kate"),
capabilities: vec!["capsule:remove".into()],
unsafe_admin: false,
},
)
.await
});
let (r1, r2) = (t1.await.unwrap(), t2.await.unwrap());
assert_success(&r1);
assert_success(&r2);
let profile = kernel.profile_cache.resolve(&pid("kate")).unwrap();
assert!(profile.grants.iter().any(|c| c == "capsule:install"));
assert!(profile.grants.iter().any(|c| c == "capsule:remove"));
}