-
Notifications
You must be signed in to change notification settings - Fork 50
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
auth: manual token expiration, better auxiliary token handling, improve tests #665
Conversation
Whilst the new ExpireToken() method only has any meaning or effect when a token is being cached, in practise this all the existing Authorizers wrap themselves with a CachedAuthorizer. For consistency this is added to the Authorizer interface to make downstream implementation simpler and this should not cause any incompatibility or errors where one of the provided constructors is used.
…th both standard and auxiliary access tokens
814e1ea
to
82cc448
Compare
Hey, The https://github.com/hashicorp/go-azure-sdk/blob/main/sdk/client/client.go#L337 |
@industrialzombie Yes it will, I had thought to add that separately for additional safety downstream, but on balance it's probably fine to add it here and merge it all together 👍 |
Is it possible to get an estimate on when this might be reviewed? |
@industrialzombie We'll look at this after today's release, so that we have a few days to catch any potential bugs before shipping. |
Thanks, I appreciate the feedback! |
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.
Left some comments inline but otherwise 👍
sdk/auth/interface.go
Outdated
Token(ctx context.Context, request *http.Request) (*oauth2.Token, error) | ||
|
||
AuxiliaryTokens(ctx context.Context, request *http.Request) ([]*oauth2.Token, error) | ||
ExpireTokens() error |
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 this one (but also in retrospect) is it worth adding some documentation for this one? As it stands the "will do nothing" comments on each implementation will show as needed, but don't strictly explain the purpose for this one?
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.
good shout, will add 👍
sdk/auth/token.go
Outdated
) | ||
|
||
// SetAuthHeaders decorates a *http.Request with necessary authorization headers for Azure APIs. For more information about the vendor-specific | ||
// `x-ms-authorization-auxiliary` header, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/authenticate-multi-tenant | ||
func SetAuthHeaders(ctx context.Context, req *http.Request, authorizer Authorizer) error { |
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.
since this is specific to Resource Manager, this probably wants to go into the Resource Manager Client?
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.
agreed, other APIs may perhaps support the concept of multiple tenants but the implementation will be different
sdk/client/client.go
Outdated
@@ -327,14 +327,11 @@ func (c *Client) Execute(ctx context.Context, req *Request) (*Response, error) { | |||
return nil, fmt.Errorf("req.Request was nil") | |||
} | |||
|
|||
// at this point we're ready to send the HTTP Request, as such let's get the Authorization token | |||
// and add that to the request | |||
// Set Authorization and X-Ms-Authorization-Auxiliary headers |
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.
Doing this will cause issues with Storage Data Plane which is super picky about the headers it supports
Perhaps Client should have a ConfigureAuthorizerForRequest
function/method defined against the struct which the implementations can pass-in?
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.
Makes sense, I've added this and set a custom authorization function for ResourceManagerClient
. Where such custom function is not specified, the base client will fall back to setting a standard Authorization
header with a bearer token (i.e. its current behavior).
sdk/auth/shared_key_authorizer.go
Outdated
// --- | ||
// ExpireToken has no effect with shared keys | ||
func (c *SharedKeyAuthorizer) ExpireTokens() error { | ||
return fmt.Errorf("SharedKeyAuthorizer tokens cannot expire") |
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.
given the other authorizers are a noop, why not do the same here? perhaps this method wants renaming to InvalidateTokenCache
or something to better reflect what it's doing?
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've done away with this ambiguity and added a CachingAuthorizer
interface which can be tested for at runtime to call the InvalidateTokenCache()
method.
* Add a custom authorization function for ResourceManagerClient, which sets the `Authorization` and `x-ms-authorization-auxiliary` headers. * When custom authorization function is not defined, use the auth.SetAuthHeader() function to decorate requests with a standard `Authorization` header having a bearer token.
Thanks for the review @tombuildsstuff, I've reworked it to remove the anbiguity over invalidation by adding a |
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.
A couple of minor comments but otherwise 👍
sdk/auth/cached_authorizer.go
Outdated
// tokens to be acquired when Token() or AuxiliaryTokens() are next called | ||
func (c *CachedAuthorizer) InvalidateCachedTokens() error { | ||
if c.token == nil { | ||
return fmt.Errorf("internal-error: c.token was nil") |
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.
tbh I think it's worth this being a noop?
return fmt.Errorf("internal-error: c.token was nil") | |
return nil |
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.
Makes sense, the end result should be the same 👍
auxTokenValues := make([]string, 0) | ||
for _, auxToken := range auxTokens { | ||
auxTokenValues = append(auxTokenValues, fmt.Sprintf("%s %s", auxToken.Type(), auxToken.AccessToken)) | ||
} | ||
req.Header.Set("X-Ms-Authorization-Auxiliary", strings.Join(auxTokenValues, ", ")) |
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.
if there aren't any, should we omit this?
Better Handling of Auxiliary Tokens
Adds the
AuthorizeRequest
field toclient.Client
which API clients can define for customized authoriation behavior. This is then utilized byresourcemanager.Client
to decorate requests with bothAuthorization
andx-ms-authorization-auxiliary
headers.Adds a helper function
SetAuthHeader()
which decorates an*http.Request
with a bearer token, e.g. adds theAuthorization
header. This is called bybase.Client
whenAuthorizeRequest
is nil.Manual Token Expiration
This provides the ability to intentionally expire a cached access token, which causes a new token to be automatically acquired the next time the
Token()
orAuxiliaryTokens()
methods are called.Downstream implementers can test whether an
auth.Authorizer
implementsauth.CachingAuthorizer
, in case they receive a standardauth.Authorizer
, and then callInvalidateTokenCache()
prior to making a new request. This requires that the method is called as-needed when an action is taken that is likely to necessitate a new access token, for example with management groups which make use of token claims for access control.Closes: #459
Related:
AuthorizationFailed
error when creating assignments for newly created custom roles or policies in Management Group hierarchy terraform-provider-azurerm#19595