History

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.

Aditya Manthramurthy committed 4 years ago

Commit 4c0f48c1 parent 4ce6d3512 files · +579 −77Browse files

Unified · Split

12 files
  1. buildscripts/minio-upgrade.sh +3 −0
  2. cmd/auth-handler.go +3 −2
  3. cmd/config-current.go +2 −2
  4. cmd/iam.go +113 −27
  5. cmd/sts-handlers.go +51 −34
  6. cmd/sts-handlers_test.go +167 −3
  7. docs/sts/web-identity.md +16 −0
  8. go.mod +2 −2
  9. go.sum +5 −4
  10. internal/arn/arn.go +147 −0
  11. internal/config/identity/openid/help.go +18 −0
  12. internal/config/identity/openid/jwt.go +52 −3
buildscripts/minio-upgrade.sh+3−0
@@ -43,6 +43,9 @@ add_alias() { · 42 unmodified lines
43 echo "...waiting... for 5secs" && sleep 543 echo "...waiting... for 5secs" && sleep 5
44 done44 done
45 done45 done
46
47 echo "Sleeping for nginx"
48 sleep 20
46}49}
47 50
48__init__() {51__init__() {
cmd/auth-handler.go+3−2
@@ -245,9 +245,10 @@ func getClaimsFromToken(token string) (map[string]interface{}, error) { · 244 unmodified lines
245 245
246 // Session token must have a policy, reject requests without policy246 // Session token must have a policy, reject requests without policy
247 // claim.247 // claim.
248 _, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]248 _, pokOpenIDClaimName := claims.MapClaims[iamPolicyClaimNameOpenID()]
249 _, pokOpenIDRoleArn := claims.MapClaims[roleArnClaim]
249 _, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]250 _, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
250 if !pokOpenID && !pokSA {251 if !pokOpenIDClaimName && !pokOpenIDRoleArn && !pokSA {
251 return nil, errAuthentication252 return nil, errAuthentication
252 }253 }
253 254
cmd/config-current.go+2−2
@@ -327,7 +327,7 @@ func validateConfig(s config.Config) error { · 326 unmodified lines
327 }327 }
328 }328 }
329 if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],329 if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
330 NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {330 NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
331 return err331 return err
332 }332 }
333 333
@@ -513,7 +513,7 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) { · 179 unmodified lines
513 }513 }
514 514
515 globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],515 globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
516 NewGatewayHTTPTransport(), xhttp.DrainBody)516 NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region)
517 if err != nil {517 if err != nil {
518 logger.LogIf(ctx, fmt.Errorf("Unable to initialize OpenID: %w", err))518 logger.LogIf(ctx, fmt.Errorf("Unable to initialize OpenID: %w", err))
519 }519 }
cmd/iam.go+113−27
@@ -33,7 +33,9 @@ import ( · 32 unmodified lines
33 humanize "github.com/dustin/go-humanize"33 humanize "github.com/dustin/go-humanize"
34 "github.com/minio/madmin-go"34 "github.com/minio/madmin-go"
35 "github.com/minio/minio-go/v7/pkg/set"35 "github.com/minio/minio-go/v7/pkg/set"
36 "github.com/minio/minio/internal/arn"
36 "github.com/minio/minio/internal/auth"37 "github.com/minio/minio/internal/auth"
38 "github.com/minio/minio/internal/color"
37 "github.com/minio/minio/internal/logger"39 "github.com/minio/minio/internal/logger"
38 iampolicy "github.com/minio/pkg/iam/policy"40 iampolicy "github.com/minio/pkg/iam/policy"
39 etcd "go.etcd.io/etcd/client/v3"41 etcd "go.etcd.io/etcd/client/v3"
@@ -66,6 +68,8 @@ type IAMSys struct { · 26 unmodified lines
66 68
67 usersSysType UsersSysType69 usersSysType UsersSysType
68 70
71 rolesMap map[arn.ARN]string
72
69 // Persistence layer for IAM subsystem73 // Persistence layer for IAM subsystem
70 store *IAMStoreSys74 store *IAMStoreSys
71 75
@@ -215,6 +219,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc · 143 unmodified lines
215 219
216 r := rand.New(rand.NewSource(time.Now().UnixNano()))220 r := rand.New(rand.NewSource(time.Now().UnixNano()))
217 221
222 // Migrate storage format if needed.
218 for {223 for {
219 // let one of the server acquire the lock, if not let them timeout.224 // let one of the server acquire the lock, if not let them timeout.
220 // which shall be retried again by this loop.225 // which shall be retried again by this loop.
@@ -263,6 +268,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc · 42 unmodified lines
263 break268 break
264 }269 }
265 270
271 // Load IAM data from storage.
266 for {272 for {
267 if err := sys.Load(retryCtx); err != nil {273 if err := sys.Load(retryCtx); err != nil {
268 if configRetriableErrors(err) {274 if configRetriableErrors(err) {
@@ -308,7 +314,40 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc · 39 unmodified lines
308 }()314 }()
309 }315 }
310 316
317 // Start watching changes to storage.
311 go sys.watch(ctx)318 go sys.watch(ctx)
319
320 // Load RoleARN
321 if roleARN, rolePolicy, enabled := globalOpenIDConfig.GetRoleInfo(); enabled {
322 numPolicies := len(strings.Split(rolePolicy, ","))
323 validPolicies, _ := sys.store.FilterPolicies(rolePolicy, "")
324 numValidPolicies := len(strings.Split(validPolicies, ","))
325 if numPolicies != numValidPolicies {
326 logger.LogIf(ctx, fmt.Errorf("Some specified role policies (%s) were not defined - role based policies will not be enabled.", rolePolicy))
327 return
328 }
329 sys.rolesMap = map[arn.ARN]string{
330 roleARN: rolePolicy,
331 }
332 }
333
334 sys.printIAMRoles()
335}
336
337// Prints IAM role ARNs.
338func (sys *IAMSys) printIAMRoles() {
339 arns := sys.GetRoleARNs()
340
341 if len(arns) == 0 {
342 return
343 }
344
345 msgs := make([]string, 0, len(arns))
346 for _, arn := range arns {
347 msgs = append(msgs, color.Bold(arn))
348 }
349
350 logStartupMessage(fmt.Sprintf("%s %s", color.Blue("IAM Roles:"), strings.Join(msgs, " ")))
312}351}
313 352
314// HasWatcher - returns if the IAM system has a watcher to be notified of353// 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 · 74 unmodified lines
389 return err428 return err
390}429}
391 430
431// GetRoleARNs - returns a list of enabled role ARNs.
432func (sys *IAMSys) GetRoleARNs() []string {
433 var res []string
434 for arn := range sys.rolesMap {
435 res = append(res, arn.String())
436 }
437 return res
438}
439
440// GetRolePolicy - returns policies associated with a role ARN.
441func (sys *IAMSys) GetRolePolicy(arnStr string) (string, error) {
442 arn, err := arn.Parse(arnStr)
443 if err != nil {
444 return "", fmt.Errorf("RoleARN parse err: %v", err)
445 }
446 rolePolicy, ok := sys.rolesMap[arn]
447 if !ok {
448 return "", fmt.Errorf("RoleARN %s is not defined.", arnStr)
449 }
450 return rolePolicy, nil
451}
452
392// DeletePolicy - deletes a canned policy from backend or etcd.453// DeletePolicy - deletes a canned policy from backend or etcd.
393func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string) error {454func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string) error {
394 if !sys.Initialized() {455 if !sys.Initialized() {
@@ -1029,7 +1090,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin · 634 unmodified lines
1029 return false1090 return false
1030 }1091 }
1031 1092
1032 // Check policy for this service account.1093 // Check policy for parent user of service account.
1033 svcPolicies, err := sys.PolicyDBGet(parentUser, false, args.Groups...)1094 svcPolicies, err := sys.PolicyDBGet(parentUser, false, args.Groups...)
1034 if err != nil {1095 if err != nil {
1035 logger.LogIf(GlobalContext, err)1096 logger.LogIf(GlobalContext, err)
@@ -1037,9 +1098,20 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin · 1 unmodified line
1037 }1098 }
1038 1099
1039 if len(svcPolicies) == 0 {1100 if len(svcPolicies) == 0 {
1040 // If parent user has no policies, look in OpenID claims in case it exists.1101 // If parent user has no policies, check for OpenID
1041 policySet, ok := iampolicy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())1102 // claims/RoleARN in case it exists.
1042 if ok {1103 roleArn := args.GetRoleArn()
1104 if roleArn != "" {
1105 arn, err := arn.Parse(roleArn)
1106 if err != nil {
1107 logger.LogIf(GlobalContext, fmt.Errorf("error parsing role ARN %s: %v", roleArn, err))
1108 return false
1109 }
1110 svcPolicies = newMappedPolicy(sys.rolesMap[arn]).toSlice()
1111 } else {
1112 // If there is no roleArn claim, check the OpenID
1113 // provider's policy claim.
1114 policySet, _ := iampolicy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())
1043 svcPolicies = policySet.ToSlice()1115 svcPolicies = policySet.ToSlice()
1044 }1116 }
1045 if len(svcPolicies) == 0 {1117 if len(svcPolicies) == 0 {
@@ -1156,35 +1228,49 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool { · 110 unmodified lines
1156 return sys.IsAllowedLDAPSTS(args, parentUser)1228 return sys.IsAllowedLDAPSTS(args, parentUser)
1157 }1229 }
1158 1230
1159 policies, ok := args.GetPolicies(iamPolicyClaimNameOpenID())1231 var policies []string
1160 if !ok {1232 roleArn := args.GetRoleArn()
1161 // When claims are set, it should have a policy claim field.1233 if roleArn != "" {
1162 return false1234 arn, err := arn.Parse(roleArn)
1163 }1235 if err != nil {
1236 logger.LogIf(GlobalContext, fmt.Errorf("error parsing role ARN %s: %v", roleArn, err))
1237 return false
1238 }
1239 policies = newMappedPolicy(sys.rolesMap[arn]).toSlice()
1240 } else {
1241 // If roleArn is not used, we fall back to using policy claim
1242 // from JWT.
1243 policySet, ok := args.GetPolicies(iamPolicyClaimNameOpenID())
1244 if !ok {
1245 // When claims are set, it should have a policy claim field.
1246 return false
1247 }
1248 // When claims are set, it should have policies as claim.
1249 if policySet.IsEmpty() {
1250 // No policy, no access!
1251 return false
1252 }
1164 1253
1165 // When claims are set, it should have policies as claim.1254 // If policy is available for given user, check the policy.
1166 if policies.IsEmpty() {1255 mp, ok := sys.store.GetMappedPolicy(args.AccountName, false)
1167 // No policy, no access!1256 if !ok {
1168 return false1257 // No policy set for the user that we can find, no access!
1169 }1258 return false
1259 }
1170 1260
1171 // If policy is available for given user, check the policy.1261 if !policySet.Equals(mp.policySet()) {
1172 mp, ok := sys.store.GetMappedPolicy(args.AccountName, false)1262 // When claims has a policy, it should match the
1173 if !ok {1263 // policy of args.AccountName which server remembers.
1174 // No policy set for the user that we can find, no access!1264 // if not reject such requests.
1175 return false1265 return false
1176 }1266 }
1177 1267
1178 if !policies.Equals(mp.policySet()) {1268 policies = policySet.ToSlice()
1179 // When claims has a policy, it should match the
1180 // policy of args.AccountName which server remembers.
1181 // if not reject such requests.
1182 return false
1183 }1269 }
1184 1270
1185 combinedPolicy, err := sys.store.GetPolicy(strings.Join(policies.ToSlice(), ","))1271 combinedPolicy, err := sys.store.GetPolicy(strings.Join(policies, ","))
1186 if err == errNoSuchPolicy {1272 if err == errNoSuchPolicy {
1187 for pname := range policies {1273 for _, pname := range policies {
1188 _, err := sys.store.GetPolicy(pname)1274 _, err := sys.store.GetPolicy(pname)
1189 if err == errNoSuchPolicy {1275 if err == errNoSuchPolicy {
1190 // all policies presented in the claim should exist1276 // all policies presented in the claim should exist
cmd/sts-handlers.go+51−34
@@ -45,6 +45,7 @@ const ( · 44 unmodified lines
45 stsAction = "Action"45 stsAction = "Action"
46 stsPolicy = "Policy"46 stsPolicy = "Policy"
47 stsToken = "Token"47 stsToken = "Token"
48 stsRoleArn = "RoleArn"
48 stsWebIdentityToken = "WebIdentityToken"49 stsWebIdentityToken = "WebIdentityToken"
49 stsWebIdentityAccessToken = "WebIdentityAccessToken" // only valid if UserInfo is enabled.50 stsWebIdentityAccessToken = "WebIdentityAccessToken" // only valid if UserInfo is enabled.
50 stsDurationSeconds = "DurationSeconds"51 stsDurationSeconds = "DurationSeconds"
@@ -73,6 +74,9 @@ const ( · 22 unmodified lines
73 // LDAP claim keys74 // LDAP claim keys
74 ldapUser = "ldapUser"75 ldapUser = "ldapUser"
75 ldapUserN = "ldapUsername"76 ldapUserN = "ldapUsername"
77
78 // Role Claim key
79 roleArnClaim = "roleArn"
76)80)
77 81
78func parseOpenIDParentUser(parentUser string) (userID string, err error) {82func parseOpenIDParentUser(parentUser string) (userID string, err error) {
@@ -399,45 +403,42 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ · 320 unmodified lines
399 }403 }
400 }404 }
401 405
402 var subFromToken string
403 if v, ok := m[subClaim]; ok {
404 subFromToken, _ = v.(string)
405 }
406
407 if subFromToken == "" {
408 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
409 errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
410 return
411 }
412
413 var issFromToken string
414 if v, ok := m[issClaim]; ok {
415 issFromToken, _ = v.(string)
416 }
417
418 // JWT has requested a custom claim with policy value set.
419 // This is a MinIO STS API specific value, this value should
420 // be set and configured on your identity provider as part of
421 // JWT custom claims.
422 var policyName string406 var policyName string
423 policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID())407 roleArn := r.Form.Get(stsRoleArn)
424 policies := strings.Join(policySet.ToSlice(), ",")408 if roleArn != "" {
425 if ok {409 _, err := globalIAMSys.GetRolePolicy(roleArn)
426 policyName = globalIAMSys.CurrentPolicies(policies)410 if err != nil {
427 }
428
429 if globalPolicyOPA == nil {
430 if !ok {
431 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
432 fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
433 return
434 } else if policyName == "" {
435 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,411 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
436 fmt.Errorf("None of the given policies (`%s`) are defined, credentials will not be generated", policies))412 fmt.Errorf("Error processing %s parameter: %v", stsRoleArn, err))
437 return413 return
438 }414 }
415 // If roleArn is used, we set it as a claim, and use the
416 // associated policy when credentials are used.
417 m[roleArnClaim] = roleArn
418 } else {
419 // JWT has requested a custom claim with policy value set.
420 // This is a MinIO STS API specific value, this value should
421 // be set and configured on your identity provider as part of
422 // JWT custom claims.
423 policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID())
424 policies := strings.Join(policySet.ToSlice(), ",")
425 if ok {
426 policyName = globalIAMSys.CurrentPolicies(policies)
427 }
428
429 if globalPolicyOPA == nil {
430 if !ok {
431 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
432 fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
433 return
434 } else if policyName == "" {
435 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
436 fmt.Errorf("None of the given policies (`%s`) are defined, credentials will not be generated", policies))
437 return
438 }
439 }
440 m[iamPolicyClaimNameOpenID()] = policyName
439 }441 }
440 m[iamPolicyClaimNameOpenID()] = policyName
441 442
442 sessionPolicyStr := r.Form.Get(stsPolicy)443 sessionPolicyStr := r.Form.Get(stsPolicy)
443 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html444 // 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 · 32 unmodified lines
476 // this is to ensure that ParentUser doesn't change and we get to use477 // this is to ensure that ParentUser doesn't change and we get to use
477 // parentUser as per the requirements for service accounts for OpenID478 // parentUser as per the requirements for service accounts for OpenID
478 // based logins.479 // based logins.
480 var subFromToken string
481 if v, ok := m[subClaim]; ok {
482 subFromToken, _ = v.(string)
483 }
484
485 if subFromToken == "" {
486 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
487 errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
488 return
489 }
490
491 var issFromToken string
492 if v, ok := m[issClaim]; ok {
493 issFromToken, _ = v.(string)
494 }
495
479 cred.ParentUser = "openid:" + subFromToken + ":" + issFromToken496 cred.ParentUser = "openid:" + subFromToken + ":" + issFromToken
480 497
481 // Set the newly generated credentials.498 // Set the newly generated credentials.
cmd/sts-handlers_test.go+167−3
@@ -741,7 +741,7 @@ const ( · 740 unmodified lines
741 741
742// SetUpOpenID - expects to setup an OpenID test server using the test OpenID742// SetUpOpenID - expects to setup an OpenID test server using the test OpenID
743// container and canned data from https://github.com/minio/minio-ldap-testing743// container and canned data from https://github.com/minio/minio-ldap-testing
744func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string) {744func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string, rolePolicy string) {
745 ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)745 ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
746 defer cancel()746 defer cancel()
747 747
@@ -750,10 +750,14 @@ func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string) { · 2 unmodified lines
750 fmt.Sprintf("config_url=%s/.well-known/openid-configuration", serverAddr),750 fmt.Sprintf("config_url=%s/.well-known/openid-configuration", serverAddr),
751 "client_id=minio-client-app",751 "client_id=minio-client-app",
752 "client_secret=minio-client-app-secret",752 "client_secret=minio-client-app-secret",
753 "claim_name=groups",
754 "scopes=openid,groups",753 "scopes=openid,groups",
755 "redirect_uri=http://127.0.0.1:10000/oauth_callback",754 "redirect_uri=http://127.0.0.1:10000/oauth_callback",
756 }755 }
756 if rolePolicy != "" {
757 configCmds = append(configCmds, fmt.Sprintf("role_policy=%s", rolePolicy))
758 } else {
759 configCmds = append(configCmds, "claim_name=groups")
760 }
757 _, err := s.adm.SetConfigKV(ctx, strings.Join(configCmds, " "))761 _, err := s.adm.SetConfigKV(ctx, strings.Join(configCmds, " "))
758 if err != nil {762 if err != nil {
759 c.Fatalf("unable to setup OpenID for tests: %v", err)763 c.Fatalf("unable to setup OpenID for tests: %v", err)
@@ -797,7 +801,7 @@ func TestIAMWithOpenIDServerSuite(t *testing.T) { · 37 unmodified lines
797 }801 }
798 802
799 suite.SetUpSuite(c)803 suite.SetUpSuite(c)
800 suite.SetUpOpenID(c, openIDServer)804 suite.SetUpOpenID(c, openIDServer, "")
801 suite.TestOpenIDSTS(c)805 suite.TestOpenIDSTS(c)
802 suite.TestOpenIDServiceAcc(c)806 suite.TestOpenIDServiceAcc(c)
803 suite.TearDownSuite(c)807 suite.TearDownSuite(c)
@@ -805,3 +809,163 @@ func TestIAMWithOpenIDServerSuite(t *testing.T) { · 1 unmodified line
805 )809 )
806 }810 }
807}811}
812
813func TestIAMWithOpenIDWithRolePolicyServerSuite(t *testing.T) {
814 baseTestCases := []TestSuiteCommon{
815 // Init and run test on FS backend with signature v4.
816 {serverType: "FS", signer: signerV4},
817 // Init and run test on FS backend, with tls enabled.
818 {serverType: "FS", signer: signerV4, secure: true},
819 // Init and run test on Erasure backend.
820 {serverType: "Erasure", signer: signerV4},
821 // Init and run test on ErasureSet backend.
822 {serverType: "ErasureSet", signer: signerV4},
823 }
824 testCases := []*TestSuiteIAM{}
825 for _, bt := range baseTestCases {
826 testCases = append(testCases,
827 newTestSuiteIAM(bt, false),
828 newTestSuiteIAM(bt, true),
829 )
830 }
831 for i, testCase := range testCases {
832 etcdStr := ""
833 if testCase.withEtcdBackend {
834 etcdStr = " (with etcd backend)"
835 }
836 t.Run(
837 fmt.Sprintf("Test: %d, ServerType: %s%s", i+1, testCase.serverType, etcdStr),
838 func(t *testing.T) {
839 c := &check{t, testCase.serverType}
840 suite := testCase
841
842 openIDServer := os.Getenv(EnvTestOpenIDServer)
843 if openIDServer == "" {
844 c.Skip("Skipping OpenID test as no OpenID server is provided.")
845 }
846
847 suite.SetUpSuite(c)
848 suite.SetUpOpenID(c, openIDServer, "readwrite")
849 suite.TestOpenIDSTSWithRolePolicy(c)
850 suite.TestOpenIDServiceAccWithRolePolicy(c)
851 suite.TearDownSuite(c)
852 },
853 )
854 }
855}
856
857const (
858 testRoleARN = "arn:minio:iam:::role/127.0.0.1_minio-cl"
859)
860
861func (s *TestSuiteIAM) TestOpenIDSTSWithRolePolicy(c *check) {
862 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
863 defer cancel()
864
865 bucket := getRandomBucketName()
866 err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
867 if err != nil {
868 c.Fatalf("bucket create error: %v", err)
869 }
870
871 // Generate web identity STS token by interacting with OpenID IDP.
872 token, err := mockTestUserInteraction(ctx, testProvider, "dillon@example.io", "dillon")
873 if err != nil {
874 c.Fatalf("mock user err: %v", err)
875 }
876
877 webID := cr.STSWebIdentity{
878 Client: s.TestSuiteCommon.client,
879 STSEndpoint: s.endPoint,
880 GetWebIDTokenExpiry: func() (*cr.WebIdentityToken, error) {
881 return &cr.WebIdentityToken{
882 Token: token,
883 }, nil
884 },
885 RoleARN: testRoleARN,
886 }
887
888 value, err := webID.Retrieve()
889 if err != nil {
890 c.Fatalf("Expected to generate STS creds, got err: %#v", err)
891 }
892 // fmt.Printf("value: %#v\n", value)
893
894 minioClient, err := minio.New(s.endpoint, &minio.Options{
895 Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
896 Secure: s.secure,
897 Transport: s.TestSuiteCommon.client.Transport,
898 })
899 if err != nil {
900 c.Fatalf("Error initializing client: %v", err)
901 }
902
903 // Validate that the client from sts creds can access the bucket.
904 c.mustListObjects(ctx, minioClient, bucket)
905}
906
907func (s *TestSuiteIAM) TestOpenIDServiceAccWithRolePolicy(c *check) {
908 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
909 defer cancel()
910
911 bucket := getRandomBucketName()
912 err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
913 if err != nil {
914 c.Fatalf("bucket create error: %v", err)
915 }
916
917 // Generate web identity STS token by interacting with OpenID IDP.
918 token, err := mockTestUserInteraction(ctx, testProvider, "dillon@example.io", "dillon")
919 if err != nil {
920 c.Fatalf("mock user err: %v", err)
921 }
922
923 webID := cr.STSWebIdentity{
924 Client: s.TestSuiteCommon.client,
925 STSEndpoint: s.endPoint,
926 GetWebIDTokenExpiry: func() (*cr.WebIdentityToken, error) {
927 return &cr.WebIdentityToken{
928 Token: token,
929 }, nil
930 },
931 RoleARN: testRoleARN,
932 }
933
934 value, err := webID.Retrieve()
935 if err != nil {
936 c.Fatalf("Expected to generate STS creds, got err: %#v", err)
937 }
938
939 // Create an madmin client with user creds
940 userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
941 Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
942 Secure: s.secure,
943 })
944 if err != nil {
945 c.Fatalf("Err creating user admin client: %v", err)
946 }
947 userAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
948
949 // Create svc acc
950 cr := c.mustCreateSvcAccount(ctx, value.AccessKeyID, userAdmClient)
951
952 // 1. Check that svc account appears in listing
953 c.assertSvcAccAppearsInListing(ctx, userAdmClient, value.AccessKeyID, cr.AccessKey)
954
955 // 2. Check that svc account info can be queried
956 c.assertSvcAccInfoQueryable(ctx, userAdmClient, value.AccessKeyID, cr.AccessKey, true)
957
958 // 3. Check S3 access
959 c.assertSvcAccS3Access(ctx, s, cr, bucket)
960
961 // 4. Check that svc account can restrict the policy, and that the
962 // session policy can be updated.
963 c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
964
965 // 4. Check that service account's secret key and account status can be
966 // updated.
967 c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
968
969 // 5. Check that service account can be deleted.
970 c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
971}
docs/sts/web-identity.md+16−0
@@ -6,6 +6,14 @@ Calling AssumeRoleWithWebIdentity does not require the use of MinIO default cred · 5 unmodified lines
6 6
7By default, the temporary security credentials created by AssumeRoleWithWebIdentity last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 365 days.7By default, the temporary security credentials created by AssumeRoleWithWebIdentity last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 365 days.
8 8
9## Access Control Policies
10
11MinIO's AssumeRoleWithWebIdentity supports specifying access control policies in two ways:
12
131. Role Policy (Recommended): When specified, all users authenticating via this API are authorized to (only) use the specified role policy. The policy to associate with such users is specified when configuring OpenID provider in the server, via the `role_policy` configuration parameter or the `MINIO_IDENTITY_OPENID_ROLE_POLICY` environment variable. The value is a comma-separated list of IAM access policy names already defined in the server. In this situation, the server prints a role ARN at startup that must be specified as a `RoleARN` API request parameter in the STS AssumeRoleWithWebIdentity API call.
14
152. `id_token` claims: When the role policy is not configured, MinIO looks for a specific claim in the `id_token` (JWT) returned by the OpenID provider. The default claim is `policy` and can be overridden by the `claim_name` configuration parameter or the `MINIO_IDENTITY_OPENID_CLAIM_NAME` environment variable. The claim value can be a string (comma-separated list) or an array of IAM access policy names defined in the server. A `RoleARN` API request parameter *must not* be specified in the STS AssumeRoleWithWebIdentity API call.
16
9## API Request Parameters17## API Request Parameters
10### WebIdentityToken18### WebIdentityToken
11The OAuth 2.0 id_token that is provided by the web identity provider. Application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.19The OAuth 2.0 id_token that is provided by the web identity provider. Application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.
@@ -24,6 +32,14 @@ There are situations when identity provider does not provide user claims in `id_ · 12 unmodified lines
24| *Type* | *String* |32| *Type* | *String* |
25| *Required* | *No* |33| *Required* | *No* |
26 34
35### RoleARN
36The role ARN to use. This must be specified if and only if the web identity provider is configured with a role policy.
37
38| Params | Value |
39| :-- | :-- |
40| *Type* | *String* |
41| *Required* | *No* |
42
27### Version43### Version
28Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.44Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
29 45
go.mod+2−2
@@ -49,7 +49,8 @@ require ( · 48 unmodified lines
49 github.com/minio/highwayhash v1.0.249 github.com/minio/highwayhash v1.0.2
50 github.com/minio/kes v0.14.050 github.com/minio/kes v0.14.0
51 github.com/minio/madmin-go v1.1.1651 github.com/minio/madmin-go v1.1.16
52 github.com/minio/minio-go/v7 v7.0.16-0.20211108161804-a7a36ee131df52 github.com/minio/mc v0.0.0-20211118223026-df75eed32e9e // indirect
53 github.com/minio/minio-go/v7 v7.0.16-0.20211117164632-e517704ccb36
53 github.com/minio/parquet-go v1.1.054 github.com/minio/parquet-go v1.1.0
54 github.com/minio/pkg v1.1.955 github.com/minio/pkg v1.1.9
55 github.com/minio/selfupdate v0.3.156 github.com/minio/selfupdate v0.3.1
@@ -166,7 +167,6 @@ require ( · 110 unmodified lines
166 github.com/minio/colorjson v1.0.1 // indirect167 github.com/minio/colorjson v1.0.1 // indirect
167 github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf // indirect168 github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf // indirect
168 github.com/minio/filepath v1.0.0 // indirect169 github.com/minio/filepath v1.0.0 // indirect
169 github.com/minio/mc v0.0.0-20211115052100-7fd441ec6c5b // indirect
170 github.com/minio/md5-simd v1.1.2 // indirect170 github.com/minio/md5-simd v1.1.2 // indirect
171 github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7 // indirect171 github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7 // indirect
172 github.com/minio/operator/logsearchapi v0.0.0-20211011212245-31460bbbc4b7 // indirect172 github.com/minio/operator/logsearchapi v0.0.0-20211011212245-31460bbbc4b7 // indirect
go.sum+5−4
@@ -1086,12 +1086,12 @@ github.com/minio/kes v0.14.0/go.mod h1:OUensXz2BpgMfiogslKxv7Anyx/wj+6bFC6qA7BQc · 1085 unmodified lines
1086github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=1086github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
1087github.com/minio/madmin-go v1.1.11-0.20211102182201-e51fd3d6b104/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=1087github.com/minio/madmin-go v1.1.11-0.20211102182201-e51fd3d6b104/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
1088github.com/minio/madmin-go v1.1.12/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=1088github.com/minio/madmin-go v1.1.12/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
1089github.com/minio/madmin-go v1.1.13/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=1089github.com/minio/madmin-go v1.1.15/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
1090github.com/minio/madmin-go v1.1.16 h1:c96vQBF3W9sPXiY04rjNa06FfOmWDjeFuChuqtOzLmE=1090github.com/minio/madmin-go v1.1.16 h1:c96vQBF3W9sPXiY04rjNa06FfOmWDjeFuChuqtOzLmE=
1091github.com/minio/madmin-go v1.1.16/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=1091github.com/minio/madmin-go v1.1.16/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
1092github.com/minio/mc v0.0.0-20211110003602-1461b652d920/go.mod h1:V8NmUfU0W3G/mrifeO6nm4CWFTiXY2nx7FJyMge/aHk=1092github.com/minio/mc v0.0.0-20211110003602-1461b652d920/go.mod h1:V8NmUfU0W3G/mrifeO6nm4CWFTiXY2nx7FJyMge/aHk=
1093github.com/minio/mc v0.0.0-20211115052100-7fd441ec6c5b h1:crCI2lSbzWzMuk/U6fMqSl5eF2V2VKDFNX+ILSD1sxU=1093github.com/minio/mc v0.0.0-20211118223026-df75eed32e9e h1:6EoG2tWc6y89CTX6h2jvbAaSSjd78zBKaL4U1wEJ3yA=
1094github.com/minio/mc v0.0.0-20211115052100-7fd441ec6c5b/go.mod h1:2fFAzMBmEYcN4mjcmQdlLuSabP+bvQC5UpqfLzRgrQQ=1094github.com/minio/mc v0.0.0-20211118223026-df75eed32e9e/go.mod h1:sXbvyABnNzmpnMEFT2aOexxnI8O0x802lZxbXo8aDgA=
1095github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=1095github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
1096github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=1096github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
1097github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=1097github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
@@ -1099,8 +1099,9 @@ github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8 · 1 unmodified line
1099github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=1099github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
1100github.com/minio/minio-go/v7 v7.0.15-0.20211004160302-3b57c1e369ca/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=1100github.com/minio/minio-go/v7 v7.0.15-0.20211004160302-3b57c1e369ca/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=
1101github.com/minio/minio-go/v7 v7.0.15/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=1101github.com/minio/minio-go/v7 v7.0.15/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=
1102github.com/minio/minio-go/v7 v7.0.16-0.20211108161804-a7a36ee131df h1:7BfpVODGh5reCjIx2lUqE7CxRMjo58XJw7ZjKKNW/vc=
1103github.com/minio/minio-go/v7 v7.0.16-0.20211108161804-a7a36ee131df/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=1102github.com/minio/minio-go/v7 v7.0.16-0.20211108161804-a7a36ee131df/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=
1103github.com/minio/minio-go/v7 v7.0.16-0.20211117164632-e517704ccb36 h1:amnEPz1PuZxUUSKQvQn7E4Pd+B7tIqmqiFeuc9yy2r4=
1104github.com/minio/minio-go/v7 v7.0.16-0.20211117164632-e517704ccb36/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=
1104github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7 h1:dkfuMNslMjGoJ4ArAMSoQhidYNdm3SgzLBP+f96O3/E=1105github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7 h1:dkfuMNslMjGoJ4ArAMSoQhidYNdm3SgzLBP+f96O3/E=
1105github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7/go.mod h1:lDpuz8nwsfhKlfiBaA3Z8AW019fWEAjO2gltfLbdorE=1106github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7/go.mod h1:lDpuz8nwsfhKlfiBaA3Z8AW019fWEAjO2gltfLbdorE=
1106github.com/minio/operator/logsearchapi v0.0.0-20211011212245-31460bbbc4b7 h1:vFtQqCt67ETp0JAkOKRWTKkgwFv14Vc1jJSxmQ8wJE0=1107github.com/minio/operator/logsearchapi v0.0.0-20211011212245-31460bbbc4b7 h1:vFtQqCt67ETp0JAkOKRWTKkgwFv14Vc1jJSxmQ8wJE0=
internal/arn/arn.go+147−0
@@ -0,0 +1,147 @@
1// Copyright (c) 2015-2021 MinIO, Inc.
2//
3// This file is part of MinIO Object Storage stack
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18package arn
19
20import (
21 "fmt"
22 "regexp"
23 "strings"
24)
25
26// ARN structure:
27//
28// arn:partition:service:region:account-id:resource-type/resource-id
29//
30// In this implementation, account-id is empty.
31//
32// Reference: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
33
34type arnPartition string
35
36const (
37 arnPartitionMinio arnPartition = "minio"
38)
39
40type arnService string
41
42const (
43 arnServiceIAM arnService = "iam"
44)
45
46type arnResourceType string
47
48const (
49 arnResourceTypeRole arnResourceType = "role"
50)
51
52// ARN - representation of resources based on AWS ARNs.
53type ARN struct {
54 Partition arnPartition
55 Service arnService
56 Region string
57 ResourceType arnResourceType
58 ResourceID string
59}
60
61var (
62 // Allows lower-case chars, numbers, '.', '-', '_' and '/'. Starts with
63 // a letter or digit. At least 1 character long.
64 validResourceIDRegex = regexp.MustCompile(`^[a-z0-9][a-z0-9_/\.-]*$`)
65)
66
67// NewIAMRoleARN - returns an ARN for a role in MinIO.
68func NewIAMRoleARN(resourceID, serverRegion string) (ARN, error) {
69 if !validResourceIDRegex.MatchString(resourceID) {
70 return ARN{}, fmt.Errorf("Invalid resource ID: %s", resourceID)
71 }
72 return ARN{
73 Partition: arnPartitionMinio,
74 Service: arnServiceIAM,
75 Region: serverRegion,
76 ResourceType: arnResourceTypeRole,
77 ResourceID: resourceID,
78 }, nil
79}
80
81// String - returns string representation of the ARN.
82func (arn ARN) String() string {
83 return strings.Join(
84 []string{
85 "arn",
86 string(arn.Partition),
87 string(arn.Service),
88 arn.Region,
89 "", // account-id is always empty in this implementation
90 string(arn.ResourceType) + "/" + arn.ResourceID,
91 },
92 ":",
93 )
94}
95
96// Parse - parses an ARN string into a type.
97func Parse(arnStr string) (arn ARN, err error) {
98 ps := strings.Split(arnStr, ":")
99 if len(ps) != 6 ||
100 ps[0] != "arn" {
101 err = fmt.Errorf("Invalid ARN string format")
102 return
103 }
104
105 if ps[1] != string(arnPartitionMinio) {
106 err = fmt.Errorf("Invalid ARN - bad partition field")
107 return
108 }
109
110 if ps[2] != string(arnServiceIAM) {
111 err = fmt.Errorf("Invalid ARN - bad service field")
112 return
113 }
114
115 // ps[3] is region and is not validated here. If the region is invalid,
116 // the ARN would not match any configured ARNs in the server.
117
118 if ps[4] != "" {
119 err = fmt.Errorf("Invalid ARN - unsupported account-id field")
120 return
121 }
122
123 res := strings.SplitN(ps[5], "/", 2)
124 if len(res) != 2 {
125 err = fmt.Errorf("Invalid ARN - resource does not contain a \"/\"")
126 return
127 }
128
129 if res[0] != string(arnResourceTypeRole) {
130 err = fmt.Errorf("Invalid ARN: resource type is invalid.")
131 return
132 }
133
134 if !validResourceIDRegex.MatchString(res[1]) {
135 err = fmt.Errorf("Invalid resource ID: %s", res[1])
136 return
137 }
138
139 arn = ARN{
140 Partition: arnPartitionMinio,
141 Service: arnServiceIAM,
142 Region: ps[3],
143 ResourceType: arnResourceTypeRole,
144 ResourceID: res[1],
145 }
146 return
147}
internal/config/identity/openid/help.go+18−0
@@ -50,6 +50,12 @@ var ( · 49 unmodified lines
50 Optional: true,50 Optional: true,
51 Type: "on|off",51 Type: "on|off",
52 },52 },
53 config.HelpKV{
54 Key: RolePolicy,
55 Description: `Set the IAM access policies applicable to this client application and IDP e.g. "app-bucket-write,app-bucket-list"`,
56 Optional: true,
57 Type: "string",
58 },
53 config.HelpKV{59 config.HelpKV{
54 Key: Scopes,60 Key: Scopes,
55 Description: `Comma separated list of OpenID scopes for server, defaults to advertised scopes from discovery document e.g. "email,admin"`,61 Description: `Comma separated list of OpenID scopes for server, defaults to advertised scopes from discovery document e.g. "email,admin"`,
@@ -98,5 +104,17 @@ var ( · 42 unmodified lines
98 Optional: true,104 Optional: true,
99 Type: "sentence",105 Type: "sentence",
100 },106 },
107 config.HelpKV{
108 Key: ClaimPrefix,
109 Description: `[DEPRECATED use 'claim_name'] JWT claim namespace prefix e.g. "customer1/"`,
110 Optional: true,
111 Type: "string",
112 },
113 config.HelpKV{
114 Key: RedirectURI,
115 Description: `[DEPRECATED use env 'MINIO_BROWSER_REDIRECT_URL'] Configure custom redirect_uri for OpenID login flow callback`,
116 Optional: true,
117 Type: "string",
118 },
101 }119 }
102)120)
internal/config/identity/openid/jwt.go+52−3
@@ -23,6 +23,7 @@ import ( · 22 unmodified lines
23 "errors"23 "errors"
24 "fmt"24 "fmt"
25 "io"25 "io"
26 "net"
26 "net/http"27 "net/http"
27 "strconv"28 "strconv"
28 "strings"29 "strings"
@@ -30,6 +31,7 @@ import ( · 1 unmodified line
30 "time"31 "time"
31 32
32 jwtgo "github.com/golang-jwt/jwt/v4"33 jwtgo "github.com/golang-jwt/jwt/v4"
34 "github.com/minio/minio/internal/arn"
33 "github.com/minio/minio/internal/auth"35 "github.com/minio/minio/internal/auth"
34 "github.com/minio/minio/internal/config"36 "github.com/minio/minio/internal/config"
35 "github.com/minio/minio/internal/config/identity/openid/provider"37 "github.com/minio/minio/internal/config/identity/openid/provider"
@@ -56,7 +58,9 @@ type Config struct { · 20 unmodified lines
56 DiscoveryDoc DiscoveryDoc58 DiscoveryDoc DiscoveryDoc
57 ClientID string59 ClientID string
58 ClientSecret string60 ClientSecret string
61 RolePolicy string
59 62
63 roleArn arn.ARN
60 provider provider.Provider64 provider provider.Provider
61 publicKeys map[string]crypto.PublicKey65 publicKeys map[string]crypto.PublicKey
62 transport *http.Transport66 transport *http.Transport
@@ -167,6 +171,12 @@ func (r Config) ProviderEnabled() bool { · 104 unmodified lines
167 return r.Enabled && r.provider != nil171 return r.Enabled && r.provider != nil
168}172}
169 173
174// GetRoleInfo - returns role ARN and policy if present, otherwise returns false
175// boolean.
176func (r Config) GetRoleInfo() (arn.ARN, string, bool) {
177 return r.roleArn, r.RolePolicy, r.RolePolicy != ""
178}
179
170// InitializeKeycloakProvider - initializes keycloak provider180// InitializeKeycloakProvider - initializes keycloak provider
171func (r *Config) InitializeKeycloakProvider(adminURL, realm string) error {181func (r *Config) InitializeKeycloakProvider(adminURL, realm string) error {
172 var err error182 var err error
@@ -366,6 +376,7 @@ const ( · 193 unmodified lines
366 ClaimPrefix = "claim_prefix"376 ClaimPrefix = "claim_prefix"
367 ClientID = "client_id"377 ClientID = "client_id"
368 ClientSecret = "client_secret"378 ClientSecret = "client_secret"
379 RolePolicy = "role_policy"
369 380
370 Vendor = "vendor"381 Vendor = "vendor"
371 Scopes = "scopes"382 Scopes = "scopes"
@@ -383,6 +394,7 @@ const ( · 11 unmodified lines
383 EnvIdentityOpenIDClaimName = "MINIO_IDENTITY_OPENID_CLAIM_NAME"394 EnvIdentityOpenIDClaimName = "MINIO_IDENTITY_OPENID_CLAIM_NAME"
384 EnvIdentityOpenIDClaimUserInfo = "MINIO_IDENTITY_OPENID_CLAIM_USERINFO"395 EnvIdentityOpenIDClaimUserInfo = "MINIO_IDENTITY_OPENID_CLAIM_USERINFO"
385 EnvIdentityOpenIDClaimPrefix = "MINIO_IDENTITY_OPENID_CLAIM_PREFIX"396 EnvIdentityOpenIDClaimPrefix = "MINIO_IDENTITY_OPENID_CLAIM_PREFIX"
397 EnvIdentityOpenIDRolePolicy = "MINIO_IDENTITY_OPENID_ROLE_POLICY"
386 EnvIdentityOpenIDRedirectURI = "MINIO_IDENTITY_OPENID_REDIRECT_URI"398 EnvIdentityOpenIDRedirectURI = "MINIO_IDENTITY_OPENID_REDIRECT_URI"
387 EnvIdentityOpenIDRedirectURIDynamic = "MINIO_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC"399 EnvIdentityOpenIDRedirectURIDynamic = "MINIO_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC"
388 EnvIdentityOpenIDScopes = "MINIO_IDENTITY_OPENID_SCOPES"400 EnvIdentityOpenIDScopes = "MINIO_IDENTITY_OPENID_SCOPES"
@@ -458,6 +470,10 @@ var ( · 69 unmodified lines
458 Key: ClaimUserinfo,470 Key: ClaimUserinfo,
459 Value: "",471 Value: "",
460 },472 },
473 config.KV{
474 Key: RolePolicy,
475 Value: "",
476 },
461 config.KV{477 config.KV{
462 Key: ClaimPrefix,478 Key: ClaimPrefix,
463 Value: "",479 Value: "",
@@ -483,7 +499,7 @@ func Enabled(kvs config.KVS) bool { · 19 unmodified lines
483}499}
484 500
485// LookupConfig lookup jwks from config, override with any ENVs.501// LookupConfig lookup jwks from config, override with any ENVs.
486func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io.ReadCloser)) (c Config, err error) {502func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io.ReadCloser), serverRegion string) (c Config, err error) {
487 // remove this since we have removed this already.503 // remove this since we have removed this already.
488 kvs.Delete(JwksURL)504 kvs.Delete(JwksURL)
489 505
@@ -501,16 +517,19 @@ func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io · 11 unmodified lines
501 publicKeys: make(map[string]crypto.PublicKey),517 publicKeys: make(map[string]crypto.PublicKey),
502 ClientID: env.Get(EnvIdentityOpenIDClientID, kvs.Get(ClientID)),518 ClientID: env.Get(EnvIdentityOpenIDClientID, kvs.Get(ClientID)),
503 ClientSecret: env.Get(EnvIdentityOpenIDClientSecret, kvs.Get(ClientSecret)),519 ClientSecret: env.Get(EnvIdentityOpenIDClientSecret, kvs.Get(ClientSecret)),
520 RolePolicy: env.Get(EnvIdentityOpenIDRolePolicy, kvs.Get(RolePolicy)),
504 transport: transport,521 transport: transport,
505 closeRespFn: closeRespFn,522 closeRespFn: closeRespFn,
506 }523 }
507 524
508 configURL := env.Get(EnvIdentityOpenIDURL, kvs.Get(ConfigURL))525 configURL := env.Get(EnvIdentityOpenIDURL, kvs.Get(ConfigURL))
526 var configURLDomain string
509 if configURL != "" {527 if configURL != "" {
510 c.URL, err = xnet.ParseHTTPURL(configURL)528 c.URL, err = xnet.ParseHTTPURL(configURL)
511 if err != nil {529 if err != nil {
512 return c, err530 return c, err
513 }531 }
532 configURLDomain, _, _ = net.SplitHostPort(c.URL.Host)
514 c.DiscoveryDoc, err = parseDiscoveryDoc(c.URL, transport, closeRespFn)533 c.DiscoveryDoc, err = parseDiscoveryDoc(c.URL, transport, closeRespFn)
515 if err != nil {534 if err != nil {
516 return c, err535 return c, err
@@ -534,8 +553,38 @@ func LookupConfig(kvs config.KVS, transport *http.Transport, closeRespFn func(io · 17 unmodified lines
534 c.DiscoveryDoc.ScopesSupported = scopes553 c.DiscoveryDoc.ScopesSupported = scopes
535 }554 }
536 555
537 if c.ClaimName == "" {556 // Check if claim name is the non-default value and role policy is set.
538 c.ClaimName = iampolicy.PolicyName557 if c.ClaimName != iampolicy.PolicyName && c.RolePolicy != "" {
558 // In the unlikely event that the user specifies
559 // `iampolicy.PolicyName` as the claim name explicitly and sets
560 // a role policy, this check is thwarted, but we will be using
561 // the role policy anyway.
562 return c, config.Errorf("Role Policy and Claim Name cannot both be set.")
563 }
564
565 if c.RolePolicy != "" {
566 // RolePolicy is valided by IAM System during its
567 // initialization.
568
569 // Generate role ARN as combination of provider domain and
570 // prefix of client ID.
571 domain := configURLDomain
572 if domain == "" {
573 // Attempt to parse the JWKs URI.
574 domain, _, _ = net.SplitHostPort(c.JWKS.URL.Host)
575 if domain == "" {
576 return c, config.Errorf("unable to generate a domain from the OpenID config.")
577 }
578 }
579 clientIDFragment := c.ClientID[:8]
580 if clientIDFragment == "" {
581 return c, config.Errorf("unable to get a non-empty clientID fragment from the OpenID config.")
582 }
583 resourceID := domain + "_" + clientIDFragment
584 c.roleArn, err = arn.NewIAMRoleARN(resourceID, serverRegion)
585 if err != nil {
586 return c, config.Errorf("unable to generate ARN from the OpenID config: %v", err)
587 }
539 }588 }
540 589
541 jwksURL := c.DiscoveryDoc.JwksURI590 jwksURL := c.DiscoveryDoc.JwksURI

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t