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.
This commit is contained in:
Harshavardhana 2020-01-08 17:21:58 -08:00 committed by kannappanr
parent fd56aa42a6
commit abc1c1070a
8 changed files with 90 additions and 43 deletions

View file

@ -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
}

View file

@ -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",
},

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}
// 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
}
// Sub policy is set and valid.
p, ok := sys.iamPolicyDocsMap[pnameSlice[0]]
return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args)
}
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
}
// Policy without Version string value reject it.
if subPolicy.Version == "" {
return false
}
// 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.

View file

@ -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)
}

View file

@ -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) {

View file

@ -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"`