mirror of
https://github.com/minio/minio.git
synced 2025-11-07 04:42:56 -05:00
Add role ARN support for OIDC identity provider (#13651)
- Allows setting a role policy parameter when configuring OIDC provider - When role policy is set, the server prints a role ARN usable in STS API requests - The given role policy is applied to STS API requests when the roleARN parameter is provided. - Service accounts for role policy are also possible and work as expected.
This commit is contained in:
committed by
GitHub
parent
4ce6d35e30
commit
4c0f48c548
@@ -245,9 +245,10 @@ func getClaimsFromToken(token string) (map[string]interface{}, error) {
|
||||
|
||||
// Session token must have a policy, reject requests without policy
|
||||
// claim.
|
||||
_, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]
|
||||
_, pokOpenIDClaimName := claims.MapClaims[iamPolicyClaimNameOpenID()]
|
||||
_, pokOpenIDRoleArn := claims.MapClaims[roleArnClaim]
|
||||
_, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
|
||||
if !pokOpenID && !pokSA {
|
||||
if !pokOpenIDClaimName && !pokOpenIDRoleArn && !pokSA {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ func validateConfig(s config.Config) error {
|
||||
}
|
||||
}
|
||||
if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
|
||||
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -513,7 +513,7 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
||||
}
|
||||
|
||||
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||
NewGatewayHTTPTransport(), xhttp.DrainBody)
|
||||
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize OpenID: %w", err))
|
||||
}
|
||||
|
||||
146
cmd/iam.go
146
cmd/iam.go
@@ -33,7 +33,9 @@ import (
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/arn"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
etcd "go.etcd.io/etcd/client/v3"
|
||||
@@ -66,6 +68,8 @@ type IAMSys struct {
|
||||
|
||||
usersSysType UsersSysType
|
||||
|
||||
rolesMap map[arn.ARN]string
|
||||
|
||||
// Persistence layer for IAM subsystem
|
||||
store *IAMStoreSys
|
||||
|
||||
@@ -215,6 +219,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// Migrate storage format if needed.
|
||||
for {
|
||||
// let one of the server acquire the lock, if not let them timeout.
|
||||
// which shall be retried again by this loop.
|
||||
@@ -263,6 +268,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
break
|
||||
}
|
||||
|
||||
// Load IAM data from storage.
|
||||
for {
|
||||
if err := sys.Load(retryCtx); err != nil {
|
||||
if configRetriableErrors(err) {
|
||||
@@ -308,7 +314,40 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
}()
|
||||
}
|
||||
|
||||
// Start watching changes to storage.
|
||||
go sys.watch(ctx)
|
||||
|
||||
// Load RoleARN
|
||||
if roleARN, rolePolicy, enabled := globalOpenIDConfig.GetRoleInfo(); enabled {
|
||||
numPolicies := len(strings.Split(rolePolicy, ","))
|
||||
validPolicies, _ := sys.store.FilterPolicies(rolePolicy, "")
|
||||
numValidPolicies := len(strings.Split(validPolicies, ","))
|
||||
if numPolicies != numValidPolicies {
|
||||
logger.LogIf(ctx, fmt.Errorf("Some specified role policies (%s) were not defined - role based policies will not be enabled.", rolePolicy))
|
||||
return
|
||||
}
|
||||
sys.rolesMap = map[arn.ARN]string{
|
||||
roleARN: rolePolicy,
|
||||
}
|
||||
}
|
||||
|
||||
sys.printIAMRoles()
|
||||
}
|
||||
|
||||
// Prints IAM role ARNs.
|
||||
func (sys *IAMSys) printIAMRoles() {
|
||||
arns := sys.GetRoleARNs()
|
||||
|
||||
if len(arns) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
msgs := make([]string, 0, len(arns))
|
||||
for _, arn := range arns {
|
||||
msgs = append(msgs, color.Bold(arn))
|
||||
}
|
||||
|
||||
logStartupMessage(fmt.Sprintf("%s %s", color.Blue("IAM Roles:"), strings.Join(msgs, " ")))
|
||||
}
|
||||
|
||||
// HasWatcher - returns if the IAM system has a watcher to be notified of
|
||||
@@ -389,6 +428,28 @@ func (sys *IAMSys) loadWatchedEvent(ctx context.Context, event iamWatchEvent) (e
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRoleARNs - returns a list of enabled role ARNs.
|
||||
func (sys *IAMSys) GetRoleARNs() []string {
|
||||
var res []string
|
||||
for arn := range sys.rolesMap {
|
||||
res = append(res, arn.String())
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GetRolePolicy - returns policies associated with a role ARN.
|
||||
func (sys *IAMSys) GetRolePolicy(arnStr string) (string, error) {
|
||||
arn, err := arn.Parse(arnStr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("RoleARN parse err: %v", err)
|
||||
}
|
||||
rolePolicy, ok := sys.rolesMap[arn]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("RoleARN %s is not defined.", arnStr)
|
||||
}
|
||||
return rolePolicy, nil
|
||||
}
|
||||
|
||||
// DeletePolicy - deletes a canned policy from backend or etcd.
|
||||
func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string) error {
|
||||
if !sys.Initialized() {
|
||||
@@ -1029,7 +1090,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
||||
return false
|
||||
}
|
||||
|
||||
// Check policy for this service account.
|
||||
// Check policy for parent user of service account.
|
||||
svcPolicies, err := sys.PolicyDBGet(parentUser, false, args.Groups...)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
@@ -1037,9 +1098,20 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
||||
}
|
||||
|
||||
if len(svcPolicies) == 0 {
|
||||
// If parent user has no policies, look in OpenID claims in case it exists.
|
||||
policySet, ok := iampolicy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())
|
||||
if ok {
|
||||
// If parent user has no policies, check for OpenID
|
||||
// claims/RoleARN in case it exists.
|
||||
roleArn := args.GetRoleArn()
|
||||
if roleArn != "" {
|
||||
arn, err := arn.Parse(roleArn)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("error parsing role ARN %s: %v", roleArn, err))
|
||||
return false
|
||||
}
|
||||
svcPolicies = newMappedPolicy(sys.rolesMap[arn]).toSlice()
|
||||
} else {
|
||||
// If there is no roleArn claim, check the OpenID
|
||||
// provider's policy claim.
|
||||
policySet, _ := iampolicy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())
|
||||
svcPolicies = policySet.ToSlice()
|
||||
}
|
||||
if len(svcPolicies) == 0 {
|
||||
@@ -1156,35 +1228,49 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
|
||||
return sys.IsAllowedLDAPSTS(args, parentUser)
|
||||
}
|
||||
|
||||
policies, ok := args.GetPolicies(iamPolicyClaimNameOpenID())
|
||||
if !ok {
|
||||
// When claims are set, it should have a policy claim field.
|
||||
return false
|
||||
var policies []string
|
||||
roleArn := args.GetRoleArn()
|
||||
if roleArn != "" {
|
||||
arn, err := arn.Parse(roleArn)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("error parsing role ARN %s: %v", roleArn, err))
|
||||
return false
|
||||
}
|
||||
policies = newMappedPolicy(sys.rolesMap[arn]).toSlice()
|
||||
} else {
|
||||
// If roleArn is not used, we fall back to using policy claim
|
||||
// from JWT.
|
||||
policySet, ok := args.GetPolicies(iamPolicyClaimNameOpenID())
|
||||
if !ok {
|
||||
// When claims are set, it should have a policy claim field.
|
||||
return false
|
||||
}
|
||||
// When claims are set, it should have policies as claim.
|
||||
if policySet.IsEmpty() {
|
||||
// No policy, no access!
|
||||
return false
|
||||
}
|
||||
|
||||
// If policy is available for given user, check the policy.
|
||||
mp, ok := sys.store.GetMappedPolicy(args.AccountName, false)
|
||||
if !ok {
|
||||
// No policy set for the user that we can find, no access!
|
||||
return false
|
||||
}
|
||||
|
||||
if !policySet.Equals(mp.policySet()) {
|
||||
// When claims has a policy, it should match the
|
||||
// policy of args.AccountName which server remembers.
|
||||
// if not reject such requests.
|
||||
return false
|
||||
}
|
||||
|
||||
policies = policySet.ToSlice()
|
||||
}
|
||||
|
||||
// When claims are set, it should have policies as claim.
|
||||
if policies.IsEmpty() {
|
||||
// No policy, no access!
|
||||
return false
|
||||
}
|
||||
|
||||
// If policy is available for given user, check the policy.
|
||||
mp, ok := sys.store.GetMappedPolicy(args.AccountName, false)
|
||||
if !ok {
|
||||
// No policy set for the user that we can find, no access!
|
||||
return false
|
||||
}
|
||||
|
||||
if !policies.Equals(mp.policySet()) {
|
||||
// When claims has a policy, it should match the
|
||||
// policy of args.AccountName which server remembers.
|
||||
// if not reject such requests.
|
||||
return false
|
||||
}
|
||||
|
||||
combinedPolicy, err := sys.store.GetPolicy(strings.Join(policies.ToSlice(), ","))
|
||||
combinedPolicy, err := sys.store.GetPolicy(strings.Join(policies, ","))
|
||||
if err == errNoSuchPolicy {
|
||||
for pname := range policies {
|
||||
for _, pname := range policies {
|
||||
_, err := sys.store.GetPolicy(pname)
|
||||
if err == errNoSuchPolicy {
|
||||
// all policies presented in the claim should exist
|
||||
|
||||
@@ -45,6 +45,7 @@ const (
|
||||
stsAction = "Action"
|
||||
stsPolicy = "Policy"
|
||||
stsToken = "Token"
|
||||
stsRoleArn = "RoleArn"
|
||||
stsWebIdentityToken = "WebIdentityToken"
|
||||
stsWebIdentityAccessToken = "WebIdentityAccessToken" // only valid if UserInfo is enabled.
|
||||
stsDurationSeconds = "DurationSeconds"
|
||||
@@ -73,6 +74,9 @@ const (
|
||||
// LDAP claim keys
|
||||
ldapUser = "ldapUser"
|
||||
ldapUserN = "ldapUsername"
|
||||
|
||||
// Role Claim key
|
||||
roleArnClaim = "roleArn"
|
||||
)
|
||||
|
||||
func parseOpenIDParentUser(parentUser string) (userID string, err error) {
|
||||
@@ -399,45 +403,42 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
}
|
||||
|
||||
var subFromToken string
|
||||
if v, ok := m[subClaim]; ok {
|
||||
subFromToken, _ = v.(string)
|
||||
}
|
||||
|
||||
if subFromToken == "" {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||
errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
|
||||
return
|
||||
}
|
||||
|
||||
var issFromToken string
|
||||
if v, ok := m[issClaim]; ok {
|
||||
issFromToken, _ = v.(string)
|
||||
}
|
||||
|
||||
// JWT has requested a custom claim with policy value set.
|
||||
// This is a MinIO STS API specific value, this value should
|
||||
// be set and configured on your identity provider as part of
|
||||
// JWT custom claims.
|
||||
var policyName string
|
||||
policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID())
|
||||
policies := strings.Join(policySet.ToSlice(), ",")
|
||||
if ok {
|
||||
policyName = globalIAMSys.CurrentPolicies(policies)
|
||||
}
|
||||
|
||||
if globalPolicyOPA == nil {
|
||||
if !ok {
|
||||
roleArn := r.Form.Get(stsRoleArn)
|
||||
if roleArn != "" {
|
||||
_, err := globalIAMSys.GetRolePolicy(roleArn)
|
||||
if err != nil {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||
fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
|
||||
return
|
||||
} else if policyName == "" {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||
fmt.Errorf("None of the given policies (`%s`) are defined, credentials will not be generated", policies))
|
||||
fmt.Errorf("Error processing %s parameter: %v", stsRoleArn, err))
|
||||
return
|
||||
}
|
||||
// If roleArn is used, we set it as a claim, and use the
|
||||
// associated policy when credentials are used.
|
||||
m[roleArnClaim] = roleArn
|
||||
} else {
|
||||
// JWT has requested a custom claim with policy value set.
|
||||
// This is a MinIO STS API specific value, this value should
|
||||
// be set and configured on your identity provider as part of
|
||||
// JWT custom claims.
|
||||
policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID())
|
||||
policies := strings.Join(policySet.ToSlice(), ",")
|
||||
if ok {
|
||||
policyName = globalIAMSys.CurrentPolicies(policies)
|
||||
}
|
||||
|
||||
if globalPolicyOPA == nil {
|
||||
if !ok {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||
fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
|
||||
return
|
||||
} else if policyName == "" {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||
fmt.Errorf("None of the given policies (`%s`) are defined, credentials will not be generated", policies))
|
||||
return
|
||||
}
|
||||
}
|
||||
m[iamPolicyClaimNameOpenID()] = policyName
|
||||
}
|
||||
m[iamPolicyClaimNameOpenID()] = policyName
|
||||
|
||||
sessionPolicyStr := r.Form.Get(stsPolicy)
|
||||
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
|
||||
@@ -476,6 +477,22 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
|
||||
// this is to ensure that ParentUser doesn't change and we get to use
|
||||
// parentUser as per the requirements for service accounts for OpenID
|
||||
// based logins.
|
||||
var subFromToken string
|
||||
if v, ok := m[subClaim]; ok {
|
||||
subFromToken, _ = v.(string)
|
||||
}
|
||||
|
||||
if subFromToken == "" {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||
errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
|
||||
return
|
||||
}
|
||||
|
||||
var issFromToken string
|
||||
if v, ok := m[issClaim]; ok {
|
||||
issFromToken, _ = v.(string)
|
||||
}
|
||||
|
||||
cred.ParentUser = "openid:" + subFromToken + ":" + issFromToken
|
||||
|
||||
// Set the newly generated credentials.
|
||||
|
||||
@@ -741,7 +741,7 @@ const (
|
||||
|
||||
// SetUpOpenID - expects to setup an OpenID test server using the test OpenID
|
||||
// container and canned data from https://github.com/minio/minio-ldap-testing
|
||||
func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string) {
|
||||
func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string, rolePolicy string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -750,10 +750,14 @@ func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string) {
|
||||
fmt.Sprintf("config_url=%s/.well-known/openid-configuration", serverAddr),
|
||||
"client_id=minio-client-app",
|
||||
"client_secret=minio-client-app-secret",
|
||||
"claim_name=groups",
|
||||
"scopes=openid,groups",
|
||||
"redirect_uri=http://127.0.0.1:10000/oauth_callback",
|
||||
}
|
||||
if rolePolicy != "" {
|
||||
configCmds = append(configCmds, fmt.Sprintf("role_policy=%s", rolePolicy))
|
||||
} else {
|
||||
configCmds = append(configCmds, "claim_name=groups")
|
||||
}
|
||||
_, err := s.adm.SetConfigKV(ctx, strings.Join(configCmds, " "))
|
||||
if err != nil {
|
||||
c.Fatalf("unable to setup OpenID for tests: %v", err)
|
||||
@@ -797,7 +801,7 @@ func TestIAMWithOpenIDServerSuite(t *testing.T) {
|
||||
}
|
||||
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpOpenID(c, openIDServer)
|
||||
suite.SetUpOpenID(c, openIDServer, "")
|
||||
suite.TestOpenIDSTS(c)
|
||||
suite.TestOpenIDServiceAcc(c)
|
||||
suite.TearDownSuite(c)
|
||||
@@ -805,3 +809,163 @@ func TestIAMWithOpenIDServerSuite(t *testing.T) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIAMWithOpenIDWithRolePolicyServerSuite(t *testing.T) {
|
||||
baseTestCases := []TestSuiteCommon{
|
||||
// Init and run test on FS backend with signature v4.
|
||||
{serverType: "FS", signer: signerV4},
|
||||
// Init and run test on FS backend, with tls enabled.
|
||||
{serverType: "FS", signer: signerV4, secure: true},
|
||||
// Init and run test on Erasure backend.
|
||||
{serverType: "Erasure", signer: signerV4},
|
||||
// Init and run test on ErasureSet backend.
|
||||
{serverType: "ErasureSet", signer: signerV4},
|
||||
}
|
||||
testCases := []*TestSuiteIAM{}
|
||||
for _, bt := range baseTestCases {
|
||||
testCases = append(testCases,
|
||||
newTestSuiteIAM(bt, false),
|
||||
newTestSuiteIAM(bt, true),
|
||||
)
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
etcdStr := ""
|
||||
if testCase.withEtcdBackend {
|
||||
etcdStr = " (with etcd backend)"
|
||||
}
|
||||
t.Run(
|
||||
fmt.Sprintf("Test: %d, ServerType: %s%s", i+1, testCase.serverType, etcdStr),
|
||||
func(t *testing.T) {
|
||||
c := &check{t, testCase.serverType}
|
||||
suite := testCase
|
||||
|
||||
openIDServer := os.Getenv(EnvTestOpenIDServer)
|
||||
if openIDServer == "" {
|
||||
c.Skip("Skipping OpenID test as no OpenID server is provided.")
|
||||
}
|
||||
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpOpenID(c, openIDServer, "readwrite")
|
||||
suite.TestOpenIDSTSWithRolePolicy(c)
|
||||
suite.TestOpenIDServiceAccWithRolePolicy(c)
|
||||
suite.TearDownSuite(c)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
testRoleARN = "arn:minio:iam:::role/127.0.0.1_minio-cl"
|
||||
)
|
||||
|
||||
func (s *TestSuiteIAM) TestOpenIDSTSWithRolePolicy(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
bucket := getRandomBucketName()
|
||||
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("bucket create error: %v", err)
|
||||
}
|
||||
|
||||
// Generate web identity STS token by interacting with OpenID IDP.
|
||||
token, err := mockTestUserInteraction(ctx, testProvider, "dillon@example.io", "dillon")
|
||||
if err != nil {
|
||||
c.Fatalf("mock user err: %v", err)
|
||||
}
|
||||
|
||||
webID := cr.STSWebIdentity{
|
||||
Client: s.TestSuiteCommon.client,
|
||||
STSEndpoint: s.endPoint,
|
||||
GetWebIDTokenExpiry: func() (*cr.WebIdentityToken, error) {
|
||||
return &cr.WebIdentityToken{
|
||||
Token: token,
|
||||
}, nil
|
||||
},
|
||||
RoleARN: testRoleARN,
|
||||
}
|
||||
|
||||
value, err := webID.Retrieve()
|
||||
if err != nil {
|
||||
c.Fatalf("Expected to generate STS creds, got err: %#v", err)
|
||||
}
|
||||
// fmt.Printf("value: %#v\n", value)
|
||||
|
||||
minioClient, err := minio.New(s.endpoint, &minio.Options{
|
||||
Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
|
||||
Secure: s.secure,
|
||||
Transport: s.TestSuiteCommon.client.Transport,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Error initializing client: %v", err)
|
||||
}
|
||||
|
||||
// Validate that the client from sts creds can access the bucket.
|
||||
c.mustListObjects(ctx, minioClient, bucket)
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestOpenIDServiceAccWithRolePolicy(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
bucket := getRandomBucketName()
|
||||
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("bucket create error: %v", err)
|
||||
}
|
||||
|
||||
// Generate web identity STS token by interacting with OpenID IDP.
|
||||
token, err := mockTestUserInteraction(ctx, testProvider, "dillon@example.io", "dillon")
|
||||
if err != nil {
|
||||
c.Fatalf("mock user err: %v", err)
|
||||
}
|
||||
|
||||
webID := cr.STSWebIdentity{
|
||||
Client: s.TestSuiteCommon.client,
|
||||
STSEndpoint: s.endPoint,
|
||||
GetWebIDTokenExpiry: func() (*cr.WebIdentityToken, error) {
|
||||
return &cr.WebIdentityToken{
|
||||
Token: token,
|
||||
}, nil
|
||||
},
|
||||
RoleARN: testRoleARN,
|
||||
}
|
||||
|
||||
value, err := webID.Retrieve()
|
||||
if err != nil {
|
||||
c.Fatalf("Expected to generate STS creds, got err: %#v", err)
|
||||
}
|
||||
|
||||
// Create an madmin client with user creds
|
||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||
Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
|
||||
Secure: s.secure,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Err creating user admin client: %v", err)
|
||||
}
|
||||
userAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
|
||||
|
||||
// Create svc acc
|
||||
cr := c.mustCreateSvcAccount(ctx, value.AccessKeyID, userAdmClient)
|
||||
|
||||
// 1. Check that svc account appears in listing
|
||||
c.assertSvcAccAppearsInListing(ctx, userAdmClient, value.AccessKeyID, cr.AccessKey)
|
||||
|
||||
// 2. Check that svc account info can be queried
|
||||
c.assertSvcAccInfoQueryable(ctx, userAdmClient, value.AccessKeyID, cr.AccessKey, true)
|
||||
|
||||
// 3. Check S3 access
|
||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||
|
||||
// 4. Check that svc account can restrict the policy, and that the
|
||||
// session policy can be updated.
|
||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 4. Check that service account's secret key and account status can be
|
||||
// updated.
|
||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 5. Check that service account can be deleted.
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user