diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index 3ae1b746b..dc2745b60 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -226,7 +226,7 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) { // If OPA is not set, session token should // have a policy and its mandatory, reject // requests without policy claim. - p, pok := claims[iamPolicyName()] + p, pok := claims[iamPolicyClaimName()] if !pok { return nil, errAuthentication } diff --git a/cmd/config/identity/openid/help.go b/cmd/config/identity/openid/help.go index 280135823..9bb249c75 100644 --- a/cmd/config/identity/openid/help.go +++ b/cmd/config/identity/openid/help.go @@ -32,9 +32,15 @@ var ( Type: "string", Optional: true, }, + config.HelpKV{ + Key: ClaimName, + Description: `JWT canned policy claim name, defaults to "policy"`, + Optional: true, + Type: "string", + }, config.HelpKV{ Key: ClaimPrefix, - Description: `JWT claim namespace prefix e.g. "customer1"`, + Description: `JWT claim namespace prefix e.g. "customer1/"`, Optional: true, Type: "string", }, diff --git a/cmd/config/identity/openid/jwt.go b/cmd/config/identity/openid/jwt.go index 8b77f8910..d3231e33b 100644 --- a/cmd/config/identity/openid/jwt.go +++ b/cmd/config/identity/openid/jwt.go @@ -30,6 +30,7 @@ import ( "github.com/minio/minio/cmd/config" "github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/env" + iampolicy "github.com/minio/minio/pkg/iam/policy" xnet "github.com/minio/minio/pkg/net" ) @@ -41,6 +42,7 @@ type Config struct { } `json:"jwks"` URL *xnet.URL `json:"url,omitempty"` ClaimPrefix string `json:"claimPrefix,omitempty"` + ClaimName string `json:"claimName,omitempty"` DiscoveryDoc DiscoveryDoc ClientID string publicKeys map[string]crypto.PublicKey @@ -209,12 +211,14 @@ func (p *JWT) ID() ID { const ( JwksURL = "jwks_url" ConfigURL = "config_url" + ClaimName = "claim_name" ClaimPrefix = "claim_prefix" ClientID = "client_id" EnvIdentityOpenIDClientID = "MINIO_IDENTITY_OPENID_CLIENT_ID" EnvIdentityOpenIDJWKSURL = "MINIO_IDENTITY_OPENID_JWKS_URL" EnvIdentityOpenIDURL = "MINIO_IDENTITY_OPENID_CONFIG_URL" + EnvIdentityOpenIDClaimName = "MINIO_IDENTITY_OPENID_CLAIM_NAME" EnvIdentityOpenIDClaimPrefix = "MINIO_IDENTITY_OPENID_CLAIM_PREFIX" ) @@ -271,6 +275,10 @@ var ( Key: ClientID, Value: "", }, + config.KV{ + Key: ClaimName, + Value: iampolicy.PolicyName, + }, config.KV{ Key: ClaimPrefix, Value: "", @@ -299,6 +307,7 @@ func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io } c = Config{ + ClaimName: env.Get(EnvIdentityOpenIDClaimName, kvs.Get(ClaimName)), ClaimPrefix: env.Get(EnvIdentityOpenIDClaimPrefix, kvs.Get(ClaimPrefix)), publicKeys: make(map[string]crypto.PublicKey), ClientID: env.Get(EnvIdentityOpenIDClientID, kvs.Get(ClientID)), @@ -330,9 +339,11 @@ func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io if err != nil { return c, err } + if err = c.PopulatePublicKey(); err != nil { return c, err } + return c, nil } diff --git a/cmd/crypto/metadata_test.go b/cmd/crypto/metadata_test.go index e77f4e7e4..828eea0b1 100644 --- a/cmd/crypto/metadata_test.go +++ b/cmd/crypto/metadata_test.go @@ -188,9 +188,17 @@ var s3ParseMetadataTests = []struct { func TestS3ParseMetadata(t *testing.T) { for i, test := range s3ParseMetadataTests { keyID, dataKey, sealedKey, err := S3.ParseMetadata(test.Metadata) - if err != test.ExpectedErr { + if err != nil && test.ExpectedErr == nil { t.Errorf("Test %d: got error '%v' - want error '%v'", i, err, test.ExpectedErr) } + if err == nil && test.ExpectedErr != nil { + t.Errorf("Test %d: got error '%v' - want error '%v'", i, err, test.ExpectedErr) + } + if err != nil && test.ExpectedErr != nil { + if err.Error() != test.ExpectedErr.Error() { + t.Errorf("Test %d: got error '%v' - want error '%v'", i, err, test.ExpectedErr) + } + } if !bytes.Equal(dataKey, test.DataKey) { t.Errorf("Test %d: got data key '%v' - want data key '%v'", i, dataKey, test.DataKey) } @@ -267,9 +275,17 @@ func TestCreateMultipartMetadata(t *testing.T) { func TestSSECParseMetadata(t *testing.T) { for i, test := range ssecParseMetadataTests { sealedKey, err := SSEC.ParseMetadata(test.Metadata) - if err != test.ExpectedErr { + if err != nil && test.ExpectedErr == nil { t.Errorf("Test %d: got error '%v' - want error '%v'", i, err, test.ExpectedErr) } + if err == nil && test.ExpectedErr != nil { + t.Errorf("Test %d: got error '%v' - want error '%v'", i, err, test.ExpectedErr) + } + if err != nil && test.ExpectedErr != nil { + if err.Error() != test.ExpectedErr.Error() { + t.Errorf("Test %d: got error '%v' - want error '%v'", i, err, test.ExpectedErr) + } + } if sealedKey.Algorithm != test.SealedKey.Algorithm { t.Errorf("Test %d: got sealed key algorithm '%v' - want sealed key algorithm '%v'", i, sealedKey.Algorithm, test.SealedKey.Algorithm) } diff --git a/cmd/iam.go b/cmd/iam.go index b596927d1..d19584ec3 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1288,14 +1288,14 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { return combinedPolicy.IsAllowed(args) } - pname, ok := args.Claims[iamPolicyName()] + pnameSlice, ok := args.GetPolicies(iamPolicyClaimName()) if !ok { - // When claims are set, it should have a "policy" field. + // When claims are set, it should have a policy claim field. return false } - pnameStr, ok := pname.(string) - if !ok { - // When claims has "policy" field, it should be string. + + // When claims are set, it should have a policy claim field. + if len(pnameSlice) == 0 { return false } @@ -1310,7 +1310,7 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { } name := mp.Policy - if pnameStr != name { + if pnameSlice[0] != name { // When claims has a policy, it should match the // policy of args.AccountName which server remembers. // if not reject such requests. @@ -1319,36 +1319,36 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { // Now check if we have a sessionPolicy. spolicy, ok := args.Claims[iampolicy.SessionPolicyName] - if !ok { - // Sub policy not set, this is most common since subPolicy - // is optional, use the top level policy only. - p, ok := sys.iamPolicyDocsMap[pnameStr] - return ok && p.IsAllowed(args) - } + if ok { + spolicyStr, ok := spolicy.(string) + if !ok { + // Sub policy if set, should be a string reject + // malformed/malicious requests. + return false + } - spolicyStr, ok := spolicy.(string) - if !ok { - // Sub policy if set, should be a string reject - // malformed/malicious requests. - return false - } + // Check if policy is parseable. + subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr))) + if err != nil { + // Log any error in input session policy config. + logger.LogIf(context.Background(), err) + return false + } - // Check if policy is parseable. - subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr))) - if err != nil { - // Log any error in input session policy config. - logger.LogIf(context.Background(), err) - return false - } + // Policy without Version string value reject it. + if subPolicy.Version == "" { + return false + } - // Policy without Version string value reject it. - if subPolicy.Version == "" { - return false + // Sub policy is set and valid. + p, ok := sys.iamPolicyDocsMap[pnameSlice[0]] + return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args) } - // Sub policy is set and valid. - p, ok := sys.iamPolicyDocsMap[pnameStr] - return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args) + // Sub policy not set, this is most common since subPolicy + // is optional, use the top level policy only. + p, ok := sys.iamPolicyDocsMap[pnameSlice[0]] + return ok && p.IsAllowed(args) } // IsAllowed - checks given policy args is allowed to continue the Rest API. diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go index 79208beed..e418cd1db 100644 --- a/cmd/sts-handlers.go +++ b/cmd/sts-handlers.go @@ -214,7 +214,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) { // This policy is the policy associated with the user // requesting for temporary credentials. The temporary // credentials will inherit the same policy requirements. - m[iamPolicyName()] = policyName + m[iamPolicyClaimName()] = policyName if len(sessionPolicyStr) > 0 { m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr)) @@ -350,7 +350,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ // be set and configured on your identity provider as part of // JWT custom claims. var policyName string - if v, ok := m[iamPolicyName()]; ok { + if v, ok := m[iamPolicyClaimName()]; ok { policyName, _ = v.(string) } diff --git a/cmd/utils.go b/cmd/utils.go index ac87dca3c..8d2053167 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -39,7 +39,6 @@ import ( xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/handlers" - iampolicy "github.com/minio/minio/pkg/iam/policy" humanize "github.com/dustin/go-humanize" "github.com/gorilla/mux" @@ -535,8 +534,8 @@ func splitN(str, delim string, num int) []string { return retSplit } -func iamPolicyName() string { - return globalOpenIDConfig.ClaimPrefix + iampolicy.PolicyName +func iamPolicyClaimName() string { + return globalOpenIDConfig.ClaimPrefix + globalOpenIDConfig.ClaimName } func isWORMEnabled(bucket string) (Retention, bool) { diff --git a/pkg/iam/policy/policy.go b/pkg/iam/policy/policy.go index cba9771cf..38585cd18 100644 --- a/pkg/iam/policy/policy.go +++ b/pkg/iam/policy/policy.go @@ -19,6 +19,7 @@ package iampolicy import ( "encoding/json" "io" + "strings" "github.com/minio/minio/pkg/policy" ) @@ -37,6 +38,20 @@ type Args struct { Claims map[string]interface{} `json:"claims"` } +// GetPolicies get policies +func (a Args) GetPolicies(policyClaimName string) ([]string, bool) { + pname, ok := a.Claims[policyClaimName] + if !ok { + return nil, false + } + pnameStr, ok := pname.(string) + if ok { + return strings.Split(pnameStr, ","), true + } + pnameSlice, ok := pname.([]string) + return pnameSlice, ok +} + // Policy - iam bucket iamp. type Policy struct { ID policy.ID `json:"ID,omitempty"`