From 4629abd5a2ec0a5d9410c3dbbeec55beb0b85d44 Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Thu, 12 May 2022 15:24:19 -0700 Subject: [PATCH] Add tests for Access Management Plugin (#14909) --- .github/workflows/iam-integrations.yaml | 11 ++ cmd/admin-handlers-users_test.go | 208 +++++++++++++++++++++--- cmd/sts-handlers_test.go | 23 --- docs/iam/access-manager-plugin.go | 1 + 4 files changed, 197 insertions(+), 46 deletions(-) diff --git a/.github/workflows/iam-integrations.yaml b/.github/workflows/iam-integrations.yaml index ecfe24dbd..c642959f4 100644 --- a/.github/workflows/iam-integrations.yaml +++ b/.github/workflows/iam-integrations.yaml @@ -108,6 +108,17 @@ jobs: sudo sysctl net.ipv6.conf.all.disable_ipv6=0 sudo sysctl net.ipv6.conf.default.disable_ipv6=0 make test-iam + - name: Test with Access Management Plugin enabled + env: + LDAP_TEST_SERVER: ${{ matrix.ldap }} + ETCD_SERVER: ${{ matrix.etcd }} + OPENID_TEST_SERVER: ${{ matrix.openid }} + POLICY_PLUGIN_ENDPOINT: "http://127.0.0.1:8080" + run: | + sudo sysctl net.ipv6.conf.all.disable_ipv6=0 + sudo sysctl net.ipv6.conf.default.disable_ipv6=0 + go run docs/iam/access-manager-plugin.go & + make test-iam - name: Test LDAP for automatic site replication if: matrix.ldap == 'localhost:389' run: | diff --git a/cmd/admin-handlers-users_test.go b/cmd/admin-handlers-users_test.go index 53616eb9d..865dbdc92 100644 --- a/cmd/admin-handlers-users_test.go +++ b/cmd/admin-handlers-users_test.go @@ -97,6 +97,29 @@ func (s *TestSuiteIAM) iamSetup(c *check) { } } +// List of all IAM test suites (i.e. test server configuration combinations) +// common to tests. +var iamTestSuites = func() []*TestSuiteIAM { + 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), + ) + } + return testCases +}() + const ( EnvTestEtcdBackend = "ETCD_SERVER" ) @@ -166,30 +189,9 @@ func (s *TestSuiteIAM) getUserClient(c *check, accessKey, secretKey, sessionToke } func TestIAMInternalIDPServerSuite(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)" - } + for i, testCase := range iamTestSuites { t.Run( - fmt.Sprintf("Test: %d, ServerType: %s%s", i+1, testCase.serverType, etcdStr), + fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.ServerTypeDescription), func(t *testing.T) { suite := testCase c := &check{t, testCase.serverType} @@ -973,6 +975,166 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) { c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket) } +func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) { + ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) + defer cancel() + + pluginEndpoint := os.Getenv("POLICY_PLUGIN_ENDPOINT") + if pluginEndpoint == "" { + c.Skip("POLICY_PLUGIN_ENDPOINT not given - skipping.") + } + + configCmds := []string{ + "policy_plugin", + "url=" + pluginEndpoint, + } + + _, err := s.adm.SetConfigKV(ctx, strings.Join(configCmds, " ")) + if err != nil { + c.Fatalf("unable to setup access management plugin for tests: %v", err) + } + + s.RestartIAMSuite(c) +} + +// TestIAM_AMPInternalIDPServerSuite - tests for access management plugin +func TestIAM_AMPInternalIDPServerSuite(t *testing.T) { + for i, testCase := range iamTestSuites { + t.Run( + fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.ServerTypeDescription), + func(t *testing.T) { + suite := testCase + c := &check{t, testCase.serverType} + + suite.SetUpSuite(c) + defer suite.TearDownSuite(c) + + suite.SetUpAccMgmtPlugin(c) + + suite.TestAccMgmtPlugin(c) + }, + ) + } +} + +// TestAccMgmtPlugin - this test assumes that the access-management-plugin is +// the same as the example in `docs/iam/access-manager-plugin.go` - +// specifically, it denies only `s3:Put*` operations on non-root accounts. +func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) { + ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) + defer cancel() + + // 0. Check that owner is able to make-bucket. + bucket := getRandomBucketName() + err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) + if err != nil { + c.Fatalf("bucket creat error: %v", err) + } + + // 1. Create a user. + accessKey, secretKey := mustGenerateCredentials(c) + err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled) + if err != nil { + c.Fatalf("Unable to set user: %v", err) + } + + // 2. Check new user appears in listing + usersMap, err := s.adm.ListUsers(ctx) + if err != nil { + c.Fatalf("error listing: %v", err) + } + v, ok := usersMap[accessKey] + if !ok { + c.Fatalf("user not listed: %s", accessKey) + } + c.Assert(v.Status, madmin.AccountEnabled) + + // 3. Check that user is able to make a bucket. + client := s.getUserClient(c, accessKey, secretKey, "") + err = client.MakeBucket(ctx, getRandomBucketName(), minio.MakeBucketOptions{}) + if err != nil { + c.Fatalf("user not create bucket: %v", err) + } + + // 3.1 check user has access to bucket + c.mustListObjects(ctx, client, bucket) + + // 3.2 check that user cannot upload an object. + _, err = client.PutObject(ctx, bucket, "objectName", bytes.NewBuffer([]byte("some content")), 12, minio.PutObjectOptions{}) + if err == nil { + c.Fatalf("user was able to upload unexpectedly") + } + + // Create an madmin client with user creds + userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{ + Creds: cr.NewStaticV4(accessKey, secretKey, ""), + 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, accessKey, userAdmClient) + + // 1. Check that svc account appears in listing + c.assertSvcAccAppearsInListing(ctx, userAdmClient, accessKey, cr.AccessKey) + + // 2. Check that svc account info can be queried + c.assertSvcAccInfoQueryable(ctx, userAdmClient, accessKey, cr.AccessKey, false) + + // 3. Check S3 access + c.assertSvcAccS3Access(ctx, s, cr, bucket) + + // Check that session policies do not apply - as policy enforcement is + // delegated to plugin. + { + svcAK, svcSK := mustGenerateCredentials(c) + + // This policy does not allow listing objects. + policyBytes := []byte(fmt.Sprintf(`{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::%s/*" + ] + } + ] +}`, bucket)) + cr, err := userAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{ + Policy: policyBytes, + TargetUser: accessKey, + AccessKey: svcAK, + SecretKey: svcSK, + }) + if err != nil { + c.Fatalf("Unable to create svc acc: %v", err) + } + svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "") + // Though the attached policy does not allow listing, it will be + // ignored because the plugin allows it. + c.mustListObjects(ctx, svcClient, bucket) + } + + // 4. Check that service account's secret key and account status can be + // updated. + c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, accessKey, bucket) + + // 5. Check that service account can be deleted. + c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket) + + // 6. Check that service account **can** be created for some other user. + // This is possible because of the policy enforced in the plugin. + c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient) +} + func (c *check) mustCreateIAMUser(ctx context.Context, admClnt *madmin.AdminClient) madmin.Credentials { randUser := mustGetUUID() randPass := mustGetUUID() diff --git a/cmd/sts-handlers_test.go b/cmd/sts-handlers_test.go index 7280f69dc..4fe997765 100644 --- a/cmd/sts-handlers_test.go +++ b/cmd/sts-handlers_test.go @@ -1311,29 +1311,6 @@ func (s *TestSuiteIAM) TestOpenIDServiceAccWithRolePolicy(c *check) { c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket) } -// List of all IAM test suites (i.e. test server configuration combinations) -// common to tests. -var iamTestSuites = func() []*TestSuiteIAM { - 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), - ) - } - return testCases -}() - func TestIAMWithOpenIDMultipleConfigsValidation(t *testing.T) { openIDServer := os.Getenv(EnvTestOpenIDServer) openIDServer2 := os.Getenv(EnvTestOpenIDServer2) diff --git a/docs/iam/access-manager-plugin.go b/docs/iam/access-manager-plugin.go index 118daee3d..ae79f83aa 100644 --- a/docs/iam/access-manager-plugin.go +++ b/docs/iam/access-manager-plugin.go @@ -79,5 +79,6 @@ func mainHandler(w http.ResponseWriter, r *http.Request) { func main() { http.HandleFunc("/", mainHandler) + log.Print("Listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }