Skip to content
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

Rework IsRequestAuthorized to reduce auth scope mismatches #986

Merged
merged 6 commits into from
Sep 27, 2023

Conversation

evankanderson
Copy link
Member

It would be nice to be able to tie the auth scope from the proto more fully into the resource model, but sometimes the model comes from database lookups, and other times directly from the proto without any database mediation, so I ended up with an Error-level log if we use one of the constructs wrong.

Please point out if you see a way to do these better. I also tried not to change any semantics where the previous semantic intent was obvious by either proto or IsRequestAuthorized invocations.

@evankanderson
Copy link
Member Author

Thinking about this, the right way to do this would be to check authorization on domain objects, rather than on request fields. I'm not ready to do that yet, but that feels like the correct direction. At which point, we might have something like:

type DomainObject interface {
  GetOrgOwner() int32
  GetGroupOwner() int32
  // etc
}

or possibly simply a method that combined claims, rpcOpts, and the domain object ownership tree to make an auth decision.

@jhrozek jhrozek self-assigned this Sep 25, 2023
Copy link
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

some questions inline, but the code looks good. On to testing...

internal/controlplane/handlers_authz.go Outdated Show resolved Hide resolved
internal/controlplane/handlers_authz.go Outdated Show resolved Hide resolved
// AuthorizedOnUser checks if the request is authorized for the given
// user, and returns an error if the request is not authorized.
func AuthorizedOnUser(ctx context.Context, userId int32) error {
claims, _ := ctx.Value(auth.TokenInfoKey).(auth.UserClaims)
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we check the second return value here to avoid crashing if someone puts a different type into the auth.TokenInfoKey value?

Copy link
Member Author

Choose a reason for hiding this comment

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

I copy-pasted this, but you're right. Added a check.

Copy link
Member Author

Choose a reason for hiding this comment

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

... and I replaced these with a proper context accessor which will return a sentinel invalid default value if the key is not in the context.

return &resp, nil
}
return nil, status.Errorf(codes.PermissionDenied, "user is not authorized to access this resource")
return &resp, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻 on reversing the logic

Copy link
Member Author

Choose a reason for hiding this comment

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

I actually moved the auth check earlier in the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, but I meant returning resp, nil (the happy path) in the outermost indent level, not nested. I like that.

jhrozek
jhrozek previously approved these changes Sep 25, 2023
Copy link
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

I think the PR is fine, adding two more nits but both could be fixed in a subsequent PR.

if !IsRequestAuthorized(ctx, req.GroupId) {
return nil, status.Errorf(codes.PermissionDenied, "user is not authorized to access this resource")
if err := AuthorizedOnGroup(ctx, req.GroupId); err != nil {
return nil, err
}

// if we do not have a group, check if we can infer it
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we could move the infer of the default group to before the authorisation decision so that the user doesn't have to call enroll (which is the typical caller) while specifying the group ID explicitly?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can just move the AuthorizedOnGroup check after the defaulting. Done.

proto/mediator/v1/mediator.proto Outdated Show resolved Hide resolved
jhrozek
jhrozek previously approved these changes Sep 26, 2023
Copy link
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

thank you, this version is great!

@jhrozek
Copy link
Contributor

jhrozek commented Sep 26, 2023

oops, a conflict. I'll just robot-approve when you push a resolved version.

@jhrozek
Copy link
Contributor

jhrozek commented Sep 27, 2023

Merging on Evan's behalf because there is quite a few patches in flight and we risk even more conflicts

@jhrozek jhrozek merged commit 7de154f into mindersec:main Sep 27, 2023
13 checks passed
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.

2 participants