Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions nexus/db-queries/src/policy_test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,27 @@ async fn test_iam_roles_behavior() {
),
)));

// The built-in constant actors. Their privileges come from fixed Polar
// rules (db-init, internal-api) and seeded fleet role assignments
// (external-authn) rather than per-resource role grants, so the role users
// above never exercise them.
for (name, authn) in [
("db-init", authn::Context::internal_db_init()),
("internal-api", authn::Context::internal_api()),
("external-authn", authn::Context::external_authn()),
] {
let user_log = logctx.log.new(o!("actor" => name));
user_contexts.push(Arc::new((
String::from(name),
OpContext::for_background(
user_log,
Arc::clone(&authz),
authn,
Arc::clone(&datastore) as Arc<dyn nexus_auth::storage::Storage>,
),
)));
}

// Create an output stream that writes to stdout as well as an in-memory
// buffer. The test run will write a textual summary to the stream. Then
// we'll use use expectorate to verify it. We do this rather than assert
Expand Down
15 changes: 15 additions & 0 deletions nexus/db-queries/src/policy_test/resource_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ impl<'a> ResourceBuilder<'a> {
self.resources.push(Arc::new(resource));
}

/// Register a user as a test actor without granting it any role.
///
/// Unlike [`Self::new_resource_with_users`], this creates no role
/// assignment. Bind `user_id` to the owning user of a resource already in
/// the set (e.g. a `SiloUser`'s own id) to exercise identity-based
/// self-access, which role assignments alone can't reach.
pub fn push_user(&mut self, username: &str, user_id: SiloUserUuid) {
self.users.push((username.to_string(), user_id));
}

/// Register a new resource for later testing and also: for each supported
/// role on this resource, create a user that has that role on this resource
pub async fn new_resource_with_users<T>(&mut self, resource: T)
Expand Down Expand Up @@ -246,6 +256,7 @@ impl_dyn_authorized_resource_for_resource!(authz::AffinityGroup);
impl_dyn_authorized_resource_for_resource!(authz::AntiAffinityGroup);
impl_dyn_authorized_resource_for_resource!(authz::Blueprint);
impl_dyn_authorized_resource_for_resource!(authz::Certificate);
impl_dyn_authorized_resource_for_resource!(authz::ConsoleSession);
impl_dyn_authorized_resource_for_resource!(authz::DeviceAccessToken);
impl_dyn_authorized_resource_for_resource!(authz::DeviceAuthRequest);
impl_dyn_authorized_resource_for_resource!(authz::Disk);
Expand All @@ -259,11 +270,13 @@ impl_dyn_authorized_resource_for_resource!(authz::InstanceNetworkInterface);
impl_dyn_authorized_resource_for_resource!(authz::InternetGateway);
impl_dyn_authorized_resource_for_resource!(authz::InternetGatewayIpAddress);
impl_dyn_authorized_resource_for_resource!(authz::InternetGatewayIpPool);
impl_dyn_authorized_resource_for_resource!(authz::IpPool);
impl_dyn_authorized_resource_for_resource!(authz::LoopbackAddress);
impl_dyn_authorized_resource_for_resource!(authz::Rack);
impl_dyn_authorized_resource_for_resource!(authz::PhysicalDisk);
impl_dyn_authorized_resource_for_resource!(authz::Project);
impl_dyn_authorized_resource_for_resource!(authz::ProjectImage);
impl_dyn_authorized_resource_for_resource!(authz::RouterRoute);
impl_dyn_authorized_resource_for_resource!(authz::SamlIdentityProvider);
impl_dyn_authorized_resource_for_resource!(authz::ScimClientBearerToken);
impl_dyn_authorized_resource_for_resource!(authz::Service);
Expand All @@ -279,7 +292,9 @@ impl_dyn_authorized_resource_for_resource!(authz::SupportBundle);
impl_dyn_authorized_resource_for_resource!(authz::TufArtifact);
impl_dyn_authorized_resource_for_resource!(authz::TufRepo);
impl_dyn_authorized_resource_for_resource!(authz::TufTrustRoot);
impl_dyn_authorized_resource_for_resource!(authz::UserBuiltin);
impl_dyn_authorized_resource_for_resource!(authz::Vpc);
impl_dyn_authorized_resource_for_resource!(authz::VpcRouter);
impl_dyn_authorized_resource_for_resource!(authz::VpcSubnet);
impl_dyn_authorized_resource_for_resource!(authz::Alert);
impl_dyn_authorized_resource_for_resource!(authz::AlertReceiver);
Expand Down
52 changes: 47 additions & 5 deletions nexus/db-queries/src/policy_test/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use super::resource_builder::ResourceSet;
use nexus_auth::authz;
use omicron_common::api::external::LookupType;
use omicron_uuid_kinds::AccessTokenKind;
use omicron_uuid_kinds::BuiltInUserKind;
use omicron_uuid_kinds::ConsoleSessionKind;
use omicron_uuid_kinds::GenericUuid;
use omicron_uuid_kinds::PhysicalDiskUuid;
use omicron_uuid_kinds::RackUuid;
Expand Down Expand Up @@ -205,6 +207,29 @@ pub async fn make_resources(
LookupType::by_id(subnet_pool_id),
));

let ip_pool_id = "f9bf2e93-1f3f-4f4e-9c0f-3c8f6a8d6f2a".parse().unwrap();
builder.new_resource(authz::IpPool::new(
authz::FLEET,
ip_pool_id,
LookupType::by_id(ip_pool_id),
));

let console_session_id: TypedUuid<ConsoleSessionKind> =
"a1b2c3d4-0000-4000-8000-000000000001".parse().unwrap();
builder.new_resource(authz::ConsoleSession::new(
authz::FLEET,
console_session_id,
LookupType::by_id(console_session_id),
));

let user_builtin_id: TypedUuid<BuiltInUserKind> =
"a1b2c3d4-0000-4000-8000-000000000002".parse().unwrap();
builder.new_resource(authz::UserBuiltin::new(
authz::FLEET,
user_builtin_id,
LookupType::by_id(user_builtin_id),
));

builder.build()
}

Expand Down Expand Up @@ -286,6 +311,13 @@ async fn make_silo(
LookupType::ByName(format!("{}-user", silo_name)),
);
builder.new_resource(silo_user.clone());
// Register this silo user itself as a roleless actor (only in the branch
// whose silo is the main silo, where actors live). This exercises
// identity-based self-access: the actor acting on its own SiloUser, SshKey,
// and session/token lists, which no role assignment can grant.
if first_branch {
builder.push_user(&format!("{}-user-self", silo_name), silo_user_id);
}
let ssh_key_id = Uuid::new_v4();
builder.new_resource(authz::SshKey::new(
silo_user.clone(),
Expand Down Expand Up @@ -411,6 +443,19 @@ async fn make_project(
Uuid::new_v4(),
LookupType::ByName(format!("{}-subnet1", vpc1_name)),
));
let router_name = format!("{}-router1", vpc1_name);
let router = authz::VpcRouter::new(
vpc1.clone(),
Uuid::new_v4(),
LookupType::ByName(router_name.clone()),
);
builder.new_resource(router.clone());
// Test a resource nested three levels below Project
builder.new_resource(authz::RouterRoute::new(
router,
Uuid::new_v4(),
LookupType::ByName(format!("{}-route1", router_name)),
));

builder.new_resource(authz::Snapshot::new(
project.clone(),
Expand Down Expand Up @@ -522,11 +567,8 @@ pub fn exempted_authz_classes() -> BTreeSet<String> {
// need to call the macro `impl_dyn_authorized_resource_for_resource!`
// for the type you are implementing the test for. See
// resource_builder.rs for examples.
authz::IpPool::get_polar_class(),
authz::VpcRouter::get_polar_class(),
authz::RouterRoute::get_polar_class(),
authz::ConsoleSession::get_polar_class(),
authz::UserBuiltin::get_polar_class(),
//
// none yet.
]
.into_iter()
.map(|c| c.name)
Expand Down
Loading
Loading