mirror of
https://github.com/minio/minio.git
synced 2025-01-25 21:53:16 -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
|
// 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)
|
||||||
}
|
}
|
||||||
|
68
cmd/iam.go
68
cmd/iam.go
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
// Sub policy not set, this is most common since subPolicy
|
||||||
if !ok {
|
// is optional, use the top level policy only.
|
||||||
// Sub policy if set, should be a string reject
|
p, ok := sys.iamPolicyDocsMap[pnameSlice[0]]
|
||||||
// malformed/malicious requests.
|
return ok && p.IsAllowed(args)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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…
x
Reference in New Issue
Block a user