From f28a8eca917575c6f621a8d964e123e6ab34df2e Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Fri, 13 May 2022 12:48:02 -0700 Subject: [PATCH] Add Access Management Plugin tests with OpenID (#14919) --- cmd/admin-handlers-users_test.go | 10 ++ cmd/sts-handlers_test.go | 266 +++++++++++++++++++++++-------- 2 files changed, 207 insertions(+), 69 deletions(-) diff --git a/cmd/admin-handlers-users_test.go b/cmd/admin-handlers-users_test.go index 865dbdc92..438bdae40 100644 --- a/cmd/admin-handlers-users_test.go +++ b/cmd/admin-handlers-users_test.go @@ -1201,6 +1201,16 @@ func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucke } } +func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) { + _, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{}) + if e, ok := err.(minio.ErrorResponse); ok { + if e.Code == "AccessDenied" { + return + } + } + c.Fatalf("upload did not get an AccessDenied error - got %#v instead", err) +} + func (c *check) assertSvcAccS3Access(ctx context.Context, s *TestSuiteIAM, cr madmin.Credentials, bucket string) { svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "") c.mustListObjects(ctx, svcClient, bucket) diff --git a/cmd/sts-handlers_test.go b/cmd/sts-handlers_test.go index 4fe997765..5ef18ee9f 100644 --- a/cmd/sts-handlers_test.go +++ b/cmd/sts-handlers_test.go @@ -356,30 +356,9 @@ const ( ) 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)" - } + 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) { c := &check{t, testCase.serverType} suite := testCase @@ -1070,30 +1049,9 @@ func (s *TestSuiteIAM) SetUpOpenID(c *check, serverAddr string, rolePolicy strin } 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)" - } + 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) { c := &check{t, testCase.serverType} suite := testCase @@ -1115,30 +1073,9 @@ func TestIAMWithOpenIDServerSuite(t *testing.T) { } 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)" - } + 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) { c := &check{t, testCase.serverType} suite := testCase @@ -1376,3 +1313,194 @@ func TestIAMWithOpenIDWithMultipleRolesServerSuite(t *testing.T) { ) } } + +// Access Management Plugin tests +func TestIAM_AMPWithOpenIDWithMultipleRolesServerSuite(t *testing.T) { + openIDServer := os.Getenv(EnvTestOpenIDServer) + openIDServer2 := os.Getenv(EnvTestOpenIDServer2) + if openIDServer == "" || openIDServer2 == "" { + t.Skip("Skipping OpenID test as enough OpenID servers are not provided.") + } + testApps := testClientApps + + rolePolicies := []string{ + "consoleAdmin", + "readwrite", + } + + 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 + + suite.SetUpSuite(c) + defer suite.TearDownSuite(c) + + err := suite.SetUpOpenIDs(c, testApps, rolePolicies) + if err != nil { + c.Fatalf("Error setting up openid providers for tests: %v", err) + } + + suite.SetUpAccMgmtPlugin(c) + + suite.TestOpenIDSTSWithRolePolicyUnderAMP(c, testRoleARNs[0], testRoleMap[testRoleARNs[0]]) + suite.TestOpenIDSTSWithRolePolicyUnderAMP(c, testRoleARNs[1], testRoleMap[testRoleARNs[1]]) + suite.TestOpenIDServiceAccWithRolePolicyUnderAMP(c) + }, + ) + } +} + +func (s *TestSuiteIAM) TestOpenIDSTSWithRolePolicyUnderAMP(c *check, roleARN string, clientApp OpenIDClientAppParams) { + 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 JWT by interacting with OpenID IDP. + token, err := MockOpenIDTestUserInteraction(ctx, clientApp, "dillon@example.io", "dillon") + if err != nil { + c.Fatalf("mock user err: %v", err) + } + + // Generate STS credential. + webID := cr.STSWebIdentity{ + Client: s.TestSuiteCommon.client, + STSEndpoint: s.endPoint, + GetWebIDTokenExpiry: func() (*cr.WebIdentityToken, error) { + return &cr.WebIdentityToken{ + Token: token, + }, nil + }, + RoleARN: roleARN, + } + + 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) + + // Validate that the client from STS creds cannot upload any object as + // it is denied by the plugin. + c.mustNotUpload(ctx, minioClient, bucket) +} + +func (s *TestSuiteIAM) TestOpenIDServiceAccWithRolePolicyUnderAMP(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) + // 3.1 Validate that the client from STS creds cannot upload any object as + // it is denied by the plugin. + c.mustNotUpload(ctx, s.getUserClient(c, cr.AccessKey, cr.SecretKey, ""), 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: value.AccessKeyID, + 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, value.AccessKeyID, bucket) + + // 5. Check that service account can be deleted. + c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket) +}