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

acl: improve the ResolveToken interface #11690

Open
dnephin opened this issue Nov 30, 2021 · 1 comment
Open

acl: improve the ResolveToken interface #11690

dnephin opened this issue Nov 30, 2021 · 1 comment
Labels
theme/acls ACL and token generation theme/internal-cleanup Used to identify tech debt, testing improvements, code refactoring, and non-impactful optimization

Comments

@dnephin
Copy link
Contributor

dnephin commented Nov 30, 2021

The interface for resolving a token is a bit fragmented. The following methods exist:

  • Server.ResolveTokenAndDefaultMeta
  • Client.ResolveTokenAndDefaultMeta - duplicate of Server.ResolveTokenAndDefaultMeta
  • Server.ResolveTokenToIdentity - mostly the same underlying logic as above, but different args and return value
  • Server.ResolveToken - basically the same with fewer args and return values, legacy call that has not been cleaned up yet
  • Client.ResolveTokenToIdentity - duplicate of Server.ResolveTokenToIdentity
  • ACLResolver.ResolveTokenToIdentityAndAuthorizer - called by the above
  • ACLResolver.ResolveTokenToIdentity similar to the above
  • Server.ResolveIdentityFromToken - used internally by ACLResolver, but occasionally not, see acl: extract a backend type for the ACLResolverBackend #11221

The large number of similar methods makes it difficult to understand which one to use, and increase the potential for bugs when the wrong one is called.

The "main" call is supposed to be {Server, Client}.ResolveTokenAndDefaultMeta. That method has a few problems:

  1. the entMeta and authzContext args are things to "fill". There are other ways to fill these two things, and it's not really obvious from the function signature how this "fill" works
  2. there is no way to access the acl.Identity from the return value, which is why we have the additional ResolveTokenToIdentity calls. In practice all we really need from the Identity is an accessor ID. The rest of the acl.Identity methods are internal to the ACL system.
  3. the returned acl.Authorizer is a nice interface, but all of its methods return an EnforcementDecision instead of an error. This makes it difficult to solve problems like Get Token call to API, returns 403 rather than 404 if token not found #8428 and Reading non-existing policy returns "ACL Not found" in a 403 Forbidden #9933. We'd like to be able to return more information to authenticated, but not authorized requests, but we can't do that from a central place right now because of this interface.

Proposal

To address these problems I propose we replace all of these methods with a single new method on ACLResolver.

The agent.delegate interface can replace its two methods with a single ACLResolver() ACLResolver for getting access to the resolver. That alone removes the duplication between Client and Server.

The remaining problems can be addressed by changing the interface to something like this:

func (a *ACLResolver) ResolveToken(req Request) (Authorizer, error) {
...
}

type Request interface {
    TokenSecret() string
    SetEnterpriseIdentity(structs.EnterpriseMeta) 
}

All requests should already embed either WriteRequest, or QueryOptions, and both of those already implement TokenSecret. Most requests should already have an EnterpriseMeta. EnterpriseMeta.Merge is effectively what we want for SetEnterpriseIdentity, so we could either rename Merge, or make it an alias. Any ones that do already embed EnterpriseMeta can easily embed a struct with a no-op implementation of that method.

The returned Authorizer would be a struct (not an interface), with methods similar to the existing acl.Authorizer (https://pkg.go.dev/github.com/hashicorp/consul@v1.10.4/acl#Authorizer). The difference would be that:

  • Instead of accepting a string and an AuthContext , we would accept an interface that accepts an identifier of a resource. Both request structs and data structs (items in responses) can then implement this interface to communicate the correct identity (including EnterpriseMeta). Any authorization that doesn't require the AuthorizerContext (ex: OperatorRead/Write , or KeyRead/Write) would accept no args.
  • instead of returning an EnforcementDecision we would return a typed error. That error would allow API and RPC endpoints to return the appropriate amount of information based on if the request was authenticated or not.
  • it should have an AccessorID() string method for returning the accessor ID, so that we don't need duplicate methods for exposing the accessor ID.
@dnephin dnephin added theme/acls ACL and token generation type/enhancement Proposed improvement or new feature labels Nov 30, 2021
@jkirschner-hashicorp jkirschner-hashicorp added the theme/internal-cleanup Used to identify tech debt, testing improvements, code refactoring, and non-impactful optimization label Nov 30, 2021
@dnephin dnephin removed the type/enhancement Proposed improvement or new feature label Nov 30, 2021
@dnephin
Copy link
Contributor Author

dnephin commented Jan 23, 2022

I've opened a few PRs (you can see linked above this comment) which implement most of this proposal. The first 3 remove the duplication, and the last 2 draft PRs demonstrate the new proposed interface. There is definitely more work to do on the last 2 to apply the changes everywhere, but I think these PRs are better able to demonstrate this change, and show that if we are going to change every permission check, we may as well use a better interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme/acls ACL and token generation theme/internal-cleanup Used to identify tech debt, testing improvements, code refactoring, and non-impactful optimization
Projects
None yet
Development

No branches or pull requests

2 participants