From 3307aa12608b69af4c3233de64ca4ffa8056370b Mon Sep 17 00:00:00 2001 From: Javier Adriel Date: Tue, 4 Oct 2022 12:05:09 -0500 Subject: [PATCH] Implement KMS handlers (#15737) --- cmd/generic-handlers.go | 9 +- cmd/kms-handlers.go | 607 +++++++++++++++++++++++++++++++++ cmd/kms-router.go | 80 +++++ cmd/routers.go | 3 + internal/kms/kes.go | 4 +- internal/kms/policy-manager.go | 2 +- 6 files changed, 700 insertions(+), 5 deletions(-) create mode 100644 cmd/kms-handlers.go create mode 100644 cmd/kms-router.go diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go index 7c9985e4b..05f83519a 100644 --- a/cmd/generic-handlers.go +++ b/cmd/generic-handlers.go @@ -245,6 +245,12 @@ func isAdminReq(r *http.Request) bool { return strings.HasPrefix(r.URL.Path, adminPathPrefix) } +// Check to allow access to the reserved "bucket" `/minio` for KMS +// API requests. +func isKMSReq(r *http.Request) bool { + return strings.HasPrefix(r.URL.Path, kmsPathPrefix) +} + // Supported Amz date headers. var amzDateHeaders = []string{ // Do not chane this order, x-amz-date value should be @@ -428,12 +434,11 @@ func setRequestValidityHandler(h http.Handler) http.Handler { // For all other requests reject access to reserved buckets bucketName, _ := request2BucketObjectName(r) if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) { - if !guessIsRPCReq(r) && !guessIsBrowserReq(r) && !guessIsHealthCheckReq(r) && !guessIsMetricsReq(r) && !isAdminReq(r) { + if !guessIsRPCReq(r) && !guessIsBrowserReq(r) && !guessIsHealthCheckReq(r) && !guessIsMetricsReq(r) && !isAdminReq(r) && !isKMSReq(r) { if ok { tc.funcName = "handler.ValidRequest" tc.responseRecorder.LogErrBody = true } - writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL) return } diff --git a/cmd/kms-handlers.go b/cmd/kms-handlers.go new file mode 100644 index 000000000..73ea2e5d8 --- /dev/null +++ b/cmd/kms-handlers.go @@ -0,0 +1,607 @@ +// Copyright (c) 2015-2022 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 ( + "crypto/subtle" + "encoding/json" + "net/http" + "time" + + "github.com/minio/kes" + "github.com/minio/madmin-go" + "github.com/minio/minio/internal/kms" + "github.com/minio/minio/internal/logger" + iampolicy "github.com/minio/pkg/iam/policy" +) + +// KMSStatusHandler - GET /minio/kms/v1/status +func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSStatus") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSStatusAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + + stat, err := GlobalKMS.Stat(ctx) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + + status := madmin.KMSStatus{ + Name: stat.Name, + DefaultKeyID: stat.DefaultKey, + Endpoints: make(map[string]madmin.ItemState, len(stat.Endpoints)), + } + for _, endpoint := range stat.Endpoints { + status.Endpoints[endpoint] = madmin.ItemOnline // TODO(aead): Implement an online check for mTLS + } + + resp, err := json.Marshal(status) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, resp) +} + +// KMSCreateKeyHandler - POST /minio/kms/v1/key/create?key-id= +func (a kmsAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSCreateKey") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + + manager, ok := GlobalKMS.(kms.KeyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + + if err := manager.CreateKey(ctx, r.Form.Get("key-id")); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSDeleteKeyHandler - DELETE /minio/kms/v1/key/delete?key-id= +func (a kmsAPIHandlers) KMSDeleteKeyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSDeleteKey") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteKeyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.KeyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + if err := manager.DeleteKey(ctx, r.Form.Get("key-id")); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSListKeysHandler - GET /minio/kms/v1/key/list?pattern= +func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSListKeys") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListKeysAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.KeyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + keys, err := manager.ListKeys(ctx, r.Form.Get("pattern")) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + values, err := keys.Values(0) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + if res, err := json.Marshal(values); err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + } else { + writeSuccessResponseJSON(w, res) + } +} + +type importKeyRequest struct { + Bytes string +} + +// KMSImportKeyHandler - POST /minio/kms/v1/key/import?key-id= +func (a kmsAPIHandlers) KMSImportKeyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSImportKey") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSImportKeyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.KeyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + var request importKeyRequest + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + if err := manager.ImportKey(ctx, r.Form.Get("key-id"), []byte(request.Bytes)); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSKeyStatusHandler - GET /minio/kms/v1/key/status?key-id= +func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSKeyStatus") + + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + + stat, err := GlobalKMS.Stat(ctx) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + + keyID := r.Form.Get("key-id") + if keyID == "" { + keyID = stat.DefaultKey + } + response := madmin.KMSKeyStatus{ + KeyID: keyID, + } + + kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation + // 1. Generate a new key using the KMS. + key, err := GlobalKMS.GenerateKey(ctx, keyID, kmsContext) + if err != nil { + response.EncryptionErr = err.Error() + resp, err := json.Marshal(response) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, resp) + return + } + + // 2. Verify that we can indeed decrypt the (encrypted) key + decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext) + if err != nil { + response.DecryptionErr = err.Error() + resp, err := json.Marshal(response) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, resp) + return + } + + // 3. Compare generated key with decrypted key + if subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1 { + response.DecryptionErr = "The generated and the decrypted data key do not match" + resp, err := json.Marshal(response) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, resp) + return + } + + resp, err := json.Marshal(response) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, resp) +} + +// KMSDescribePolicyHandler - GET /minio/kms/v1/policy/describe?policy= +func (a kmsAPIHandlers) KMSDescribePolicyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSDescribePolicy") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribePolicyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.PolicyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + policy, err := manager.DescribePolicy(ctx, r.Form.Get("policy")) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + p, err := json.Marshal(policy) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, p) +} + +type assignPolicyRequest struct { + Identity string +} + +// KMSAssignPolicyHandler - POST /minio/kms/v1/policy/assign?policy= +func (a kmsAPIHandlers) KMSAssignPolicyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSAssignPolicy") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAssignPolicyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.PolicyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + var request assignPolicyRequest + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + err := manager.AssignPolicy(ctx, r.Form.Get("policy"), request.Identity) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSSetPolicyHandler - POST /minio/kms/v1/policy/policy?policy= +func (a kmsAPIHandlers) KMSSetPolicyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSSetPolicy") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSSetPolicyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.PolicyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + var policy kes.Policy + if err := json.NewDecoder(r.Body).Decode(&policy); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + if err := manager.SetPolicy(ctx, r.Form.Get("policy"), &policy); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSDeletePolicyHandler - DELETE /minio/kms/v1/policy/delete?policy= +func (a kmsAPIHandlers) KMSDeletePolicyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSDeletePolicy") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeletePolicyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + + manager, ok := GlobalKMS.(kms.PolicyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + if err := manager.DeletePolicy(ctx, r.Form.Get("policy")); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSListPoliciesHandler - GET /minio/kms/v1/policy/list?pattern= +func (a kmsAPIHandlers) KMSListPoliciesHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSListPolicies") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListPoliciesAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.PolicyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + policies, err := manager.ListPolicies(ctx, r.Form.Get("pattern")) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + values, err := policies.Values(0) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + if res, err := json.Marshal(values); err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + } else { + writeSuccessResponseJSON(w, res) + } +} + +// KMSGetPolicyHandler - GET /minio/kms/v1/policy/get?policy= +func (a kmsAPIHandlers) KMSGetPolicyHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSGetPolicy") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSGetPolicyAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.PolicyManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + policy, err := manager.GetPolicy(ctx, r.Form.Get("policy")) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + if p, err := json.Marshal(policy); err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + } else { + writeSuccessResponseJSON(w, p) + } +} + +// KMSDescribeIdentityHandler - GET /minio/kms/v1/identity/describe?identity= +func (a kmsAPIHandlers) KMSDescribeIdentityHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSDescribeIdentity") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeIdentityAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.IdentityManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + identity, err := manager.DescribeIdentity(ctx, r.Form.Get("identity")) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + i, err := json.Marshal(identity) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, i) +} + +type describeSelfIdentityResponse struct { + Policy *kes.Policy `json:"policy"` + PolicyName string `json:"policyName"` + Identity string `json:"identity"` + IsAdmin bool `json:"isAdmin"` + CreatedAt time.Time `json:"createdAt"` + CreatedBy string `json:"createdBy"` +} + +// KMSDescribeSelfIdentityHandler - GET /minio/kms/v1/identity/describe-self +func (a kmsAPIHandlers) KMSDescribeSelfIdentityHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSDescribeSelfIdentity") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeSelfIdentityAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.IdentityManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + identity, policy, err := manager.DescribeSelfIdentity(ctx) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + res := &describeSelfIdentityResponse{ + Policy: policy, + PolicyName: identity.Policy, + Identity: identity.Identity.String(), + IsAdmin: identity.IsAdmin, + CreatedAt: identity.CreatedAt, + CreatedBy: identity.CreatedBy.String(), + } + i, err := json.Marshal(res) + if err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + return + } + writeSuccessResponseJSON(w, i) +} + +// KMSDeleteIdentityHandler - DELETE /minio/kms/v1/identity/delete?identity= +func (a kmsAPIHandlers) KMSDeleteIdentityHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSDeleteIdentity") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteIdentityAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.IdentityManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + + if err := manager.DeleteIdentity(ctx, r.Form.Get("policy")); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + writeSuccessResponseHeadersOnly(w) +} + +// KMSListIdentitiesHandler - GET /minio/kms/v1/identity/list?pattern= +func (a kmsAPIHandlers) KMSListIdentitiesHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "KMSListIdentities") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListIdentitiesAction) + if objectAPI == nil { + return + } + + if GlobalKMS == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) + return + } + manager, ok := GlobalKMS.(kms.IdentityManager) + if !ok { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + identities, err := manager.ListIdentities(ctx, r.Form.Get("pattern")) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + values, err := identities.Values(0) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + if res, err := json.Marshal(values); err != nil { + writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) + } else { + writeSuccessResponseJSON(w, res) + } +} diff --git a/cmd/kms-router.go b/cmd/kms-router.go new file mode 100644 index 000000000..fc4f06438 --- /dev/null +++ b/cmd/kms-router.go @@ -0,0 +1,80 @@ +// Copyright (c) 2015-2022 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 ( + "net/http" + + "github.com/gorilla/mux" + "github.com/klauspost/compress/gzhttp" + "github.com/klauspost/compress/gzip" + "github.com/minio/minio/internal/logger" +) + +const ( + kmsPathPrefix = minioReservedBucketPath + "/kms" + kmsAPIVersion = "v1" + kmsAPIVersionPrefix = SlashSeparator + kmsAPIVersion +) + +type kmsAPIHandlers struct{} + +// registerKMSRouter - Registers KMS APIs +func registerKMSRouter(router *mux.Router) { + kmsAPI := kmsAPIHandlers{} + kmsRouter := router.PathPrefix(kmsPathPrefix).Subrouter() + + KMSVersions := []string{ + kmsAPIVersionPrefix, + } + + gz, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed)) + if err != nil { + // Static params, so this is very unlikely. + logger.Fatal(err, "Unable to initialize server") + } + + for _, version := range KMSVersions { + // KMS Status APIs + kmsRouter.Methods(http.MethodGet).Path(version + "/status").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSStatusHandler))) + // KMS Key APIs + kmsRouter.Methods(http.MethodPost).Path(version+"/key/create").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}") + kmsRouter.Methods(http.MethodPost).Path(version+"/key/import").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSImportKeyHandler))).Queries("key-id", "{key-id:.*}") + kmsRouter.Methods(http.MethodDelete).Path(version+"/key/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeleteKeyHandler))).Queries("key-id", "{key-id:.*}") + kmsRouter.Methods(http.MethodGet).Path(version+"/key/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListKeysHandler))).Queries("pattern", "{pattern:.*}") + kmsRouter.Methods(http.MethodGet).Path(version + "/key/status").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSKeyStatusHandler))) + + // KMS Policy APIs + kmsRouter.Methods(http.MethodPost).Path(version+"/policy/set").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSSetPolicyHandler))).Queries("policy", "{policy:.*}") + kmsRouter.Methods(http.MethodPost).Path(version+"/policy/assign").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSAssignPolicyHandler))).Queries("policy", "{policy:.*}") + kmsRouter.Methods(http.MethodGet).Path(version+"/policy/describe").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribePolicyHandler))).Queries("policy", "{policy:.*}") + kmsRouter.Methods(http.MethodGet).Path(version+"/policy/get").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSGetPolicyHandler))).Queries("policy", "{policy:.*}") + kmsRouter.Methods(http.MethodDelete).Path(version+"/policy/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeletePolicyHandler))).Queries("policy", "{policy:.*}") + kmsRouter.Methods(http.MethodGet).Path(version+"/policy/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListPoliciesHandler))).Queries("pattern", "{pattern:.*}") + + // KMS Identity APIs + kmsRouter.Methods(http.MethodGet).Path(version+"/identity/describe").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribeIdentityHandler))).Queries("identity", "{identity:.*}") + kmsRouter.Methods(http.MethodGet).Path(version + "/identity/describe-self").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribeSelfIdentityHandler))) + kmsRouter.Methods(http.MethodDelete).Path(version+"/identity/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeleteIdentityHandler))).Queries("identity", "{identity:.*}") + kmsRouter.Methods(http.MethodGet).Path(version+"/identity/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListIdentitiesHandler))).Queries("pattern", "{pattern:.*}") + } + + // If none of the routes match add default error handler routes + kmsRouter.NotFoundHandler = httpTraceAll(errorResponseHandler) + kmsRouter.MethodNotAllowedHandler = httpTraceAll(methodNotAllowedHandler("KMS")) +} diff --git a/cmd/routers.go b/cmd/routers.go index ed044d626..d04dac112 100644 --- a/cmd/routers.go +++ b/cmd/routers.go @@ -89,6 +89,9 @@ func configureServerHandler(endpointServerPools EndpointServerPools) (http.Handl // Add STS router always. registerSTSRouter(router) + // Add KMS router + registerKMSRouter(router) + // Add API router registerAPIRouter(router) diff --git a/internal/kms/kes.go b/internal/kms/kes.go index ad4323daa..4695c4ef8 100644 --- a/internal/kms/kes.go +++ b/internal/kms/kes.go @@ -314,10 +314,10 @@ func (c *kesClient) ListPolicies(ctx context.Context, pattern string) (*kes.Poli } // SetPolicy creates or updates a policy. -func (c *kesClient) SetPolicy(ctx context.Context, policy, data string) error { +func (c *kesClient) SetPolicy(ctx context.Context, policy string, policyItem *kes.Policy) error { c.lock.RLock() defer c.lock.RUnlock() - return c.client.SetPolicy(ctx, policy, &kes.Policy{Allow: []string{"*"}, Info: kes.PolicyInfo{Name: "my-app2"}}) + return c.client.SetPolicy(ctx, policy, policyItem) } // GetPolicy gets a policy from KMS. diff --git a/internal/kms/policy-manager.go b/internal/kms/policy-manager.go index 865de405b..d4c872632 100644 --- a/internal/kms/policy-manager.go +++ b/internal/kms/policy-manager.go @@ -37,7 +37,7 @@ type PolicyManager interface { AssignPolicy(ctx context.Context, policy, identity string) error // SetPolicy creates or updates a policy. - SetPolicy(ctx context.Context, policy, data string) error + SetPolicy(ctx context.Context, policy string, policyItem *kes.Policy) error // GetPolicy gets a policy from KMS. GetPolicy(ctx context.Context, policy string) (*kes.Policy, error)