Skip to content
Open
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
44 changes: 33 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@ termtree = "0.5.1"
textwrap = { version = "0.16.2", features = [ "terminal_size" ] }
test-strategy = "0.4.3"
thiserror = "2.0"
tinystr = { version = "0.8.1", features = ["serde"] }
tofino = { git = "https://github.com/oxidecomputer/tofino", branch = "main" }
tokio = "1.47.0"
tokio-postgres = { version = "0.7", features = [ "with-chrono-0_4", "with-uuid-1" ] }
Expand Down
1 change: 1 addition & 0 deletions nexus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ slog-term.workspace = true
steno.workspace = true
tempfile.workspace = true
thiserror.workspace = true
tinystr.workspace = true
tokio = { workspace = true, features = ["full"] }
tokio-postgres = { workspace = true, features = ["with-serde_json-1"] }
tokio-util = { workspace = true, features = ["codec", "rt"] }
Expand Down
1 change: 1 addition & 0 deletions nexus/auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ nexus-types.workspace = true
omicron-common.workspace = true
omicron-uuid-kinds.workspace = true
omicron-workspace-hack.workspace = true
tinystr.workspace = true

[dev-dependencies]
omicron-test-utils.workspace = true
2 changes: 2 additions & 0 deletions nexus/auth/src/authn/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ mod test {
user_builtin_id: "1c91bab2-4841-669f-cc32-de80da5bbf39"
.parse()
.unwrap(),
user_name: "actor1",
};
let grunt1 = Box::new(GruntScheme {
name: name1,
Expand All @@ -283,6 +284,7 @@ mod test {
user_builtin_id: "799684af-533a-cb66-b5ac-ab55a791d5ef"
.parse()
.unwrap(),
user_name: "actor2",
};
let grunt2 = Box::new(GruntScheme {
name: name2,
Expand Down
9 changes: 9 additions & 0 deletions nexus/auth/src/authn/external/session_cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub trait Session {
fn id(&self) -> ConsoleSessionUuid;
fn silo_user_id(&self) -> SiloUserUuid;
fn silo_id(&self) -> Uuid;
fn silo_name(&self) -> &str;
fn time_last_used(&self) -> DateTime<Utc>;
fn time_created(&self) -> DateTime<Utc>;
}
Expand Down Expand Up @@ -128,6 +129,7 @@ where
let actor = Actor::SiloUser {
silo_user_id: session.silo_user_id(),
silo_id: session.silo_id(),
silo_name: session.silo_name().parse().unwrap(),
};

// if the session has gone unused for longer than idle_timeout, it is
Expand Down Expand Up @@ -220,6 +222,7 @@ mod test {
token: String,
silo_user_id: SiloUserUuid,
silo_id: Uuid,
silo_name: &'static str,
time_created: DateTime<Utc>,
time_last_used: DateTime<Utc>,
}
Expand All @@ -234,6 +237,9 @@ mod test {
fn silo_id(&self) -> Uuid {
self.silo_id
}
fn silo_name(&self) -> &str {
self.silo_name
}
fn time_created(&self) -> DateTime<Utc> {
self.time_created
}
Expand Down Expand Up @@ -335,6 +341,7 @@ mod test {
token: "abc".to_string(),
silo_user_id: SiloUserUuid::new_v4(),
silo_id: Uuid::new_v4(),
silo_name: "test-silo",
time_last_used: Utc::now() - Duration::hours(2),
time_created: Utc::now() - Duration::hours(2),
}]),
Expand All @@ -361,6 +368,7 @@ mod test {
token: "abc".to_string(),
silo_user_id: SiloUserUuid::new_v4(),
silo_id: Uuid::new_v4(),
silo_name: "test-silo",
time_last_used: Utc::now(),
time_created: Utc::now() - Duration::hours(20),
}]),
Expand Down Expand Up @@ -388,6 +396,7 @@ mod test {
token: "abc".to_string(),
silo_user_id: SiloUserUuid::new_v4(),
silo_id: Uuid::new_v4(),
silo_name: "test-silo",
time_last_used,
time_created: Utc::now(),
}]),
Expand Down
3 changes: 2 additions & 1 deletion nexus/auth/src/authn/external/spoof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ static SPOOF_RESERVED_BAD_CREDS_ACTOR: LazyLock<Actor> =
user_builtin_id: "22222222-2222-2222-2222-222222222222"
.parse()
.unwrap(),
user_name: "spoof-user".parse().unwrap(),
});

/// Complete HTTP header value to trigger the "bad actor" error
Expand Down Expand Up @@ -108,7 +109,7 @@ where
match ctx.silo_user_silo(silo_user_id).await {
Err(error) => SchemeResult::Failed(error),
Ok(silo_id) => {
let actor = Actor::SiloUser { silo_id, silo_user_id };
let actor = Actor::SiloUser { silo_id, silo_user_id, silo_name: "spoof-user".parse().unwrap()};
SchemeResult::Authenticated(Details { actor })
}
}
Expand Down
42 changes: 28 additions & 14 deletions nexus/auth/src/authn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use tinystr::TinyAsciiStr;
use uuid::Uuid;

/// Describes how the actor performing the current operation is authenticated
Expand Down Expand Up @@ -169,40 +170,40 @@ impl Context {

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

/// Returns an authenticated context for saga recovery
pub fn internal_saga_recovery() -> Context {
Context::context_for_builtin_user(USER_SAGA_RECOVERY.id)
Context::context_for_builtin_user(USER_SAGA_RECOVERY.id, USER_SAGA_RECOVERY.name.as_str())
}

/// Returns an authenticated context for use by internal resource allocation
pub fn internal_read() -> Context {
Context::context_for_builtin_user(USER_INTERNAL_READ.id)
Context::context_for_builtin_user(USER_INTERNAL_READ.id, USER_INTERNAL_READ.name.as_str())
}

/// Returns an authenticated context for use for authenticating external
/// requests
pub fn external_authn() -> Context {
Context::context_for_builtin_user(USER_EXTERNAL_AUTHN.id)
Context::context_for_builtin_user(USER_EXTERNAL_AUTHN.id, USER_EXTERNAL_AUTHN.name.as_str())
}

/// Returns an authenticated context for Nexus-startup database
/// initialization
pub fn internal_db_init() -> Context {
Context::context_for_builtin_user(USER_DB_INIT.id)
Context::context_for_builtin_user(USER_DB_INIT.id, USER_DB_INIT.name.as_str())
}

/// Returns an authenticated context for Nexus-driven service balancing.
pub fn internal_service_balancer() -> Context {
Context::context_for_builtin_user(USER_SERVICE_BALANCER.id)
Context::context_for_builtin_user(USER_SERVICE_BALANCER.id, USER_SERVICE_BALANCER.name.as_str())
}

fn context_for_builtin_user(user_builtin_id: BuiltInUserUuid) -> Context {
fn context_for_builtin_user(user_builtin_id: BuiltInUserUuid, user_name: &'static str) -> Context {
Context {
kind: Kind::Authenticated(
Details { actor: Actor::UserBuiltin { user_builtin_id } },
Details { actor: Actor::UserBuiltin { user_builtin_id, user_name: user_name.parse().unwrap() } },
None,
),
schemes_tried: Vec::new(),
Expand All @@ -219,6 +220,7 @@ impl Context {
actor: Actor::SiloUser {
silo_user_id: USER_TEST_PRIVILEGED.id(),
silo_id: USER_TEST_PRIVILEGED.silo_id,
silo_name: "test-privileged".parse().unwrap(),
},
},
Some(SiloAuthnPolicy::try_from(&*DEFAULT_SILO).unwrap()),
Expand All @@ -234,6 +236,7 @@ impl Context {
Context::for_test_user(
USER_TEST_UNPRIVILEGED.id(),
USER_TEST_UNPRIVILEGED.silo_id,
"test-unprivileged",
SiloAuthnPolicy::try_from(&*DEFAULT_SILO).unwrap(),
)
}
Expand All @@ -243,11 +246,12 @@ impl Context {
pub fn for_test_user(
silo_user_id: SiloUserUuid,
silo_id: Uuid,
silo_name: &'static str,
silo_authn_policy: SiloAuthnPolicy,
) -> Context {
Context {
kind: Kind::Authenticated(
Details { actor: Actor::SiloUser { silo_user_id, silo_id } },
Details { actor: Actor::SiloUser { silo_user_id, silo_id, silo_name: silo_name.parse().unwrap() } },
Some(silo_authn_policy),
),
schemes_tried: Vec::new(),
Expand Down Expand Up @@ -368,8 +372,15 @@ pub struct Details {
/// Who is performing an operation
#[derive(Clone, Copy, Deserialize, Eq, PartialEq, Serialize)]
pub enum Actor {
UserBuiltin { user_builtin_id: BuiltInUserUuid },
SiloUser { silo_user_id: SiloUserUuid, silo_id: Uuid },
UserBuiltin {
user_builtin_id: BuiltInUserUuid,
user_name: TinyAsciiStr<32>,
},
SiloUser {
silo_user_id: SiloUserUuid,
silo_id: Uuid,
silo_name: TinyAsciiStr<32>,
},
}

impl Actor {
Expand All @@ -389,7 +400,7 @@ impl Actor {

pub fn built_in_user_id(&self) -> Option<BuiltInUserUuid> {
match self {
Actor::UserBuiltin { user_builtin_id } => Some(*user_builtin_id),
Actor::UserBuiltin { user_builtin_id, .. } => Some(*user_builtin_id),
Actor::SiloUser { .. } => None,
}
}
Expand All @@ -416,14 +427,16 @@ impl std::fmt::Debug for Actor {
// Do NOT include sensitive fields (e.g., private key or a bearer
// token) in this output!
match self {
Actor::UserBuiltin { user_builtin_id } => f
Actor::UserBuiltin { user_builtin_id, user_name } => f
.debug_struct("Actor::UserBuiltin")
.field("user_builtin_id", &user_builtin_id)
.field("user_name", &user_name)
.finish_non_exhaustive(),
Actor::SiloUser { silo_user_id, silo_id } => f
Actor::SiloUser { silo_user_id, silo_id, silo_name } => f
.debug_struct("Actor::SiloUser")
.field("silo_user_id", &silo_user_id)
.field("silo_id", &silo_id)
.field("silo_name", &silo_name)
.finish_non_exhaustive(),
}
}
Expand All @@ -434,6 +447,7 @@ impl std::fmt::Debug for Actor {
pub struct ConsoleSessionWithSiloId {
pub console_session: nexus_db_model::ConsoleSession,
pub silo_id: Uuid,
pub silo_name: TinyAsciiStr<32>,
}

/// Label for a particular authentication scheme (used in log messages and
Expand Down
2 changes: 2 additions & 0 deletions nexus/auth/src/authz/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl oso::PolarClass for AuthenticatedActor {
AuthenticatedActor {
actor: authn::Actor::UserBuiltin {
user_builtin_id: authn::USER_DB_INIT.id,
user_name: authn::USER_DB_INIT.name.as_str().parse().unwrap(),
},
roles: RoleSet::new(),
silo_policy: None,
Expand All @@ -116,6 +117,7 @@ impl oso::PolarClass for AuthenticatedActor {
AuthenticatedActor {
actor: authn::Actor::UserBuiltin {
user_builtin_id: authn::USER_INTERNAL_API.id,
user_name: authn::USER_INTERNAL_API.name.as_str().parse().unwrap(),
},
roles: RoleSet::new(),
silo_policy: None,
Expand Down
Loading