fix: handle array policies in JWT claim (#10041)

PR #10014 was not complete as only handled
policy claims partially.
This commit is contained in:
Harshavardhana 2020-07-14 10:26:47 -07:00 committed by GitHub
parent 778e9c864f
commit 369a876ebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 27 deletions

View File

@ -213,15 +213,15 @@ func getClaimsFromToken(r *http.Request, token string) (map[string]interface{},
if globalPolicyOPA == nil { if globalPolicyOPA == nil {
// If OPA is not set and if ldap claim key is set, allow the claim. // If OPA is not set and if ldap claim key is set, allow the claim.
if _, ok := claims.Lookup(ldapUser); ok { if _, ok := claims.MapClaims[ldapUser]; ok {
return claims.Map(), nil return claims.Map(), nil
} }
// If OPA is not set, session token should // If OPA is not set, session token should
// have a policy and its mandatory, reject // have a policy and its mandatory, reject
// requests without policy claim. // requests without policy claim.
_, pokOpenID := claims.Lookup(iamPolicyClaimNameOpenID()) _, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]
_, pokSA := claims.Lookup(iamPolicyClaimNameSA()) _, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
if !pokOpenID && !pokSA { if !pokOpenID && !pokSA {
return nil, errAuthentication return nil, errAuthentication
} }
@ -360,6 +360,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
// Request is allowed return the appropriate access key. // Request is allowed return the appropriate access key.
return cred.AccessKey, owner, ErrNone return cred.AccessKey, owner, ErrNone
} }
return cred.AccessKey, owner, ErrAccessDenied return cred.AccessKey, owner, ErrAccessDenied
} }

View File

@ -8,31 +8,31 @@ Configure and install keycloak server by following [Keycloak Installation Guide]
### Configure Keycloak UI ### Configure Keycloak UI
- Go to Clients - Go to Clients
-> Click on account - Click on account
-> Settings - Settings
-> Enable `Implicit Flow` - Enable `Implicit Flow`
-> Save - Save
- Go to Users - Go to Users
-> Click on the user - Click on the user
-> Attribute, add a new attribute `Key` is `policy`, `Value` is name of the `policy` on MinIO (ex: `readwrite`) - Attribute, add a new attribute `Key` is `policy`, `Value` is name of the `policy` on MinIO (ex: `readwrite`)
-> Add and Save - Add and Save
- Go to Clients - Go to Clients
-> Click on `account` - Click on `account`
-> Settings, set `Valid Redirect URIs` to `*`, expand `Advanced Settings` and set `Access Token Lifespan` to `1 Hours` - Settings, set `Valid Redirect URIs` to `*`, expand `Advanced Settings` and set `Access Token Lifespan` to `1 Hours`
-> Save - Save
- Go to Clients - Go to Clients
-> Client on `account` - Client on `account`
-> Mappers - Mappers
-> Create - Create
- `Name` with any text - `Name` with any text
- `Mapper Type` is `User Attribute` - `Mapper Type` is `User Attribute`
- `User Attribute` is `policy` - `User Attribute` is `policy`
- `Token Claim Name` is `policy` - `Token Claim Name` is `policy`
- `Claim JSON Type` is `string` - `Claim JSON Type` is `string`
-> Save - Save
- Open http://localhost:8080/auth/realms/demo/.well-known/openid-configuration to verify OpenID discovery document, verify it has `authorization_endpoint` and `jwks_uri` - Open http://localhost:8080/auth/realms/demo/.well-known/openid-configuration to verify OpenID discovery document, verify it has `authorization_endpoint` and `jwks_uri`

View File

@ -31,7 +31,9 @@ const (
// JWTJti JWT unique identifier claim substitution. // JWTJti JWT unique identifier claim substitution.
JWTJti Key = "jwt:jti" JWTJti Key = "jwt:jti"
JWTUpn Key = "jwt:upn"
JWTName Key = "jwt:name" JWTName Key = "jwt:name"
JWTGroups Key = "jwt:groups"
JWTGivenName Key = "jwt:given_name" JWTGivenName Key = "jwt:given_name"
JWTFamilyName Key = "jwt:family_name" JWTFamilyName Key = "jwt:family_name"
JWTMiddleName Key = "jwt:middle_name" JWTMiddleName Key = "jwt:middle_name"
@ -57,6 +59,8 @@ var JWTKeys = []Key{
JWTAud, JWTAud,
JWTJti, JWTJti,
JWTName, JWTName,
JWTUpn,
JWTGroups,
JWTGivenName, JWTGivenName,
JWTFamilyName, JWTFamilyName,
JWTMiddleName, JWTMiddleName,

View File

@ -47,23 +47,36 @@ func GetPoliciesFromClaims(claims map[string]interface{}, policyClaimName string
if !ok { if !ok {
return s, false return s, false
} }
pnames, ok := pname.([]string) pnames, ok := pname.([]interface{})
if !ok { if !ok {
pnameStr, ok := pname.(string) pnameStr, ok := pname.(string)
if ok { if ok {
pnames = strings.Split(pnameStr, ",") for _, pname := range strings.Split(pnameStr, ",") {
} else { pname = strings.TrimSpace(pname)
return s, false if pname == "" {
// ignore any empty strings, considerate
// towards some user errors.
continue
}
s.Add(pname)
}
return s, true
} }
return s, false
} }
for _, pname := range pnames { for _, pname := range pnames {
pname = strings.TrimSpace(pname) pnameStr, ok := pname.(string)
if pname == "" { if ok {
// ignore any empty strings, considerate for _, pnameStr := range strings.Split(pnameStr, ",") {
// towards some user errors. pnameStr = strings.TrimSpace(pnameStr)
continue if pnameStr == "" {
// ignore any empty strings, considerate
// towards some user errors.
continue
}
s.Add(pnameStr)
}
} }
s.Add(pname)
} }
return s, true return s, true
} }

View File

@ -22,10 +22,55 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/bucket/policy/condition" "github.com/minio/minio/pkg/bucket/policy/condition"
) )
func TestGetPoliciesFromClaims(t *testing.T) {
attributesArray := `{
"exp": 1594690452,
"iat": 1594689552,
"auth_time": 1594689552,
"jti": "18ed05c9-2c69-45d5-a33f-8c94aca99ad5",
"iss": "http://localhost:8080/auth/realms/minio",
"aud": "account",
"sub": "7e5e2f30-1c97-4616-8623-2eae14dee9b1",
"typ": "ID",
"azp": "account",
"nonce": "66ZoLzwJbjdkiedI",
"session_state": "3df7b526-5310-4038-9f35-50ecd295a31d",
"acr": "1",
"upn": "harsha",
"address": {},
"email_verified": false,
"groups": [
"offline_access"
],
"preferred_username": "harsha",
"policy": [
"readwrite",
"readwrite,readonly",
" readonly",
""
]}`
var m = make(map[string]interface{})
if err := json.Unmarshal([]byte(attributesArray), &m); err != nil {
t.Fatal(err)
}
var expectedSet = set.CreateStringSet("readwrite", "readonly")
gotSet, ok := GetPoliciesFromClaims(m, "policy")
if !ok {
t.Fatal("no policy claim was found")
}
if gotSet.IsEmpty() {
t.Fatal("no policies were found in policy claim")
}
if !gotSet.Equals(expectedSet) {
t.Fatalf("Expected %v got %v", expectedSet, gotSet)
}
}
func TestPolicyIsAllowed(t *testing.T) { func TestPolicyIsAllowed(t *testing.T) {
case1Policy := Policy{ case1Policy := Policy{
Version: DefaultVersion, Version: DefaultVersion,