// Copyright (c) 2015-2024 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 <http://www.gnu.org/licenses/>.

package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/minio/madmin-go/v3"
	"github.com/minio/minio/internal/kms"
	"github.com/minio/pkg/v3/policy"
)

const (
	// KMS API paths
	// For example: /minio/kms/v1/key/list?pattern=*
	kmsURL           = kmsPathPrefix + kmsAPIVersionPrefix
	kmsStatusPath    = kmsURL + "/status"
	kmsMetricsPath   = kmsURL + "/metrics"
	kmsAPIsPath      = kmsURL + "/apis"
	kmsVersionPath   = kmsURL + "/version"
	kmsKeyCreatePath = kmsURL + "/key/create"
	kmsKeyListPath   = kmsURL + "/key/list"
	kmsKeyStatusPath = kmsURL + "/key/status"

	// Admin API paths
	// For example: /minio/admin/v3/kms/status
	adminURL              = adminPathPrefix + adminAPIVersionPrefix
	kmsAdminStatusPath    = adminURL + "/kms/status"
	kmsAdminKeyStatusPath = adminURL + "/kms/key/status"
	kmsAdminKeyCreate     = adminURL + "/kms/key/create"
)

const (
	userAccessKey = "miniofakeuseraccesskey"
	userSecretKey = "miniofakeusersecret"
)

type kmsTestCase struct {
	name   string
	method string
	path   string
	query  map[string]string

	// User credentials and policy for request
	policy string
	asRoot bool

	// Wanted in response.
	wantStatusCode int
	wantKeyNames   []string
	wantResp       []string
}

func TestKMSHandlersCreateKey(t *testing.T) {
	adminTestBed, tearDown := setupKMSTest(t, true)
	defer tearDown()

	tests := []kmsTestCase{
		// Create key test
		{
			name:   "create key as user with no policy want forbidden",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "new-test-key"},
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "create key as user with no resources specified want success",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "new-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:CreateKey"] }`,

			wantStatusCode: http.StatusOK,
		},
		{
			name:   "create key as user set policy to allow want success",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "second-new-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:CreateKey"],
				"Resource": ["arn:minio:kms:::second-new-test-*"] }`,

			wantStatusCode: http.StatusOK,
		},
		{
			name:   "create key as user set policy to non matching resource want forbidden",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "third-new-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:CreateKey"],
				"Resource": ["arn:minio:kms:::non-matching-key-name"] }`,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
	}
	for testNum, test := range tests {
		t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
			execKMSTest(t, test, adminTestBed)
		})
	}
}

func TestKMSHandlersKeyStatus(t *testing.T) {
	adminTestBed, tearDown := setupKMSTest(t, true)
	defer tearDown()

	tests := []kmsTestCase{
		{
			name:   "create a first key root user",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: true,

			wantStatusCode: http.StatusOK,
		},
		{
			name:   "key status as root want success",
			method: http.MethodGet,
			path:   kmsKeyStatusPath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"abc-test-key"},
		},
		{
			name:   "key status as user no policy want forbidden",
			method: http.MethodGet,
			path:   kmsKeyStatusPath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "key status as user legacy no resources specified want success",
			method: http.MethodGet,
			path:   kmsKeyStatusPath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
					"Action": ["kms:KeyStatus"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"abc-test-key"},
		},
		{
			name:   "key status as user set policy to allow only one key",
			method: http.MethodGet,
			path:   kmsKeyStatusPath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
					"Action": ["kms:KeyStatus"],
					"Resource": ["arn:minio:kms:::abc-test-*"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"abc-test-key"},
		},
		{
			name:   "key status as user set policy to allow non-matching key",
			method: http.MethodGet,
			path:   kmsKeyStatusPath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
					"Action": ["kms:KeyStatus"],
					"Resource": ["arn:minio:kms:::xyz-test-key"] }`,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
	}
	for testNum, test := range tests {
		t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
			execKMSTest(t, test, adminTestBed)
		})
	}
}

func TestKMSHandlersAPIs(t *testing.T) {
	adminTestBed, tearDown := setupKMSTest(t, true)
	defer tearDown()

	tests := []kmsTestCase{
		// Version test
		{
			name:   "version as root want success",
			method: http.MethodGet,
			path:   kmsVersionPath,
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"version"},
		},
		{
			name:   "version as user with no policy want forbidden",
			method: http.MethodGet,
			path:   kmsVersionPath,
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "version as user with policy ignores resource want success",
			method: http.MethodGet,
			path:   kmsVersionPath,
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:Version"],
				"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"version"},
		},

		// APIs test
		{
			name:   "apis as root want success",
			method: http.MethodGet,
			path:   kmsAPIsPath,
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"stub/path"},
		},
		{
			name:   "apis as user with no policy want forbidden",
			method: http.MethodGet,
			path:   kmsAPIsPath,
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "apis as user with policy ignores resource want success",
			method: http.MethodGet,
			path:   kmsAPIsPath,
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:API"],
				"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"stub/path"},
		},

		// Metrics test
		{
			name:   "metrics as root want success",
			method: http.MethodGet,
			path:   kmsMetricsPath,
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"kms"},
		},
		{
			name:   "metrics as user with no policy want forbidden",
			method: http.MethodGet,
			path:   kmsMetricsPath,
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "metrics as user with policy ignores resource want success",
			method: http.MethodGet,
			path:   kmsMetricsPath,
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:Metrics"],
				"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"kms"},
		},

		// Status tests
		{
			name:   "status as root want success",
			method: http.MethodGet,
			path:   kmsStatusPath,
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"MinIO builtin"},
		},
		{
			name:   "status as user with no policy want forbidden",
			method: http.MethodGet,
			path:   kmsStatusPath,
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "status as user with policy ignores resource want success",
			method: http.MethodGet,
			path:   kmsStatusPath,
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:Status"],
				"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"]}`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"MinIO builtin"},
		},
	}
	for testNum, test := range tests {
		t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
			execKMSTest(t, test, adminTestBed)
		})
	}
}

func TestKMSHandlersListKeys(t *testing.T) {
	adminTestBed, tearDown := setupKMSTest(t, true)
	defer tearDown()

	tests := []kmsTestCase{
		{
			name:   "create a first key root user",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: true,

			wantStatusCode: http.StatusOK,
		},
		{
			name:   "create a second key root user",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "xyz-test-key"},
			asRoot: true,

			wantStatusCode: http.StatusOK,
		},

		// List keys tests
		{
			name:   "list keys as root want all to be returned",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantKeyNames:   []string{"default-test-key", "abc-test-key", "xyz-test-key"},
		},
		{
			name:   "list keys as user with no policy want forbidden",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "list keys as user with no resources specified want success",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["kms:ListKeys"]
			}`,

			wantStatusCode: http.StatusOK,
			wantKeyNames:   []string{"default-test-key", "abc-test-key", "xyz-test-key"},
		},
		{
			name:   "list keys as user set policy resource to allow only one key",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
			asRoot: false,

			policy: `{"Effect": "Allow",
					"Action": ["kms:ListKeys"],
					"Resource": ["arn:minio:kms:::abc*"]}`,

			wantStatusCode: http.StatusOK,
			wantKeyNames:   []string{"abc-test-key"},
		},
		{
			name:   "list keys as user set policy to allow only one key, use pattern that includes correct key",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "abc*"},

			policy: `{"Effect": "Allow",
					"Action": ["kms:ListKeys"],
					"Resource": ["arn:minio:kms:::abc*"]}`,

			wantStatusCode: http.StatusOK,
			wantKeyNames:   []string{"abc-test-key"},
		},
		{
			name:   "list keys as user set policy to allow only one key, use pattern that excludes correct key",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "xyz*"},
			asRoot: false,

			policy: `{"Effect": "Allow",
					"Action": ["kms:ListKeys"],
					"Resource": ["arn:minio:kms:::abc*"]}`,

			wantStatusCode: http.StatusOK,
			wantKeyNames:   []string{},
		},
		{
			name:   "list keys as user set policy that has no matching key resources",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
			asRoot: false,

			policy: `{"Effect": "Allow",
					"Action": ["kms:ListKeys"],
					"Resource": ["arn:minio:kms:::nonematch*"]}`,

			wantStatusCode: http.StatusOK,
			wantKeyNames:   []string{},
		},
		{
			name:   "list keys as user set policy that allows listing but denies specific keys",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
			asRoot: false,

			// It looks like this should allow listing any key that isn't "default-test-key", however
			// the policy engine matches all Deny statements first, without regard to Resources (for KMS).
			// This is for backwards compatibility where historically KMS statements ignored Resources.
			policy: `{
						"Effect": "Allow",
						"Action": ["kms:ListKeys"]
					},{
						"Effect": "Deny",
						"Action": ["kms:ListKeys"],
						"Resource": ["arn:minio:kms:::default-test-key"]
					}`,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
	}

	for testNum, test := range tests {
		t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
			execKMSTest(t, test, adminTestBed)
		})
	}
}

func TestKMSHandlerAdminAPI(t *testing.T) {
	adminTestBed, tearDown := setupKMSTest(t, true)
	defer tearDown()

	tests := []kmsTestCase{
		// Create key tests
		{
			name:   "create a key root user",
			method: http.MethodPost,
			path:   kmsAdminKeyCreate,
			query:  map[string]string{"key-id": "abc-test-key"},
			asRoot: true,

			wantStatusCode: http.StatusOK,
		},
		{
			name:   "create key as user with no policy want forbidden",
			method: http.MethodPost,
			path:   kmsAdminKeyCreate,
			query:  map[string]string{"key-id": "new-test-key"},
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "create key as user with no resources specified want success",
			method: http.MethodPost,
			path:   kmsAdminKeyCreate,
			query:  map[string]string{"key-id": "new-test-key"},
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["admin:KMSCreateKey"] }`,

			wantStatusCode: http.StatusOK,
		},
		{
			name:   "create key as user set policy to non matching resource want success",
			method: http.MethodPost,
			path:   kmsAdminKeyCreate,
			query:  map[string]string{"key-id": "third-new-test-key"},
			asRoot: false,

			// Admin actions ignore Resources
			policy: `{"Effect": "Allow",
				"Action": ["admin:KMSCreateKey"],
				"Resource": ["arn:minio:kms:::this-is-disregarded"] }`,

			wantStatusCode: http.StatusOK,
		},

		// Status tests
		{
			name:   "status as root want success",
			method: http.MethodPost,
			path:   kmsAdminStatusPath,
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"MinIO builtin"},
		},
		{
			name:   "status as user with no policy want forbidden",
			method: http.MethodPost,
			path:   kmsAdminStatusPath,
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "status as user with policy ignores resource want success",
			method: http.MethodPost,
			path:   kmsAdminStatusPath,
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["admin:KMSKeyStatus"],
				"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"MinIO builtin"},
		},

		// Key status tests
		{
			name:   "key status as root want success",
			method: http.MethodGet,
			path:   kmsAdminKeyStatusPath,
			asRoot: true,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"key-id"},
		},
		{
			name:   "key status as user with no policy want forbidden",
			method: http.MethodGet,
			path:   kmsAdminKeyStatusPath,
			asRoot: false,

			wantStatusCode: http.StatusForbidden,
			wantResp:       []string{"AccessDenied"},
		},
		{
			name:   "key status as user with policy ignores resource want success",
			method: http.MethodGet,
			path:   kmsAdminKeyStatusPath,
			asRoot: false,

			policy: `{"Effect": "Allow",
				"Action": ["admin:KMSKeyStatus"],
				"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,

			wantStatusCode: http.StatusOK,
			wantResp:       []string{"key-id"},
		},
	}

	for testNum, test := range tests {
		t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
			execKMSTest(t, test, adminTestBed)
		})
	}
}

// execKMSTest runs a single test case for KMS handlers
func execKMSTest(t *testing.T, test kmsTestCase, adminTestBed *adminErasureTestBed) {
	var accessKey, secretKey string
	if test.asRoot {
		accessKey, secretKey = globalActiveCred.AccessKey, globalActiveCred.SecretKey
	} else {
		setupKMSUser(t, userAccessKey, userSecretKey, test.policy)
		accessKey = userAccessKey
		secretKey = userSecretKey
	}

	req := buildKMSRequest(t, test.method, test.path, accessKey, secretKey, test.query)
	rec := httptest.NewRecorder()
	adminTestBed.router.ServeHTTP(rec, req)

	t.Logf("HTTP req: %s, resp code: %d, resp body: %s", req.URL.String(), rec.Code, rec.Body.String())

	// Check status code
	if rec.Code != test.wantStatusCode {
		t.Errorf("want status code %d, got %d", test.wantStatusCode, rec.Code)
	}

	// Check returned key list is correct
	if test.wantKeyNames != nil {
		gotKeyNames := keyNamesFromListKeysResp(t, rec.Body.Bytes())
		if len(test.wantKeyNames) != len(gotKeyNames) {
			t.Fatalf("want keys len: %d, got len: %d", len(test.wantKeyNames), len(gotKeyNames))
		}
		for i, wantKeyName := range test.wantKeyNames {
			if gotKeyNames[i] != wantKeyName {
				t.Fatalf("want key name %s, in position %d, got %s", wantKeyName, i, gotKeyNames[i])
			}
		}
	}

	// Check generic text in the response
	if test.wantResp != nil {
		for _, want := range test.wantResp {
			if !strings.Contains(rec.Body.String(), want) {
				t.Fatalf("want response to contain %s, got %s", want, rec.Body.String())
			}
		}
	}
}

// TestKMSHandlerNotConfiguredOrInvalidCreds tests KMS handlers for situations where KMS is not configured
// or invalid credentials are provided.
func TestKMSHandlerNotConfiguredOrInvalidCreds(t *testing.T) {
	adminTestBed, tearDown := setupKMSTest(t, false)
	defer tearDown()

	tests := []struct {
		name   string
		method string
		path   string
		query  map[string]string
	}{
		{
			name:   "GET status",
			method: http.MethodGet,
			path:   kmsStatusPath,
		},
		{
			name:   "GET metrics",
			method: http.MethodGet,
			path:   kmsMetricsPath,
		},
		{
			name:   "GET apis",
			method: http.MethodGet,
			path:   kmsAPIsPath,
		},
		{
			name:   "GET version",
			method: http.MethodGet,
			path:   kmsVersionPath,
		},
		{
			name:   "POST key create",
			method: http.MethodPost,
			path:   kmsKeyCreatePath,
			query:  map[string]string{"key-id": "master-key-id"},
		},
		{
			name:   "GET key list",
			method: http.MethodGet,
			path:   kmsKeyListPath,
			query:  map[string]string{"pattern": "*"},
		},
		{
			name:   "GET key status",
			method: http.MethodGet,
			path:   kmsKeyStatusPath,
			query:  map[string]string{"key-id": "master-key-id"},
		},
	}

	// Test when the GlobalKMS is not configured
	for _, test := range tests {
		t.Run(test.name+" not configured", func(t *testing.T) {
			req := buildKMSRequest(t, test.method, test.path, "", "", test.query)
			rec := httptest.NewRecorder()
			adminTestBed.router.ServeHTTP(rec, req)
			if rec.Code != http.StatusNotImplemented {
				t.Errorf("want status code %d, got %d", http.StatusNotImplemented, rec.Code)
			}
		})
	}

	// Test when the GlobalKMS is configured but the credentials are invalid
	GlobalKMS = kms.NewStub("default-test-key")
	for _, test := range tests {
		t.Run(test.name+" invalid credentials", func(t *testing.T) {
			req := buildKMSRequest(t, test.method, test.path, userAccessKey, userSecretKey, test.query)
			rec := httptest.NewRecorder()
			adminTestBed.router.ServeHTTP(rec, req)
			if rec.Code != http.StatusForbidden {
				t.Errorf("want status code %d, got %d", http.StatusForbidden, rec.Code)
			}
		})
	}
}

func setupKMSTest(t *testing.T, enableKMS bool) (*adminErasureTestBed, func()) {
	adminTestBed, err := prepareAdminErasureTestBed(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	registerKMSRouter(adminTestBed.router)

	if enableKMS {
		GlobalKMS = kms.NewStub("default-test-key")
	}

	tearDown := func() {
		adminTestBed.TearDown()
		GlobalKMS = nil
	}
	return adminTestBed, tearDown
}

func buildKMSRequest(t *testing.T, method, path, accessKey, secretKey string, query map[string]string) *http.Request {
	if len(query) > 0 {
		queryVal := url.Values{}
		for k, v := range query {
			queryVal.Add(k, v)
		}
		path = path + "?" + queryVal.Encode()
	}

	if accessKey == "" && secretKey == "" {
		accessKey = globalActiveCred.AccessKey
		secretKey = globalActiveCred.SecretKey
	}

	req, err := newTestSignedRequestV4(method, path, 0, nil, accessKey, secretKey, nil)
	if err != nil {
		t.Fatal(err)
	}
	return req
}

// setupKMSUser is a test helper that creates a new user with the provided access key and secret key
// and applies the given policy to the user.
func setupKMSUser(t *testing.T, accessKey, secretKey, p string) {
	ctx := context.Background()
	createUserParams := madmin.AddOrUpdateUserReq{
		SecretKey: secretKey,
		Status:    madmin.AccountEnabled,
	}
	_, err := globalIAMSys.CreateUser(ctx, accessKey, createUserParams)
	if err != nil {
		t.Fatal(err)
	}

	testKMSPolicyName := "testKMSPolicy"
	if p != "" {
		p = `{"Version":"2012-10-17","Statement":[` + p + `]}`
		policyData, err := policy.ParseConfig(strings.NewReader(p))
		if err != nil {
			t.Fatal(err)
		}
		_, err = globalIAMSys.SetPolicy(ctx, testKMSPolicyName, *policyData)
		if err != nil {
			t.Fatal(err)
		}
		_, err = globalIAMSys.PolicyDBSet(ctx, accessKey, testKMSPolicyName, regUser, false)
		if err != nil {
			t.Fatal(err)
		}
	} else {
		err = globalIAMSys.DeletePolicy(ctx, testKMSPolicyName, false)
		if err != nil {
			t.Fatal(err)
		}
		_, err = globalIAMSys.PolicyDBSet(ctx, accessKey, "", regUser, false)
		if err != nil {
			t.Fatal(err)
		}
	}
}

func keyNamesFromListKeysResp(t *testing.T, b []byte) []string {
	var keyInfos []madmin.KMSKeyInfo
	err := json.Unmarshal(b, &keyInfos)
	if err != nil {
		t.Fatalf("cannot unmarshal '%s', err: %v", b, err)
	}

	var gotKeyNames []string
	for _, keyInfo := range keyInfos {
		gotKeyNames = append(gotKeyNames, keyInfo.Name)
	}
	return gotKeyNames
}