From 3735450e7e4cef71c343360b443618c1d77bb3cc Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 27 Jul 2021 18:37:51 -0700 Subject: [PATCH] fix: allow audience claim to be an array (#12810) Some incorrect setups might have multiple audiences where they are trying to use a single authentication endpoint for multiple services. Nevertheless OpenID spec allows it to make it even more confusin for no good reason. > It MUST contain the OAuth 2.0 client_id of the > Relying Party as an audience value. It MAY also > contain identifiers for other audiences. In the > general case, the aud value is an array of case > sensitive strings. In the common special case > when there is one audience, the aud value MAY > be a single case sensitive string. fixes #12809 --- cmd/sts-handlers.go | 44 ++++++++++++++++++++++++++++++++-------- docs/sts/web-identity.md | 2 +- docs/sts/web-identity.py | 15 +++++++------- docs/sts/wso2.md | 22 ++++++++++---------- go.mod | 2 +- go.sum | 4 ++-- 6 files changed, 58 insertions(+), 31 deletions(-) diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go index 96a6c040c..147d49858 100644 --- a/cmd/sts-handlers.go +++ b/cmd/sts-handlers.go @@ -59,6 +59,7 @@ const ( expClaim = "exp" subClaim = "sub" audClaim = "aud" + azpClaim = "azp" issClaim = "iss" // JWT claim to check the parent user @@ -333,9 +334,40 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ return } - var audFromToken string - if v, ok := m[audClaim]; ok { - audFromToken, _ = v.(string) + // REQUIRED. Audience(s) that this ID Token is intended for. + // It MUST contain the OAuth 2.0 client_id of the Relying Party + // as an audience value. It MAY also contain identifiers for + // other audiences. In the general case, the aud value is an + // array of case sensitive strings. In the common special case + // when there is one audience, the aud value MAY be a single + // case sensitive + audValues, ok := iampolicy.GetValuesFromClaims(m, audClaim) + if !ok { + writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, + errors.New("STS JWT Token has `aud` claim invalid, `aud` must match configured OpenID Client ID")) + return + } + if !audValues.Contains(globalOpenIDConfig.ClientID) { + // if audience claims is missing, look for "azp" claims. + // OPTIONAL. Authorized party - the party to which the ID + // Token was issued. If present, it MUST contain the OAuth + // 2.0 Client ID of this party. This Claim is only needed + // when the ID Token has a single audience value and that + // audience is different than the authorized party. It MAY + // be included even when the authorized party is the same + // as the sole audience. The azp value is a case sensitive + // string containing a StringOrURI value + azpValues, ok := iampolicy.GetValuesFromClaims(m, azpClaim) + if !ok { + writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, + errors.New("STS JWT Token has `aud` claim invalid, `aud` must match configured OpenID Client ID")) + return + } + if !azpValues.Contains(globalOpenIDConfig.ClientID) { + writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, + errors.New("STS JWT Token has `azp` claim invalid, `azp` must match configured OpenID Client ID")) + return + } } var subFromToken string @@ -349,12 +381,6 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ return } - if audFromToken != globalOpenIDConfig.ClientID { - writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, - errors.New("STS JWT Token has `aud` claim invalid, `aud` must match configured OpenID Client ID")) - return - } - var issFromToken string if v, ok := m[issClaim]; ok { issFromToken, _ = v.(string) diff --git a/docs/sts/web-identity.md b/docs/sts/web-identity.md index 95eeda853..b441ddb38 100644 --- a/docs/sts/web-identity.md +++ b/docs/sts/web-identity.md @@ -121,7 +121,7 @@ $ go run web-identity.go -cid 204367807228-ok7601k6gj1pgge7m09h7d79co8p35xx.apps - Visit http://localhost:8080, login will direct the user to the Google OAuth2 Auth URL to obtain a permission grant. - The redirection URI (callback handler) receives the OAuth2 callback, verifies the state parameter, and obtains a Token. - Using the access token the callback handler further talks to Google OAuth2 Token URL to obtain an JWT id_token. -- Once obtained the JWT id_token is further sent to STS endpoint i.e MinIO to retrive temporary credentials. +- Once obtained the JWT id_token is further sent to STS endpoint i.e MinIO to retrieve temporary credentials. - Temporary credentials are displayed on the browser upon successful retrieval. ## Using MinIO Console diff --git a/docs/sts/web-identity.py b/docs/sts/web-identity.py index 58ac3f385..45c5f2a04 100644 --- a/docs/sts/web-identity.py +++ b/docs/sts/web-identity.py @@ -64,19 +64,20 @@ def callback(): data = {'grant_type': 'authorization_code', 'code': authorization_code, 'redirect_uri': callback_uri} - access_token_response = requests.post( - token_url, data=data, verify=False, allow_redirects=False, auth=(client_id, client_secret)) + id_token_response = requests.post( + token_url, data=data, verify=False, + allow_redirects=False, auth=(client_id, client_secret)) - print('body: ' + access_token_response.text) + print('body: ' + id_token_response.text) - # we can now use the access_token as much as we want to access protected resources. - tokens = json.loads(access_token_response.text) - access_token = tokens['access_token'] + # we can now use the id_token as much as we want to access protected resources. + tokens = json.loads(id_token_response.text) + id_token = tokens['id_token'] response = sts_client.assume_role_with_web_identity( RoleArn='arn:aws:iam::123456789012:user/svc-internal-api', RoleSessionName='test', - WebIdentityToken=access_token, + WebIdentityToken=id_token, DurationSeconds=3600 ) diff --git a/docs/sts/wso2.md b/docs/sts/wso2.md index d863159c8..f9810f011 100644 --- a/docs/sts/wso2.md +++ b/docs/sts/wso2.md @@ -43,7 +43,7 @@ curl -u PoEgXP6uVO45IsENRngDXj5Au5Ya:eKsw6z8CtOJVBtrOWvhRWL4TUCga -k -d "grant_t In response, the self-contained JWT access token will be returned as shown below. ``` { - "access_token": "eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTUzNDg5MTc3OCwiaWF0IjoxNTM0ODg4MTc4LCJqdGkiOiIxODQ0MzI5Yy1kNjVhLTQ4YTMtODIyOC05ZGY3M2ZlODNkNTYifQ.ELZ8ujk2Xp9xTGgMqnCa5ehuimaAPXWlSCW5QeBbTJIT4M5OB_2XEVIV6p89kftjUdKu50oiYe4SbfrxmLm6NGSGd2qxkjzJK3SRKqsrmVWEn19juj8fz1neKtUdXVHuSZu6ws_bMDy4f_9hN2Jv9dFnkoyeNT54r4jSTJ4A2FzN2rkiURheVVsc8qlm8O7g64Az-5h4UGryyXU4zsnjDCBKYk9jdbEpcUskrFMYhuUlj1RWSASiGhHHHDU5dTRqHkVLIItfG48k_fb-ehU60T7EFWH1JBdNjOxM9oN_yb0hGwOjLUyCUJO_Y7xcd5F4dZzrBg8LffFmvJ09wzHNtQ", + "id_token": "eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTUzNDg5MTc3OCwiaWF0IjoxNTM0ODg4MTc4LCJqdGkiOiIxODQ0MzI5Yy1kNjVhLTQ4YTMtODIyOC05ZGY3M2ZlODNkNTYifQ.ELZ8ujk2Xp9xTGgMqnCa5ehuimaAPXWlSCW5QeBbTJIT4M5OB_2XEVIV6p89kftjUdKu50oiYe4SbfrxmLm6NGSGd2qxkjzJK3SRKqsrmVWEn19juj8fz1neKtUdXVHuSZu6ws_bMDy4f_9hN2Jv9dFnkoyeNT54r4jSTJ4A2FzN2rkiURheVVsc8qlm8O7g64Az-5h4UGryyXU4zsnjDCBKYk9jdbEpcUskrFMYhuUlj1RWSASiGhHHHDU5dTRqHkVLIItfG48k_fb-ehU60T7EFWH1JBdNjOxM9oN_yb0hGwOjLUyCUJO_Y7xcd5F4dZzrBg8LffFmvJ09wzHNtQ", "token_type": "Bearer", "expires_in": 3600 } @@ -52,17 +52,17 @@ In response, the self-contained JWT access token will be returned as shown below ### 4. JWT Claims The access token received is a signed JSON Web Token (JWT). Use a JWT decoder to decode the access token to access the payload of the token that includes following JWT claims: -|Claim Name|Type|Claim Value| -|:--:|:--:|:--:| -|iss| _string_ | The issuer of the JWT. The '> Identity Provider Entity Id ' value of the OAuth2/OpenID Connect Inbound Authentication configuration of the Resident Identity Provider is returned here. | -|aud| _string array_ | The token audience list. The client identifier of the OAuth clients that the JWT is intended for, is sent herewith. | -|azp| _string_ | The authorized party for which the token is issued to. The client identifier of the OAuth client that the token is issued for, is sent herewith. | -|iat| _integer_ | The token issue time. | -|exp| _integer_ | The token expiration time. | -|jti| _string_ | Unique identifier for the JWT token. | -|policy| _string_ | Canned policy name to be applied for STS credentials. (Recommended) | +| Claim Name | Type | Claim Value | +|:----------:|:--------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| iss | _string_ | The issuer of the JWT. The '> Identity Provider Entity Id ' value of the OAuth2/OpenID Connect Inbound Authentication configuration of the Resident Identity Provider is returned here. | +| aud | _string array_ | The token audience list. The client identifier of the OAuth clients that the JWT is intended for, is sent herewith. | +| azp | _string_ | The authorized party for which the token is issued to. The client identifier of the OAuth client that the token is issued for, is sent herewith. | +| iat | _integer_ | The token issue time. | +| exp | _integer_ | The token expiration time. | +| jti | _string_ | Unique identifier for the JWT token. | +| policy | _string_ | Canned policy name to be applied for STS credentials. (Recommended) | -Using the above `access_token` we can perform an STS request to MinIO to get temporary credentials for MinIO API operations. MinIO STS API uses [JSON Web Key Set Endpoint](https://docs.wso2.com/display/IS541/JSON+Web+Key+Set+Endpoint) to validate if JWT is valid and is properly signed. +Using the above `id_token` we can perform an STS request to MinIO to get temporary credentials for MinIO API operations. MinIO STS API uses [JSON Web Key Set Endpoint](https://docs.wso2.com/display/IS541/JSON+Web+Key+Set+Endpoint) to validate if JWT is valid and is properly signed. **We recommend setting `policy` as a custom claim for the JWT service provider follow [here](https://docs.wso2.com/display/IS550/Configuring+Claims+for+a+Service+Provider) and [here](https://docs.wso2.com/display/IS550/Handling+Custom+Claims+with+the+JWT+Bearer+Grant+Type) for relevant docs on how to configure claims for a service provider.** diff --git a/go.mod b/go.mod index 40071f7e7..b7898b14a 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/minio/madmin-go v1.0.19 github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e github.com/minio/parquet-go v1.0.0 - github.com/minio/pkg v1.0.10 + github.com/minio/pkg v1.0.11 github.com/minio/selfupdate v0.3.1 github.com/minio/sha256-simd v1.0.0 github.com/minio/simdjson-go v0.2.1 diff --git a/go.sum b/go.sum index 98b933f92..3932ce44d 100644 --- a/go.sum +++ b/go.sum @@ -1045,8 +1045,8 @@ github.com/minio/parquet-go v1.0.0/go.mod h1:aQlkSOfOq2AtQKkuou3mosNVMwNokd+faTa github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8= github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8= github.com/minio/pkg v1.0.8/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14= -github.com/minio/pkg v1.0.10 h1:fohpAm/0ttQFf4BzmzH5r6A9JUIfg63AyGCPM0f9/9U= -github.com/minio/pkg v1.0.10/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14= +github.com/minio/pkg v1.0.11 h1:gfpkP7SiznM7EyyHIfQ7lu98Ae4hV4Z+8YsoFQbH7PY= +github.com/minio/pkg v1.0.11/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14= github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs= github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=