-
Notifications
You must be signed in to change notification settings - Fork 62
/policy for current silo, /global/policy for fleet
#1573
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
Changes from all commits
f374892
4339550
e531ea1
f0b7175
03719ae
1ce5a09
aa1ee4b
69ae4c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,12 +80,10 @@ impl super::Nexus { | |
| pub async fn silo_fetch_policy( | ||
| &self, | ||
| opctx: &OpContext, | ||
| silo_name: &Name, | ||
| silo_lookup: db::lookup::Silo<'_>, | ||
| ) -> LookupResult<shared::Policy<authz::SiloRole>> { | ||
| let (.., authz_silo) = LookupPath::new(opctx, &self.db_datastore) | ||
| .silo_name(silo_name) | ||
| .lookup_for(authz::Action::ReadPolicy) | ||
| .await?; | ||
| let (.., authz_silo) = | ||
| silo_lookup.lookup_for(authz::Action::ReadPolicy).await?; | ||
| let role_assignments = self | ||
| .db_datastore | ||
| .role_assignment_fetch_visible(opctx, &authz_silo) | ||
|
|
@@ -100,13 +98,11 @@ impl super::Nexus { | |
| pub async fn silo_update_policy( | ||
| &self, | ||
| opctx: &OpContext, | ||
| silo_name: &Name, | ||
| silo_lookup: db::lookup::Silo<'_>, | ||
| policy: &shared::Policy<authz::SiloRole>, | ||
| ) -> UpdateResult<shared::Policy<authz::SiloRole>> { | ||
| let (.., authz_silo) = LookupPath::new(opctx, &self.db_datastore) | ||
| .silo_name(silo_name) | ||
| .lookup_for(authz::Action::ModifyPolicy) | ||
| .await?; | ||
| let (.., authz_silo) = | ||
| silo_lookup.lookup_for(authz::Action::ModifyPolicy).await?; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming the static lifetime isn't wrong, this approach of taking a lookup instead of a name or ID is rad. I like it. |
||
|
|
||
| let role_assignments = self | ||
| .db_datastore | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,9 @@ type NexusApiDescription = ApiDescription<Arc<ServerContext>>; | |
| /// Returns a description of the external nexus API | ||
| pub fn external_api() -> NexusApiDescription { | ||
| fn register_endpoints(api: &mut NexusApiDescription) -> Result<(), String> { | ||
| api.register(global_policy_view)?; | ||
| api.register(global_policy_update)?; | ||
|
|
||
| api.register(policy_view)?; | ||
| api.register(policy_update)?; | ||
|
|
||
|
|
@@ -316,10 +319,10 @@ pub fn external_api() -> NexusApiDescription { | |
| /// Fetch the top-level IAM policy | ||
| #[endpoint { | ||
| method = GET, | ||
| path = "/policy", | ||
| path = "/global/policy", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| tags = ["policy"], | ||
| }] | ||
| async fn policy_view( | ||
| async fn global_policy_view( | ||
| rqctx: Arc<RequestContext<Arc<ServerContext>>>, | ||
| ) -> Result<HttpResponseOk<shared::Policy<authz::FleetRole>>, HttpError> { | ||
| let apictx = rqctx.context(); | ||
|
|
@@ -342,10 +345,10 @@ struct ByIdPathParams { | |
| /// Update the top-level IAM policy | ||
| #[endpoint { | ||
| method = PUT, | ||
| path = "/policy", | ||
| path = "/global/policy", | ||
| tags = ["policy"], | ||
| }] | ||
| async fn policy_update( | ||
| async fn global_policy_update( | ||
| rqctx: Arc<RequestContext<Arc<ServerContext>>>, | ||
| new_policy: TypedBody<shared::Policy<authz::FleetRole>>, | ||
| ) -> Result<HttpResponseOk<shared::Policy<authz::FleetRole>>, HttpError> { | ||
|
|
@@ -364,6 +367,62 @@ async fn policy_update( | |
| apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await | ||
| } | ||
|
|
||
| /// Fetch the current silo's IAM policy | ||
| #[endpoint { | ||
| method = GET, | ||
| path = "/policy", | ||
| tags = ["silos"], | ||
| }] | ||
| pub async fn policy_view( | ||
| rqctx: Arc<RequestContext<Arc<ServerContext>>>, | ||
| ) -> Result<HttpResponseOk<shared::Policy<authz::SiloRole>>, HttpError> { | ||
| let apictx = rqctx.context(); | ||
| let nexus = &apictx.nexus; | ||
| let handler = async { | ||
| let opctx = OpContext::for_external_api(&rqctx).await?; | ||
| let authz_silo = opctx | ||
| .authn | ||
| .silo_required() | ||
| .internal_context("loading current silo")?; | ||
|
|
||
| let lookup = nexus.db_lookup(&opctx).silo_id(authz_silo.id()); | ||
| let policy = nexus.silo_fetch_policy(&opctx, lookup).await?; | ||
| Ok(HttpResponseOk(policy)) | ||
| }; | ||
| apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await | ||
| } | ||
|
|
||
| /// Update the current silo's IAM policy | ||
| #[endpoint { | ||
| method = PUT, | ||
| path = "/policy", | ||
| tags = ["silos"], | ||
| }] | ||
| async fn policy_update( | ||
| rqctx: Arc<RequestContext<Arc<ServerContext>>>, | ||
| new_policy: TypedBody<shared::Policy<authz::SiloRole>>, | ||
| ) -> Result<HttpResponseOk<shared::Policy<authz::SiloRole>>, HttpError> { | ||
| let apictx = rqctx.context(); | ||
| let nexus = &apictx.nexus; | ||
| let new_policy = new_policy.into_inner(); | ||
|
|
||
| let handler = async { | ||
| let nasgns = new_policy.role_assignments.len(); | ||
| // This should have been validated during parsing. | ||
| bail_unless!(nasgns <= shared::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); | ||
| let opctx = OpContext::for_external_api(&rqctx).await?; | ||
| let authz_silo = opctx | ||
| .authn | ||
| .silo_required() | ||
| .internal_context("loading current silo")?; | ||
| let lookup = nexus.db_lookup(&opctx).silo_id(authz_silo.id()); | ||
| let policy = | ||
| nexus.silo_update_policy(&opctx, lookup, &new_policy).await?; | ||
| Ok(HttpResponseOk(policy)) | ||
| }; | ||
| apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await | ||
| } | ||
|
|
||
| /// List silos | ||
| /// | ||
| /// Lists silos that are discoverable based on the current permissions. | ||
|
|
@@ -502,7 +561,8 @@ async fn silo_policy_view( | |
|
|
||
| let handler = async { | ||
| let opctx = OpContext::for_external_api(&rqctx).await?; | ||
| let policy = nexus.silo_fetch_policy(&opctx, silo_name).await?; | ||
| let lookup = nexus.db_lookup(&opctx).silo_name(silo_name); | ||
| let policy = nexus.silo_fetch_policy(&opctx, lookup).await?; | ||
| Ok(HttpResponseOk(policy)) | ||
| }; | ||
| apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await | ||
|
|
@@ -530,8 +590,9 @@ async fn silo_policy_update( | |
| // This should have been validated during parsing. | ||
| bail_unless!(nasgns <= shared::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); | ||
| let opctx = OpContext::for_external_api(&rqctx).await?; | ||
| let lookup = nexus.db_lookup(&opctx).silo_name(silo_name); | ||
| let policy = | ||
| nexus.silo_update_policy(&opctx, silo_name, &new_policy).await?; | ||
| nexus.silo_update_policy(&opctx, lookup, &new_policy).await?; | ||
| Ok(HttpResponseOk(policy)) | ||
| }; | ||
| apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,7 +40,7 @@ lazy_static! { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| format!("/hardware/sleds/{}", SLED_AGENT_UUID); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Global policy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub static ref POLICY_URL: &'static str = "/policy"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub static ref GLOBAL_POLICY_URL: &'static str = "/global/policy"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Silo used for testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub static ref DEMO_SILO_NAME: Name = "demo-silo".parse().unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -520,7 +520,7 @@ lazy_static! { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub static ref VERIFY_ENDPOINTS: Vec<VerifyEndpoint> = vec![ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Global IAM policy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VerifyEndpoint { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: *POLICY_URL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: *GLOBAL_POLICY_URL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| visibility: Visibility::Public, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unprivileged_access: UnprivilegedAccess::None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allowed_methods: vec![ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -676,6 +676,21 @@ lazy_static! { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VerifyEndpoint { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: "/policy", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| visibility: Visibility::Public, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unprivileged_access: UnprivilegedAccess::ReadOnly, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allowed_methods: vec![ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AllowedMethod::Get, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AllowedMethod::Put( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serde_json::to_value( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &shared::Policy::<authz::SiloRole> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| role_assignments: vec![] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ).unwrap() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fails with the following. It's making a request with the unprivileged user that it expects to 404 but it's 200ing instead. I don't understand why the unprivileged user would 200 — we are doing operations that require omicron/nexus/tests/integration_tests/unauthorized.rs Lines 444 to 457 in 000333b
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First pass at debugging using the lovely guide. Seems like the unprivileged user does have the permission in question, which is weird?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the explanation of what I was seeing. The existing silo policy test was not asking about the user's own silo, which is why the unprivileged user didn't have access. Even the unpriv user has access to their own silo. I am writing an issue to think about how we could do this differently if we wanted to. omicron/nexus/src/authz/omicron.polar Lines 148 to 163 in 4528641
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VerifyEndpoint { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: "/users", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,8 +99,8 @@ organization_view_by_id /by-id/organizations/{id} | |
|
|
||
| API operations found with tag "policy" | ||
| OPERATION ID URL PATH | ||
| policy_update /policy | ||
| policy_view /policy | ||
| global_policy_update /global/policy | ||
| global_policy_view /global/policy | ||
|
|
||
| API operations found with tag "projects" | ||
| OPERATION ID URL PATH | ||
|
|
@@ -132,6 +132,8 @@ session_sshkey_view /session/me/sshkeys/{ssh_key_name} | |
|
|
||
| API operations found with tag "silos" | ||
| OPERATION ID URL PATH | ||
| policy_update /policy | ||
| policy_view /policy | ||
| silo_create /silos | ||
| silo_delete /silos/{silo_name} | ||
| silo_identity_provider_create /silos/{silo_name}/saml-identity-providers | ||
|
Comment on lines
+135
to
139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Eh, on second thought most of the stuff here will be grouped under the global tag eventually so it's probably moot. Still unsure if |
||
|
|
||
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.
As I say in the comment, we can do without this, but having to refer explicitly to
nexus.datastore()inside an endpoint handler to build the lookup feels like it violates the abstraction boundary of theNexusobject. No caller will ever have to make a lookup with a different datastore than the one that hangs ofnexus.