mirror of
				https://github.com/minio/minio.git
				synced 2025-10-30 00:05:02 -04:00 
			
		
		
		
	fix: IAM import/export: remove sts group handling (#19422)
There are no separate STS group mappings to be handled. Also add tests for basic import/export sanity.
This commit is contained in:
		
							parent
							
								
									91f91d8f47
								
							
						
					
					
						commit
						8ff2a7a2b9
					
				| @ -1756,15 +1756,14 @@ func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *ht | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	allPoliciesFile            = "policies.json" | ||||
| 	allUsersFile               = "users.json" | ||||
| 	allGroupsFile              = "groups.json" | ||||
| 	allSvcAcctsFile            = "svcaccts.json" | ||||
| 	userPolicyMappingsFile     = "user_mappings.json" | ||||
| 	groupPolicyMappingsFile    = "group_mappings.json" | ||||
| 	stsUserPolicyMappingsFile  = "stsuser_mappings.json" | ||||
| 	stsGroupPolicyMappingsFile = "stsgroup_mappings.json" | ||||
| 	iamAssetsDir               = "iam-assets" | ||||
| 	allPoliciesFile           = "policies.json" | ||||
| 	allUsersFile              = "users.json" | ||||
| 	allGroupsFile             = "groups.json" | ||||
| 	allSvcAcctsFile           = "svcaccts.json" | ||||
| 	userPolicyMappingsFile    = "user_mappings.json" | ||||
| 	groupPolicyMappingsFile   = "group_mappings.json" | ||||
| 	stsUserPolicyMappingsFile = "stsuser_mappings.json" | ||||
| 	iamAssetsDir              = "iam-assets" | ||||
| ) | ||||
| 
 | ||||
| // ExportIAMHandler - exports all iam info as a zipped file | ||||
| @ -1813,7 +1812,6 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) { | ||||
| 		userPolicyMappingsFile, | ||||
| 		groupPolicyMappingsFile, | ||||
| 		stsUserPolicyMappingsFile, | ||||
| 		stsGroupPolicyMappingsFile, | ||||
| 	} | ||||
| 	for _, f := range iamFiles { | ||||
| 		iamFile := pathJoin(iamAssetsDir, f) | ||||
| @ -1985,22 +1983,6 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) { | ||||
| 				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) | ||||
| 				return | ||||
| 			} | ||||
| 		case stsGroupPolicyMappingsFile: | ||||
| 			groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]() | ||||
| 			err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap) | ||||
| 			if err != nil { | ||||
| 				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) | ||||
| 				return | ||||
| 			} | ||||
| 			grpPolData, err := json.Marshal(mappedPoliciesToMap(groupPolicyMap)) | ||||
| 			if err != nil { | ||||
| 				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) | ||||
| 				return | ||||
| 			} | ||||
| 			if err = rawDataFn(bytes.NewReader(grpPolData), iamFile, len(grpPolData)); err != nil { | ||||
| 				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -2391,35 +2373,6 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// import sts group policy mappings | ||||
| 	{ | ||||
| 		f, err := zr.Open(pathJoin(iamAssetsDir, stsGroupPolicyMappingsFile)) | ||||
| 		switch { | ||||
| 		case errors.Is(err, os.ErrNotExist): | ||||
| 		case err != nil: | ||||
| 			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL) | ||||
| 			return | ||||
| 		default: | ||||
| 			defer f.Close() | ||||
| 			var grpPolicyMap map[string]MappedPolicy | ||||
| 			data, err := io.ReadAll(f) | ||||
| 			if err != nil { | ||||
| 				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL) | ||||
| 				return | ||||
| 			} | ||||
| 			if err = json.Unmarshal(data, &grpPolicyMap); err != nil { | ||||
| 				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, stsGroupPolicyMappingsFile, ""), r.URL) | ||||
| 				return | ||||
| 			} | ||||
| 			for g, pm := range grpPolicyMap { | ||||
| 				if _, err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, unknownIAMUserType, true); err != nil { | ||||
| 					writeErrorResponseJSON(ctx, w, importError(ctx, err, stsGroupPolicyMappingsFile, g), r.URL) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) { | ||||
|  | ||||
| @ -18,9 +18,12 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @ -752,6 +755,172 @@ func TestIAMWithLDAPNonNormalizedBaseDNConfigServerSuite(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestIAMExportImportWithLDAP(t *testing.T) { | ||||
| 	for i, testCase := range iamTestSuites { | ||||
| 		t.Run( | ||||
| 			fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.ServerTypeDescription), | ||||
| 			func(t *testing.T) { | ||||
| 				c := &check{t, testCase.serverType} | ||||
| 				suite := testCase | ||||
| 
 | ||||
| 				ldapServer := os.Getenv(EnvTestLDAPServer) | ||||
| 				if ldapServer == "" { | ||||
| 					c.Skipf("Skipping LDAP test as no LDAP server is provided via %s", EnvTestLDAPServer) | ||||
| 				} | ||||
| 
 | ||||
| 				iamTestContentCases := []iamTestContent{ | ||||
| 					{ | ||||
| 						policies: map[string][]byte{ | ||||
| 							"mypolicy": []byte(`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject","s3:ListBucket","s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/*"]}]}`), | ||||
| 						}, | ||||
| 						ldapUserPolicyMappings: map[string][]string{ | ||||
| 							"uid=dillon,ou=people,ou=swengg,dc=min,dc=io": {"mypolicy"}, | ||||
| 							"uid=liza,ou=people,ou=swengg,dc=min,dc=io":   {"consoleAdmin"}, | ||||
| 						}, | ||||
| 						ldapGroupPolicyMappings: map[string][]string{ | ||||
| 							"cn=projectb,ou=groups,ou=swengg,dc=min,dc=io": {"mypolicy"}, | ||||
| 							"cn=projecta,ou=groups,ou=swengg,dc=min,dc=io": {"consoleAdmin"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				} | ||||
| 
 | ||||
| 				for caseNum, content := range iamTestContentCases { | ||||
| 					suite.SetUpSuite(c) | ||||
| 					suite.SetUpLDAP(c, ldapServer) | ||||
| 					exportedContent := suite.TestIAMExport(c, caseNum, content) | ||||
| 					suite.TearDownSuite(c) | ||||
| 					suite.SetUpSuite(c) | ||||
| 					suite.SetUpLDAP(c, ldapServer) | ||||
| 					suite.TestIAMImport(c, exportedContent, caseNum, content) | ||||
| 					suite.TearDownSuite(c) | ||||
| 				} | ||||
| 			}, | ||||
| 		) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type iamTestContent struct { | ||||
| 	policies                map[string][]byte | ||||
| 	ldapUserPolicyMappings  map[string][]string | ||||
| 	ldapGroupPolicyMappings map[string][]string | ||||
| } | ||||
| 
 | ||||
| func (s *TestSuiteIAM) TestIAMExport(c *check, caseNum int, content iamTestContent) []byte { | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) | ||||
| 	defer cancel() | ||||
| 
 | ||||
| 	for policy, policyBytes := range content.policies { | ||||
| 		err := s.adm.AddCannedPolicy(ctx, policy, policyBytes) | ||||
| 		if err != nil { | ||||
| 			c.Fatalf("export %d: policy add error: %v", caseNum, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for userDN, policies := range content.ldapUserPolicyMappings { | ||||
| 		_, err := s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{ | ||||
| 			Policies: policies, | ||||
| 			User:     userDN, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			c.Fatalf("export %d: Unable to attach policy: %v", caseNum, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for groupDN, policies := range content.ldapGroupPolicyMappings { | ||||
| 		_, err := s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{ | ||||
| 			Policies: policies, | ||||
| 			Group:    groupDN, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			c.Fatalf("export %d: Unable to attach group policy: %v", caseNum, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	contentReader, err := s.adm.ExportIAM(ctx) | ||||
| 	if err != nil { | ||||
| 		c.Fatalf("export %d: Unable to export IAM: %v", caseNum, err) | ||||
| 	} | ||||
| 	defer contentReader.Close() | ||||
| 
 | ||||
| 	expContent, err := io.ReadAll(contentReader) | ||||
| 	if err != nil { | ||||
| 		c.Fatalf("export %d: Unable to read exported content: %v", caseNum, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return expContent | ||||
| } | ||||
| 
 | ||||
| type dummyCloser struct { | ||||
| 	io.Reader | ||||
| } | ||||
| 
 | ||||
| func (d dummyCloser) Close() error { return nil } | ||||
| 
 | ||||
| func (s *TestSuiteIAM) TestIAMImport(c *check, exportedContent []byte, caseNum int, content iamTestContent) { | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) | ||||
| 	defer cancel() | ||||
| 
 | ||||
| 	dummyCloser := dummyCloser{bytes.NewReader(exportedContent)} | ||||
| 	err := s.adm.ImportIAM(ctx, dummyCloser) | ||||
| 	if err != nil { | ||||
| 		c.Fatalf("import %d: Unable to import IAM: %v", caseNum, err) | ||||
| 	} | ||||
| 
 | ||||
| 	gotContent := iamTestContent{ | ||||
| 		policies:                make(map[string][]byte), | ||||
| 		ldapUserPolicyMappings:  make(map[string][]string), | ||||
| 		ldapGroupPolicyMappings: make(map[string][]string), | ||||
| 	} | ||||
| 	policyContentMap, err := s.adm.ListCannedPolicies(ctx) | ||||
| 	if err != nil { | ||||
| 		c.Fatalf("import %d: Unable to list policies: %v", caseNum, err) | ||||
| 	} | ||||
| 	defaultCannedPolicies := set.CreateStringSet("consoleAdmin", "readwrite", "readonly", | ||||
| 		"diagnostics", "writeonly") | ||||
| 	for policy, policyBytes := range policyContentMap { | ||||
| 		if defaultCannedPolicies.Contains(policy) { | ||||
| 			continue | ||||
| 		} | ||||
| 		gotContent.policies[policy] = policyBytes | ||||
| 	} | ||||
| 
 | ||||
| 	policyQueryRes, err := s.adm.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{}) | ||||
| 	if err != nil { | ||||
| 		c.Fatalf("import %d: Unable to get policy entities: %v", caseNum, err) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, entity := range policyQueryRes.PolicyMappings { | ||||
| 		m := gotContent.ldapUserPolicyMappings | ||||
| 		for _, user := range entity.Users { | ||||
| 			m[user] = append(m[user], entity.Policy) | ||||
| 		} | ||||
| 		m = gotContent.ldapGroupPolicyMappings | ||||
| 		for _, group := range entity.Groups { | ||||
| 			m[group] = append(m[group], entity.Policy) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	{ | ||||
| 		// We don't compare the values of the canned policies because server is | ||||
| 		// re-encoding them. (FIXME?) | ||||
| 		for k := range content.policies { | ||||
| 			content.policies[k] = nil | ||||
| 			gotContent.policies[k] = nil | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(content.policies, gotContent.policies) { | ||||
| 			c.Fatalf("import %d: policies mismatch: expected: %v, got: %v", caseNum, content.policies, gotContent.policies) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(content.ldapUserPolicyMappings, gotContent.ldapUserPolicyMappings) { | ||||
| 		c.Fatalf("import %d: user policy mappings mismatch: expected: %v, got: %v", caseNum, content.ldapUserPolicyMappings, gotContent.ldapUserPolicyMappings) | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(content.ldapGroupPolicyMappings, gotContent.ldapGroupPolicyMappings) { | ||||
| 		c.Fatalf("import %d: group policy mappings mismatch: expected: %v, got: %v", caseNum, content.ldapGroupPolicyMappings, gotContent.ldapGroupPolicyMappings) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *TestSuiteIAM) TestLDAPSTS(c *check) { | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) | ||||
| 	defer cancel() | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user