Add custom policy claim name (#8764)

In certain organizations policy claim names
can be not just 'policy' but also things like
'roles', the value of this field might also
be *string* or *[]string* support this as well

In this PR we are still not supporting multiple
policies per STS account which will require a
more comprehensive change.
master
Harshavardhana 5 years ago committed by kannappanr
parent fd56aa42a6
commit abc1c1070a
  1. 2
      cmd/auth-handler.go
  2. 8
      cmd/config/identity/openid/help.go
  3. 11
      cmd/config/identity/openid/jwt.go
  4. 20
      cmd/crypto/metadata_test.go
  5. 62
      cmd/iam.go
  6. 4
      cmd/sts-handlers.go
  7. 5
      cmd/utils.go
  8. 15
      pkg/iam/policy/policy.go

@ -226,7 +226,7 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
// 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.
p, pok := claims[iamPolicyName()] p, pok := claims[iamPolicyClaimName()]
if !pok { if !pok {
return nil, errAuthentication return nil, errAuthentication
} }

@ -32,9 +32,15 @@ var (
Type: "string", Type: "string",
Optional: true, Optional: true,
}, },
config.HelpKV{
Key: ClaimName,
Description: `JWT canned policy claim name, defaults to "policy"`,
Optional: true,
Type: "string",
},
config.HelpKV{ config.HelpKV{
Key: ClaimPrefix, Key: ClaimPrefix,
Description: `JWT claim namespace prefix e.g. "customer1"`, Description: `JWT claim namespace prefix e.g. "customer1/"`,
Optional: true, Optional: true,
Type: "string", Type: "string",
}, },

@ -30,6 +30,7 @@ import (
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
iampolicy "github.com/minio/minio/pkg/iam/policy"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
) )
@ -41,6 +42,7 @@ type Config struct {
} `json:"jwks"` } `json:"jwks"`
URL *xnet.URL `json:"url,omitempty"` URL *xnet.URL `json:"url,omitempty"`
ClaimPrefix string `json:"claimPrefix,omitempty"` ClaimPrefix string `json:"claimPrefix,omitempty"`
ClaimName string `json:"claimName,omitempty"`
DiscoveryDoc DiscoveryDoc DiscoveryDoc DiscoveryDoc
ClientID string ClientID string
publicKeys map[string]crypto.PublicKey publicKeys map[string]crypto.PublicKey
@ -209,12 +211,14 @@ func (p *JWT) ID() ID {
const ( const (
JwksURL = "jwks_url" JwksURL = "jwks_url"
ConfigURL = "config_url" ConfigURL = "config_url"
ClaimName = "claim_name"
ClaimPrefix = "claim_prefix" ClaimPrefix = "claim_prefix"
ClientID = "client_id" ClientID = "client_id"
EnvIdentityOpenIDClientID = "MINIO_IDENTITY_OPENID_CLIENT_ID" EnvIdentityOpenIDClientID = "MINIO_IDENTITY_OPENID_CLIENT_ID"
EnvIdentityOpenIDJWKSURL = "MINIO_IDENTITY_OPENID_JWKS_URL" EnvIdentityOpenIDJWKSURL = "MINIO_IDENTITY_OPENID_JWKS_URL"
EnvIdentityOpenIDURL = "MINIO_IDENTITY_OPENID_CONFIG_URL" EnvIdentityOpenIDURL = "MINIO_IDENTITY_OPENID_CONFIG_URL"
EnvIdentityOpenIDClaimName = "MINIO_IDENTITY_OPENID_CLAIM_NAME"
EnvIdentityOpenIDClaimPrefix = "MINIO_IDENTITY_OPENID_CLAIM_PREFIX" EnvIdentityOpenIDClaimPrefix = "MINIO_IDENTITY_OPENID_CLAIM_PREFIX"
) )
@ -271,6 +275,10 @@ var (
Key: ClientID, Key: ClientID,
Value: "", Value: "",
}, },
config.KV{
Key: ClaimName,
Value: iampolicy.PolicyName,
},
config.KV{ config.KV{
Key: ClaimPrefix, Key: ClaimPrefix,
Value: "", Value: "",
@ -299,6 +307,7 @@ func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io
} }
c = Config{ c = Config{
ClaimName: env.Get(EnvIdentityOpenIDClaimName, kvs.Get(ClaimName)),
ClaimPrefix: env.Get(EnvIdentityOpenIDClaimPrefix, kvs.Get(ClaimPrefix)), ClaimPrefix: env.Get(EnvIdentityOpenIDClaimPrefix, kvs.Get(ClaimPrefix)),
publicKeys: make(map[string]crypto.PublicKey), publicKeys: make(map[string]crypto.PublicKey),
ClientID: env.Get(EnvIdentityOpenIDClientID, kvs.Get(ClientID)), 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 { if err != nil {
return c, err return c, err
} }
if err = c.PopulatePublicKey(); err != nil { if err = c.PopulatePublicKey(); err != nil {
return c, err return c, err
} }
return c, nil return c, nil
} }

@ -188,9 +188,17 @@ var s3ParseMetadataTests = []struct {
func TestS3ParseMetadata(t *testing.T) { func TestS3ParseMetadata(t *testing.T) {
for i, test := range s3ParseMetadataTests { for i, test := range s3ParseMetadataTests {
keyID, dataKey, sealedKey, err := S3.ParseMetadata(test.Metadata) 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) 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) { if !bytes.Equal(dataKey, test.DataKey) {
t.Errorf("Test %d: got data key '%v' - want data key '%v'", i, 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) { func TestSSECParseMetadata(t *testing.T) {
for i, test := range ssecParseMetadataTests { for i, test := range ssecParseMetadataTests {
sealedKey, err := SSEC.ParseMetadata(test.Metadata) 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) 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 { 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) t.Errorf("Test %d: got sealed key algorithm '%v' - want sealed key algorithm '%v'", i, sealedKey.Algorithm, test.SealedKey.Algorithm)
} }

@ -1288,14 +1288,14 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
return combinedPolicy.IsAllowed(args) return combinedPolicy.IsAllowed(args)
} }
pname, ok := args.Claims[iamPolicyName()] pnameSlice, ok := args.GetPolicies(iamPolicyClaimName())
if !ok { 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 return false
} }
pnameStr, ok := pname.(string)
if !ok { // When claims are set, it should have a policy claim field.
// When claims has "policy" field, it should be string. if len(pnameSlice) == 0 {
return false return false
} }
@ -1310,7 +1310,7 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
} }
name := mp.Policy name := mp.Policy
if pnameStr != name { if pnameSlice[0] != name {
// When claims has a policy, it should match the // When claims has a policy, it should match the
// policy of args.AccountName which server remembers. // policy of args.AccountName which server remembers.
// if not reject such requests. // if not reject such requests.
@ -1319,36 +1319,36 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
// Now check if we have a sessionPolicy. // Now check if we have a sessionPolicy.
spolicy, ok := args.Claims[iampolicy.SessionPolicyName] spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
if !ok { if ok {
// Sub policy not set, this is most common since subPolicy spolicyStr, ok := spolicy.(string)
// is optional, use the top level policy only. if !ok {
p, ok := sys.iamPolicyDocsMap[pnameStr] // Sub policy if set, should be a string reject
return ok && p.IsAllowed(args) // malformed/malicious requests.
} return false
}
spolicyStr, ok := spolicy.(string) // Check if policy is parseable.
if !ok { subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
// Sub policy if set, should be a string reject if err != nil {
// malformed/malicious requests. // Log any error in input session policy config.
return false logger.LogIf(context.Background(), err)
} return false
}
// Check if policy is parseable. // Policy without Version string value reject it.
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr))) if subPolicy.Version == "" {
if err != nil { return false
// Log any error in input session policy config. }
logger.LogIf(context.Background(), err)
return false
}
// Policy without Version string value reject it. // Sub policy is set and valid.
if subPolicy.Version == "" { p, ok := sys.iamPolicyDocsMap[pnameSlice[0]]
return false return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args)
} }
// Sub policy is set and valid. // Sub policy not set, this is most common since subPolicy
p, ok := sys.iamPolicyDocsMap[pnameStr] // is optional, use the top level policy only.
return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args) p, ok := sys.iamPolicyDocsMap[pnameSlice[0]]
return ok && p.IsAllowed(args)
} }
// IsAllowed - checks given policy args is allowed to continue the Rest API. // IsAllowed - checks given policy args is allowed to continue the Rest API.

@ -214,7 +214,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
// This policy is the policy associated with the user // This policy is the policy associated with the user
// requesting for temporary credentials. The temporary // requesting for temporary credentials. The temporary
// credentials will inherit the same policy requirements. // credentials will inherit the same policy requirements.
m[iamPolicyName()] = policyName m[iamPolicyClaimName()] = policyName
if len(sessionPolicyStr) > 0 { if len(sessionPolicyStr) > 0 {
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr)) 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 // be set and configured on your identity provider as part of
// JWT custom claims. // JWT custom claims.
var policyName string var policyName string
if v, ok := m[iamPolicyName()]; ok { if v, ok := m[iamPolicyClaimName()]; ok {
policyName, _ = v.(string) policyName, _ = v.(string)
} }

@ -39,7 +39,6 @@ import (
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/handlers"
iampolicy "github.com/minio/minio/pkg/iam/policy"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -535,8 +534,8 @@ func splitN(str, delim string, num int) []string {
return retSplit return retSplit
} }
func iamPolicyName() string { func iamPolicyClaimName() string {
return globalOpenIDConfig.ClaimPrefix + iampolicy.PolicyName return globalOpenIDConfig.ClaimPrefix + globalOpenIDConfig.ClaimName
} }
func isWORMEnabled(bucket string) (Retention, bool) { func isWORMEnabled(bucket string) (Retention, bool) {

@ -19,6 +19,7 @@ package iampolicy
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"strings"
"github.com/minio/minio/pkg/policy" "github.com/minio/minio/pkg/policy"
) )
@ -37,6 +38,20 @@ type Args struct {
Claims map[string]interface{} `json:"claims"` 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. // Policy - iam bucket iamp.
type Policy struct { type Policy struct {
ID policy.ID `json:"ID,omitempty"` ID policy.ID `json:"ID,omitempty"`

Loading…
Cancel
Save