From 450f906e8f54e7b6999de8256a74730d7475f6e3 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Fri, 31 May 2024 18:53:35 -0700 Subject: [PATCH] [nexus] Split authn/authz and db-fixed-data into new crates (#5849) As a part of the ongoing effort to split Nexus into smaller pieces, this PR splits out two new crates: - `nexus-auth` takes the contents of `nexus/db-queries/src/auth{n,z}`, as well as `nexus/db-queries/src/context.rs`, and separates this logic into a new bespoke crate. Although this crate **does** have a dependency on the datastore itself, it only actually invokes a single method, and can be abstracted via a new trait, defined in `nexus/auth/storage`. - `nexus-db-fixed-data` takes the contents of `nexus/db-queries/src/db/fixed-data` and separates this logic into a new crate. --- Cargo.lock | 67 ++++- Cargo.toml | 6 + nexus/Cargo.toml | 1 + nexus/auth/Cargo.toml | 48 ++++ nexus/auth/build.rs | 10 + .../src/authn/external/cookies.rs | 2 + .../src/authn/external/mod.rs | 1 + .../src/authn/external/session_cookie.rs | 1 + .../src/authn/external/spoof.rs | 1 + .../src/authn/external/token.rs | 0 nexus/{db-queries => auth}/src/authn/mod.rs | 40 +-- nexus/{db-queries => auth}/src/authn/saga.rs | 0 nexus/{db-queries => auth}/src/authn/silos.rs | 61 +---- nexus/{db-queries => auth}/src/authz/actor.rs | 0 .../src/authz/api_resources.rs | 240 ++++++------------ .../{db-queries => auth}/src/authz/context.rs | 108 ++++---- nexus/{db-queries => auth}/src/authz/mod.rs | 7 +- .../src/authz/omicron.polar | 0 .../src/authz/oso_generic.rs | 25 +- nexus/{db-queries => auth}/src/authz/roles.rs | 11 +- nexus/{db-queries => auth}/src/context.rs | 65 +++-- nexus/auth/src/lib.rs | 11 + nexus/auth/src/storage.rs | 27 ++ nexus/db-fixed-data/Cargo.toml | 25 ++ nexus/db-fixed-data/build.rs | 10 + .../src}/allow_list.rs | 0 .../mod.rs => db-fixed-data/src/lib.rs} | 0 .../src}/project.rs | 10 +- .../src}/role_assignment.rs | 4 +- .../src}/role_builtin.rs | 2 +- .../fixed_data => db-fixed-data/src}/silo.rs | 10 +- .../src}/silo_user.rs | 43 ++-- .../src}/user_builtin.rs | 0 .../fixed_data => db-fixed-data/src}/vpc.rs | 8 +- .../src}/vpc_firewall_rule.rs | 0 .../src}/vpc_subnet.rs | 2 +- nexus/db-queries/Cargo.toml | 14 +- .../db-queries/src/db/datastore/allow_list.rs | 2 +- nexus/db-queries/src/db/datastore/auth.rs | 81 ++++++ .../src/db/datastore/cockroachdb_settings.rs | 6 +- .../src/db/datastore/identity_provider.rs | 48 ++++ nexus/db-queries/src/db/datastore/instance.rs | 3 +- nexus/db-queries/src/db/datastore/mod.rs | 18 +- .../src/db/datastore/network_interface.rs | 2 +- nexus/db-queries/src/db/datastore/project.rs | 4 +- .../src/db/datastore/pub_test_utils.rs | 8 +- nexus/db-queries/src/db/datastore/rack.rs | 8 +- nexus/db-queries/src/db/datastore/role.rs | 65 +---- nexus/db-queries/src/db/datastore/silo.rs | 2 +- .../db-queries/src/db/datastore/silo_user.rs | 4 +- .../virtual_provisioning_collection.rs | 7 +- nexus/db-queries/src/db/datastore/vpc.rs | 20 +- nexus/db-queries/src/db/lookup.rs | 6 +- nexus/db-queries/src/db/mod.rs | 2 +- .../virtual_provisioning_collection_update.rs | 2 +- nexus/db-queries/src/db/saga_recovery.rs | 10 +- nexus/db-queries/src/lib.rs | 12 +- .../src/{authz => }/policy_test/coverage.rs | 5 +- .../src/{authz => }/policy_test/mod.rs | 18 +- .../policy_test/resource_builder.rs | 96 ++++--- .../src/{authz => }/policy_test/resources.rs | 6 +- nexus/db-queries/tests/output/authz-roles.out | 2 +- nexus/src/app/mod.rs | 18 +- nexus/src/app/test_interfaces.rs | 12 +- nexus/src/external_api/console_api.rs | 21 +- nexus/src/populate.rs | 4 +- nexus/tests/integration_tests/saml.rs | 25 +- nexus/tests/integration_tests/silos.rs | 27 +- workspace-hack/Cargo.toml | 1 + 69 files changed, 800 insertions(+), 605 deletions(-) create mode 100644 nexus/auth/Cargo.toml create mode 100644 nexus/auth/build.rs rename nexus/{db-queries => auth}/src/authn/external/cookies.rs (98%) rename nexus/{db-queries => auth}/src/authn/external/mod.rs (99%) rename nexus/{db-queries => auth}/src/authn/external/session_cookie.rs (99%) rename nexus/{db-queries => auth}/src/authn/external/spoof.rs (99%) rename nexus/{db-queries => auth}/src/authn/external/token.rs (100%) rename nexus/{db-queries => auth}/src/authn/mod.rs (94%) rename nexus/{db-queries => auth}/src/authn/saga.rs (100%) rename nexus/{db-queries => auth}/src/authn/silos.rs (86%) rename nexus/{db-queries => auth}/src/authz/actor.rs (100%) rename nexus/{db-queries => auth}/src/authz/api_resources.rs (83%) rename nexus/{db-queries => auth}/src/authz/context.rs (80%) rename nexus/{db-queries => auth}/src/authz/mod.rs (98%) rename nexus/{db-queries => auth}/src/authz/omicron.polar (100%) rename nexus/{db-queries => auth}/src/authz/oso_generic.rs (96%) rename nexus/{db-queries => auth}/src/authz/roles.rs (96%) rename nexus/{db-queries => auth}/src/context.rs (92%) create mode 100644 nexus/auth/src/lib.rs create mode 100644 nexus/auth/src/storage.rs create mode 100644 nexus/db-fixed-data/Cargo.toml create mode 100644 nexus/db-fixed-data/build.rs rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/allow_list.rs (100%) rename nexus/{db-queries/src/db/fixed_data/mod.rs => db-fixed-data/src/lib.rs} (100%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/project.rs (79%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/role_assignment.rs (97%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/role_builtin.rs (99%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/silo.rs (91%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/silo_user.rs (66%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/user_builtin.rs (100%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/vpc.rs (91%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/vpc_firewall_rule.rs (100%) rename nexus/{db-queries/src/db/fixed_data => db-fixed-data/src}/vpc_subnet.rs (98%) create mode 100644 nexus/db-queries/src/db/datastore/auth.rs rename nexus/db-queries/src/{authz => }/policy_test/coverage.rs (97%) rename nexus/db-queries/src/{authz => }/policy_test/mod.rs (97%) rename nexus/db-queries/src/{authz => }/policy_test/resource_builder.rs (74%) rename nexus/db-queries/src/{authz => }/policy_test/resources.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 9072aff98c..4f4fa019c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4473,6 +4473,44 @@ dependencies = [ "rustc_version 0.1.7", ] +[[package]] +name = "nexus-auth" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "authz-macros", + "base64 0.22.1", + "chrono", + "cookie 0.18.1", + "dropshot", + "futures", + "headers", + "http 0.2.12", + "hyper 0.14.28", + "newtype_derive", + "nexus-db-fixed-data", + "nexus-db-model", + "nexus-types", + "omicron-common", + "omicron-rpaths", + "omicron-test-utils", + "omicron-uuid-kinds", + "omicron-workspace-hack", + "once_cell", + "openssl", + "oso", + "pq-sys", + "samael", + "serde", + "serde_urlencoded", + "slog", + "strum", + "thiserror", + "tokio", + "uuid", +] + [[package]] name = "nexus-client" version = "0.1.0" @@ -4515,6 +4553,21 @@ dependencies = [ "uuid", ] +[[package]] +name = "nexus-db-fixed-data" +version = "0.1.0" +dependencies = [ + "nexus-db-model", + "nexus-types", + "omicron-common", + "omicron-rpaths", + "omicron-workspace-hack", + "once_cell", + "pq-sys", + "strum", + "uuid", +] + [[package]] name = "nexus-db-model" version = "0.1.0" @@ -4568,14 +4621,11 @@ dependencies = [ "assert_matches", "async-bb8-diesel", "async-trait", - "authz-macros", - "base64 0.22.1", "bb8", "camino", "camino-tempfile", "chrono", "const_format", - "cookie 0.18.1", "db-macros", "diesel", "diesel-dtrace", @@ -4583,17 +4633,15 @@ dependencies = [ "expectorate", "futures", "gateway-client", - "headers", - "http 0.2.12", - "hyper 0.14.28", "hyper-rustls 0.26.0", "illumos-utils", "internal-dns", "ipnetwork", "itertools 0.12.1", "macaddr", - "newtype_derive", + "nexus-auth", "nexus-config", + "nexus-db-fixed-data", "nexus-db-model", "nexus-inventory", "nexus-reconfigurator-planning", @@ -4608,7 +4656,6 @@ dependencies = [ "omicron-workspace-hack", "once_cell", "openapiv3", - "openssl", "oso", "oximeter", "oxnet", @@ -4623,12 +4670,10 @@ dependencies = [ "ref-cast", "regex", "rustls 0.22.4", - "samael", "schemars", "semver 1.0.23", "serde", "serde_json", - "serde_urlencoded", "serde_with", "sled-agent-client", "slog", @@ -5437,6 +5482,7 @@ dependencies = [ "itertools 0.12.1", "macaddr", "mg-admin-client", + "nexus-auth", "nexus-client", "nexus-config", "nexus-db-model", @@ -5921,6 +5967,7 @@ dependencies = [ "trust-dns-proto", "unicode-bidi", "unicode-normalization", + "unicode-xid", "usdt 0.5.0", "usdt-impl 0.5.0", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 5c0433a662..4eb76f5859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ members = [ "nexus", "nexus-config", "nexus/authz-macros", + "nexus/auth", + "nexus/db-fixed-data", "nexus/db-macros", "nexus/db-model", "nexus/db-queries", @@ -123,9 +125,11 @@ default-members = [ "nexus", "nexus-config", "nexus/authz-macros", + "nexus/auth", "nexus/macros-common", "nexus/metrics-producer-gc", "nexus/networking", + "nexus/db-fixed-data", "nexus/db-macros", "nexus/db-model", "nexus/db-queries", @@ -317,8 +321,10 @@ newtype_derive = "0.1.6" mg-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "5630887d0373857f77cb264f84aa19bdec720ce3" } ddm-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "5630887d0373857f77cb264f84aa19bdec720ce3" } multimap = "0.10.0" +nexus-auth = { path = "nexus/auth" } nexus-client = { path = "clients/nexus-client" } nexus-config = { path = "nexus-config" } +nexus-db-fixed-data = { path = "nexus/db-fixed-data" } nexus-db-model = { path = "nexus/db-model" } nexus-db-queries = { path = "nexus/db-queries" } nexus-defaults = { path = "nexus/defaults" } diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index 0b0bd097bc..58a1e824cb 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -86,6 +86,7 @@ tough.workspace = true trust-dns-resolver.workspace = true uuid.workspace = true +nexus-auth.workspace = true nexus-defaults.workspace = true nexus-db-model.workspace = true nexus-db-queries.workspace = true diff --git a/nexus/auth/Cargo.toml b/nexus/auth/Cargo.toml new file mode 100644 index 0000000000..1a926f1789 --- /dev/null +++ b/nexus/auth/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "nexus-auth" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[lints] +workspace = true + +[build-dependencies] +omicron-rpaths.workspace = true + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +base64.workspace = true +chrono.workspace = true +cookie.workspace = true +dropshot.workspace = true +futures.workspace = true +headers.workspace = true +http.workspace = true +hyper.workspace = true +newtype_derive.workspace = true +# See omicron-rpaths for more about the "pq-sys" dependency. +pq-sys = "*" +once_cell.workspace = true +openssl.workspace = true +oso.workspace = true +samael.workspace = true +serde.workspace = true +serde_urlencoded.workspace = true +slog.workspace = true +strum.workspace = true +thiserror.workspace = true +tokio = { workspace = true, features = ["full"] } +uuid.workspace = true + +authz-macros.workspace = true +nexus-db-fixed-data.workspace = true +nexus-db-model.workspace = true +nexus-types.workspace = true +omicron-common.workspace = true +omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true + +[dev-dependencies] +omicron-test-utils.workspace = true diff --git a/nexus/auth/build.rs b/nexus/auth/build.rs new file mode 100644 index 0000000000..1ba9acd41c --- /dev/null +++ b/nexus/auth/build.rs @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// See omicron-rpaths for documentation. +// NOTE: This file MUST be kept in sync with the other build.rs files in this +// repository. +fn main() { + omicron_rpaths::configure_default_omicron_rpaths(); +} diff --git a/nexus/db-queries/src/authn/external/cookies.rs b/nexus/auth/src/authn/external/cookies.rs similarity index 98% rename from nexus/db-queries/src/authn/external/cookies.rs rename to nexus/auth/src/authn/external/cookies.rs index e3ad2e3264..35e697475b 100644 --- a/nexus/db-queries/src/authn/external/cookies.rs +++ b/nexus/auth/src/authn/external/cookies.rs @@ -9,6 +9,8 @@ use dropshot::{ ApiEndpointBodyContentType, ExtensionMode, ExtractorMetadata, HttpError, RequestContext, ServerContext, SharedExtractor, }; +use newtype_derive::NewtypeDeref; +use newtype_derive::NewtypeFrom; pub fn parse_cookies( headers: &http::HeaderMap, diff --git a/nexus/db-queries/src/authn/external/mod.rs b/nexus/auth/src/authn/external/mod.rs similarity index 99% rename from nexus/db-queries/src/authn/external/mod.rs rename to nexus/auth/src/authn/external/mod.rs index 623544d38c..ccb7218285 100644 --- a/nexus/db-queries/src/authn/external/mod.rs +++ b/nexus/auth/src/authn/external/mod.rs @@ -9,6 +9,7 @@ use super::SiloAuthnPolicy; use crate::authn; use async_trait::async_trait; use authn::Reason; +use slog::trace; use std::borrow::Borrow; use uuid::Uuid; diff --git a/nexus/db-queries/src/authn/external/session_cookie.rs b/nexus/auth/src/authn/external/session_cookie.rs similarity index 99% rename from nexus/db-queries/src/authn/external/session_cookie.rs rename to nexus/auth/src/authn/external/session_cookie.rs index 74faafef9b..7811bf2826 100644 --- a/nexus/db-queries/src/authn/external/session_cookie.rs +++ b/nexus/auth/src/authn/external/session_cookie.rs @@ -13,6 +13,7 @@ use async_trait::async_trait; use chrono::{DateTime, Duration, Utc}; use dropshot::HttpError; use http::HeaderValue; +use slog::debug; use uuid::Uuid; // many parts of the implementation will reference this OWASP guide diff --git a/nexus/db-queries/src/authn/external/spoof.rs b/nexus/auth/src/authn/external/spoof.rs similarity index 99% rename from nexus/db-queries/src/authn/external/spoof.rs rename to nexus/auth/src/authn/external/spoof.rs index 9b5ed94bde..326d529431 100644 --- a/nexus/db-queries/src/authn/external/spoof.rs +++ b/nexus/auth/src/authn/external/spoof.rs @@ -17,6 +17,7 @@ use async_trait::async_trait; use headers::authorization::{Authorization, Bearer}; use headers::HeaderMapExt; use once_cell::sync::Lazy; +use slog::debug; use uuid::Uuid; // This scheme is intended for demos, development, and testing until we have a diff --git a/nexus/db-queries/src/authn/external/token.rs b/nexus/auth/src/authn/external/token.rs similarity index 100% rename from nexus/db-queries/src/authn/external/token.rs rename to nexus/auth/src/authn/external/token.rs diff --git a/nexus/db-queries/src/authn/mod.rs b/nexus/auth/src/authn/mod.rs similarity index 94% rename from nexus/db-queries/src/authn/mod.rs rename to nexus/auth/src/authn/mod.rs index 305c359820..08b27b9773 100644 --- a/nexus/db-queries/src/authn/mod.rs +++ b/nexus/auth/src/authn/mod.rs @@ -28,22 +28,21 @@ pub mod external; pub mod saga; pub mod silos; -pub use crate::db::fixed_data::silo_user::USER_TEST_PRIVILEGED; -pub use crate::db::fixed_data::silo_user::USER_TEST_UNPRIVILEGED; -pub use crate::db::fixed_data::user_builtin::USER_DB_INIT; -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; +pub use nexus_db_fixed_data::silo_user::USER_TEST_PRIVILEGED; +pub use nexus_db_fixed_data::silo_user::USER_TEST_UNPRIVILEGED; +pub use nexus_db_fixed_data::user_builtin::USER_DB_INIT; +pub use nexus_db_fixed_data::user_builtin::USER_EXTERNAL_AUTHN; +pub use nexus_db_fixed_data::user_builtin::USER_INTERNAL_API; +pub use nexus_db_fixed_data::user_builtin::USER_INTERNAL_READ; +pub use nexus_db_fixed_data::user_builtin::USER_SAGA_RECOVERY; +pub use nexus_db_fixed_data::user_builtin::USER_SERVICE_BALANCER; use crate::authz; -use crate::db; -use crate::db::fixed_data::silo::DEFAULT_SILO; -use crate::db::identity::Asset; +use newtype_derive::NewtypeDisplay; +use nexus_db_fixed_data::silo::DEFAULT_SILO; use nexus_types::external_api::shared::FleetRole; use nexus_types::external_api::shared::SiloRole; +use nexus_types::identity::Asset; use omicron_common::api::external::LookupType; use serde::Deserialize; use serde::Serialize; @@ -254,7 +253,6 @@ pub struct SiloAuthnPolicy { } impl SiloAuthnPolicy { - #[cfg(test)] pub fn new( mapped_fleet_roles: BTreeMap>, ) -> SiloAuthnPolicy { @@ -290,8 +288,8 @@ mod test { 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; - use crate::db::identity::Asset; + use nexus_db_fixed_data::user_builtin::USER_EXTERNAL_AUTHN; + use nexus_types::identity::Asset; #[test] fn test_internal_users() { @@ -386,11 +384,13 @@ impl Actor { } } -impl From<&Actor> for db::model::IdentityType { - fn from(actor: &Actor) -> db::model::IdentityType { +impl From<&Actor> for nexus_db_model::IdentityType { + fn from(actor: &Actor) -> nexus_db_model::IdentityType { match actor { - Actor::UserBuiltin { .. } => db::model::IdentityType::UserBuiltin, - Actor::SiloUser { .. } => db::model::IdentityType::SiloUser, + Actor::UserBuiltin { .. } => { + nexus_db_model::IdentityType::UserBuiltin + } + Actor::SiloUser { .. } => nexus_db_model::IdentityType::SiloUser, } } } @@ -421,7 +421,7 @@ impl std::fmt::Debug for Actor { /// A console session with the silo id of the authenticated user #[derive(Clone, Debug)] pub struct ConsoleSessionWithSiloId { - pub console_session: ConsoleSession, + pub console_session: nexus_db_model::ConsoleSession, pub silo_id: Uuid, } diff --git a/nexus/db-queries/src/authn/saga.rs b/nexus/auth/src/authn/saga.rs similarity index 100% rename from nexus/db-queries/src/authn/saga.rs rename to nexus/auth/src/authn/saga.rs diff --git a/nexus/db-queries/src/authn/silos.rs b/nexus/auth/src/authn/silos.rs similarity index 86% rename from nexus/db-queries/src/authn/silos.rs rename to nexus/auth/src/authn/silos.rs index fc5068fc3c..40b6346fa0 100644 --- a/nexus/db-queries/src/authn/silos.rs +++ b/nexus/auth/src/authn/silos.rs @@ -4,12 +4,6 @@ //! Silo related authentication types and functions -use crate::authz; -use crate::context::OpContext; -use crate::db::lookup::LookupPath; -use crate::db::{model, DataStore}; -use omicron_common::api::external::LookupResult; - use anyhow::{anyhow, Result}; use base64::Engine; use dropshot::HttpError; @@ -36,10 +30,10 @@ pub struct SamlIdentityProvider { pub group_attribute_name: Option, } -impl TryFrom for SamlIdentityProvider { +impl TryFrom for SamlIdentityProvider { type Error = anyhow::Error; fn try_from( - model: model::SamlIdentityProvider, + model: nexus_db_model::SamlIdentityProvider, ) -> Result { let provider = SamlIdentityProvider { idp_metadata_document_string: model.idp_metadata_document_string, @@ -68,57 +62,6 @@ pub enum IdentityProviderType { Saml(SamlIdentityProvider), } -impl IdentityProviderType { - /// First, look up the provider type, then look in for the specific - /// provider details. - pub async fn lookup( - datastore: &DataStore, - opctx: &OpContext, - silo_name: &model::Name, - provider_name: &model::Name, - ) -> LookupResult<(authz::Silo, model::Silo, Self)> { - let (authz_silo, db_silo) = LookupPath::new(opctx, datastore) - .silo_name(silo_name) - .fetch() - .await?; - - let (.., identity_provider) = LookupPath::new(opctx, datastore) - .silo_name(silo_name) - .identity_provider_name(provider_name) - .fetch() - .await?; - - match identity_provider.provider_type { - model::IdentityProviderType::Saml => { - let (.., saml_identity_provider) = - LookupPath::new(opctx, datastore) - .silo_name(silo_name) - .saml_identity_provider_name(provider_name) - .fetch() - .await?; - - let saml_identity_provider = IdentityProviderType::Saml( - saml_identity_provider.try_into() - .map_err(|e: anyhow::Error| - // If an error is encountered converting from the - // model to the authn type here, this is a server - // error: it was validated before it went into the - // DB. - omicron_common::api::external::Error::internal_error( - &format!( - "saml_identity_provider.try_into() failed! {}", - &e.to_string() - ) - ) - )? - ); - - Ok((authz_silo, db_silo, saml_identity_provider)) - } - } - } -} - impl SamlIdentityProvider { pub fn sign_in_url(&self, relay_state: Option) -> Result { let idp_metadata: EntityDescriptor = diff --git a/nexus/db-queries/src/authz/actor.rs b/nexus/auth/src/authz/actor.rs similarity index 100% rename from nexus/db-queries/src/authz/actor.rs rename to nexus/auth/src/authz/actor.rs diff --git a/nexus/db-queries/src/authz/api_resources.rs b/nexus/auth/src/authz/api_resources.rs similarity index 83% rename from nexus/db-queries/src/authz/api_resources.rs rename to nexus/auth/src/authz/api_resources.rs index 69b883a8cf..98a24b68b5 100644 --- a/nexus/db-queries/src/authz/api_resources.rs +++ b/nexus/auth/src/authz/api_resources.rs @@ -34,13 +34,11 @@ use super::Action; use super::{actor::AuthenticatedActor, Authz}; use crate::authn; use crate::context::OpContext; -use crate::db; -use crate::db::fixed_data::FLEET_ID; -use crate::db::model::{ArtifactId, SemverVersion}; -use crate::db::DataStore; use authz_macros::authz_resource; use futures::future::BoxFuture; use futures::FutureExt; +use nexus_db_fixed_data::FLEET_ID; +use nexus_db_model::{ArtifactId, SemverVersion}; use nexus_types::external_api::shared::{FleetRole, ProjectRole, SiloRole}; use omicron_common::api::external::{Error, LookupType, ResourceType}; use once_cell::sync::Lazy; @@ -103,27 +101,21 @@ pub trait ApiResourceWithRoles: ApiResource { pub trait ApiResourceWithRolesType: ApiResourceWithRoles { type AllowedRoles: serde::Serialize + serde::de::DeserializeOwned - + db::model::DatabaseString + + nexus_db_model::DatabaseString + Clone; } -impl AuthorizedResource for T { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { - load_roles_for_resource_tree(self, opctx, datastore, authn, roleset) - .boxed() +impl AuthorizedResource for T +where + T: ApiResource + oso::PolarClass + Clone, +{ + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> BoxFuture<'fut, Result<(), Error>> { + load_roles_for_resource_tree(self, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -263,26 +255,17 @@ impl oso::PolarClass for BlueprintConfig { } impl AuthorizedResource for BlueprintConfig { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // There are no roles on the BlueprintConfig, only permissions. But we // still need to load the Fleet-related roles to verify that the actor // has the "admin" role on the Fleet (possibly conferred from a Silo // role). - load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) - .boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -323,22 +306,13 @@ impl oso::PolarClass for ConsoleSessionList { } impl AuthorizedResource for ConsoleSessionList { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { - load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) - .boxed() + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -379,22 +353,13 @@ impl oso::PolarClass for DnsConfig { } impl AuthorizedResource for DnsConfig { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { - load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) - .boxed() + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -435,25 +400,16 @@ impl oso::PolarClass for IpPoolList { } impl AuthorizedResource for IpPoolList { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // There are no roles on the IpPoolList, only permissions. But we still // need to load the Fleet-related roles to verify that the actor has the // "admin" role on the Fleet (possibly conferred from a Silo role). - load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) - .boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -486,25 +442,16 @@ impl oso::PolarClass for DeviceAuthRequestList { } impl AuthorizedResource for DeviceAuthRequestList { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // There are no roles on the DeviceAuthRequestList, only permissions. But we // still need to load the Fleet-related roles to verify that the actor has the // "admin" role on the Fleet. - load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) - .boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -544,22 +491,13 @@ impl oso::PolarClass for Inventory { } impl AuthorizedResource for Inventory { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { - load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) - .boxed() + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() } fn on_unauthorized( @@ -603,23 +541,15 @@ impl oso::PolarClass for SiloCertificateList { } impl AuthorizedResource for SiloCertificateList { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, datastore, authn, roleset) + self.silo().load_roles(opctx, authn, roleset) } fn on_unauthorized( @@ -663,23 +593,15 @@ impl oso::PolarClass for SiloIdentityProviderList { } impl AuthorizedResource for SiloIdentityProviderList { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, datastore, authn, roleset) + self.silo().load_roles(opctx, authn, roleset) } fn on_unauthorized( @@ -720,23 +642,15 @@ impl oso::PolarClass for SiloUserList { } impl AuthorizedResource for SiloUserList { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, datastore, authn, roleset) + self.silo().load_roles(opctx, authn, roleset) } fn on_unauthorized( diff --git a/nexus/db-queries/src/authz/context.rs b/nexus/auth/src/authz/context.rs similarity index 80% rename from nexus/db-queries/src/authz/context.rs rename to nexus/auth/src/authz/context.rs index 0d6f2a73ac..bd375321e3 100644 --- a/nexus/db-queries/src/authz/context.rs +++ b/nexus/auth/src/authz/context.rs @@ -10,12 +10,13 @@ use crate::authn; use crate::authz::oso_generic; use crate::authz::Action; use crate::context::OpContext; -use crate::db::DataStore; +use crate::storage::Storage; use futures::future::BoxFuture; use omicron_common::api::external::Error; use omicron_common::bail_unless; use oso::Oso; use oso::OsoError; +use slog::debug; use std::collections::BTreeSet; use std::sync::Arc; @@ -51,7 +52,6 @@ impl Authz { self.oso.is_allowed(actor.clone(), action, resource.clone()) } - #[cfg(test)] pub fn into_class_names(self) -> BTreeSet { self.class_names } @@ -66,18 +66,22 @@ impl Authz { pub struct Context { authn: Arc, authz: Arc, - datastore: Arc, + datastore: Arc, } impl Context { pub fn new( authn: Arc, authz: Arc, - datastore: Arc, + datastore: Arc, ) -> Context { Context { authn, authz, datastore } } + pub(crate) fn datastore(&self) -> &Arc { + &self.datastore + } + /// Check whether the actor performing this request is authorized for /// `action` on `resource`. pub async fn authorize( @@ -111,9 +115,7 @@ impl Context { ); let mut roles = RoleSet::new(); - resource - .load_roles(opctx, &self.datastore, &self.authn, &mut roles) - .await?; + resource.load_roles(opctx, &self.authn, &mut roles).await?; debug!(opctx.log, "roles"; "roles" => ?roles); let actor = AnyActor::new(&self.authn, roles); let is_authn = self.authn.actor().is_some(); @@ -162,19 +164,12 @@ pub trait AuthorizedResource: oso::ToPolar + Send + Sync + 'static { /// That's how this works for most resources. There are other kinds of /// resources (like the Database itself) that aren't stored in the database /// and for which a different mechanism might be used. - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - opctx: &'b OpContext, - datastore: &'c DataStore, - authn: &'d authn::Context, - roleset: &'e mut RoleSet, - ) -> BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f; + fn load_roles<'fut>( + &'fut self, + opctx: &'fut OpContext, + authn: &'fut authn::Context, + roleset: &'fut mut RoleSet, + ) -> BoxFuture<'fut, Result<(), Error>>; /// Invoked on authz failure to determine the final authz result /// @@ -196,17 +191,45 @@ pub trait AuthorizedResource: oso::ToPolar + Send + Sync + 'static { mod test { use crate::authn; use crate::authz::Action; + use crate::authz::AnyActor; use crate::authz::Authz; use crate::authz::Context; - use crate::db::DataStore; - use nexus_test_utils::db::test_setup_database; + use crate::authz::RoleSet; + use crate::context::OpContext; + use nexus_db_model::IdentityType; + use nexus_db_model::RoleAssignment; + use omicron_common::api::external::Error; + use omicron_common::api::external::ResourceType; use omicron_test_utils::dev; use std::sync::Arc; + use uuid::Uuid; + + struct FakeStorage {} + + impl FakeStorage { + fn new() -> Arc { + Arc::new(Self {}) + } + } + + #[async_trait::async_trait] + impl crate::storage::Storage for FakeStorage { + async fn role_asgn_list_for( + &self, + _opctx: &OpContext, + _identity_type: IdentityType, + _identity_id: Uuid, + _resource_type: ResourceType, + _resource_id: Uuid, + ) -> Result, Error> { + unimplemented!("This test is not expected to access the database"); + } + } fn authz_context_for_actor( log: &slog::Logger, authn: authn::Context, - datastore: Arc, + datastore: Arc, ) -> Context { let authz = Authz::new(log); Context::new(Arc::new(authn), Arc::new(authz), datastore) @@ -215,34 +238,26 @@ mod test { #[tokio::test] async fn test_unregistered_resource() { let logctx = dev::test_setup_log("test_unregistered_resource"); - let mut db = test_setup_database(&logctx.log).await; - let (opctx, datastore) = - crate::db::datastore::test_utils::datastore_test(&logctx, &db) - .await; + let datastore = FakeStorage::new(); + let opctx = OpContext::for_background( + logctx.log.new(o!()), + Arc::new(Authz::new(&logctx.log)), + authn::Context::internal_db_init(), + Arc::clone(&datastore) as Arc, + ); // Define a resource that we "forget" to register with Oso. - use super::AuthorizedResource; - use crate::authz::actor::AnyActor; - use crate::authz::roles::RoleSet; - use crate::context::OpContext; - use omicron_common::api::external::Error; + use crate::authz::AuthorizedResource; use oso::PolarClass; #[derive(Clone, PolarClass)] struct UnregisteredResource; impl AuthorizedResource for UnregisteredResource { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - _: &'b OpContext, - _: &'c DataStore, - _: &'d authn::Context, - _: &'e mut RoleSet, - ) -> futures::future::BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, + fn load_roles<'fut>( + &'fut self, + _: &'fut OpContext, + _: &'fut authn::Context, + _: &'fut mut RoleSet, + ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { // authorize() shouldn't get far enough to call this. unimplemented!(); @@ -270,7 +285,7 @@ mod test { let authz_privileged = authz_context_for_actor( &logctx.log, authn::Context::privileged_test_user(), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); let error = authz_privileged .authorize(&opctx, Action::Read, unregistered_resource) @@ -281,7 +296,6 @@ mod test { }) if internal_message == "attempted authz check \ on unregistered resource: \"UnregisteredResource\"")); - db.cleanup().await.unwrap(); logctx.cleanup_successful(); } } diff --git a/nexus/db-queries/src/authz/mod.rs b/nexus/auth/src/authz/mod.rs similarity index 98% rename from nexus/db-queries/src/authz/mod.rs rename to nexus/auth/src/authz/mod.rs index 6b7dab7208..1c666d2296 100644 --- a/nexus/db-queries/src/authz/mod.rs +++ b/nexus/auth/src/authz/mod.rs @@ -168,6 +168,8 @@ //! allowed. Otherwise, it's not. mod actor; +pub use actor::AnyActor; +pub use actor::AuthenticatedActor; mod api_resources; pub use api_resources::*; @@ -179,9 +181,8 @@ pub use context::Context; mod oso_generic; pub use oso_generic::Action; +pub use oso_generic::Database; pub use oso_generic::DATABASE; mod roles; - -#[cfg(test)] -mod policy_test; +pub use roles::RoleSet; diff --git a/nexus/db-queries/src/authz/omicron.polar b/nexus/auth/src/authz/omicron.polar similarity index 100% rename from nexus/db-queries/src/authz/omicron.polar rename to nexus/auth/src/authz/omicron.polar diff --git a/nexus/db-queries/src/authz/oso_generic.rs b/nexus/auth/src/authz/oso_generic.rs similarity index 96% rename from nexus/db-queries/src/authz/oso_generic.rs rename to nexus/auth/src/authz/oso_generic.rs index dd646a1c98..383a06e985 100644 --- a/nexus/db-queries/src/authz/oso_generic.rs +++ b/nexus/auth/src/authz/oso_generic.rs @@ -12,7 +12,6 @@ use super::roles::RoleSet; use super::Authz; use crate::authn; use crate::context::OpContext; -use crate::db::DataStore; use anyhow::ensure; use anyhow::Context; use futures::future::BoxFuture; @@ -20,6 +19,7 @@ use futures::FutureExt; use omicron_common::api::external::Error; use oso::Oso; use oso::PolarClass; +use slog::info; use std::collections::BTreeSet; use std::fmt; @@ -172,8 +172,7 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result { /// /// There's currently just one enum of Actions for all of Omicron. We expect /// most objects to support mostly the same set of actions. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr(test, derive(strum::EnumIter))] +#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::EnumIter)] pub enum Action { Query, // only used for `Database` Read, @@ -267,20 +266,12 @@ impl oso::PolarClass for Database { } impl AuthorizedResource for Database { - fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( - &'a self, - _: &'b OpContext, - _: &'c DataStore, - _: &'d authn::Context, - _: &'e mut RoleSet, - ) -> BoxFuture<'f, Result<(), Error>> - where - 'a: 'f, - 'b: 'f, - 'c: 'f, - 'd: 'f, - 'e: 'f, - { + fn load_roles<'fut>( + &'fut self, + _: &'fut OpContext, + _: &'fut authn::Context, + _: &'fut mut RoleSet, + ) -> BoxFuture<'fut, Result<(), Error>> { // We don't use (database) roles to grant access to the database. The // role assignment is hardcoded for all authenticated users. See the // "has_role" Polar method above. diff --git a/nexus/db-queries/src/authz/roles.rs b/nexus/auth/src/authz/roles.rs similarity index 96% rename from nexus/db-queries/src/authz/roles.rs rename to nexus/auth/src/authz/roles.rs index 11b3d482d1..0716e05bc7 100644 --- a/nexus/db-queries/src/authz/roles.rs +++ b/nexus/auth/src/authz/roles.rs @@ -37,9 +37,9 @@ use super::api_resources::ApiResource; use crate::authn; use crate::context::OpContext; -use crate::db::DataStore; use omicron_common::api::external::Error; use omicron_common::api::external::ResourceType; +use slog::trace; use std::collections::BTreeSet; use uuid::Uuid; @@ -87,7 +87,6 @@ impl RoleSet { pub async fn load_roles_for_resource_tree( resource: &R, opctx: &OpContext, - datastore: &DataStore, authn: &authn::Context, roleset: &mut RoleSet, ) -> Result<(), Error> @@ -100,7 +99,6 @@ where let resource_id = with_roles.resource_id(); load_directly_attached_roles( opctx, - datastore, authn, resource_type, resource_id, @@ -115,7 +113,6 @@ where { load_directly_attached_roles( opctx, - datastore, authn, resource_type, resource_id, @@ -135,7 +132,7 @@ where // it's clearer to just call this "parent" than // "related_resources_whose_roles_might_grant_access_to_this".) if let Some(parent) = resource.parent() { - parent.load_roles(opctx, datastore, authn, roleset).await?; + parent.load_roles(opctx, authn, roleset).await?; } Ok(()) @@ -143,7 +140,6 @@ where async fn load_directly_attached_roles( opctx: &OpContext, - datastore: &DataStore, authn: &authn::Context, resource_type: ResourceType, resource_id: Uuid, @@ -159,7 +155,8 @@ async fn load_directly_attached_roles( "resource_id" => resource_id.to_string(), ); - let roles = datastore + let roles = opctx + .datastore() .role_asgn_list_for( opctx, actor.into(), diff --git a/nexus/db-queries/src/context.rs b/nexus/auth/src/context.rs similarity index 92% rename from nexus/db-queries/src/context.rs rename to nexus/auth/src/context.rs index dfd1fe4322..0aac0900c5 100644 --- a/nexus/db-queries/src/context.rs +++ b/nexus/auth/src/context.rs @@ -8,9 +8,12 @@ use super::authz; use crate::authn::external::session_cookie::Session; use crate::authn::ConsoleSessionWithSiloId; use crate::authz::AuthorizedResource; -use crate::db::DataStore; +use crate::storage::Storage; use chrono::{DateTime, Utc}; use omicron_common::api::external::Error; +use slog::debug; +use slog::o; +use slog::trace; use std::collections::BTreeMap; use std::fmt::Debug; use std::sync::Arc; @@ -111,6 +114,10 @@ impl OpContext { }) } + pub(crate) fn datastore(&self) -> &Arc { + self.authz.datastore() + } + fn log_and_metadata_for_authn( log: &slog::Logger, authn: &authn::Context, @@ -135,8 +142,8 @@ impl OpContext { (log, metadata) } - pub fn load_request_metadata( - rqctx: &dropshot::RequestContext, + pub fn load_request_metadata( + rqctx: &dropshot::RequestContext, metadata: &mut BTreeMap, ) { let request = &rqctx.request; @@ -151,7 +158,7 @@ impl OpContext { log: slog::Logger, authz: Arc, authn: authn::Context, - datastore: Arc, + datastore: Arc, ) -> OpContext { let created_instant = Instant::now(); let created_walltime = SystemTime::now(); @@ -180,7 +187,7 @@ impl OpContext { // outside public interfaces. pub fn for_tests( log: slog::Logger, - datastore: Arc, + datastore: Arc, ) -> OpContext { let created_instant = Instant::now(); let created_walltime = SystemTime::now(); @@ -207,7 +214,7 @@ impl OpContext { /// functionally the same as one that you already have, but where you want /// to provide extra debugging information (in the form of key-value pairs) /// in both the OpContext itself and its logger. - pub fn child(&self, new_metadata: BTreeMap) -> OpContext { + pub fn child(&self, new_metadata: BTreeMap) -> Self { let created_instant = Instant::now(); let created_walltime = SystemTime::now(); let mut metadata = self.metadata.clone(); @@ -346,19 +353,42 @@ mod test { use crate::authn; use crate::authz; use authz::Action; - use nexus_test_utils::db::test_setup_database; + use nexus_db_model::IdentityType; + use nexus_db_model::RoleAssignment; use omicron_common::api::external::Error; + use omicron_common::api::external::ResourceType; use omicron_test_utils::dev; use std::collections::BTreeMap; use std::sync::Arc; + use uuid::Uuid; + + struct FakeStorage {} + + impl FakeStorage { + fn new() -> Arc { + Arc::new(Self {}) + } + } + + #[async_trait::async_trait] + impl crate::storage::Storage for FakeStorage { + async fn role_asgn_list_for( + &self, + _opctx: &OpContext, + _identity_type: IdentityType, + _identity_id: Uuid, + _resource_type: ResourceType, + _resource_id: Uuid, + ) -> Result, Error> { + unimplemented!("This test is not expected to access the database"); + } + } #[tokio::test] async fn test_background_context() { let logctx = dev::test_setup_log("test_background_context"); - let mut db = test_setup_database(&logctx.log).await; - let (_, datastore) = - crate::db::datastore::test_utils::datastore_test(&logctx, &db) - .await; + + let datastore = FakeStorage::new(); let opctx = OpContext::for_background( logctx.log.new(o!()), Arc::new(authz::Authz::new(&logctx.log)), @@ -381,17 +411,13 @@ mod test { .await .expect_err("expected authorization error"); assert!(matches!(error, Error::Unauthenticated { .. })); - db.cleanup().await.unwrap(); logctx.cleanup_successful(); } #[tokio::test] async fn test_test_context() { let logctx = dev::test_setup_log("test_background_context"); - let mut db = test_setup_database(&logctx.log).await; - let (_, datastore) = - crate::db::datastore::test_utils::datastore_test(&logctx, &db) - .await; + let datastore = FakeStorage::new(); let opctx = OpContext::for_tests(logctx.log.new(o!()), datastore); // Like in test_background_context(), this is essentially a test of the @@ -403,17 +429,13 @@ mod test { .authorize(Action::Query, &authz::DATABASE) .await .expect("expected authorization to succeed"); - db.cleanup().await.unwrap(); logctx.cleanup_successful(); } #[tokio::test] async fn test_child_context() { let logctx = dev::test_setup_log("test_child_context"); - let mut db = test_setup_database(&logctx.log).await; - let (_, datastore) = - crate::db::datastore::test_utils::datastore_test(&logctx, &db) - .await; + let datastore = FakeStorage::new(); let opctx = OpContext::for_background( logctx.log.new(o!()), Arc::new(authz::Authz::new(&logctx.log)), @@ -451,7 +473,6 @@ mod test { assert_eq!(grandchild_opctx.metadata["one"], "seven"); assert_eq!(grandchild_opctx.metadata["five"], "six"); - db.cleanup().await.unwrap(); logctx.cleanup_successful(); } } diff --git a/nexus/auth/src/lib.rs b/nexus/auth/src/lib.rs new file mode 100644 index 0000000000..0f0b9064b2 --- /dev/null +++ b/nexus/auth/src/lib.rs @@ -0,0 +1,11 @@ +pub mod authn; +pub mod authz; +pub mod context; +pub mod storage; + +#[macro_use] +extern crate newtype_derive; + +#[allow(unused_imports)] +#[macro_use] +extern crate slog; diff --git a/nexus/auth/src/storage.rs b/nexus/auth/src/storage.rs new file mode 100644 index 0000000000..c1d2fcedd8 --- /dev/null +++ b/nexus/auth/src/storage.rs @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Describes the dependency from the auth system on the datastore. +//! +//! Auth and storage are intertwined, but by isolating the interface from +//! auth on the database, we can avoid a circular dependency. + +use crate::context::OpContext; +use nexus_db_model::IdentityType; +use nexus_db_model::RoleAssignment; +use omicron_common::api::external::Error; +use omicron_common::api::external::ResourceType; +use uuid::Uuid; + +#[async_trait::async_trait] +pub trait Storage: Send + Sync { + async fn role_asgn_list_for( + &self, + opctx: &OpContext, + identity_type: IdentityType, + identity_id: Uuid, + resource_type: ResourceType, + resource_id: Uuid, + ) -> Result, Error>; +} diff --git a/nexus/db-fixed-data/Cargo.toml b/nexus/db-fixed-data/Cargo.toml new file mode 100644 index 0000000000..486df15686 --- /dev/null +++ b/nexus/db-fixed-data/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "nexus-db-fixed-data" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" +description = "Hard-coded database data, including defaults and built-ins" + +[lints] +workspace = true + +[build-dependencies] +omicron-rpaths.workspace = true + +[dependencies] +once_cell.workspace = true +# See omicron-rpaths for more about the "pq-sys" dependency. +pq-sys = "*" +strum.workspace = true +uuid.workspace = true + +nexus-db-model.workspace = true +nexus-types.workspace = true +omicron-common.workspace = true +omicron-workspace-hack.workspace = true + diff --git a/nexus/db-fixed-data/build.rs b/nexus/db-fixed-data/build.rs new file mode 100644 index 0000000000..1ba9acd41c --- /dev/null +++ b/nexus/db-fixed-data/build.rs @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// See omicron-rpaths for documentation. +// NOTE: This file MUST be kept in sync with the other build.rs files in this +// repository. +fn main() { + omicron_rpaths::configure_default_omicron_rpaths(); +} diff --git a/nexus/db-queries/src/db/fixed_data/allow_list.rs b/nexus/db-fixed-data/src/allow_list.rs similarity index 100% rename from nexus/db-queries/src/db/fixed_data/allow_list.rs rename to nexus/db-fixed-data/src/allow_list.rs diff --git a/nexus/db-queries/src/db/fixed_data/mod.rs b/nexus/db-fixed-data/src/lib.rs similarity index 100% rename from nexus/db-queries/src/db/fixed_data/mod.rs rename to nexus/db-fixed-data/src/lib.rs diff --git a/nexus/db-queries/src/db/fixed_data/project.rs b/nexus/db-fixed-data/src/project.rs similarity index 79% rename from nexus/db-queries/src/db/fixed_data/project.rs rename to nexus/db-fixed-data/src/project.rs index e240900e0c..6b9f005916 100644 --- a/nexus/db-queries/src/db/fixed_data/project.rs +++ b/nexus/db-fixed-data/src/project.rs @@ -2,12 +2,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::db; -use crate::db::datastore::SERVICES_DB_NAME; +use nexus_db_model as model; use nexus_types::external_api::params; use omicron_common::api::external::IdentityMetadataCreateParams; use once_cell::sync::Lazy; +/// The name of the built-in Project and VPC for Oxide services. +pub const SERVICES_DB_NAME: &str = "oxide-services"; + /// UUID of built-in project for internal services on the rack. pub static SERVICES_PROJECT_ID: Lazy = Lazy::new(|| { "001de000-4401-4000-8000-000000000000" @@ -16,8 +18,8 @@ pub static SERVICES_PROJECT_ID: Lazy = Lazy::new(|| { }); /// Built-in Project for internal services on the rack. -pub static SERVICES_PROJECT: Lazy = Lazy::new(|| { - db::model::Project::new_with_id( +pub static SERVICES_PROJECT: Lazy = Lazy::new(|| { + model::Project::new_with_id( *SERVICES_PROJECT_ID, *super::silo::INTERNAL_SILO_ID, params::ProjectCreate { diff --git a/nexus/db-queries/src/db/fixed_data/role_assignment.rs b/nexus/db-fixed-data/src/role_assignment.rs similarity index 97% rename from nexus/db-queries/src/db/fixed_data/role_assignment.rs rename to nexus/db-fixed-data/src/role_assignment.rs index d6c95d47b6..25b26786f8 100644 --- a/nexus/db-queries/src/db/fixed_data/role_assignment.rs +++ b/nexus/db-fixed-data/src/role_assignment.rs @@ -6,8 +6,8 @@ use super::role_builtin; use super::user_builtin; use super::FLEET_ID; -use crate::db::model::IdentityType; -use crate::db::model::RoleAssignment; +use nexus_db_model::IdentityType; +use nexus_db_model::RoleAssignment; use once_cell::sync::Lazy; pub static BUILTIN_ROLE_ASSIGNMENTS: Lazy> = diff --git a/nexus/db-queries/src/db/fixed_data/role_builtin.rs b/nexus/db-fixed-data/src/role_builtin.rs similarity index 99% rename from nexus/db-queries/src/db/fixed_data/role_builtin.rs rename to nexus/db-fixed-data/src/role_builtin.rs index f58077fc3f..c617874e98 100644 --- a/nexus/db-queries/src/db/fixed_data/role_builtin.rs +++ b/nexus/db-fixed-data/src/role_builtin.rs @@ -83,7 +83,7 @@ pub static BUILTIN_ROLES: Lazy> = Lazy::new(|| { #[cfg(test)] mod test { use super::BUILTIN_ROLES; - use crate::db::model::DatabaseString; + use nexus_db_model::DatabaseString; use nexus_types::external_api::shared::{FleetRole, ProjectRole, SiloRole}; use omicron_common::api::external::ResourceType; use strum::IntoEnumIterator; diff --git a/nexus/db-queries/src/db/fixed_data/silo.rs b/nexus/db-fixed-data/src/silo.rs similarity index 91% rename from nexus/db-queries/src/db/fixed_data/silo.rs rename to nexus/db-fixed-data/src/silo.rs index dc5f19fc2f..ebc6776923 100644 --- a/nexus/db-queries/src/db/fixed_data/silo.rs +++ b/nexus/db-fixed-data/src/silo.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::db; +use nexus_db_model as model; use nexus_types::external_api::{params, shared}; use omicron_common::api::external::IdentityMetadataCreateParams; use once_cell::sync::Lazy; @@ -17,8 +17,8 @@ pub static DEFAULT_SILO_ID: Lazy = Lazy::new(|| { /// /// This was historically used for demos and the unit tests. The plan is to /// remove it per omicron#2305. -pub static DEFAULT_SILO: Lazy = Lazy::new(|| { - db::model::Silo::new_with_id( +pub static DEFAULT_SILO: Lazy = Lazy::new(|| { + model::Silo::new_with_id( *DEFAULT_SILO_ID, params::SiloCreate { identity: IdentityMetadataCreateParams { @@ -47,8 +47,8 @@ pub static INTERNAL_SILO_ID: Lazy = Lazy::new(|| { /// Built-in Silo to house internal resources. It contains no users and /// can't be logged into. -pub static INTERNAL_SILO: Lazy = Lazy::new(|| { - db::model::Silo::new_with_id( +pub static INTERNAL_SILO: Lazy = Lazy::new(|| { + model::Silo::new_with_id( *INTERNAL_SILO_ID, params::SiloCreate { identity: IdentityMetadataCreateParams { diff --git a/nexus/db-queries/src/db/fixed_data/silo_user.rs b/nexus/db-fixed-data/src/silo_user.rs similarity index 66% rename from nexus/db-queries/src/db/fixed_data/silo_user.rs rename to nexus/db-fixed-data/src/silo_user.rs index eb49093152..defaa9bd52 100644 --- a/nexus/db-queries/src/db/fixed_data/silo_user.rs +++ b/nexus/db-fixed-data/src/silo_user.rs @@ -4,8 +4,8 @@ //! Built-in Silo Users use super::role_builtin; -use crate::db; -use crate::db::identity::Asset; +use nexus_db_model as model; +use nexus_types::identity::Asset; use once_cell::sync::Lazy; /// Test user that's granted all privileges, used for automated testing and @@ -13,9 +13,9 @@ use once_cell::sync::Lazy; // TODO-security Once we have a way to bootstrap the initial Silo with the // initial privileged user, this user should be created in the test suite, // not automatically at Nexus startup. See omicron#2305. -pub static USER_TEST_PRIVILEGED: Lazy = Lazy::new(|| { - db::model::SiloUser::new( - *db::fixed_data::silo::DEFAULT_SILO_ID, +pub static USER_TEST_PRIVILEGED: Lazy = Lazy::new(|| { + model::SiloUser::new( + *crate::silo::DEFAULT_SILO_ID, // "4007" looks a bit like "root". "001de000-05e4-4000-8000-000000004007".parse().unwrap(), "privileged".into(), @@ -23,23 +23,23 @@ pub static USER_TEST_PRIVILEGED: Lazy = Lazy::new(|| { }); /// Role assignments needed for the privileged user -pub static ROLE_ASSIGNMENTS_PRIVILEGED: Lazy> = +pub static ROLE_ASSIGNMENTS_PRIVILEGED: Lazy> = Lazy::new(|| { vec![ // The "test-privileged" user gets the "admin" role on the sole // Fleet as well as the default Silo. - db::model::RoleAssignment::new( - db::model::IdentityType::SiloUser, + model::RoleAssignment::new( + model::IdentityType::SiloUser, USER_TEST_PRIVILEGED.id(), role_builtin::FLEET_ADMIN.resource_type, - *db::fixed_data::FLEET_ID, + *crate::FLEET_ID, role_builtin::FLEET_ADMIN.role_name, ), - db::model::RoleAssignment::new( - db::model::IdentityType::SiloUser, + model::RoleAssignment::new( + model::IdentityType::SiloUser, USER_TEST_PRIVILEGED.id(), role_builtin::SILO_ADMIN.resource_type, - *db::fixed_data::silo::DEFAULT_SILO_ID, + *crate::silo::DEFAULT_SILO_ID, role_builtin::SILO_ADMIN.role_name, ), ] @@ -49,22 +49,21 @@ pub static ROLE_ASSIGNMENTS_PRIVILEGED: Lazy> = // TODO-security Once we have a way to bootstrap the initial Silo with the // initial privileged user, this user should be created in the test suite, // not automatically at Nexus startup. See omicron#2305. -pub static USER_TEST_UNPRIVILEGED: Lazy = - Lazy::new(|| { - db::model::SiloUser::new( - *db::fixed_data::silo::DEFAULT_SILO_ID, - // 60001 is the decimal uid for "nobody" on Helios. - "001de000-05e4-4000-8000-000000060001".parse().unwrap(), - "unprivileged".into(), - ) - }); +pub static USER_TEST_UNPRIVILEGED: Lazy = Lazy::new(|| { + model::SiloUser::new( + *crate::silo::DEFAULT_SILO_ID, + // 60001 is the decimal uid for "nobody" on Helios. + "001de000-05e4-4000-8000-000000060001".parse().unwrap(), + "unprivileged".into(), + ) +}); #[cfg(test)] mod test { use super::super::assert_valid_uuid; use super::USER_TEST_PRIVILEGED; use super::USER_TEST_UNPRIVILEGED; - use crate::db::identity::Asset; + use nexus_types::identity::Asset; #[test] fn test_silo_user_ids_are_valid() { diff --git a/nexus/db-queries/src/db/fixed_data/user_builtin.rs b/nexus/db-fixed-data/src/user_builtin.rs similarity index 100% rename from nexus/db-queries/src/db/fixed_data/user_builtin.rs rename to nexus/db-fixed-data/src/user_builtin.rs diff --git a/nexus/db-queries/src/db/fixed_data/vpc.rs b/nexus/db-fixed-data/src/vpc.rs similarity index 91% rename from nexus/db-queries/src/db/fixed_data/vpc.rs rename to nexus/db-fixed-data/src/vpc.rs index c71b655ddc..25628a83b5 100644 --- a/nexus/db-queries/src/db/fixed_data/vpc.rs +++ b/nexus/db-fixed-data/src/vpc.rs @@ -2,8 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::db; -use crate::db::datastore::SERVICES_DB_NAME; +use crate::project::SERVICES_DB_NAME; +use nexus_db_model as model; use nexus_types::external_api::params; use omicron_common::address::SERVICE_VPC_IPV6_PREFIX; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -31,8 +31,8 @@ pub static SERVICES_VPC_DEFAULT_ROUTE_ID: Lazy = Lazy::new(|| { }); /// Built-in VPC for internal services on the rack. -pub static SERVICES_VPC: Lazy = Lazy::new(|| { - db::model::IncompleteVpc::new( +pub static SERVICES_VPC: Lazy = Lazy::new(|| { + model::IncompleteVpc::new( *SERVICES_VPC_ID, *super::project::SERVICES_PROJECT_ID, *SERVICES_VPC_ROUTER_ID, diff --git a/nexus/db-queries/src/db/fixed_data/vpc_firewall_rule.rs b/nexus/db-fixed-data/src/vpc_firewall_rule.rs similarity index 100% rename from nexus/db-queries/src/db/fixed_data/vpc_firewall_rule.rs rename to nexus/db-fixed-data/src/vpc_firewall_rule.rs diff --git a/nexus/db-queries/src/db/fixed_data/vpc_subnet.rs b/nexus/db-fixed-data/src/vpc_subnet.rs similarity index 98% rename from nexus/db-queries/src/db/fixed_data/vpc_subnet.rs rename to nexus/db-fixed-data/src/vpc_subnet.rs index c42d4121c9..622799b000 100644 --- a/nexus/db-queries/src/db/fixed_data/vpc_subnet.rs +++ b/nexus/db-fixed-data/src/vpc_subnet.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::db::model::VpcSubnet; +use nexus_db_model::VpcSubnet; use omicron_common::address::{ DNS_OPTE_IPV4_SUBNET, DNS_OPTE_IPV6_SUBNET, NEXUS_OPTE_IPV4_SUBNET, NEXUS_OPTE_IPV6_SUBNET, NTP_OPTE_IPV4_SUBNET, NTP_OPTE_IPV6_SUBNET, diff --git a/nexus/db-queries/Cargo.toml b/nexus/db-queries/Cargo.toml index 135f2fcdf7..cb7061f4ce 100644 --- a/nexus/db-queries/Cargo.toml +++ b/nexus/db-queries/Cargo.toml @@ -14,37 +14,27 @@ omicron-rpaths.workspace = true anyhow.workspace = true async-bb8-diesel.workspace = true async-trait.workspace = true -base64.workspace = true bb8.workspace = true camino.workspace = true chrono.workspace = true const_format.workspace = true -cookie.workspace = true diesel.workspace = true diesel-dtrace.workspace = true dropshot.workspace = true futures.workspace = true -headers.workspace = true -http.workspace = true -hyper.workspace = true ipnetwork.workspace = true macaddr.workspace = true -newtype_derive.workspace = true once_cell.workspace = true -openssl.workspace = true -oso.workspace = true oxnet.workspace = true paste.workspace = true # See omicron-rpaths for more about the "pq-sys" dependency. pq-sys = "*" rand.workspace = true ref-cast.workspace = true -samael.workspace = true schemars.workspace = true semver.workspace = true serde.workspace = true serde_json.workspace = true -serde_urlencoded.workspace = true serde_with.workspace = true sled-agent-client.workspace = true slog.workspace = true @@ -58,9 +48,10 @@ tokio = { workspace = true, features = ["full"] } uuid.workspace = true usdt.workspace = true -authz-macros.workspace = true db-macros.workspace = true +nexus-auth.workspace = true nexus-config.workspace = true +nexus-db-fixed-data.workspace = true nexus-db-model.workspace = true nexus-types.workspace = true omicron-common.workspace = true @@ -91,6 +82,7 @@ nexus-test-utils.workspace = true omicron-sled-agent.workspace = true omicron-test-utils.workspace = true openapiv3.workspace = true +oso.workspace = true pem.workspace = true petgraph.workspace = true predicates.workspace = true diff --git a/nexus/db-queries/src/db/datastore/allow_list.rs b/nexus/db-queries/src/db/datastore/allow_list.rs index 111ccad08f..7c1643451f 100644 --- a/nexus/db-queries/src/db/datastore/allow_list.rs +++ b/nexus/db-queries/src/db/datastore/allow_list.rs @@ -8,12 +8,12 @@ use crate::authz; use crate::context::OpContext; use crate::db::error::public_error_from_diesel; use crate::db::error::ErrorHandler; -use crate::db::fixed_data::allow_list::USER_FACING_SERVICES_ALLOW_LIST_ID; use crate::db::DbConnection; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::ExpressionMethods; use diesel::QueryDsl; use diesel::SelectableHelper; +use nexus_db_fixed_data::allow_list::USER_FACING_SERVICES_ALLOW_LIST_ID; use nexus_db_model::schema::allow_list; use nexus_db_model::AllowList; use omicron_common::api::external::AllowedSourceIps; diff --git a/nexus/db-queries/src/db/datastore/auth.rs b/nexus/db-queries/src/db/datastore/auth.rs new file mode 100644 index 0000000000..3b1d1d18e3 --- /dev/null +++ b/nexus/db-queries/src/db/datastore/auth.rs @@ -0,0 +1,81 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Implements the [Storage] interface for [nexus_auth] integration. + +use crate::db; +use crate::db::error::public_error_from_diesel; +use crate::db::error::ErrorHandler; + +use async_bb8_diesel::AsyncRunQueryDsl; +use diesel::prelude::*; +use nexus_auth::context::OpContext; +use nexus_auth::storage::Storage; +use nexus_db_model::IdentityType; +use nexus_db_model::RoleAssignment; +use omicron_common::api::external::Error; +use omicron_common::api::external::ResourceType; +use uuid::Uuid; + +#[async_trait::async_trait] +impl Storage for super::DataStore { + /// Return the built-in roles that the given built-in user has for the given + /// resource + async fn role_asgn_list_for( + &self, + opctx: &OpContext, + identity_type: IdentityType, + identity_id: Uuid, + resource_type: ResourceType, + resource_id: Uuid, + ) -> Result, Error> { + use db::schema::role_assignment::dsl as role_dsl; + use db::schema::silo_group_membership::dsl as group_dsl; + + // There is no resource-specific authorization check because all + // authenticated users need to be able to list their own roles -- + // otherwise we can't do any authorization checks. + // TODO-security rethink this -- how do we know the user is looking up + // their own roles? Maybe this should use an internal authz context. + + // TODO-scalability TODO-security This needs to be paginated. It's not + // exposed via an external API right now but someone could still put us + // into some hurt by assigning loads of roles to someone and having that + // person attempt to access anything. + + let direct_roles_query = role_dsl::role_assignment + .filter(role_dsl::identity_type.eq(identity_type.clone())) + .filter(role_dsl::identity_id.eq(identity_id)) + .filter(role_dsl::resource_type.eq(resource_type.to_string())) + .filter(role_dsl::resource_id.eq(resource_id)) + .select(RoleAssignment::as_select()); + + let roles_from_groups_query = role_dsl::role_assignment + .filter(role_dsl::identity_type.eq(IdentityType::SiloGroup)) + .filter( + role_dsl::identity_id.eq_any( + group_dsl::silo_group_membership + .filter(group_dsl::silo_user_id.eq(identity_id)) + .select(group_dsl::silo_group_id), + ), + ) + .filter(role_dsl::resource_type.eq(resource_type.to_string())) + .filter(role_dsl::resource_id.eq(resource_id)) + .select(RoleAssignment::as_select()); + + let conn = self.pool_connection_authorized(opctx).await?; + if identity_type == IdentityType::SiloUser { + direct_roles_query + .union(roles_from_groups_query) + .load_async::(&*conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } else { + direct_roles_query + .load_async::(&*conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + } +} diff --git a/nexus/db-queries/src/db/datastore/cockroachdb_settings.rs b/nexus/db-queries/src/db/datastore/cockroachdb_settings.rs index 177cf673e7..e7a975fa69 100644 --- a/nexus/db-queries/src/db/datastore/cockroachdb_settings.rs +++ b/nexus/db-queries/src/db/datastore/cockroachdb_settings.rs @@ -147,8 +147,10 @@ mod test { let (_, datastore) = crate::db::datastore::test_utils::datastore_test(&logctx, &db) .await; - let opctx = - OpContext::for_tests(logctx.log.new(o!()), Arc::clone(&datastore)); + let opctx = OpContext::for_tests( + logctx.log.new(o!()), + Arc::clone(&datastore) as Arc, + ); let settings = datastore.cockroachdb_settings(&opctx).await.unwrap(); // With a fresh cluster, this is the expected state diff --git a/nexus/db-queries/src/db/datastore/identity_provider.rs b/nexus/db-queries/src/db/datastore/identity_provider.rs index cee577acd6..e7ab9bde16 100644 --- a/nexus/db-queries/src/db/datastore/identity_provider.rs +++ b/nexus/db-queries/src/db/datastore/identity_provider.rs @@ -11,18 +11,66 @@ use crate::db; use crate::db::error::public_error_from_diesel; use crate::db::error::ErrorHandler; use crate::db::identity::Resource; +use crate::db::lookup::LookupPath; +use crate::db::model; use crate::db::model::IdentityProvider; use crate::db::model::Name; use crate::db::pagination::paginated; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::prelude::*; +use nexus_auth::authn::silos::IdentityProviderType; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; use omicron_common::api::external::ListResultVec; +use omicron_common::api::external::LookupResult; use omicron_common::api::external::ResourceType; use ref_cast::RefCast; impl DataStore { + pub async fn identity_provider_lookup( + &self, + opctx: &OpContext, + silo_name: &model::Name, + provider_name: &model::Name, + ) -> LookupResult<(authz::Silo, model::Silo, IdentityProviderType)> { + let (authz_silo, db_silo) = + LookupPath::new(opctx, self).silo_name(silo_name).fetch().await?; + + let (.., identity_provider) = LookupPath::new(opctx, self) + .silo_name(silo_name) + .identity_provider_name(provider_name) + .fetch() + .await?; + + match identity_provider.provider_type { + model::IdentityProviderType::Saml => { + let (.., saml_identity_provider) = LookupPath::new(opctx, self) + .silo_name(silo_name) + .saml_identity_provider_name(provider_name) + .fetch() + .await?; + + let saml_identity_provider = IdentityProviderType::Saml( + saml_identity_provider.try_into() + .map_err(|e: anyhow::Error| + // If an error is encountered converting from the + // model to the authn type here, this is a server + // error: it was validated before it went into the + // DB. + omicron_common::api::external::Error::internal_error( + &format!( + "saml_identity_provider.try_into() failed! {}", + &e.to_string() + ) + ) + )? + ); + + Ok((authz_silo, db_silo, saml_identity_provider)) + } + } + } + pub async fn identity_provider_list( &self, opctx: &OpContext, diff --git a/nexus/db-queries/src/db/datastore/instance.rs b/nexus/db-queries/src/db/datastore/instance.rs index 60fd5c9dc3..3b655e5bb9 100644 --- a/nexus/db-queries/src/db/datastore/instance.rs +++ b/nexus/db-queries/src/db/datastore/instance.rs @@ -783,7 +783,6 @@ impl DataStore { mod tests { use super::*; use crate::db::datastore::test_utils::datastore_test; - use crate::db::fixed_data; use crate::db::lookup::LookupPath; use nexus_db_model::Project; use nexus_test_utils::db::test_setup_database; @@ -796,7 +795,7 @@ mod tests { datastore: &DataStore, opctx: &OpContext, ) -> authz::Instance { - let silo_id = *fixed_data::silo::DEFAULT_SILO_ID; + let silo_id = *nexus_db_fixed_data::silo::DEFAULT_SILO_ID; let project_id = Uuid::new_v4(); let instance_id = Uuid::new_v4(); diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index b90f81affb..9ec3575860 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -49,6 +49,7 @@ use uuid::Uuid; mod address_lot; mod allow_list; +mod auth; mod bfd; mod bgp; mod bootstore; @@ -130,9 +131,6 @@ pub const REGION_REDUNDANCY_THRESHOLD: usize = 3; /// The name of the built-in IP pool for Oxide services. pub const SERVICE_IP_POOL_NAME: &str = "oxide-service-pool"; -/// The name of the built-in Project and VPC for Oxide services. -pub const SERVICES_DB_NAME: &str = "oxide-services"; - /// "limit" to be used in SQL queries that paginate through large result sets /// /// This value is chosen to be small enough to avoid any queries being too @@ -385,8 +383,6 @@ mod test { IneligibleSledKind, IneligibleSleds, }; use crate::db::explain::ExplainableAsync; - use crate::db::fixed_data::silo::DEFAULT_SILO; - use crate::db::fixed_data::silo::DEFAULT_SILO_ID; use crate::db::identity::Asset; use crate::db::lookup::LookupPath; use crate::db::model::{ @@ -400,6 +396,8 @@ mod test { use futures::stream; use futures::StreamExt; use nexus_config::RegionAllocationStrategy; + use nexus_db_fixed_data::silo::DEFAULT_SILO; + use nexus_db_fixed_data::silo::DEFAULT_SILO_ID; use nexus_db_model::IpAttachState; use nexus_db_model::{to_db_typed_uuid, Generation}; use nexus_test_utils::db::test_setup_database; @@ -485,7 +483,7 @@ mod test { logctx.log.new(o!("component" => "TestExternalAuthn")), Arc::new(authz::Authz::new(&logctx.log)), authn::Context::external_authn(), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); let token = "a_token".to_string(); @@ -587,7 +585,7 @@ mod test { *DEFAULT_SILO_ID, SiloAuthnPolicy::try_from(&*DEFAULT_SILO).unwrap(), ), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); let delete = datastore .session_hard_delete(&silo_user_opctx, &authz_session) @@ -1624,8 +1622,10 @@ mod test { let pool = Arc::new(db::Pool::new(&logctx.log, &cfg)); let datastore = Arc::new(DataStore::new(&logctx.log, pool, None).await.unwrap()); - let opctx = - OpContext::for_tests(logctx.log.new(o!()), datastore.clone()); + let opctx = OpContext::for_tests( + logctx.log.new(o!()), + Arc::clone(&datastore) as Arc, + ); let rack_id = Uuid::new_v4(); let addr1 = "[fd00:1de::1]:12345".parse().unwrap(); diff --git a/nexus/db-queries/src/db/datastore/network_interface.rs b/nexus/db-queries/src/db/datastore/network_interface.rs index af3f832e35..3ea2945b2f 100644 --- a/nexus/db-queries/src/db/datastore/network_interface.rs +++ b/nexus/db-queries/src/db/datastore/network_interface.rs @@ -854,8 +854,8 @@ impl DataStore { mod tests { use super::*; use crate::db::datastore::test_utils::datastore_test; - use crate::db::fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; + use nexus_db_fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; use nexus_test_utils::db::test_setup_database; use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; use omicron_test_utils::dev; diff --git a/nexus/db-queries/src/db/datastore/project.rs b/nexus/db-queries/src/db/datastore/project.rs index 08647b421e..42ccca4ed6 100644 --- a/nexus/db-queries/src/db/datastore/project.rs +++ b/nexus/db-queries/src/db/datastore/project.rs @@ -13,8 +13,6 @@ use crate::db::collection_insert::AsyncInsertError; use crate::db::collection_insert::DatastoreCollection; use crate::db::error::public_error_from_diesel; use crate::db::error::ErrorHandler; -use crate::db::fixed_data::project::SERVICES_PROJECT; -use crate::db::fixed_data::silo::INTERNAL_SILO_ID; use crate::db::identity::Resource; use crate::db::model::CollectionTypeProvisioned; use crate::db::model::Name; @@ -27,6 +25,8 @@ use crate::transaction_retry::OptionalError; use async_bb8_diesel::AsyncRunQueryDsl; use chrono::Utc; use diesel::prelude::*; +use nexus_db_fixed_data::project::SERVICES_PROJECT; +use nexus_db_fixed_data::silo::INTERNAL_SILO_ID; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; diff --git a/nexus/db-queries/src/db/datastore/pub_test_utils.rs b/nexus/db-queries/src/db/datastore/pub_test_utils.rs index 5259a03656..93a172bd15 100644 --- a/nexus/db-queries/src/db/datastore/pub_test_utils.rs +++ b/nexus/db-queries/src/db/datastore/pub_test_utils.rs @@ -39,7 +39,7 @@ pub async fn datastore_test( logctx.log.new(o!()), Arc::new(authz::Authz::new(&logctx.log)), authn::Context::internal_db_init(), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); // TODO: Can we just call "Populate" instead of doing this? @@ -59,8 +59,10 @@ pub async fn datastore_test( // Create an OpContext with the credentials of "test-privileged" for general // testing. - let opctx = - OpContext::for_tests(logctx.log.new(o!()), Arc::clone(&datastore)); + let opctx = OpContext::for_tests( + logctx.log.new(o!()), + Arc::clone(&datastore) as Arc, + ); (opctx, datastore) } diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index d836185d87..4af6bf7263 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -16,10 +16,6 @@ use crate::db::error::public_error_from_diesel; use crate::db::error::retryable; use crate::db::error::ErrorHandler; use crate::db::error::MaybeRetryable::*; -use crate::db::fixed_data::silo::INTERNAL_SILO_ID; -use crate::db::fixed_data::vpc_subnet::DNS_VPC_SUBNET; -use crate::db::fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; -use crate::db::fixed_data::vpc_subnet::NTP_VPC_SUBNET; use crate::db::identity::Asset; use crate::db::lookup::LookupPath; use crate::db::model::Dataset; @@ -37,6 +33,10 @@ use diesel::prelude::*; use diesel::result::Error as DieselError; use diesel::upsert::excluded; use ipnetwork::IpNetwork; +use nexus_db_fixed_data::silo::INTERNAL_SILO_ID; +use nexus_db_fixed_data::vpc_subnet::DNS_VPC_SUBNET; +use nexus_db_fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; +use nexus_db_fixed_data::vpc_subnet::NTP_VPC_SUBNET; use nexus_db_model::IncompleteNetworkInterface; use nexus_db_model::InitialDnsGroup; use nexus_db_model::PasswordHashString; diff --git a/nexus/db-queries/src/db/datastore/role.rs b/nexus/db-queries/src/db/datastore/role.rs index 3a57ffc44c..b91597ad1d 100644 --- a/nexus/db-queries/src/db/datastore/role.rs +++ b/nexus/db-queries/src/db/datastore/role.rs @@ -14,8 +14,6 @@ use crate::db::datastore::RunnableQueryNoReturn; use crate::db::error::public_error_from_diesel; use crate::db::error::ErrorHandler; use crate::db::error::TransactionError; -use crate::db::fixed_data::role_assignment::BUILTIN_ROLE_ASSIGNMENTS; -use crate::db::fixed_data::role_builtin::BUILTIN_ROLES; use crate::db::model::DatabaseString; use crate::db::model::IdentityType; use crate::db::model::RoleAssignment; @@ -25,13 +23,13 @@ use crate::db::pool::DbConnection; use async_bb8_diesel::AsyncConnection; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::prelude::*; +use nexus_db_fixed_data::role_assignment::BUILTIN_ROLE_ASSIGNMENTS; +use nexus_db_fixed_data::role_builtin::BUILTIN_ROLES; use nexus_types::external_api::shared; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; -use omicron_common::api::external::ResourceType; use omicron_common::bail_unless; -use uuid::Uuid; impl DataStore { /// List built-in roles @@ -117,65 +115,6 @@ impl DataStore { Ok(()) } - /// Return the built-in roles that the given built-in user has for the given - /// resource - pub async fn role_asgn_list_for( - &self, - opctx: &OpContext, - identity_type: IdentityType, - identity_id: Uuid, - resource_type: ResourceType, - resource_id: Uuid, - ) -> Result, Error> { - use db::schema::role_assignment::dsl as role_dsl; - use db::schema::silo_group_membership::dsl as group_dsl; - - // There is no resource-specific authorization check because all - // authenticated users need to be able to list their own roles -- - // otherwise we can't do any authorization checks. - // TODO-security rethink this -- how do we know the user is looking up - // their own roles? Maybe this should use an internal authz context. - - // TODO-scalability TODO-security This needs to be paginated. It's not - // exposed via an external API right now but someone could still put us - // into some hurt by assigning loads of roles to someone and having that - // person attempt to access anything. - - let direct_roles_query = role_dsl::role_assignment - .filter(role_dsl::identity_type.eq(identity_type.clone())) - .filter(role_dsl::identity_id.eq(identity_id)) - .filter(role_dsl::resource_type.eq(resource_type.to_string())) - .filter(role_dsl::resource_id.eq(resource_id)) - .select(RoleAssignment::as_select()); - - let roles_from_groups_query = role_dsl::role_assignment - .filter(role_dsl::identity_type.eq(IdentityType::SiloGroup)) - .filter( - role_dsl::identity_id.eq_any( - group_dsl::silo_group_membership - .filter(group_dsl::silo_user_id.eq(identity_id)) - .select(group_dsl::silo_group_id), - ), - ) - .filter(role_dsl::resource_type.eq(resource_type.to_string())) - .filter(role_dsl::resource_id.eq(resource_id)) - .select(RoleAssignment::as_select()); - - let conn = self.pool_connection_authorized(opctx).await?; - if identity_type == IdentityType::SiloUser { - direct_roles_query - .union(roles_from_groups_query) - .load_async::(&*conn) - .await - .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) - } else { - direct_roles_query - .load_async::(&*conn) - .await - .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) - } - } - /// Fetches all of the externally-visible role assignments for the specified /// resource /// diff --git a/nexus/db-queries/src/db/datastore/silo.rs b/nexus/db-queries/src/db/datastore/silo.rs index 0fd858b900..2b7afa3270 100644 --- a/nexus/db-queries/src/db/datastore/silo.rs +++ b/nexus/db-queries/src/db/datastore/silo.rs @@ -15,7 +15,6 @@ use crate::db::error::public_error_from_diesel; use crate::db::error::retryable; use crate::db::error::ErrorHandler; use crate::db::error::TransactionError; -use crate::db::fixed_data::silo::{DEFAULT_SILO, INTERNAL_SILO}; use crate::db::identity::Resource; use crate::db::model::CollectionTypeProvisioned; use crate::db::model::IpPoolResourceType; @@ -29,6 +28,7 @@ use async_bb8_diesel::AsyncConnection; use async_bb8_diesel::AsyncRunQueryDsl; use chrono::Utc; use diesel::prelude::*; +use nexus_db_fixed_data::silo::{DEFAULT_SILO, INTERNAL_SILO}; use nexus_db_model::Certificate; use nexus_db_model::ServiceKind; use nexus_db_model::SiloQuotas; diff --git a/nexus/db-queries/src/db/datastore/silo_user.rs b/nexus/db-queries/src/db/datastore/silo_user.rs index 59cb19a609..2825e2a310 100644 --- a/nexus/db-queries/src/db/datastore/silo_user.rs +++ b/nexus/db-queries/src/db/datastore/silo_user.rs @@ -429,7 +429,9 @@ impl DataStore { use db::schema::role_assignment::dsl; debug!(opctx.log, "attempting to create silo user role assignments"); let count = diesel::insert_into(dsl::role_assignment) - .values(&*db::fixed_data::silo_user::ROLE_ASSIGNMENTS_PRIVILEGED) + .values( + &*nexus_db_fixed_data::silo_user::ROLE_ASSIGNMENTS_PRIVILEGED, + ) .on_conflict(( dsl::identity_type, dsl::identity_id, diff --git a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs index 3630231b63..9738f05ff6 100644 --- a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs +++ b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs @@ -312,7 +312,7 @@ impl DataStore { &self, opctx: &OpContext, ) -> Result<(), Error> { - let id = *db::fixed_data::FLEET_ID; + let id = *nexus_db_fixed_data::FLEET_ID; self.virtual_provisioning_collection_create( opctx, db::model::VirtualProvisioningCollection::new( @@ -331,7 +331,6 @@ mod test { use super::*; use crate::db::datastore::test_utils::datastore_test; - use crate::db::fixed_data; use crate::db::lookup::LookupPath; use nexus_db_model::Instance; use nexus_db_model::Project; @@ -384,8 +383,8 @@ mod test { datastore: &DataStore, opctx: &OpContext, ) -> TestData { - let fleet_id = *fixed_data::FLEET_ID; - let silo_id = *fixed_data::silo::DEFAULT_SILO_ID; + let fleet_id = *nexus_db_fixed_data::FLEET_ID; + let silo_id = *nexus_db_fixed_data::silo::DEFAULT_SILO_ID; let project_id = Uuid::new_v4(); let (authz_project, _project) = datastore diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 98af47f0e2..5322e20dbf 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -12,7 +12,6 @@ use crate::db::collection_insert::AsyncInsertError; use crate::db::collection_insert::DatastoreCollection; use crate::db::error::public_error_from_diesel; use crate::db::error::ErrorHandler; -use crate::db::fixed_data::vpc::SERVICES_VPC_ID; use crate::db::identity::Resource; use crate::db::model::ApplyBlueprintZoneFilterExt; use crate::db::model::ApplySledFilterExt; @@ -45,6 +44,7 @@ use diesel::prelude::*; use diesel::result::DatabaseErrorKind; use diesel::result::Error as DieselError; use ipnetwork::IpNetwork; +use nexus_db_fixed_data::vpc::SERVICES_VPC_ID; use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::SledFilter; use omicron_common::api::external::http_pagination::PaginatedBy; @@ -72,9 +72,9 @@ impl DataStore { &self, opctx: &OpContext, ) -> Result<(), Error> { - use crate::db::fixed_data::project::SERVICES_PROJECT_ID; - use crate::db::fixed_data::vpc::SERVICES_VPC; - use crate::db::fixed_data::vpc::SERVICES_VPC_DEFAULT_ROUTE_ID; + use nexus_db_fixed_data::project::SERVICES_PROJECT_ID; + use nexus_db_fixed_data::vpc::SERVICES_VPC; + use nexus_db_fixed_data::vpc::SERVICES_VPC_DEFAULT_ROUTE_ID; opctx.authorize(authz::Action::Modify, &authz::DATABASE).await?; @@ -175,8 +175,8 @@ impl DataStore { &self, opctx: &OpContext, ) -> Result<(), Error> { - use db::fixed_data::vpc_firewall_rule::DNS_VPC_FW_RULE; - use db::fixed_data::vpc_firewall_rule::NEXUS_VPC_FW_RULE; + use nexus_db_fixed_data::vpc_firewall_rule::DNS_VPC_FW_RULE; + use nexus_db_fixed_data::vpc_firewall_rule::NEXUS_VPC_FW_RULE; debug!(opctx.log, "attempting to create built-in VPC firewall rules"); @@ -229,9 +229,9 @@ impl DataStore { &self, opctx: &OpContext, ) -> Result<(), Error> { - use crate::db::fixed_data::vpc_subnet::DNS_VPC_SUBNET; - use crate::db::fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; - use crate::db::fixed_data::vpc_subnet::NTP_VPC_SUBNET; + use nexus_db_fixed_data::vpc_subnet::DNS_VPC_SUBNET; + use nexus_db_fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; + use nexus_db_fixed_data::vpc_subnet::NTP_VPC_SUBNET; debug!(opctx.log, "attempting to create built-in VPC Subnets"); @@ -1230,9 +1230,9 @@ mod tests { use crate::db::datastore::test::sled_system_hardware_for_test; use crate::db::datastore::test_utils::datastore_test; use crate::db::datastore::test_utils::IneligibleSleds; - use crate::db::fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; use crate::db::model::Project; use crate::db::queries::vpc::MAX_VNI_SEARCH_RANGE_SIZE; + use nexus_db_fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; use nexus_db_model::IncompleteNetworkInterface; use nexus_db_model::SledUpdate; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; diff --git a/nexus/db-queries/src/db/lookup.rs b/nexus/db-queries/src/db/lookup.rs index 487a68b517..0999694c54 100644 --- a/nexus/db-queries/src/db/lookup.rs +++ b/nexus/db-queries/src/db/lookup.rs @@ -924,8 +924,10 @@ mod test { let (_, datastore) = crate::db::datastore::test_utils::datastore_test(&logctx, &db) .await; - let opctx = - OpContext::for_tests(logctx.log.new(o!()), Arc::clone(&datastore)); + let opctx = OpContext::for_tests( + logctx.log.new(o!()), + Arc::clone(&datastore) as Arc, + ); let project_name: Name = Name("my-project".parse().unwrap()); let instance_name: Name = Name("my-instance".parse().unwrap()); diff --git a/nexus/db-queries/src/db/mod.rs b/nexus/db-queries/src/db/mod.rs index 7ce6890a4d..7bd1bbec61 100644 --- a/nexus/db-queries/src/db/mod.rs +++ b/nexus/db-queries/src/db/mod.rs @@ -17,7 +17,6 @@ mod cte_utils; pub mod datastore; pub(crate) mod error; mod explain; -pub mod fixed_data; pub mod lookup; mod on_conflict_ext; // Public for doctests. @@ -42,6 +41,7 @@ pub use pool_connection::DISALLOW_FULL_TABLE_SCAN_SQL; #[cfg(test)] mod test_utils; +pub use nexus_db_fixed_data as fixed_data; pub use nexus_db_model as model; use nexus_db_model::saga_types; pub use nexus_db_model::schema; diff --git a/nexus/db-queries/src/db/queries/virtual_provisioning_collection_update.rs b/nexus/db-queries/src/db/queries/virtual_provisioning_collection_update.rs index 895fee2092..b3c1a569b0 100644 --- a/nexus/db-queries/src/db/queries/virtual_provisioning_collection_update.rs +++ b/nexus/db-queries/src/db/queries/virtual_provisioning_collection_update.rs @@ -122,7 +122,7 @@ WITH UNION (SELECT ").param().sql(" AS id) ),") .bind::(project_id) - .bind::(*crate::db::fixed_data::FLEET_ID) + .bind::(*nexus_db_fixed_data::FLEET_ID) .sql(" quotas AS ( diff --git a/nexus/db-queries/src/db/saga_recovery.rs b/nexus/db-queries/src/db/saga_recovery.rs index 55cda03c3c..25f8ff788d 100644 --- a/nexus/db-queries/src/db/saga_recovery.rs +++ b/nexus/db-queries/src/db/saga_recovery.rs @@ -447,7 +447,10 @@ mod test { let (storage, sec_client, uctx) = create_storage_sec_and_context(&log, db_datastore.clone(), sec_id); let sec_log = log.new(o!("component" => "SEC")); - let opctx = OpContext::for_tests(log, Arc::clone(&db_datastore)); + let opctx = OpContext::for_tests( + log, + Arc::clone(&db_datastore) as Arc, + ); // Create and start a saga. // @@ -520,7 +523,10 @@ mod test { let (storage, sec_client, uctx) = create_storage_sec_and_context(&log, db_datastore.clone(), sec_id); let sec_log = log.new(o!("component" => "SEC")); - let opctx = OpContext::for_tests(log, Arc::clone(&db_datastore)); + let opctx = OpContext::for_tests( + log, + Arc::clone(&db_datastore) as Arc, + ); // Create and start a saga, which we expect to complete successfully. let saga_id = SagaId(Uuid::new_v4()); diff --git a/nexus/db-queries/src/lib.rs b/nexus/db-queries/src/lib.rs index 60177990e8..003310f920 100644 --- a/nexus/db-queries/src/lib.rs +++ b/nexus/db-queries/src/lib.rs @@ -4,17 +4,19 @@ //! Facilities for working with the Omicron database -pub mod authn; -pub mod authz; -pub mod context; +pub use nexus_auth::authn; +pub use nexus_auth::authz; +pub use nexus_auth::context; + pub mod db; pub mod provisioning; pub mod transaction_retry; +#[cfg(test)] +mod policy_test; + #[macro_use] extern crate slog; -#[macro_use] -extern crate newtype_derive; #[cfg(test)] #[macro_use] extern crate diesel; diff --git a/nexus/db-queries/src/authz/policy_test/coverage.rs b/nexus/db-queries/src/policy_test/coverage.rs similarity index 97% rename from nexus/db-queries/src/authz/policy_test/coverage.rs rename to nexus/db-queries/src/policy_test/coverage.rs index 021c9ef119..08235332ff 100644 --- a/nexus/db-queries/src/authz/policy_test/coverage.rs +++ b/nexus/db-queries/src/policy_test/coverage.rs @@ -2,8 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::authz; -use crate::authz::AuthorizedResource; +use nexus_auth::authz; +use nexus_auth::authz::AuthorizedResource; +use slog::{debug, error, o, warn}; use std::collections::BTreeSet; /// Helper for identifying authz resources not covered by the IAM role policy diff --git a/nexus/db-queries/src/authz/policy_test/mod.rs b/nexus/db-queries/src/policy_test/mod.rs similarity index 97% rename from nexus/db-queries/src/authz/policy_test/mod.rs rename to nexus/db-queries/src/policy_test/mod.rs index b6961bcc30..395a480c47 100644 --- a/nexus/db-queries/src/authz/policy_test/mod.rs +++ b/nexus/db-queries/src/policy_test/mod.rs @@ -14,14 +14,14 @@ mod coverage; mod resource_builder; mod resources; -use crate::authn; -use crate::authn::SiloAuthnPolicy; -use crate::authz; -use crate::context::OpContext; use crate::db; -use authn::USER_TEST_PRIVILEGED; use coverage::Coverage; use futures::StreamExt; +use nexus_auth::authn; +use nexus_auth::authn::SiloAuthnPolicy; +use nexus_auth::authn::USER_TEST_PRIVILEGED; +use nexus_auth::authz; +use nexus_auth::context::OpContext; use nexus_test_utils::db::test_setup_database; use nexus_types::external_api::shared; use nexus_types::external_api::shared::FleetRole; @@ -33,6 +33,7 @@ use omicron_test_utils::dev; use resource_builder::DynAuthorizedResource; use resource_builder::ResourceBuilder; use resource_builder::ResourceSet; +use slog::{o, trace}; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::io::Cursor; @@ -117,7 +118,7 @@ async fn test_iam_roles_behavior() { main_silo_id, SiloAuthnPolicy::default(), ), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); Arc::new((username.clone(), opctx)) @@ -140,7 +141,7 @@ async fn test_iam_roles_behavior() { user_log, Arc::clone(&authz), authn::Context::internal_unauthenticated(), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ), ))); @@ -439,7 +440,8 @@ async fn test_conferred_roles() { main_silo_id, policy.clone(), ), - Arc::clone(&datastore), + Arc::clone(&datastore) + as Arc, ); Arc::new((username.clone(), opctx)) }) diff --git a/nexus/db-queries/src/authz/policy_test/resource_builder.rs b/nexus/db-queries/src/policy_test/resource_builder.rs similarity index 74% rename from nexus/db-queries/src/authz/policy_test/resource_builder.rs rename to nexus/db-queries/src/policy_test/resource_builder.rs index 59cb283a95..3d09b2ab2d 100644 --- a/nexus/db-queries/src/authz/policy_test/resource_builder.rs +++ b/nexus/db-queries/src/policy_test/resource_builder.rs @@ -6,14 +6,14 @@ //! IAM policy test use super::coverage::Coverage; -use crate::authz; -use crate::authz::ApiResourceWithRolesType; -use crate::authz::AuthorizedResource; -use crate::context::OpContext; use crate::db; use authz::ApiResource; use futures::future::BoxFuture; use futures::FutureExt; +use nexus_auth::authz; +use nexus_auth::authz::ApiResourceWithRolesType; +use nexus_auth::authz::AuthorizedResource; +use nexus_auth::context::OpContext; use nexus_db_model::DatabaseString; use nexus_types::external_api::shared; use omicron_common::api::external::Error; @@ -192,40 +192,40 @@ pub trait DynAuthorizedResource: AuthorizedResource + std::fmt::Debug { fn resource_name(&self) -> String; } -impl DynAuthorizedResource for T -where - T: ApiResource + AuthorizedResource + oso::PolarClass + Clone, -{ - fn do_authorize<'a, 'b>( - &'a self, - opctx: &'b OpContext, - action: authz::Action, - ) -> BoxFuture<'a, Result<(), Error>> - where - 'b: 'a, - { - opctx.authorize(action, self).boxed() - } - - fn resource_name(&self) -> String { - let my_ident = match self.lookup_type() { - LookupType::ByName(name) => format!("{:?}", name), - LookupType::ById(id) => format!("id {:?}", id.to_string()), - LookupType::ByCompositeId(id) => format!("id {:?}", id), - LookupType::ByOther(_) => { - unimplemented!() +macro_rules! impl_dyn_authorized_resource_for_global { + ($t:ty) => { + impl DynAuthorizedResource for $t { + fn resource_name(&self) -> String { + String::from(stringify!($t)) } - }; - format!("{:?} {}", self.resource_type(), my_ident) - } + fn do_authorize<'a, 'b>( + &'a self, + opctx: &'b OpContext, + action: authz::Action, + ) -> BoxFuture<'a, Result<(), Error>> + where + 'b: 'a, + { + opctx.authorize(action, self).boxed() + } + } + }; } -macro_rules! impl_dyn_authorized_resource_for_global { +macro_rules! impl_dyn_authorized_resource_for_resource { ($t:ty) => { impl DynAuthorizedResource for $t { fn resource_name(&self) -> String { - String::from(stringify!($t)) + let my_ident = match self.lookup_type() { + LookupType::ByName(name) => format!("{:?}", name), + LookupType::ById(id) => format!("id {:?}", id.to_string()), + LookupType::ByCompositeId(id) => format!("id {:?}", id), + LookupType::ByOther(_) => { + unimplemented!() + } + }; + format!("{:?} {}", self.resource_type(), my_ident) } fn do_authorize<'a, 'b>( @@ -242,7 +242,39 @@ macro_rules! impl_dyn_authorized_resource_for_global { }; } -impl_dyn_authorized_resource_for_global!(authz::oso_generic::Database); +impl_dyn_authorized_resource_for_resource!(authz::AddressLot); +impl_dyn_authorized_resource_for_resource!(authz::Blueprint); +impl_dyn_authorized_resource_for_resource!(authz::Certificate); +impl_dyn_authorized_resource_for_resource!(authz::DeviceAccessToken); +impl_dyn_authorized_resource_for_resource!(authz::DeviceAuthRequest); +impl_dyn_authorized_resource_for_resource!(authz::Disk); +impl_dyn_authorized_resource_for_resource!(authz::Fleet); +impl_dyn_authorized_resource_for_resource!(authz::FloatingIp); +impl_dyn_authorized_resource_for_resource!(authz::IdentityProvider); +impl_dyn_authorized_resource_for_resource!(authz::Image); +impl_dyn_authorized_resource_for_resource!(authz::Instance); +impl_dyn_authorized_resource_for_resource!(authz::InstanceNetworkInterface); +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::SamlIdentityProvider); +impl_dyn_authorized_resource_for_resource!(authz::Service); +impl_dyn_authorized_resource_for_resource!(authz::Silo); +impl_dyn_authorized_resource_for_resource!(authz::SiloGroup); +impl_dyn_authorized_resource_for_resource!(authz::SiloImage); +impl_dyn_authorized_resource_for_resource!(authz::SiloUser); +impl_dyn_authorized_resource_for_resource!(authz::Sled); +impl_dyn_authorized_resource_for_resource!(authz::Snapshot); +impl_dyn_authorized_resource_for_resource!(authz::SshKey); +impl_dyn_authorized_resource_for_resource!(authz::TufArtifact); +impl_dyn_authorized_resource_for_resource!(authz::TufRepo); +impl_dyn_authorized_resource_for_resource!(authz::Vpc); +impl_dyn_authorized_resource_for_resource!(authz::VpcSubnet); +impl_dyn_authorized_resource_for_resource!(authz::Zpool); + +impl_dyn_authorized_resource_for_global!(authz::Database); impl_dyn_authorized_resource_for_global!(authz::BlueprintConfig); impl_dyn_authorized_resource_for_global!(authz::ConsoleSessionList); impl_dyn_authorized_resource_for_global!(authz::DeviceAuthRequestList); diff --git a/nexus/db-queries/src/authz/policy_test/resources.rs b/nexus/db-queries/src/policy_test/resources.rs similarity index 99% rename from nexus/db-queries/src/authz/policy_test/resources.rs rename to nexus/db-queries/src/policy_test/resources.rs index bc30e77fac..478fa169ff 100644 --- a/nexus/db-queries/src/authz/policy_test/resources.rs +++ b/nexus/db-queries/src/policy_test/resources.rs @@ -6,8 +6,8 @@ use super::resource_builder::ResourceBuilder; use super::resource_builder::ResourceSet; -use crate::authz; use crate::db::model::ArtifactId; +use nexus_auth::authz; use nexus_db_model::SemverVersion; use omicron_common::api::external::LookupType; use omicron_uuid_kinds::GenericUuid; @@ -367,8 +367,8 @@ pub fn exempted_authz_classes() -> BTreeSet { [ // Non-resources: authz::Action::get_polar_class(), - authz::actor::AnyActor::get_polar_class(), - authz::actor::AuthenticatedActor::get_polar_class(), + authz::AnyActor::get_polar_class(), + authz::AuthenticatedActor::get_polar_class(), // Resources whose behavior should be identical to an existing type // and we don't want to do the test twice for performance reasons: // none yet. diff --git a/nexus/db-queries/tests/output/authz-roles.out b/nexus/db-queries/tests/output/authz-roles.out index 0482cdfd2a..41a1ded3b4 100644 --- a/nexus/db-queries/tests/output/authz-roles.out +++ b/nexus/db-queries/tests/output/authz-roles.out @@ -1,4 +1,4 @@ -resource: authz::oso_generic::Database +resource: authz::Database USER Q R LC RP M MP CC D fleet-admin ✔ ✘ ✘ ✘ ✘ ✘ ✘ ✘ diff --git a/nexus/src/app/mod.rs b/nexus/src/app/mod.rs index 263ab24c70..f9bcc2cf80 100644 --- a/nexus/src/app/mod.rs +++ b/nexus/src/app/mod.rs @@ -377,7 +377,7 @@ impl Nexus { log.new(o!("component" => "DataLoader")), Arc::clone(&authz), authn::Context::internal_db_init(), - Arc::clone(&db_datastore), + Arc::clone(&db_datastore) as Arc, ); let populate_args = PopulateArgs::new(rack_id); @@ -391,7 +391,7 @@ impl Nexus { log.new(o!("component" => "BackgroundTasks")), Arc::clone(&authz), authn::Context::internal_api(), - Arc::clone(&db_datastore), + Arc::clone(&db_datastore) as Arc, ); let v2p_watcher_channel = tokio::sync::watch::channel(()); @@ -440,13 +440,15 @@ impl Nexus { log.new(o!("component" => "InstanceAllocator")), Arc::clone(&authz), authn::Context::internal_read(), - Arc::clone(&db_datastore), + Arc::clone(&db_datastore) + as Arc, ), opctx_external_authn: OpContext::for_background( log.new(o!("component" => "ExternalAuthn")), Arc::clone(&authz), authn::Context::external_authn(), - Arc::clone(&db_datastore), + Arc::clone(&db_datastore) + as Arc, ), samael_max_issue_delay: std::sync::Mutex::new(None), internal_resolver: resolver, @@ -469,7 +471,7 @@ impl Nexus { log.new(o!("component" => "SagaRecoverer")), Arc::clone(&authz), authn::Context::internal_saga_recovery(), - Arc::clone(&db_datastore), + Arc::clone(&db_datastore) as Arc, ); let saga_logger = nexus.log.new(o!("saga_type" => "recovery")); let recovery_task = db::recover( @@ -701,7 +703,8 @@ impl Nexus { self.log.new(o!("component" => "ServiceBalancer")), Arc::clone(&self.authz), authn::Context::internal_service_balancer(), - Arc::clone(&self.db_datastore), + Arc::clone(&self.db_datastore) + as Arc, ) } @@ -711,7 +714,8 @@ impl Nexus { self.log.new(o!("component" => "InternalApi")), Arc::clone(&self.authz), authn::Context::internal_api(), - Arc::clone(&self.db_datastore), + Arc::clone(&self.db_datastore) + as Arc, ) } diff --git a/nexus/src/app/test_interfaces.rs b/nexus/src/app/test_interfaces.rs index 581b9a89bb..9e7bd1582f 100644 --- a/nexus/src/app/test_interfaces.rs +++ b/nexus/src/app/test_interfaces.rs @@ -73,7 +73,8 @@ impl TestInterfaces for super::Nexus { ) -> Result>, Error> { let opctx = OpContext::for_tests( self.log.new(o!()), - Arc::clone(&self.db_datastore), + Arc::clone(&self.db_datastore) + as Arc, ); self.instance_sled_by_id_with_opctx(id, &opctx).await @@ -98,7 +99,8 @@ impl TestInterfaces for super::Nexus { ) -> Result>, Error> { let opctx = OpContext::for_tests( self.log.new(o!()), - Arc::clone(&self.db_datastore), + Arc::clone(&self.db_datastore) + as Arc, ); let (.., db_disk) = LookupPath::new(&opctx, &self.db_datastore) .disk_id(*id) @@ -112,7 +114,8 @@ impl TestInterfaces for super::Nexus { async fn instance_sled_id(&self, id: &Uuid) -> Result, Error> { let opctx = OpContext::for_tests( self.log.new(o!()), - Arc::clone(&self.db_datastore), + Arc::clone(&self.db_datastore) + as Arc, ); self.instance_sled_id_with_opctx(id, &opctx).await @@ -138,7 +141,8 @@ impl TestInterfaces for super::Nexus { async fn set_disk_as_faulted(&self, disk_id: &Uuid) -> Result { let opctx = OpContext::for_tests( self.log.new(o!()), - Arc::clone(&self.db_datastore), + Arc::clone(&self.db_datastore) + as Arc, ); let (.., authz_disk, db_disk) = diff --git a/nexus/src/external_api/console_api.rs b/nexus/src/external_api/console_api.rs index caff195047..fb0a47bbea 100644 --- a/nexus/src/external_api/console_api.rs +++ b/nexus/src/external_api/console_api.rs @@ -270,13 +270,14 @@ pub(crate) async fn login_saml_redirect( // unauthenticated. let opctx = nexus.opctx_external_authn(); - let (.., identity_provider) = IdentityProviderType::lookup( - &nexus.datastore(), - &opctx, - &path_params.silo_name, - &path_params.provider_name, - ) - .await?; + let (.., identity_provider) = nexus + .datastore() + .identity_provider_lookup( + &opctx, + &path_params.silo_name, + &path_params.provider_name, + ) + .await?; match identity_provider { IdentityProviderType::Saml(saml_identity_provider) => { @@ -330,9 +331,9 @@ pub(crate) async fn login_saml( // keep specifically for this purpose. let opctx = nexus.opctx_external_authn(); - let (authz_silo, db_silo, identity_provider) = - IdentityProviderType::lookup( - &nexus.datastore(), + let (authz_silo, db_silo, identity_provider) = nexus + .datastore() + .identity_provider_lookup( &opctx, &path_params.silo_name, &path_params.provider_name, diff --git a/nexus/src/populate.rs b/nexus/src/populate.rs index ffe67baeae..724b25162d 100644 --- a/nexus/src/populate.rs +++ b/nexus/src/populate.rs @@ -388,7 +388,7 @@ mod test { logctx.log.clone(), Arc::new(authz::Authz::new(&logctx.log)), authn::Context::internal_db_init(), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); let log = &logctx.log; @@ -444,7 +444,7 @@ mod test { logctx.log.clone(), Arc::new(authz::Authz::new(&logctx.log)), authn::Context::internal_db_init(), - Arc::clone(&datastore), + Arc::clone(&datastore) as Arc, ); info!(&log, "cleaning up database"); diff --git a/nexus/tests/integration_tests/saml.rs b/nexus/tests/integration_tests/saml.rs index 80816f2ea2..e075f3e4da 100644 --- a/nexus/tests/integration_tests/saml.rs +++ b/nexus/tests/integration_tests/saml.rs @@ -106,20 +106,23 @@ async fn test_create_a_saml_idp(cptestctx: &ControlPlaneTestContext) { .await .unwrap(); - let (.., retrieved_silo_idp_from_nexus) = IdentityProviderType::lookup( - &nexus.datastore(), - &nexus.opctx_external_authn(), - &omicron_common::api::external::Name::try_from(SILO_NAME.to_string()) + let (.., retrieved_silo_idp_from_nexus) = nexus + .datastore() + .identity_provider_lookup( + &nexus.opctx_external_authn(), + &omicron_common::api::external::Name::try_from( + SILO_NAME.to_string(), + ) + .unwrap() + .into(), + &omicron_common::api::external::Name::try_from( + "some-totally-real-saml-provider".to_string(), + ) .unwrap() .into(), - &omicron_common::api::external::Name::try_from( - "some-totally-real-saml-provider".to_string(), ) - .unwrap() - .into(), - ) - .await - .unwrap(); + .await + .unwrap(); match retrieved_silo_idp_from_nexus { IdentityProviderType::Saml(_) => { diff --git a/nexus/tests/integration_tests/silos.rs b/nexus/tests/integration_tests/silos.rs index e95b2870ca..2e6c21bb79 100644 --- a/nexus/tests/integration_tests/silos.rs +++ b/nexus/tests/integration_tests/silos.rs @@ -4,9 +4,7 @@ use crate::integration_tests::saml::SAML_IDP_DESCRIPTOR; use dropshot::ResultsPage; -use nexus_db_queries::authn::silos::{ - AuthenticatedSubject, IdentityProviderType, -}; +use nexus_db_queries::authn::silos::AuthenticatedSubject; use nexus_db_queries::authn::{USER_TEST_PRIVILEGED, USER_TEST_UNPRIVILEGED}; use nexus_db_queries::authz::{self}; use nexus_db_queries::context::OpContext; @@ -525,19 +523,22 @@ async fn test_deleting_a_silo_deletes_the_idp( // Expect that the silo is gone let nexus = &cptestctx.server.server_context().nexus; - let response = IdentityProviderType::lookup( - &nexus.datastore(), - &nexus.opctx_external_authn(), - &omicron_common::api::external::Name::try_from(SILO_NAME.to_string()) + let response = nexus + .datastore() + .identity_provider_lookup( + &nexus.opctx_external_authn(), + &omicron_common::api::external::Name::try_from( + SILO_NAME.to_string(), + ) + .unwrap() + .into(), + &omicron_common::api::external::Name::try_from( + "some-totally-real-saml-provider".to_string(), + ) .unwrap() .into(), - &omicron_common::api::external::Name::try_from( - "some-totally-real-saml-provider".to_string(), ) - .unwrap() - .into(), - ) - .await; + .await; assert!(response.is_err()); match response.err().unwrap() { diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index f82fe1c833..7880422c47 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -220,6 +220,7 @@ tracing = { version = "0.1.40", features = ["log"] } trust-dns-proto = { version = "0.22.0" } unicode-bidi = { version = "0.3.15" } unicode-normalization = { version = "0.1.23" } +unicode-xid = { version = "0.2.4" } usdt = { version = "0.5.0" } usdt-impl = { version = "0.5.0", default-features = false, features = ["asm", "des"] } uuid = { version = "1.8.0", features = ["serde", "v4"] }