diff --git a/nexus/src/app/mod.rs b/nexus/src/app/mod.rs index 0407c91d74a..5b4a10b60a3 100644 --- a/nexus/src/app/mod.rs +++ b/nexus/src/app/mod.rs @@ -223,6 +223,16 @@ impl Nexus { &self.opctx_external_authn } + /// Returns an [`OpContext`] used for balancing services. + pub fn opctx_for_service_balancer(&self) -> OpContext { + OpContext::for_background( + self.log.new(o!("component" => "ServiceBalancer")), + Arc::clone(&self.authz), + authn::Context::internal_service_balancer(), + Arc::clone(&self.db_datastore), + ) + } + /// Used as the body of a "stub" endpoint -- one that's currently /// unimplemented but that we eventually intend to implement /// diff --git a/nexus/src/authn/mod.rs b/nexus/src/authn/mod.rs index 375ae70e583..e61cd4ef7a2 100644 --- a/nexus/src/authn/mod.rs +++ b/nexus/src/authn/mod.rs @@ -35,6 +35,7 @@ pub use crate::db::fixed_data::user_builtin::USER_EXTERNAL_AUTHN; pub use crate::db::fixed_data::user_builtin::USER_INTERNAL_API; pub use crate::db::fixed_data::user_builtin::USER_INTERNAL_READ; pub use crate::db::fixed_data::user_builtin::USER_SAGA_RECOVERY; +pub use crate::db::fixed_data::user_builtin::USER_SERVICE_BALANCER; use crate::db::model::ConsoleSession; use crate::authz; @@ -170,6 +171,11 @@ impl Context { Context::context_for_builtin_user(USER_DB_INIT.id) } + /// Returns an authenticated context for Nexus-driven service balancing. + pub fn internal_service_balancer() -> Context { + Context::context_for_builtin_user(USER_SERVICE_BALANCER.id) + } + fn context_for_builtin_user(user_builtin_id: Uuid) -> Context { Context { kind: Kind::Authenticated(Details { @@ -217,6 +223,7 @@ mod test { use super::USER_INTERNAL_API; use super::USER_INTERNAL_READ; use super::USER_SAGA_RECOVERY; + use super::USER_SERVICE_BALANCER; use super::USER_TEST_PRIVILEGED; use super::USER_TEST_UNPRIVILEGED; use crate::db::fixed_data::user_builtin::USER_EXTERNAL_AUTHN; @@ -251,6 +258,10 @@ mod test { let actor = authn.actor().unwrap(); assert_eq!(actor.actor_id(), USER_DB_INIT.id); + let authn = Context::internal_service_balancer(); + let actor = authn.actor().unwrap(); + assert_eq!(actor.actor_id(), USER_SERVICE_BALANCER.id); + let authn = Context::internal_saga_recovery(); let actor = authn.actor().unwrap(); assert_eq!(actor.actor_id(), USER_SAGA_RECOVERY.id); diff --git a/nexus/src/db/datastore.rs b/nexus/src/db/datastore.rs index 9c0cc6c17db..669c661706e 100644 --- a/nexus/src/db/datastore.rs +++ b/nexus/src/db/datastore.rs @@ -3250,6 +3250,7 @@ impl DataStore { let builtin_users = [ // Note: "db_init" is also a builtin user, but that one by necessity // is created with the database. + &*authn::USER_SERVICE_BALANCER, &*authn::USER_INTERNAL_API, &*authn::USER_INTERNAL_READ, &*authn::USER_EXTERNAL_AUTHN, diff --git a/nexus/src/db/fixed_data/role_assignment.rs b/nexus/src/db/fixed_data/role_assignment.rs index 94caf552a13..f6bbb951b6d 100644 --- a/nexus/src/db/fixed_data/role_assignment.rs +++ b/nexus/src/db/fixed_data/role_assignment.rs @@ -24,6 +24,13 @@ lazy_static! { *FLEET_ID, role_builtin::FLEET_ADMIN.role_name, ), + RoleAssignment::new( + IdentityType::UserBuiltin, + user_builtin::USER_SERVICE_BALANCER.id, + role_builtin::FLEET_ADMIN.resource_type, + *FLEET_ID, + role_builtin::FLEET_ADMIN.role_name, + ), // The "internal-read" user gets the "viewer" role on the sole // Fleet. This will grant them the ability to read various control diff --git a/nexus/src/db/fixed_data/user_builtin.rs b/nexus/src/db/fixed_data/user_builtin.rs index 1e9dee1b7bf..87f33fa3558 100644 --- a/nexus/src/db/fixed_data/user_builtin.rs +++ b/nexus/src/db/fixed_data/user_builtin.rs @@ -39,6 +39,15 @@ lazy_static! { "used for seeding initial database data", ); + /// Internal user for performing operations to manage the + /// provisioning of services across the fleet. + pub static ref USER_SERVICE_BALANCER: UserBuiltinConfig = + UserBuiltinConfig::new_static( + "001de000-05e4-4000-8000-00000000bac3", + "service-balancer", + "used for Nexus-driven service balancing", + ); + /// Internal user used by Nexus when handling internal API requests pub static ref USER_INTERNAL_API: UserBuiltinConfig = UserBuiltinConfig::new_static( @@ -82,9 +91,11 @@ mod test { use super::USER_INTERNAL_API; use super::USER_INTERNAL_READ; use super::USER_SAGA_RECOVERY; + use super::USER_SERVICE_BALANCER; #[test] fn test_builtin_user_ids_are_valid() { + assert_valid_uuid(&USER_SERVICE_BALANCER.id); assert_valid_uuid(&USER_DB_INIT.id); assert_valid_uuid(&USER_INTERNAL_API.id); assert_valid_uuid(&USER_EXTERNAL_AUTHN.id); diff --git a/nexus/tests/integration_tests/users_builtin.rs b/nexus/tests/integration_tests/users_builtin.rs index b06741a3067..ee4da338fcc 100644 --- a/nexus/tests/integration_tests/users_builtin.rs +++ b/nexus/tests/integration_tests/users_builtin.rs @@ -27,6 +27,9 @@ async fn test_users_builtin(cptestctx: &ControlPlaneTestContext) { let u = users.remove(&authn::USER_DB_INIT.name.to_string()).unwrap(); assert_eq!(u.identity.id, authn::USER_DB_INIT.id); + let u = + users.remove(&authn::USER_SERVICE_BALANCER.name.to_string()).unwrap(); + assert_eq!(u.identity.id, authn::USER_SERVICE_BALANCER.id); let u = users.remove(&authn::USER_INTERNAL_API.name.to_string()).unwrap(); assert_eq!(u.identity.id, authn::USER_INTERNAL_API.id); let u = users.remove(&authn::USER_INTERNAL_READ.name.to_string()).unwrap();