mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
ldap: Normalize DNs when importing (#19528)
This is a change to IAM export/import functionality. For LDAP enabled setups, it performs additional validations: - for policy mappings on LDAP users and groups, it ensures that the corresponding user or group DN exists and if so uses a normalized form of these DNs for storage - for access keys (service accounts), it updates (i.e. validates existence and normalizes) the internally stored parent user DN and group DNs. This allows for a migration path for setups in which LDAP mappings have been stored in previous versions of the server, where the name of the mapping file stored on drives is not in a normalized form. An administrator needs to execute: `mc admin iam export ALIAS` followed by `mc admin iam import ALIAS /path/to/export/file` The validations are more strict and returns errors when multiple mappings are found for the same user/group DN. This is to ensure the mappings stored by the server are unambiguous and to reduce the potential for confusion. Bonus **bug fix**: IAM export of access keys (service accounts) did not export key name, description and expiration. This is fixed in this change too.
This commit is contained in:
committed by
GitHub
parent
dfc112c06b
commit
ae46ce9937
@@ -28,6 +28,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||
@@ -799,6 +800,122 @@ func TestIAMExportImportWithLDAP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIAMImportAssetWithLDAP(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
exportContentStrings := map[string]string{
|
||||
allPoliciesFile: `{"consoleAdmin":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["admin:*"]},{"Effect":"Allow","Action":["kms:*"]},{"Effect":"Allow","Action":["s3:*"],"Resource":["arn:aws:s3:::*"]}]},"diagnostics":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["admin:Prometheus","admin:Profiling","admin:ServerTrace","admin:ConsoleLog","admin:ServerInfo","admin:TopLocksInfo","admin:OBDInfo","admin:BandwidthMonitor"],"Resource":["arn:aws:s3:::*"]}]},"readonly":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetBucketLocation","s3:GetObject"],"Resource":["arn:aws:s3:::*"]}]},"readwrite":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:*"],"Resource":["arn:aws:s3:::*"]}]},"writeonly":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject"],"Resource":["arn:aws:s3:::*"]}]}}`,
|
||||
allUsersFile: `{}`,
|
||||
allGroupsFile: `{}`,
|
||||
allSvcAcctsFile: `{
|
||||
"u4ccRswj62HV3Ifwima7": {
|
||||
"parent": "uid=svc.algorithm,OU=swengg,DC=min,DC=io",
|
||||
"accessKey": "u4ccRswj62HV3Ifwima7",
|
||||
"secretKey": "ZoEoZdLlzVbOlT9rbhD7ZN7TLyiYXSAlB79uGEge",
|
||||
"groups": ["cn=project.c,ou=groups,OU=swengg,DC=min,DC=io"],
|
||||
"claims": {
|
||||
"accessKey": "u4ccRswj62HV3Ifwima7",
|
||||
"ldapUser": "uid=svc.algorithm,ou=swengg,dc=min,dc=io",
|
||||
"ldapUsername": "svc.algorithm",
|
||||
"parent": "uid=svc.algorithm,ou=swengg,dc=min,dc=io",
|
||||
"sa-policy": "inherited-policy"
|
||||
},
|
||||
"sessionPolicy": null,
|
||||
"status": "on",
|
||||
"name": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
`,
|
||||
userPolicyMappingsFile: `{}`,
|
||||
groupPolicyMappingsFile: `{
|
||||
"cn=project.c,ou=groups,ou=swengg,DC=min,dc=io": {
|
||||
"version": 0,
|
||||
"policy": "consoleAdmin",
|
||||
"updatedAt": "2024-04-17T23:54:28.442998301Z"
|
||||
}
|
||||
}
|
||||
`,
|
||||
stsUserPolicyMappingsFile: `{
|
||||
"uid=dillon,ou=people,OU=swengg,DC=min,DC=io": {
|
||||
"version": 0,
|
||||
"policy": "consoleAdmin",
|
||||
"updatedAt": "2024-04-17T23:54:10.606645642Z"
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
exportContent := map[string][]byte{}
|
||||
for k, v := range exportContentStrings {
|
||||
exportContent[k] = []byte(v)
|
||||
}
|
||||
|
||||
var importContent []byte
|
||||
{
|
||||
var b bytes.Buffer
|
||||
zipWriter := zip.NewWriter(&b)
|
||||
rawDataFn := func(r io.Reader, filename string, sz int) error {
|
||||
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
||||
name: filename,
|
||||
size: int64(sz),
|
||||
mode: 0o600,
|
||||
modTime: time.Now(),
|
||||
isDir: false,
|
||||
sys: nil,
|
||||
})
|
||||
if zerr != nil {
|
||||
adminLogIf(ctx, zerr)
|
||||
return nil
|
||||
}
|
||||
header.Method = zip.Deflate
|
||||
zwriter, zerr := zipWriter.CreateHeader(header)
|
||||
if zerr != nil {
|
||||
adminLogIf(ctx, zerr)
|
||||
return nil
|
||||
}
|
||||
if _, err := io.Copy(zwriter, r); err != nil {
|
||||
adminLogIf(ctx, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, f := range iamExportFiles {
|
||||
iamFile := pathJoin(iamAssetsDir, f)
|
||||
|
||||
fileContent, ok := exportContent[f]
|
||||
if !ok {
|
||||
t.Fatalf("missing content for %s", f)
|
||||
}
|
||||
|
||||
if err := rawDataFn(bytes.NewReader(fileContent), iamFile, len(fileContent)); err != nil {
|
||||
t.Fatalf("failed to write %s: %v", iamFile, err)
|
||||
}
|
||||
}
|
||||
zipWriter.Close()
|
||||
importContent = b.Bytes()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpLDAP(c, ldapServer)
|
||||
suite.TestIAMImportAssetContent(c, importContent)
|
||||
suite.TearDownSuite(c)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type iamTestContent struct {
|
||||
policies map[string][]byte
|
||||
ldapUserPolicyMappings map[string][]string
|
||||
@@ -856,6 +973,65 @@ type dummyCloser struct {
|
||||
|
||||
func (d dummyCloser) Close() error { return nil }
|
||||
|
||||
func (s *TestSuiteIAM) TestIAMImportAssetContent(c *check, content []byte) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
dummyCloser := dummyCloser{bytes.NewReader(content)}
|
||||
err := s.adm.ImportIAM(ctx, dummyCloser)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to import IAM: %v", err)
|
||||
}
|
||||
|
||||
entRes, err := s.adm.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to get policy entities: %v", err)
|
||||
}
|
||||
|
||||
expected := madmin.PolicyEntitiesResult{
|
||||
PolicyMappings: []madmin.PolicyEntities{
|
||||
{
|
||||
Policy: "consoleAdmin",
|
||||
Users: []string{"uid=dillon,ou=people,ou=swengg,dc=min,dc=io"},
|
||||
Groups: []string{"cn=project.c,ou=groups,ou=swengg,dc=min,dc=io"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
entRes.Timestamp = time.Time{}
|
||||
if !reflect.DeepEqual(expected, entRes) {
|
||||
c.Fatalf("policy entities mismatch: expected: %v, got: %v", expected, entRes)
|
||||
}
|
||||
|
||||
dn := "uid=svc.algorithm,ou=swengg,dc=min,dc=io"
|
||||
res, err := s.adm.ListAccessKeysLDAP(ctx, dn, "")
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to list access keys: %v", err)
|
||||
}
|
||||
|
||||
epochTime := time.Unix(0, 0).UTC()
|
||||
expectedAccKeys := madmin.ListAccessKeysLDAPResp{
|
||||
ServiceAccounts: []madmin.ServiceAccountInfo{
|
||||
{
|
||||
AccessKey: "u4ccRswj62HV3Ifwima7",
|
||||
Expiration: &epochTime,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedAccKeys, res) {
|
||||
c.Fatalf("access keys mismatch: expected: %v, got: %v", expectedAccKeys, res)
|
||||
}
|
||||
|
||||
accKeyInfo, err := s.adm.InfoServiceAccount(ctx, "u4ccRswj62HV3Ifwima7")
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to get service account info: %v", err)
|
||||
}
|
||||
if accKeyInfo.ParentUser != "uid=svc.algorithm,ou=swengg,dc=min,dc=io" {
|
||||
c.Fatalf("parent mismatch: expected: %s, got: %s", "uid=svc.algorithm,ou=swengg,dc=min,dc=io", accKeyInfo.ParentUser)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestIAMImport(c *check, exportedContent []byte, caseNum int, content iamTestContent) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
Reference in New Issue
Block a user