Skip to content

Create a synthetic authz resource for trust quorum and use it#9946

Open
andrewjstone wants to merge 5 commits intomainfrom
authz-tq
Open

Create a synthetic authz resource for trust quorum and use it#9946
andrewjstone wants to merge 5 commits intomainfrom
authz-tq

Conversation

@andrewjstone
Copy link
Contributor

@andrewjstone andrewjstone commented Feb 27, 2026

Closes #9749

Copy link
Collaborator

@davepacheco davepacheco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. Some suggestions below, none of them critical.

Comment on lines +740 to +754
if action == Action::Read {
return self.not_found();
}

// If the user failed an authz check, and they can't even read this
// resource, then we should produce a 404 rather than a 401/403.
match authz.is_allowed(&actor, Action::Read, self) {
Err(error) => Error::internal_error(&format!(
"failed to compute read authorization to determine visibility: \
{:#}",
error
)),
Ok(false) => self.not_found(),
Ok(true) => error,
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this behavior is wrong but it feels wrong that you should have to duplicate it here. Even though it's short, it's pretty tricky and load-bearing.

I wonder if it would make sense (or even work) to call self.rack().on_unauthorized(authz, error, actor, action), since logically you're trying to match the rack's behavior.

The other obvious option would be to impl ApiResource but I guess that doesn't make sense here because there's no ResourceType for it and we don't want one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought about implementing ApiResource but that seemed to contradict the whole synthetic resource thing.

I'll try calling self.rack().on_unauthorized. I didn't think of that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to have worked. Thanks!

fn not_found(&self) -> Error {
// The information that we are preventing from leaking is anything
// having to do with a given rack.
LookupType::ById(self.0.id()).into_not_found(ResourceType::Rack)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about self.rack().not_found() ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The need for this actually goes away when calling self.rack().on_unauthorized() below.

@@ -1014,17 +990,9 @@ impl DataStore {

// Unconditional insert
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment that the caller must have already authorized this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true for all _conn functions in this file. I'm going to add a header comment about that up top, which I should have already done.


if !acked.is_empty() {
// Write state back to DB
let authz_tq = authz::TrustQuorumConfig::new(authz::Rack::new(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see about 15-20 uses of this. It seems worth writing a helper authz::TrustQuorumConfig::for_rack_id(RackUuuid).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

rack_id: RackUuid,
epoch: Epoch,
) -> Result<Status, Error> {
let authz_tq = authz::TrustQuorumConfig::new(authz::Rack::new(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this value ultimately comes from datastore.tq_get_all_active_rack_id_and_latest_epoch(). You could consider having this thing return authz::TrustQuorumConfig objects instead of RackUuids. There are other places where the database lookup returns the authz object (like LookupPath) so that callers don't have to think about wrapping stuff in this way. It's definitely not a big deal either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'll give it a go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this returns a BTreeMap keyed by the rack_id. I don't think it makes much sense to make authz types sortable and so I think I'll just leave it for now. This will be less painful looking at the three call sites here with the new helper anyway.

epoch: Epoch,
coordinator: &BaseboardId,
) -> Result<sled_agent_client::Client, Error> {
let rack_id = RackUuid::from_untyped_uuid(authz_tq.rack().id());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(probably not actionable now)

I went to look at why we still need the rack_id here and I see we're calling datastore.sled_get_commissioned_by_baseboard_and_rack_id(). I think if we applied the same pattern you're applying in this PR to racks themselves, that function would accept an authz::Rack instead of a rack id and then this code (and presumably other code here) would have less stuff like this. But we haven't done this for racks yet and there's no reason to do it right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, and happy not to do it in this PR :)

@andrewjstone
Copy link
Contributor Author

@davepacheco All cleaned up in c36066f.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Trust quorum should have its own Authz resource object

2 participants