// Copyright (c) 2015-2021 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package cmd import ( "context" "fmt" "os" "strings" "testing" "time" "github.com/minio/madmin-go" minio "github.com/minio/minio-go/v7" cr "github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/set" ) func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) { suite.SetUpSuite(c) // The STS for root test needs to be the first one after setup. suite.TestSTSForRoot(c) suite.TestSTS(c) suite.TestSTSWithGroupPolicy(c) suite.TearDownSuite(c) } func TestIAMInternalIDPSTSServerSuite(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) { runAllIAMSTSTests(testCase, &check{t, testCase.serverType}) }, ) } } func (s *TestSuiteIAM) TestSTS(c *check) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() bucket := getRandomBucketName() err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) if err != nil { c.Fatalf("bucket creat error: %v", err) } // Create policy, user and associate policy policy := "mypolicy" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } accessKey, secretKey := mustGenerateCredentials(c) err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled) if err != nil { c.Fatalf("Unable to set user: %v", err) } err = s.adm.SetPolicy(ctx, policy, accessKey, false) if err != nil { c.Fatalf("Unable to set policy: %v", err) } // confirm that the user is able to access the bucket uClient := s.getUserClient(c, accessKey, secretKey, "") c.mustListObjects(ctx, uClient, bucket) assumeRole := cr.STSAssumeRole{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, Options: cr.STSAssumeRoleOptions{ AccessKey: accessKey, SecretKey: secretKey, Location: "", }, } value, err := assumeRole.Retrieve() if err != nil { c.Fatalf("err calling assumeRole: %v", err) } 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) // Validate that the client cannot remove any objects err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{}) if err.Error() != "Access Denied." { c.Fatalf("unexpected non-access-denied err: %v", err) } } func (s *TestSuiteIAM) TestSTSWithGroupPolicy(c *check) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() bucket := getRandomBucketName() err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) if err != nil { c.Fatalf("bucket creat error: %v", err) } // Create policy, user and associate policy policy := "mypolicy" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } accessKey, secretKey := mustGenerateCredentials(c) err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled) if err != nil { c.Fatalf("Unable to set user: %v", err) } // confirm that the user is unable to access the bucket - we have not // yet set any policy uClient := s.getUserClient(c, accessKey, secretKey, "") c.mustNotListObjects(ctx, uClient, bucket) err = s.adm.UpdateGroupMembers(ctx, madmin.GroupAddRemove{ Group: "test-group", Members: []string{accessKey}, }) if err != nil { c.Fatalf("unable to add user to group: %v", err) } err = s.adm.SetPolicy(ctx, policy, "test-group", true) if err != nil { c.Fatalf("Unable to set policy: %v", err) } // confirm that the user is able to access the bucket - permission comes // from group. c.mustListObjects(ctx, uClient, bucket) // Create STS user. assumeRole := cr.STSAssumeRole{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, Options: cr.STSAssumeRoleOptions{ AccessKey: accessKey, SecretKey: secretKey, Location: "", }, } value, err := assumeRole.Retrieve() if err != nil { c.Fatalf("err calling assumeRole: %v", err) } // Check that STS user client has access coming from parent user's // group. 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) // Validate that the client cannot remove any objects err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{}) if err.Error() != "Access Denied." { c.Fatalf("unexpected non-access-denied err: %v", err) } } // TestSTSForRoot - needs to be the first test after server setup due to the // buckets list check. func (s *TestSuiteIAM) TestSTSForRoot(c *check) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() bucket := getRandomBucketName() err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) if err != nil { c.Fatalf("bucket create error: %v", err) } assumeRole := cr.STSAssumeRole{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, Options: cr.STSAssumeRoleOptions{ AccessKey: globalActiveCred.AccessKey, SecretKey: globalActiveCred.SecretKey, Location: "", }, } value, err := assumeRole.Retrieve() if err != nil { c.Fatalf("err calling assumeRole: %v", err) } 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) // Validate that a bucket can be created bucket2 := getRandomBucketName() err = minioClient.MakeBucket(ctx, bucket2, minio.MakeBucketOptions{}) if err != nil { c.Fatalf("bucket creat error: %v", err) } // Validate that admin APIs can be called - 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) accInfo, err := userAdmClient.AccountInfo(ctx, madmin.AccountOpts{}) if err != nil { c.Fatalf("root user STS should be able to get account info: %v", err) } gotBuckets := set.NewStringSet() for _, b := range accInfo.Buckets { gotBuckets.Add(b.Name) if !(b.Access.Read && b.Access.Write) { c.Fatalf("root user should have read and write access to bucket: %v", b.Name) } } shouldHaveBuckets := set.CreateStringSet(bucket2, bucket) if !gotBuckets.Equals(shouldHaveBuckets) { c.Fatalf("root user should have access to all buckets") } } // SetUpLDAP - expects to setup an LDAP test server using the test LDAP // container and canned data from https://github.com/minio/minio-ldap-testing func (s *TestSuiteIAM) SetUpLDAP(c *check, serverAddr string) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() configCmds := []string{ "identity_ldap", fmt.Sprintf("server_addr=%s", serverAddr), "server_insecure=on", "lookup_bind_dn=cn=admin,dc=min,dc=io", "lookup_bind_password=admin", "user_dn_search_base_dn=dc=min,dc=io", "user_dn_search_filter=(uid=%s)", "group_search_base_dn=ou=swengg,dc=min,dc=io", "group_search_filter=(&(objectclass=groupofnames)(member=%d))", } _, err := s.adm.SetConfigKV(ctx, strings.Join(configCmds, " ")) if err != nil { c.Fatalf("unable to setup LDAP for tests: %v", err) } s.RestartIAMSuite(c) } const ( EnvTestLDAPServer = "LDAP_TEST_SERVER" ) func TestIAMWithLDAPServerSuite(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 ldapServer := os.Getenv(EnvTestLDAPServer) if ldapServer == "" { c.Skip("Skipping LDAP test as no LDAP server is provided.") } suite.SetUpSuite(c) suite.SetUpLDAP(c, ldapServer) suite.TestLDAPSTS(c) suite.TestLDAPSTSServiceAccounts(c) suite.TestLDAPSTSServiceAccountsWithGroups(c) suite.TearDownSuite(c) }, ) } } func (s *TestSuiteIAM) TestLDAPSTS(c *check) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() bucket := getRandomBucketName() err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) if err != nil { c.Fatalf("bucket create error: %v", err) } // Create policy policy := "mypolicy" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } ldapID := cr.LDAPIdentity{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, LDAPUsername: "dillon", LDAPPassword: "dillon", } _, err = ldapID.Retrieve() if err == nil { c.Fatalf("Expected to fail to create STS cred with no associated policy!") } // Attempting to set a non-existent policy should fail. userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io" err = s.adm.SetPolicy(ctx, policy+"x", userDN, false) if err == nil { c.Fatalf("should not be able to set non-existent policy") } err = s.adm.SetPolicy(ctx, policy, userDN, false) if err != nil { c.Fatalf("Unable to set policy: %v", err) } value, err := ldapID.Retrieve() if err != nil { c.Fatalf("Expected to generate STS creds, got err: %#v", err) } 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 user listing does not return any entries usersList, err := s.adm.ListUsers(ctx) if err != nil { c.Fatalf("list users should not fail: %v", err) } if len(usersList) > 0 { c.Fatalf("expected listing to be empty: %v", usersList) } // Validate that the client from sts creds can access the bucket. c.mustListObjects(ctx, minioClient, bucket) // Validate that the client cannot remove any objects err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{}) if err.Error() != "Access Denied." { c.Fatalf("unexpected non-access-denied err: %v", err) } // Remove the policy assignment on the user DN: err = s.adm.SetPolicy(ctx, "", userDN, false) if err != nil { c.Fatalf("Unable to remove policy setting: %v", err) } _, err = ldapID.Retrieve() if err == nil { c.Fatalf("Expected to fail to create a user with no associated policy!") } // Set policy via group and validate policy assignment. groupDN := "cn=projectb,ou=groups,ou=swengg,dc=min,dc=io" err = s.adm.SetPolicy(ctx, policy, groupDN, true) if err != nil { c.Fatalf("Unable to set group policy: %v", err) } value, err = ldapID.Retrieve() if err != nil { c.Fatalf("Expected to generate STS creds, got err: %#v", err) } 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) // Validate that the client cannot remove any objects err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{}) c.Assert(err.Error(), "Access Denied.") } func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(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) } // Create policy policy := "mypolicy" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io" err = s.adm.SetPolicy(ctx, policy, userDN, false) if err != nil { c.Fatalf("Unable to set policy: %v", err) } ldapID := cr.LDAPIdentity{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, LDAPUsername: "dillon", LDAPPassword: "dillon", } value, err := ldapID.Retrieve() if err != nil { c.Fatalf("Expected to generate STS creds, got err: %#v", err) } // Check that the LDAP sts cred is actually working. 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) // 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) } // In this test, the parent users gets their permissions from a group, rather // than having a policy set directly on them. func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithGroups(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) } // Create policy policy := "mypolicy" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } groupDN := "cn=projecta,ou=groups,ou=swengg,dc=min,dc=io" err = s.adm.SetPolicy(ctx, policy, groupDN, true) if err != nil { c.Fatalf("Unable to set policy: %v", err) } ldapID := cr.LDAPIdentity{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, LDAPUsername: "dillon", LDAPPassword: "dillon", } value, err := ldapID.Retrieve() if err != nil { c.Fatalf("Expected to generate STS creds, got err: %#v", err) } // Check that the LDAP sts cred is actually working. 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) // 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) } func (s *TestSuiteIAM) TestOpenIDSTS(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 := MockOpenIDTestUserInteraction(ctx, testAppParams, "dillon@example.io", "dillon") if err != nil { c.Fatalf("mock user err: %v", err) } // fmt.Printf("TOKEN: %s\n", token) webID := cr.STSWebIdentity{ Client: s.TestSuiteCommon.client, STSEndpoint: s.endPoint, GetWebIDTokenExpiry: func() (*cr.WebIdentityToken, error) { return &cr.WebIdentityToken{ Token: token, }, nil }, } // Create policy - with name as one of the groups in OpenID the user is // a member of. policy := "projecta" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } value, err := webID.Retrieve() if err != nil { c.Fatalf("Expected to generate STS creds, got err: %#v", err) } 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) // Validate that the client cannot remove any objects err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{}) if err.Error() != "Access Denied." { c.Fatalf("unexpected non-access-denied err: %v", err) } } func (s *TestSuiteIAM) TestOpenIDSTSAddUser(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 := MockOpenIDTestUserInteraction(ctx, testAppParams, "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 }, } // Create policy - with name as one of the groups in OpenID the user is // a member of. policy := "projecta" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } 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) c.mustNotCreateIAMUser(ctx, userAdmClient) // Create admin user policy. policyBytes = []byte(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "admin:*" ] } ] }`) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } cr := c.mustCreateIAMUser(ctx, userAdmClient) userInfo := c.mustGetIAMUserInfo(ctx, userAdmClient, cr.AccessKey) c.Assert(userInfo.Status, madmin.AccountEnabled) } func (s *TestSuiteIAM) TestOpenIDServiceAcc(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 := MockOpenIDTestUserInteraction(ctx, testAppParams, "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 }, } // Create policy - with name as one of the groups in OpenID the user is // a member of. policy := "projecta" policyBytes := []byte(fmt.Sprintf(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::%s/*" ] } ] }`, bucket)) err = s.adm.AddCannedPolicy(ctx, policy, policyBytes) if err != nil { c.Fatalf("policy add error: %v", err) } 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) } var testAppParams = OpenIDClientAppParams{ ClientID: "minio-client-app", ClientSecret: "minio-client-app-secret", ProviderURL: "http://127.0.0.1:5556/dex", RedirectURL: "http://127.0.0.1:10000/oauth_callback", } const ( EnvTestOpenIDServer = "OPENID_TEST_SERVER" ) // 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, rolePolicy string) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() configCmds := []string{ "identity_openid", fmt.Sprintf("config_url=%s/.well-known/openid-configuration", serverAddr), "client_id=minio-client-app", "client_secret=minio-client-app-secret", "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) } s.RestartIAMSuite(c) } func TestIAMWithOpenIDServerSuite(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, "") suite.TestOpenIDSTS(c) suite.TestOpenIDServiceAcc(c) suite.TestOpenIDSTSAddUser(c) suite.TearDownSuite(c) }, ) } } 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/nOybJqMNzNmroqEKq5D0EUsRZw0" ) 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 := MockOpenIDTestUserInteraction(ctx, testAppParams, "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 := MockOpenIDTestUserInteraction(ctx, testAppParams, "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) }