-
Notifications
You must be signed in to change notification settings - Fork 63
[nexus] Split authn/authz and db-fixed-data into new crates #5849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| @@ -68,57 +62,6 @@ pub enum IdentityProviderType { | |||
| Saml(SamlIdentityProvider), | |||
| } | |||
|
|
|||
| impl IdentityProviderType { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method was moved into nexus/db-queries/src/db/datastore/identity_provider.rs - it doesn't need to be defined here, and is tightly coupled with the datastore, not auth logic.
| self.oso.is_allowed(actor.clone(), action, resource.clone()) | ||
| } | ||
|
|
||
| #[cfg(test)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The policy_test (look for the files changed in this PR) was previously a "unit test" in db-queries that used auth and datastore methods extensively.
It has stayed in db-queries to access the database methods, but still needs to access methods defined in nexus-auth. As a result, some cfg(test) methods are exposed as pub to make this work. This is one such example.
| authn: Arc<authn::Context>, | ||
| authz: Arc<Authz>, | ||
| datastore: Arc<DataStore>, | ||
| datastore: Arc<dyn Storage>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another common pattern through this PR: Wherever nexus/auth depends on the DataStore, I basically use dependency injection via dyn Storage to not directly depend on the datastore. Then, the implementation of Storage allows nexus/auth to access a very limited subset of the datastore.
Turns out, that "limited interface" is only a single method. See: nexus/auth/src/storage.rs
| struct FakeStorage {} | ||
|
|
||
| impl FakeStorage { | ||
| fn new() -> Arc<dyn crate::storage::Storage> { | ||
| 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<Vec<RoleAssignment>, Error> { | ||
| todo!(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an example of a test that actively does not need access to the database, and can be replaced by a stub.
| struct FakeStorage {} | ||
|
|
||
| impl FakeStorage { | ||
| fn new() -> Arc<dyn crate::storage::Storage> { | ||
| 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<Vec<RoleAssignment>, Error> { | ||
| todo!(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another example of a test that previously required a whole database, but actually, never queries anything from the database
| #[async_trait::async_trait] | ||
| pub trait Storage: Send + Sync { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be interested in figuring out a way to make this not use async-trait.
I basically went down this route because:
- authz::Context contains one of these objects
- If it uses generics, then
OpContextalso needs a generic parameter - That changes a lot of code
- ... so I went down the
dyn Storageroute instead, as the DataStore is already wrapped in an Arc
| 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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just moved, not new
| 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method was moved exactly from nexus/db-queries/src/db/datastore/role.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could spell out assign, but I also understand not wanting to introduce that to the diff if this is used in a lot of places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grepping for role_asgn, this appears to be a pattern used beyond this method. I agree with you that I'd rather it be fully spelled out, but I'm gonna punt on this to avoid touching several files that aren't currently part of this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works for me
| use ref_cast::RefCast; | ||
|
|
||
| impl DataStore { | ||
| pub async fn identity_provider_lookup( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method was moved exactly from IdentityProvider::lookup to here (and DataStore is now a &self parameter instead of the first argument)
hawkw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, this seems good to me, although I had a few small style suggestions --- take them or leave them! And, I've mostly tried to avoid commenting on the code that was moved around but hasn't actually changed.
| use crate::authn; | ||
| use async_trait::async_trait; | ||
| use authn::Reason; | ||
| use slog::trace; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm assuming this is because there used to be a `#[macro_use] somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly:
omicron/nexus/db-queries/src/lib.rs
Lines 14 to 17 in b0dfd53
| #[macro_use] | |
| extern crate slog; | |
| #[macro_use] | |
| extern crate newtype_derive; |
| @@ -172,8 +172,7 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result<OsoInit, anyhow::Error> { | |||
| /// | |||
| /// 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))] | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, potentially, make the strum dependency optional and make this:
#[cfg_attr(feature = "strum", derive(strum::EnumIter)]and then have policy_test enable the strum feature in its dev-dependency on authz...but i'm not sure if that's really worth the effort, up to you.
andrewjstone
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
| 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could spell out assign, but I also understand not wanting to introduce that to the diff if this is used in a lot of places.
| } | ||
|
|
||
| impl_dyn_authorized_resource_for_global!(authz::oso_generic::Database); | ||
| impl_dyn_authorized_resource_for_resource!(authz::AddressLot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nifty :)
As a part of the ongoing effort to split Nexus into smaller pieces, this PR splits out two new crates:
nexus-authtakes the contents ofnexus/db-queries/src/auth{n,z}, as well asnexus/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 innexus/auth/storage.nexus-db-fixed-datatakes the contents ofnexus/db-queries/src/db/fixed-dataand separates this logic into a new crate.