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
15 changes: 12 additions & 3 deletions nexus/src/authn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
pub mod external;

pub use crate::db::fixed_data::user_builtin::USER_DB_INIT;
pub use crate::db::fixed_data::user_builtin::USER_INTERNAL_API;
pub use crate::db::fixed_data::user_builtin::USER_SAGA_RECOVERY;
pub use crate::db::fixed_data::user_builtin::USER_TEST_PRIVILEGED;
pub use crate::db::fixed_data::user_builtin::USER_TEST_UNPRIVILEGED;
Expand Down Expand Up @@ -82,6 +83,11 @@ impl Context {
Context { kind: Kind::Unauthenticated, schemes_tried: vec![] }
}

/// Returns an authenticated context for handling internal API contexts
pub fn internal_api() -> Context {
Context::context_for_actor(USER_INTERNAL_API.id)
}

/// Returns an authenticated context for saga recovery
pub fn internal_saga_recovery() -> Context {
Context::context_for_actor(USER_SAGA_RECOVERY.id)
Expand All @@ -101,15 +107,13 @@ impl Context {
}

/// Returns an authenticated context for a special testing user
#[cfg(test)]
pub fn internal_test_user() -> Context {
Context::test_context_for_actor(USER_TEST_PRIVILEGED.id)
}

/// Returns an authenticated context for a specific user
///
/// This is used for unit testing the authorization rules.
#[cfg(test)]
/// This is used for testing.
pub fn test_context_for_actor(actor_id: Uuid) -> Context {
Context::context_for_actor(actor_id)
}
Expand All @@ -119,6 +123,7 @@ impl Context {
mod test {
use super::Context;
use super::USER_DB_INIT;
use super::USER_INTERNAL_API;
use super::USER_SAGA_RECOVERY;
use super::USER_TEST_PRIVILEGED;

Expand All @@ -142,6 +147,10 @@ mod test {
let authn = Context::internal_saga_recovery();
let actor = authn.actor().unwrap();
assert_eq!(actor.0, USER_SAGA_RECOVERY.id);

let authn = Context::internal_api();
let actor = authn.actor().unwrap();
assert_eq!(actor.0, USER_INTERNAL_API.id);
}
}

Expand Down
7 changes: 5 additions & 2 deletions nexus/src/authz/api_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,8 @@ pub struct ProjectChild {
}

impl ProjectChild {
pub fn id(&self) -> &Uuid {
&self.resource_id
pub fn id(&self) -> Uuid {
self.resource_id
}
}

Expand Down Expand Up @@ -472,3 +472,6 @@ impl ApiResourceError for ProjectChild {
self.lookup_type.clone().into_not_found(self.resource_type)
}
}

pub type Disk = ProjectChild;
pub type Instance = ProjectChild;
3 changes: 2 additions & 1 deletion nexus/src/authz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,12 @@ mod actor;

mod api_resources;
pub use api_resources::ApiResourceError;
pub use api_resources::Disk;
pub use api_resources::Fleet;
pub use api_resources::FleetChild;
pub use api_resources::Instance;
pub use api_resources::Organization;
pub use api_resources::Project;
pub use api_resources::ProjectChild;
pub use api_resources::FLEET;

mod context;
Expand Down
91 changes: 68 additions & 23 deletions nexus/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub struct ServerContext {
pub log: Logger,
/** authenticator for external HTTP requests */
pub external_authn: authn::external::Authenticator<Arc<ServerContext>>,
/** authentication context used for internal HTTP requests */
pub internal_authn: Arc<authn::Context>,
/** authorizer */
pub authz: Arc<authz::Authz>,
/** internal API request latency tracker */
Expand Down Expand Up @@ -91,6 +93,7 @@ impl ServerContext {
})
.collect();
let external_authn = authn::external::Authenticator::new(nexus_schemes);
let internal_authn = Arc::new(authn::Context::internal_api());
let authz = Arc::new(authz::Authz::new());
let create_tracker = |name: &str| {
let target = HttpService { name: name.to_string(), id: config.id };
Expand Down Expand Up @@ -145,6 +148,7 @@ impl ServerContext {
),
log,
external_authn,
internal_authn,
authz,
internal_latencies,
external_latencies,
Expand Down Expand Up @@ -196,11 +200,12 @@ pub struct OpContext {
enum OpKind {
/// Handling an external API request
ExternalApiRequest,
/// Handling an internal API request
InternalApiRequest,
/// Background operations in Nexus
Background,
#[cfg(test)]
/// Unit tests
UnitTest,
/// Automated testing (unit tests and integration tests)
Test,
}

impl OpContext {
Expand All @@ -220,12 +225,55 @@ impl OpContext {
datastore,
);

let request = rqctx.request.lock().await;
let (log, metadata) =
OpContext::log_and_metadata_for_request(rqctx, &authn).await;

Ok(OpContext {
log,
authz,
authn,
created_instant,
created_walltime,
metadata,
kind: OpKind::ExternalApiRequest,
})
}

/// Returns a context suitable for use in handling internal API operations
// TODO-security this should eventually do some kind of authentication
pub async fn for_internal_api(
rqctx: &dropshot::RequestContext<Arc<ServerContext>>,
) -> OpContext {
let created_instant = Instant::now();
let created_walltime = SystemTime::now();
let apictx = rqctx.context();
let authn = Arc::clone(&apictx.internal_authn);
let datastore = Arc::clone(apictx.nexus.datastore());
let authz = authz::Context::new(
Arc::clone(&authn),
Arc::clone(&apictx.authz),
datastore,
);

let (log, metadata) =
OpContext::log_and_metadata_for_request(rqctx, &authn).await;

OpContext {
log,
authz,
authn,
created_instant,
created_walltime,
metadata,
kind: OpKind::InternalApiRequest,
}
}

async fn log_and_metadata_for_request<T: Send + Sync + 'static>(
rqctx: &dropshot::RequestContext<T>,
authn: &authn::Context,
) -> (slog::Logger, BTreeMap<String, String>) {
let mut metadata = BTreeMap::new();
metadata.insert(String::from("request_id"), rqctx.request_id.clone());
metadata
.insert(String::from("http_method"), request.method().to_string());
metadata.insert(String::from("http_uri"), request.uri().to_string());

let log = if let Some(Actor(actor_id)) = authn.actor() {
metadata
Expand All @@ -240,15 +288,13 @@ impl OpContext {
rqctx.log.new(o!("authenticated" => false))
};

Ok(OpContext {
log,
authz,
authn,
created_instant,
created_walltime,
metadata,
kind: OpKind::ExternalApiRequest,
})
let request = rqctx.request.lock().await;
metadata.insert(String::from("request_id"), rqctx.request_id.clone());
metadata
.insert(String::from("http_method"), request.method().to_string());
metadata.insert(String::from("http_uri"), request.uri().to_string());

(log, metadata)
}

/// Returns a context suitable for use in background operations in Nexus
Expand Down Expand Up @@ -277,10 +323,9 @@ impl OpContext {
}
}

/// Returns a context suitable for automated unit tests where an OpContext
/// is needed outside of a Dropshot context
#[cfg(test)]
pub fn for_unit_tests(
/// Returns a context suitable for automated tests where an OpContext is
/// needed outside of a Dropshot context
pub fn for_tests(
log: slog::Logger,
datastore: Arc<DataStore>,
) -> OpContext {
Expand All @@ -299,7 +344,7 @@ impl OpContext {
created_instant,
created_walltime,
metadata: BTreeMap::new(),
kind: OpKind::UnitTest,
kind: OpKind::Test,
}
}

Expand Down Expand Up @@ -384,7 +429,7 @@ mod test {
let mut db = test_setup_database(&logctx.log).await;
let (_, datastore) =
crate::db::datastore::datastore_test(&logctx, &db).await;
let opctx = OpContext::for_unit_tests(logctx.log.new(o!()), datastore);
let opctx = OpContext::for_tests(logctx.log.new(o!()), datastore);

// Like in test_background_context(), this is essentially a test of the
// authorization policy. The unit tests assume this user can do
Expand Down
Loading