-
Notifications
You must be signed in to change notification settings - Fork 40
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
authz: protect various endpoints #790
Conversation
@@ -29,6 +29,7 @@ pub mod saga; | |||
|
|||
pub use crate::db::fixed_data::user_builtin::USER_DB_INIT; | |||
pub use crate::db::fixed_data::user_builtin::USER_INTERNAL_API; | |||
pub use crate::db::fixed_data::user_builtin::USER_INTERNAL_READ; |
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 changes in this file are all part of adding a new "internal-read" user (described later).
/// [`crate::context::OpContext::authorize()`]. You construct one of these | ||
/// using [`Fleet::sled()`]. | ||
#[derive(Clone, Debug)] | ||
pub struct Sled { |
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 wanted to keep things simple and put sled permissions under read
permission on the fleet, but that doesn't trigger the correct 404 behavior when you ask for a Sled that doesn't exist (because it would say the fleet doesn't exist). So then I wanted to use authz::FleetChild
, but that doesn't include an id (and can't, because it's used for built-in roles that don't have one). So I created a real new struct for Sled.
Note that I want to refactor all the authz API resources to be more type-safe, but that'll be a bigger (future) change.
@@ -75,6 +75,7 @@ has_role(actor: AuthenticatedActor, "init", _resource: Database) | |||
# | |||
# - fleet.admin (superuser for the whole system) | |||
# - fleet.collaborator (can create and own orgs) | |||
# - fleet.viewer (can read fleet-wide data) |
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 added this new "Fleet Viewer" role for the new "internal-read" user (described later).
artifact: UpdateAvailableArtifact, | ||
) -> CreateResult<UpdateAvailableArtifact> { | ||
opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; |
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.
@iliana here's one of the couple of authz checks added for updates. I started as restrictive as possible: you basically have to have a near-superuser privilege on the fleet-level ("Fleet Collaborator" or "Fleet Admin") to have this permission. I imagine we'll want to rethink this but I hope this will work for now.
current_targets_role_version: i64, | ||
) -> DeleteResult { | ||
// We use the `targets_role_version` column in the table to delete any old rows, keeping | ||
// the table in sync with the current copy of artifacts.json. | ||
opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; |
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.
CC @iliana
artifact: &UpdateArtifact, | ||
) -> LookupResult<UpdateAvailableArtifact> { | ||
opctx.authorize(authz::Action::Read, &authz::FLEET).await?; |
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.
CC @iliana
@@ -47,6 +47,15 @@ lazy_static! { | |||
"used by Nexus when handling internal API requests", | |||
); | |||
|
|||
/// Internal user used by Nexus to read privileged control plane data |
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.
Added a new built-in user called "internal-read". This is used by Nexus when it needs to read control plane data that an ordinary user wouldn't have access to. For example, when a user provisions an Instance (or really any time we need to make a request to a Sled Agent), we need to look up information about the Sled in the database. That's now protected by authz. But the end user making the request probably doesn't have permission to read information about sleds. Instead, Nexus uses this new "internal-read" user.
pagparams: &DataPageParams<'_, Uuid>, | ||
) -> ListResult<db::model::Rack> { | ||
opctx.authorize(authz::Action::Read, &authz::FLEET).await?; |
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.
Racks are faked-up -- not in the database -- so their access check is right in nexus.rs.
&self, | ||
opctx: &OpContext, | ||
) -> Result<(), Error> { | ||
opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; |
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.
CC @iliana I put this check here to avoid querying the tuf repo unless the user is authorized. The database queries are separately authorized in datastore.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.
I think this works for now. In the future, this code path will be called within Nexus when automatic updates are enabled (perhaps once every few hours, or some other regular cadence). What might this look like when we start doing that?
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.
For that, we could create an OpContext
when Nexus starts up specifically for use to do automatic updates, similar to what we do for saga recovery:
Lines 211 to 216 in c66d2f6
let opctx = OpContext::for_background( | |
log.new(o!("component" => "SagaRecoverer")), | |
Arc::clone(&authz), | |
authn::Context::internal_saga_recovery(), | |
Arc::clone(&db_datastore), | |
); |
This uses
OpContext::for_background
:Line 349 in c66d2f6
pub fn for_background( |
We could create a separate built-in user for that context that would only have whatever privileges are needed for it. Then whenever we did any of these operations, we'd use that background OpContext
that's associated with that user.
&self, | ||
opctx: &OpContext, | ||
) -> Result<(), Error> { | ||
opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; |
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 think this works for now. In the future, this code path will be called within Nexus when automatic updates are enabled (perhaps once every few hours, or some other regular cadence). What might this look like when we start doing that?
Thanks for the reviews! |
This started as a small PR to protect a handful of miscellaneous unprotected endpoints (in categories that were too small for their own PR). But it grew much larger than expected. If it would be helpful for me to break this up, I can do that.
I annotated the changes with some context to help sort through them. Key ones:
authz::Sled