mirror of
https://github.com/minio/minio.git
synced 2025-01-11 15:03:22 -05:00
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:
parent
fd56aa42a6
commit
abc1c1070a
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
68
cmd/iam.go
68
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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"`
|
||||
|
Loading…
Reference in New Issue
Block a user