diff --git a/cmd/api-errors.go b/cmd/api-errors.go
index 0f17a1373..33f75f6aa 100644
--- a/cmd/api-errors.go
+++ b/cmd/api-errors.go
@@ -96,6 +96,7 @@ const (
ErrNoSuchBucket
ErrNoSuchBucketPolicy
ErrNoSuchBucketLifecycle
+ ErrNoSuchBucketSSEConfig
ErrNoSuchKey
ErrNoSuchUpload
ErrNoSuchVersion
@@ -487,6 +488,11 @@ var errorCodes = errorCodeMap{
Description: "The bucket lifecycle configuration does not exist",
HTTPStatusCode: http.StatusNotFound,
},
+ ErrNoSuchBucketSSEConfig: {
+ Code: "ServerSideEncryptionConfigurationNotFoundError",
+ Description: "The server side encryption configuration was not found",
+ HTTPStatusCode: http.StatusNotFound,
+ },
ErrNoSuchKey: {
Code: "NoSuchKey",
Description: "The specified key does not exist.",
@@ -1719,6 +1725,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrNoSuchBucketPolicy
case BucketLifecycleNotFound:
apiErr = ErrNoSuchBucketLifecycle
+ case BucketSSEConfigNotFound:
+ apiErr = ErrNoSuchBucketSSEConfig
case *event.ErrInvalidEventName:
apiErr = ErrEventNotification
case *event.ErrInvalidARN:
diff --git a/cmd/api-router.go b/cmd/api-router.go
index 78d831b82..a8e6bf703 100644
--- a/cmd/api-router.go
+++ b/cmd/api-router.go
@@ -138,6 +138,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodGet).HandlerFunc(collectAPIStats("getbucketpolicy", httpTraceAll(api.GetBucketPolicyHandler))).Queries("policy", "")
// GetBucketLifecycle
bucket.Methods(http.MethodGet).HandlerFunc(collectAPIStats("getbucketlifecycle", httpTraceAll(api.GetBucketLifecycleHandler))).Queries("lifecycle", "")
+ // GetBucketEncryption
+ bucket.Methods(http.MethodGet).HandlerFunc(collectAPIStats("getbucketencryption", httpTraceAll(api.GetBucketEncryptionHandler))).Queries("encryption", "")
// Dummy Bucket Calls
// GetBucketACL -- this is a dummy call.
@@ -183,6 +185,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodGet).HandlerFunc(collectAPIStats("listobjectsv1", httpTraceAll(api.ListObjectsV1Handler)))
// PutBucketLifecycle
bucket.Methods(http.MethodPut).HandlerFunc(collectAPIStats("putbucketlifecycle", httpTraceAll(api.PutBucketLifecycleHandler))).Queries("lifecycle", "")
+ // PutBucketEncryption
+ bucket.Methods(http.MethodPut).HandlerFunc(collectAPIStats("putbucketencryption", httpTraceAll(api.PutBucketEncryptionHandler))).Queries("encryption", "")
+
// PutBucketPolicy
bucket.Methods(http.MethodPut).HandlerFunc(collectAPIStats("putbucketpolicy", httpTraceAll(api.PutBucketPolicyHandler))).Queries("policy", "")
@@ -204,6 +209,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodDelete).HandlerFunc(collectAPIStats("deletebucketpolicy", httpTraceAll(api.DeleteBucketPolicyHandler))).Queries("policy", "")
// DeleteBucketLifecycle
bucket.Methods(http.MethodDelete).HandlerFunc(collectAPIStats("deletebucketlifecycle", httpTraceAll(api.DeleteBucketLifecycleHandler))).Queries("lifecycle", "")
+ // DeleteBucketEncryption
+ bucket.Methods(http.MethodDelete).HandlerFunc(collectAPIStats("deletebucketencryption", httpTraceAll(api.DeleteBucketEncryptionHandler))).Queries("encryption", "")
// DeleteBucket
bucket.Methods(http.MethodDelete).HandlerFunc(collectAPIStats("deletebucket", httpTraceAll(api.DeleteBucketHandler)))
}
diff --git a/cmd/bucket-encryption-handlers.go b/cmd/bucket-encryption-handlers.go
new file mode 100644
index 000000000..eed04bdef
--- /dev/null
+++ b/cmd/bucket-encryption-handlers.go
@@ -0,0 +1,180 @@
+/*
+ * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cmd
+
+import (
+ "encoding/xml"
+ "io"
+ "net/http"
+
+ "github.com/gorilla/mux"
+ "github.com/minio/minio/cmd/logger"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
+ "github.com/minio/minio/pkg/bucket/policy"
+)
+
+const (
+ // Bucket Encryption configuration file name.
+ bucketSSEConfig = "bucket-encryption.xml"
+)
+
+// PutBucketEncryptionHandler - Stores given bucket encryption configuration
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketEncryption.html
+func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
+ ctx := newContext(r, w, "PutBucketEncryption")
+
+ defer logger.AuditLog(w, r, "PutBucketEncryption", mustGetClaimsFromToken(r))
+
+ objAPI := api.ObjectAPI()
+ if objAPI == nil {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ vars := mux.Vars(r)
+ bucket := vars["bucket"]
+
+ if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketEncryptionAction, bucket, ""); s3Error != ErrNone {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Check if bucket exists.
+ if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // PutBucketEncyrption API requires Content-Md5
+ if _, ok := r.Header["Content-Md5"]; !ok {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentMD5), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Parse bucket encryption xml
+ encConfig, err := validateBucketSSEConfig(io.LimitReader(r.Body, maxBucketSSEConfigSize))
+ if err != nil {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Return error if KMS is not initialized
+ if GlobalKMS == nil {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Store the bucket encryption configuration in the object layer
+ if err = objAPI.SetBucketSSEConfig(ctx, bucket, encConfig); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Update the in-memory bucket encryption cache
+ globalBucketSSEConfigSys.Set(bucket, *encConfig)
+
+ // Update peer MinIO servers of the updated bucket encryption config
+ globalNotificationSys.SetBucketSSEConfig(ctx, bucket, encConfig)
+
+ writeSuccessResponseHeadersOnly(w)
+}
+
+// GetBucketEncryptionHandler - Returns bucket policy configuration
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html
+func (api objectAPIHandlers) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
+ ctx := newContext(r, w, "GetBucketEncryption")
+
+ defer logger.AuditLog(w, r, "GetBucketEncryption", mustGetClaimsFromToken(r))
+
+ objAPI := api.ObjectAPI()
+ if objAPI == nil {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ vars := mux.Vars(r)
+ bucket := vars["bucket"]
+
+ if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketEncryptionAction, bucket, ""); s3Error != ErrNone {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Check if bucket exists
+ var err error
+ if _, err = objAPI.GetBucketInfo(ctx, bucket); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Fetch bucket encryption configuration from object layer
+ var encConfig *bucketsse.BucketSSEConfig
+ if encConfig, err = objAPI.GetBucketSSEConfig(ctx, bucket); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ var encConfigData []byte
+ if encConfigData, err = xml.Marshal(encConfig); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Write bucket encryption configuration to client
+ writeSuccessResponseXML(w, encConfigData)
+}
+
+// DeleteBucketEncryptionHandler - Removes bucket encryption configuration
+func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
+ ctx := newContext(r, w, "DeleteBucketEncryption")
+
+ defer logger.AuditLog(w, r, "DeleteBucketEncryption", mustGetClaimsFromToken(r))
+
+ objAPI := api.ObjectAPI()
+ if objAPI == nil {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ vars := mux.Vars(r)
+ bucket := vars["bucket"]
+
+ if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketEncryptionAction, bucket, ""); s3Error != ErrNone {
+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Check if bucket exists
+ var err error
+ if _, err = objAPI.GetBucketInfo(ctx, bucket); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Delete bucket encryption config from object layer
+ if err = objAPI.DeleteBucketSSEConfig(ctx, bucket); err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
+ // Remove entry from the in-memory bucket encryption cache
+ globalBucketSSEConfigSys.Remove(bucket)
+ // Update peer MinIO servers of the updated bucket encryption config
+ globalNotificationSys.RemoveBucketSSEConfig(ctx, bucket)
+
+ writeSuccessNoContent(w)
+}
diff --git a/cmd/bucket-encryption.go b/cmd/bucket-encryption.go
new file mode 100644
index 000000000..a28fecb36
--- /dev/null
+++ b/cmd/bucket-encryption.go
@@ -0,0 +1,169 @@
+/*
+ * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cmd
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "errors"
+ "io"
+ "path"
+ "sync"
+
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
+)
+
+// BucketSSEConfigSys - in-memory cache of bucket encryption config
+type BucketSSEConfigSys struct {
+ sync.RWMutex
+ bucketSSEConfigMap map[string]bucketsse.BucketSSEConfig
+}
+
+// NewBucketSSEConfigSys - Creates an empty in-memory bucket encryption configuration cache
+func NewBucketSSEConfigSys() *BucketSSEConfigSys {
+ return &BucketSSEConfigSys{
+ bucketSSEConfigMap: make(map[string]bucketsse.BucketSSEConfig),
+ }
+}
+
+// load - Loads the bucket encryption configuration for the given list of buckets
+func (sys *BucketSSEConfigSys) load(buckets []BucketInfo, objAPI ObjectLayer) error {
+ for _, bucket := range buckets {
+ config, err := objAPI.GetBucketSSEConfig(context.Background(), bucket.Name)
+ if err != nil {
+ if _, ok := err.(BucketSSEConfigNotFound); ok {
+ sys.Remove(bucket.Name)
+ }
+ continue
+ }
+ sys.Set(bucket.Name, *config)
+ }
+
+ return nil
+}
+
+// Init - Initializes in-memory bucket encryption config cache for the given list of buckets
+func (sys *BucketSSEConfigSys) Init(buckets []BucketInfo, objAPI ObjectLayer) error {
+ if objAPI == nil {
+ return errServerNotInitialized
+ }
+
+ // We don't cache bucket encryption config in gateway mode, nothing to do.
+ if globalIsGateway {
+ return nil
+ }
+
+ // Load bucket encryption config cache once during boot.
+ return sys.load(buckets, objAPI)
+}
+
+// Get - gets bucket encryption config for the given bucket.
+func (sys *BucketSSEConfigSys) Get(bucket string) (config bucketsse.BucketSSEConfig, ok bool) {
+ // We don't cache bucket encryption config in gateway mode.
+ if globalIsGateway {
+ objAPI := newObjectLayerWithoutSafeModeFn()
+ if objAPI == nil {
+ return
+ }
+
+ cfg, err := objAPI.GetBucketSSEConfig(context.Background(), bucket)
+ if err != nil {
+ return
+ }
+ return *cfg, true
+ }
+
+ sys.Lock()
+ defer sys.Unlock()
+ config, ok = sys.bucketSSEConfigMap[bucket]
+ return
+}
+
+// Set - sets bucket encryption config to given bucket name.
+func (sys *BucketSSEConfigSys) Set(bucket string, config bucketsse.BucketSSEConfig) {
+ // We don't cache bucket encryption config in gateway mode.
+ if globalIsGateway {
+ return
+ }
+
+ sys.Lock()
+ defer sys.Unlock()
+ sys.bucketSSEConfigMap[bucket] = config
+}
+
+// Remove - removes bucket encryption config for given bucket.
+func (sys *BucketSSEConfigSys) Remove(bucket string) {
+ sys.Lock()
+ defer sys.Unlock()
+
+ delete(sys.bucketSSEConfigMap, bucket)
+}
+
+// saveBucketSSEConfig - save bucket encryption config for given bucket.
+func saveBucketSSEConfig(ctx context.Context, objAPI ObjectLayer, bucket string, config *bucketsse.BucketSSEConfig) error {
+ data, err := xml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ // Path to store bucket encryption config for the given bucket.
+ configFile := path.Join(bucketConfigPrefix, bucket, bucketSSEConfig)
+ return saveConfig(ctx, objAPI, configFile, data)
+}
+
+// getBucketSSEConfig - get bucket encryption config for given bucket.
+func getBucketSSEConfig(objAPI ObjectLayer, bucket string) (*bucketsse.BucketSSEConfig, error) {
+ // Path to bucket-encryption.xml for the given bucket.
+ configFile := path.Join(bucketConfigPrefix, bucket, bucketSSEConfig)
+ configData, err := readConfig(context.Background(), objAPI, configFile)
+ if err != nil {
+ if err == errConfigNotFound {
+ err = BucketSSEConfigNotFound{Bucket: bucket}
+ }
+ return nil, err
+ }
+
+ return bucketsse.ParseBucketSSEConfig(bytes.NewReader(configData))
+}
+
+// removeBucketSSEConfig - removes bucket encryption config for given bucket.
+func removeBucketSSEConfig(ctx context.Context, objAPI ObjectLayer, bucket string) error {
+ // Path to bucket-encryption.xml for the given bucket.
+ configFile := path.Join(bucketConfigPrefix, bucket, bucketSSEConfig)
+
+ if err := objAPI.DeleteObject(ctx, minioMetaBucket, configFile); err != nil {
+ if _, ok := err.(ObjectNotFound); ok {
+ return BucketSSEConfigNotFound{Bucket: bucket}
+ }
+ return err
+ }
+ return nil
+}
+
+// validateBucketSSEConfig parses bucket encryption configuration and validates if it is supported by MinIO.
+func validateBucketSSEConfig(r io.Reader) (*bucketsse.BucketSSEConfig, error) {
+ encConfig, err := bucketsse.ParseBucketSSEConfig(r)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(encConfig.Rules) == 1 && encConfig.Rules[0].DefaultEncryptionAction.Algorithm == bucketsse.AES256 {
+ return encConfig, nil
+ }
+ return nil, errors.New("Unsupported bucket encryption configuration")
+}
diff --git a/cmd/bucket-encryption_test.go b/cmd/bucket-encryption_test.go
new file mode 100644
index 000000000..25548caf3
--- /dev/null
+++ b/cmd/bucket-encryption_test.go
@@ -0,0 +1,70 @@
+/*
+ * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cmd
+
+import (
+ "bytes"
+ "errors"
+ "testing"
+)
+
+func TestValidateBucketSSEConfig(t *testing.T) {
+ testCases := []struct {
+ inputXML string
+ expectedErr error
+ shouldPass bool
+ }{
+ // MinIO supported XML
+ {
+ inputXML: `
+
+
+ AES256
+
+
+ `,
+ expectedErr: nil,
+ shouldPass: true,
+ },
+ // Unsupported XML
+ {
+ inputXML: `
+
+
+ aws:kms
+ arn:aws:kms:us-east-1:1234/5678example
+
+
+ `,
+ expectedErr: errors.New("Unsupported bucket encryption configuration"),
+ shouldPass: false,
+ },
+ }
+
+ for i, tc := range testCases {
+ _, err := validateBucketSSEConfig(bytes.NewReader([]byte(tc.inputXML)))
+ if tc.shouldPass && err != nil {
+ t.Fatalf("Test case %d: Expected to succeed but got %s", i+1, err)
+ }
+
+ if !tc.shouldPass {
+ if err == nil || err != nil && err.Error() != tc.expectedErr.Error() {
+ t.Fatalf("Test case %d: Expected %s but got %s", i+1, tc.expectedErr, err)
+ }
+ }
+ }
+}
diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go
index 1790822ca..f6777aae1 100644
--- a/cmd/bucket-handlers.go
+++ b/cmd/bucket-handlers.go
@@ -757,8 +757,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
pReader := NewPutObjReader(rawReader, nil, nil)
var objectEncryptionKey []byte
+ // Check if bucket encryption is enabled
+ _, encEnabled := globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions
- if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) {
+ if (globalAutoEncryption || encEnabled) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
}
// get gateway encryption options
diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go
index 727d25d43..6be6b4f8c 100644
--- a/cmd/fs-v1.go
+++ b/cmd/fs-v1.go
@@ -37,9 +37,12 @@ import (
"github.com/minio/minio/cmd/config"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
+
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
+
"github.com/minio/minio/pkg/lock"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/mimedb"
@@ -1292,6 +1295,21 @@ func (fs *FSObjects) DeleteBucketLifecycle(ctx context.Context, bucket string) e
return removeLifecycleConfig(ctx, fs, bucket)
}
+// GetBucketSSEConfig returns bucket encryption config on given bucket
+func (fs *FSObjects) GetBucketSSEConfig(ctx context.Context, bucket string) (*bucketsse.BucketSSEConfig, error) {
+ return getBucketSSEConfig(fs, bucket)
+}
+
+// SetBucketSSEConfig sets bucket encryption config on given bucket
+func (fs *FSObjects) SetBucketSSEConfig(ctx context.Context, bucket string, config *bucketsse.BucketSSEConfig) error {
+ return saveBucketSSEConfig(ctx, fs, bucket, config)
+}
+
+// DeleteBucketSSEConfig deletes bucket encryption config on given bucket
+func (fs *FSObjects) DeleteBucketSSEConfig(ctx context.Context, bucket string) error {
+ return removeBucketSSEConfig(ctx, fs, bucket)
+}
+
// ListObjectsV2 lists all blobs in bucket filtered by prefix
func (fs *FSObjects) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error) {
marker := continuationToken
diff --git a/cmd/gateway-unsupported.go b/cmd/gateway-unsupported.go
index fd8a0759c..000f32cf5 100644
--- a/cmd/gateway-unsupported.go
+++ b/cmd/gateway-unsupported.go
@@ -21,9 +21,12 @@ import (
"errors"
"github.com/minio/minio/cmd/logger"
+
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
+
"github.com/minio/minio/pkg/madmin"
)
@@ -128,6 +131,21 @@ func (a GatewayUnsupported) DeleteBucketLifecycle(ctx context.Context, bucket st
return NotImplemented{}
}
+// GetBucketSSEConfig returns bucket encryption config on given bucket
+func (a GatewayUnsupported) GetBucketSSEConfig(ctx context.Context, bucket string) (*bucketsse.BucketSSEConfig, error) {
+ return nil, NotImplemented{}
+}
+
+// SetBucketSSEConfig sets bucket encryption config on given bucket
+func (a GatewayUnsupported) SetBucketSSEConfig(ctx context.Context, bucket string, config *bucketsse.BucketSSEConfig) error {
+ return NotImplemented{}
+}
+
+// DeleteBucketSSEConfig deletes bucket encryption config on given bucket
+func (a GatewayUnsupported) DeleteBucketSSEConfig(ctx context.Context, bucket string) error {
+ return NotImplemented{}
+}
+
// ReloadFormat - Not implemented stub.
func (a GatewayUnsupported) ReloadFormat(ctx context.Context, dryRun bool) error {
return NotImplemented{}
diff --git a/cmd/globals.go b/cmd/globals.go
index 6ba6d5cdd..a535258a7 100644
--- a/cmd/globals.go
+++ b/cmd/globals.go
@@ -95,6 +95,9 @@ const (
// Limit of location constraint XML for unauthenticted PUT bucket operations.
maxLocationConstraintSize = 3 * humanize.MiByte
+
+ // Maximum size of default bucket encryption configuration allowed
+ maxBucketSSEConfigSize = 1 * humanize.MiByte
)
var globalCLIContext = struct {
@@ -144,7 +147,8 @@ var (
globalPolicySys *PolicySys
globalIAMSys *IAMSys
- globalLifecycleSys *LifecycleSys
+ globalLifecycleSys *LifecycleSys
+ globalBucketSSEConfigSys *BucketSSEConfigSys
globalStorageClass storageclass.Config
globalLDAPConfig xldap.Config
diff --git a/cmd/lifecycle.go b/cmd/lifecycle.go
index b4423ef89..03cb081bc 100644
--- a/cmd/lifecycle.go
+++ b/cmd/lifecycle.go
@@ -52,7 +52,7 @@ func (sys *LifecycleSys) Set(bucketName string, lifecycle lifecycle.Lifecycle) {
}
// Get - gets lifecycle config associated to a given bucket name.
-func (sys *LifecycleSys) Get(bucketName string) (lifecycle lifecycle.Lifecycle, ok bool) {
+func (sys *LifecycleSys) Get(bucketName string) (lc lifecycle.Lifecycle, ok bool) {
if globalIsGateway {
// When gateway is enabled, no cached value
// is used to validate life cycle policies.
@@ -60,14 +60,18 @@ func (sys *LifecycleSys) Get(bucketName string) (lifecycle lifecycle.Lifecycle,
if objAPI == nil {
return
}
+
l, err := objAPI.GetBucketLifecycle(context.Background(), bucketName)
- return *l, err == nil
+ if err != nil {
+ return
+ }
+ return *l, true
}
+
sys.Lock()
defer sys.Unlock()
-
- l, ok := sys.bucketLifecycleMap[bucketName]
- return l, ok
+ lc, ok = sys.bucketLifecycleMap[bucketName]
+ return
}
func saveLifecycleConfig(ctx context.Context, objAPI ObjectLayer, bucketName string, bucketLifecycle *lifecycle.Lifecycle) error {
@@ -149,7 +153,7 @@ func (sys *LifecycleSys) load(buckets []BucketInfo, objAPI ObjectLayer) error {
return nil
}
-// Remove - removes policy for given bucket name.
+// Remove - removes lifecycle config for given bucket name.
func (sys *LifecycleSys) Remove(bucketName string) {
sys.Lock()
defer sys.Unlock()
diff --git a/cmd/notification.go b/cmd/notification.go
index ffacd8ce3..8abbc1892 100644
--- a/cmd/notification.go
+++ b/cmd/notification.go
@@ -32,9 +32,12 @@ import (
"github.com/klauspost/compress/zip"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
+
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
"github.com/minio/minio/pkg/bucket/policy"
+
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net"
@@ -576,6 +579,41 @@ func (sys *NotificationSys) RemoveBucketLifecycle(ctx context.Context, bucketNam
}()
}
+// SetBucketSSEConfig - calls SetBucketSSEConfig on all peers.
+func (sys *NotificationSys) SetBucketSSEConfig(ctx context.Context, bucketName string,
+ encConfig *bucketsse.BucketSSEConfig) {
+ go func() {
+ ng := WithNPeers(len(sys.peerClients))
+ for idx, client := range sys.peerClients {
+ if client == nil {
+ continue
+ }
+ client := client
+ ng.Go(ctx, func() error {
+ return client.SetBucketSSEConfig(bucketName, encConfig)
+ }, idx, *client.host)
+ }
+ ng.Wait()
+ }()
+}
+
+// RemoveBucketSSEConfig - calls RemoveBucketSSEConfig on all peers.
+func (sys *NotificationSys) RemoveBucketSSEConfig(ctx context.Context, bucketName string) {
+ go func() {
+ ng := WithNPeers(len(sys.peerClients))
+ for idx, client := range sys.peerClients {
+ if client == nil {
+ continue
+ }
+ client := client
+ ng.Go(ctx, func() error {
+ return client.RemoveBucketSSEConfig(bucketName)
+ }, idx, *client.host)
+ }
+ ng.Wait()
+ }()
+}
+
// PutBucketNotification - calls PutBucketNotification RPC call on all peers.
func (sys *NotificationSys) PutBucketNotification(ctx context.Context, bucketName string, rulesMap event.RulesMap) {
go func() {
diff --git a/cmd/object-api-errors.go b/cmd/object-api-errors.go
index c9d6d7a06..bd9a99855 100644
--- a/cmd/object-api-errors.go
+++ b/cmd/object-api-errors.go
@@ -262,6 +262,13 @@ func (e BucketLifecycleNotFound) Error() string {
return "No bucket life cycle found for bucket : " + e.Bucket
}
+// BucketSSEConfigNotFound - no bucket encryption config found
+type BucketSSEConfigNotFound GenericError
+
+func (e BucketSSEConfigNotFound) Error() string {
+ return "No bucket encryption found for bucket: " + e.Bucket
+}
+
/// Bucket related errors.
// BucketNameInvalid - bucketname provided is invalid.
diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go
index bb0bcdab3..67db9027d 100644
--- a/cmd/object-api-interface.go
+++ b/cmd/object-api-interface.go
@@ -22,9 +22,11 @@ import (
"net/http"
"github.com/minio/minio-go/v6/pkg/encrypt"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
+
"github.com/minio/minio/pkg/madmin"
)
@@ -121,6 +123,11 @@ type ObjectLayer interface {
GetBucketLifecycle(context.Context, string) (*lifecycle.Lifecycle, error)
DeleteBucketLifecycle(context.Context, string) error
+ // Bucket Encryption operations
+ SetBucketSSEConfig(context.Context, string, *bucketsse.BucketSSEConfig) error
+ GetBucketSSEConfig(context.Context, string) (*bucketsse.BucketSSEConfig, error)
+ DeleteBucketSSEConfig(context.Context, string) error
+
// Backend related metrics
GetMetrics(ctx context.Context) (*Metrics, error)
diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go
index aefe1cd59..6a5a300bb 100644
--- a/cmd/object-handlers.go
+++ b/cmd/object-handlers.go
@@ -766,8 +766,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return
}
+ // Check if bucket encryption is enabled
+ _, encEnabled := globalBucketSSEConfigSys.Get(dstBucket)
// This request header needs to be set prior to setting ObjectOptions
- if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) {
+ if (globalAutoEncryption || encEnabled) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
}
@@ -1259,8 +1261,10 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
}
}
+ // Check if bucket encryption is enabled
+ _, encEnabled := globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions
- if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) {
+ if (globalAutoEncryption || encEnabled) && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) {
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
}
@@ -1426,9 +1430,10 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
-
+ // Check if bucket encryption is enabled
+ _, encEnabled := globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions
- if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) {
+ if (globalAutoEncryption || encEnabled) && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) {
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
}
diff --git a/cmd/peer-rest-client.go b/cmd/peer-rest-client.go
index 26a04a4cb..4f874c9ca 100644
--- a/cmd/peer-rest-client.go
+++ b/cmd/peer-rest-client.go
@@ -31,6 +31,7 @@ import (
"github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/rest"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
"github.com/minio/minio/pkg/bucket/policy"
@@ -411,6 +412,37 @@ func (client *peerRESTClient) SetBucketLifecycle(bucket string, bucketLifecycle
return nil
}
+// RemoveBucketSSEConfig - Remove bucket encryption configuration on the peer node
+func (client *peerRESTClient) RemoveBucketSSEConfig(bucket string) error {
+ values := make(url.Values)
+ values.Set(peerRESTBucket, bucket)
+ respBody, err := client.call(peerRESTMethodBucketEncryptionRemove, values, nil, -1)
+ if err != nil {
+ return err
+ }
+ defer http.DrainBody(respBody)
+ return nil
+}
+
+// SetBucketSSEConfig - Set bucket encryption configuration on the peer node
+func (client *peerRESTClient) SetBucketSSEConfig(bucket string, encConfig *bucketsse.BucketSSEConfig) error {
+ values := make(url.Values)
+ values.Set(peerRESTBucket, bucket)
+
+ var reader bytes.Buffer
+ err := gob.NewEncoder(&reader).Encode(encConfig)
+ if err != nil {
+ return err
+ }
+
+ respBody, err := client.call(peerRESTMethodBucketEncryptionSet, values, &reader, -1)
+ if err != nil {
+ return err
+ }
+ defer http.DrainBody(respBody)
+ return nil
+}
+
// PutBucketNotification - Put bucket notification on the peer node.
func (client *peerRESTClient) PutBucketNotification(bucket string, rulesMap event.RulesMap) error {
values := make(url.Values)
diff --git a/cmd/peer-rest-common.go b/cmd/peer-rest-common.go
index 2b62a5125..80a270305 100644
--- a/cmd/peer-rest-common.go
+++ b/cmd/peer-rest-common.go
@@ -56,6 +56,8 @@ const (
peerRESTMethodListen = "/listen"
peerRESTMethodBucketLifecycleSet = "/setbucketlifecycle"
peerRESTMethodBucketLifecycleRemove = "/removebucketlifecycle"
+ peerRESTMethodBucketEncryptionSet = "/setbucketencryption"
+ peerRESTMethodBucketEncryptionRemove = "/removebucketencryption"
peerRESTMethodLog = "/log"
peerRESTMethodHardwareCPUInfo = "/cpuhardwareinfo"
peerRESTMethodHardwareNetworkInfo = "/networkhardwareinfo"
diff --git a/cmd/peer-rest-server.go b/cmd/peer-rest-server.go
index ddc34f73b..94a64a673 100644
--- a/cmd/peer-rest-server.go
+++ b/cmd/peer-rest-server.go
@@ -31,6 +31,7 @@ import (
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
"github.com/minio/minio/pkg/bucket/policy"
@@ -693,6 +694,49 @@ func (s *peerRESTServer) SetBucketLifecycleHandler(w http.ResponseWriter, r *htt
w.(http.Flusher).Flush()
}
+// RemoveBucketSSEConfigHandler - Remove bucket encryption.
+func (s *peerRESTServer) RemoveBucketSSEConfigHandler(w http.ResponseWriter, r *http.Request) {
+ if !s.IsValid(w, r) {
+ s.writeErrorResponse(w, errors.New("Invalid request"))
+ return
+ }
+
+ vars := mux.Vars(r)
+ bucketName := vars[peerRESTBucket]
+ if bucketName == "" {
+ s.writeErrorResponse(w, errors.New("Bucket name is missing"))
+ return
+ }
+
+ globalBucketSSEConfigSys.Remove(bucketName)
+ w.(http.Flusher).Flush()
+}
+
+// SetBucketSSEConfigHandler - Set bucket encryption.
+func (s *peerRESTServer) SetBucketSSEConfigHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ bucketName := vars[peerRESTBucket]
+ if bucketName == "" {
+ s.writeErrorResponse(w, errors.New("Bucket name is missing"))
+ return
+ }
+
+ var encConfig bucketsse.BucketSSEConfig
+ if r.ContentLength < 0 {
+ s.writeErrorResponse(w, errInvalidArgument)
+ return
+ }
+
+ err := gob.NewDecoder(r.Body).Decode(&encConfig)
+ if err != nil {
+ s.writeErrorResponse(w, err)
+ return
+ }
+
+ globalBucketSSEConfigSys.Set(bucketName, encConfig)
+ w.(http.Flusher).Flush()
+}
+
type remoteTargetExistsResp struct {
Exists bool
}
@@ -1179,6 +1223,8 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodReloadFormat).HandlerFunc(httpTraceHdrs(server.ReloadFormatHandler)).Queries(restQueries(peerRESTDryRun)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodBucketLifecycleSet).HandlerFunc(httpTraceHdrs(server.SetBucketLifecycleHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodBucketLifecycleRemove).HandlerFunc(httpTraceHdrs(server.RemoveBucketLifecycleHandler)).Queries(restQueries(peerRESTBucket)...)
+ subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodBucketEncryptionSet).HandlerFunc(httpTraceHdrs(server.SetBucketSSEConfigHandler)).Queries(restQueries(peerRESTBucket)...)
+ subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodBucketEncryptionRemove).HandlerFunc(httpTraceHdrs(server.RemoveBucketSSEConfigHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodBackgroundOpsStatus).HandlerFunc(server.BackgroundOpsStatusHandler)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodTrace).HandlerFunc(server.TraceHandler)
diff --git a/cmd/server-main.go b/cmd/server-main.go
index 85349e424..62a8995b7 100644
--- a/cmd/server-main.go
+++ b/cmd/server-main.go
@@ -159,6 +159,9 @@ func newAllSubsystems() {
// Create new lifecycle system.
globalLifecycleSys = NewLifecycleSys()
+
+ // Create new bucket encryption subsystem
+ globalBucketSSEConfigSys = NewBucketSSEConfigSys()
}
func initSafeMode(buckets []BucketInfo) (err error) {
@@ -280,6 +283,10 @@ func initAllSubsystems(buckets []BucketInfo, newObject ObjectLayer) (err error)
return fmt.Errorf("Unable to initialize lifecycle system: %w", err)
}
+ // Initialize bucket encryption subsystem.
+ if err = globalBucketSSEConfigSys.Init(buckets, newObject); err != nil {
+ return fmt.Errorf("Unable to initialize bucket encryption subsystem: %w", err)
+ }
return nil
}
diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go
index 80e61149d..c8461e07a 100644
--- a/cmd/test-utils_test.go
+++ b/cmd/test-utils_test.go
@@ -374,6 +374,9 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer {
globalLifecycleSys = NewLifecycleSys()
globalLifecycleSys.Init(buckets, objLayer)
+ globalBucketSSEConfigSys = NewBucketSSEConfigSys()
+ globalBucketSSEConfigSys.Init(buckets, objLayer)
+
return testServer
}
@@ -1977,6 +1980,9 @@ func ExecObjectLayerTest(t TestErrHandler, objTest objTestType) {
globalPolicySys = NewPolicySys()
globalPolicySys.Init(buckets, objLayer)
+ globalBucketSSEConfigSys = NewBucketSSEConfigSys()
+ globalBucketSSEConfigSys.Init(buckets, objLayer)
+
// Executing the object layer tests for single node setup.
objTest(objLayer, FSTestStr, t)
diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go
index 9275bffc1..bb3a739be 100644
--- a/cmd/web-handlers.go
+++ b/cmd/web-handlers.go
@@ -987,7 +987,9 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
return
}
- if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) {
+ // Check if bucket encryption is enabled
+ _, encEnabled := globalBucketSSEConfigSys.Get(bucket)
+ if (globalAutoEncryption || encEnabled) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
}
diff --git a/cmd/xl-sets.go b/cmd/xl-sets.go
index 2a6b84d1e..fd4ceba3f 100644
--- a/cmd/xl-sets.go
+++ b/cmd/xl-sets.go
@@ -30,6 +30,7 @@ import (
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bpool"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
@@ -597,6 +598,21 @@ func (s *xlSets) DeleteBucketLifecycle(ctx context.Context, bucket string) error
return removeLifecycleConfig(ctx, s, bucket)
}
+// GetBucketSSEConfig returns bucket encryption config on given bucket
+func (s *xlSets) GetBucketSSEConfig(ctx context.Context, bucket string) (*bucketsse.BucketSSEConfig, error) {
+ return getBucketSSEConfig(s, bucket)
+}
+
+// SetBucketSSEConfig sets bucket encryption config on given bucket
+func (s *xlSets) SetBucketSSEConfig(ctx context.Context, bucket string, config *bucketsse.BucketSSEConfig) error {
+ return saveBucketSSEConfig(ctx, s, bucket, config)
+}
+
+// DeleteBucketSSEConfig deletes bucket encryption config on given bucket
+func (s *xlSets) DeleteBucketSSEConfig(ctx context.Context, bucket string) error {
+ return removeBucketSSEConfig(ctx, s, bucket)
+}
+
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
func (s *xlSets) IsNotificationSupported() bool {
return s.getHashedSet("").IsNotificationSupported()
diff --git a/cmd/xl-v1-bucket.go b/cmd/xl-v1-bucket.go
index 0c599dc04..0adde25b2 100644
--- a/cmd/xl-v1-bucket.go
+++ b/cmd/xl-v1-bucket.go
@@ -22,8 +22,10 @@ import (
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/cmd/logger"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/policy"
+
"github.com/minio/minio/pkg/sync/errgroup"
)
@@ -293,6 +295,21 @@ func (xl xlObjects) DeleteBucketLifecycle(ctx context.Context, bucket string) er
return removeLifecycleConfig(ctx, xl, bucket)
}
+// GetBucketSSEConfig returns bucket encryption config on given bucket
+func (xl xlObjects) GetBucketSSEConfig(ctx context.Context, bucket string) (*bucketsse.BucketSSEConfig, error) {
+ return getBucketSSEConfig(xl, bucket)
+}
+
+// SetBucketSSEConfig sets bucket encryption config on given bucket
+func (xl xlObjects) SetBucketSSEConfig(ctx context.Context, bucket string, config *bucketsse.BucketSSEConfig) error {
+ return saveBucketSSEConfig(ctx, xl, bucket, config)
+}
+
+// DeleteBucketSSEConfig deletes bucket encryption config on given bucket
+func (xl xlObjects) DeleteBucketSSEConfig(ctx context.Context, bucket string) error {
+ return removeBucketSSEConfig(ctx, xl, bucket)
+}
+
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
func (xl xlObjects) IsNotificationSupported() bool {
return true
diff --git a/cmd/xl-zones.go b/cmd/xl-zones.go
index 6d52c92cf..a3ab71f7b 100644
--- a/cmd/xl-zones.go
+++ b/cmd/xl-zones.go
@@ -28,6 +28,7 @@ import (
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
+ bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
@@ -1147,6 +1148,21 @@ func (z *xlZones) DeleteBucketLifecycle(ctx context.Context, bucket string) erro
return removeLifecycleConfig(ctx, z, bucket)
}
+// GetBucketSSEConfig returns bucket encryption config on given bucket
+func (z *xlZones) GetBucketSSEConfig(ctx context.Context, bucket string) (*bucketsse.BucketSSEConfig, error) {
+ return getBucketSSEConfig(z, bucket)
+}
+
+// SetBucketSSEConfig sets bucket encryption config on given bucket
+func (z *xlZones) SetBucketSSEConfig(ctx context.Context, bucket string, config *bucketsse.BucketSSEConfig) error {
+ return saveBucketSSEConfig(ctx, z, bucket, config)
+}
+
+// DeleteBucketSSEConfig deletes bucket encryption config on given bucket
+func (z *xlZones) DeleteBucketSSEConfig(ctx context.Context, bucket string) error {
+ return removeBucketSSEConfig(ctx, z, bucket)
+}
+
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
func (z *xlZones) IsNotificationSupported() bool {
return true
diff --git a/pkg/bucket/encryption/bucket-sse-config.go b/pkg/bucket/encryption/bucket-sse-config.go
new file mode 100644
index 000000000..646eb4f25
--- /dev/null
+++ b/pkg/bucket/encryption/bucket-sse-config.go
@@ -0,0 +1,103 @@
+/*
+ * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cmd
+
+import (
+ "encoding/xml"
+ "errors"
+ "io"
+)
+
+const (
+ // AES256 is used with SSE-S3
+ AES256 SSEAlgorithm = "AES256"
+ // AWSKms is used with SSE-KMS
+ AWSKms SSEAlgorithm = "aws:kms"
+)
+
+// SSEAlgorithm - represents valid SSE algorithms supported; currently only AES256 is supported
+type SSEAlgorithm string
+
+// UnmarshalXML - Unmarshals XML tag to valid SSE algorithm
+func (alg *SSEAlgorithm) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ var s string
+ if err := d.DecodeElement(&s, &start); err != nil {
+ return err
+ }
+
+ switch s {
+ case string(AES256):
+ *alg = AES256
+ case string(AWSKms):
+ *alg = AWSKms
+ default:
+ return errors.New("Unknown SSE algorithm")
+ }
+
+ return nil
+}
+
+// MarshalXML - Marshals given SSE algorithm to valid XML
+func (alg *SSEAlgorithm) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ return e.EncodeElement(string(*alg), start)
+}
+
+// EncryptionAction - for ApplyServerSideEncryptionByDefault XML tag
+type EncryptionAction struct {
+ Algorithm SSEAlgorithm `xml:"SSEAlgorithm"`
+ MasterKeyID string `xml:"KMSMasterKeyID"`
+}
+
+// SSERule - for ServerSideEncryptionConfiguration XML tag
+type SSERule struct {
+ DefaultEncryptionAction EncryptionAction `xml:"ApplyServerSideEncryptionByDefault"`
+}
+
+// BucketSSEConfig - represents default bucket encryption configuration
+type BucketSSEConfig struct {
+ XMLName xml.Name `xml:"ServerSideEncryptionConfiguration"`
+ Rules []SSERule `xml:"Rule"`
+}
+
+// ParseBucketSSEConfig - Decodes given XML to a valid default bucket encryption config
+func ParseBucketSSEConfig(r io.Reader) (*BucketSSEConfig, error) {
+ var config BucketSSEConfig
+ err := xml.NewDecoder(r).Decode(&config)
+ if err != nil {
+ return nil, err
+ }
+
+ // Validates server-side encryption config rules
+ // Only one rule is allowed on AWS S3
+ if len(config.Rules) != 1 {
+ return nil, errors.New("Only one server-side encryption rule is allowed")
+ }
+
+ for _, rule := range config.Rules {
+ switch rule.DefaultEncryptionAction.Algorithm {
+ case AES256:
+ if rule.DefaultEncryptionAction.MasterKeyID != "" {
+ return nil, errors.New("MasterKeyID is allowed with aws:kms only")
+ }
+ case AWSKms:
+ if rule.DefaultEncryptionAction.MasterKeyID == "" {
+ return nil, errors.New("MasterKeyID is missing")
+ }
+ }
+ }
+ return &config, nil
+}
diff --git a/pkg/bucket/encryption/bucket-sse-config_test.go b/pkg/bucket/encryption/bucket-sse-config_test.go
new file mode 100644
index 000000000..35a8c4fba
--- /dev/null
+++ b/pkg/bucket/encryption/bucket-sse-config_test.go
@@ -0,0 +1,159 @@
+/*
+ * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cmd
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "testing"
+)
+
+// TestParseBucketSSEConfig performs basic sanity tests on ParseBucketSSEConfig
+func TestParseBucketSSEConfig(t *testing.T) {
+ testCases := []struct {
+ inputXML string
+ expectedErr error
+ shouldPass bool
+ }{
+ // 1. Valid XML SSE-S3
+ {
+ inputXML: `
+
+
+ AES256
+
+
+ `,
+ expectedErr: nil,
+ shouldPass: true,
+ },
+ // 2. Valid XML SSE-KMS
+ {
+ inputXML: `
+
+
+ aws:kms
+ arn:aws:kms:us-east-1:1234/5678example
+
+
+ `,
+ expectedErr: nil,
+ shouldPass: true,
+ },
+ // 3. Invalid - more than one rule
+ {
+ inputXML: `
+
+
+ AES256
+
+
+
+
+ AES256
+
+
+ `,
+ expectedErr: errors.New("Only one server-side encryption rule is allowed"),
+ shouldPass: false,
+ },
+ // 4. Invalid XML - master key ID present in AES256
+ {
+ inputXML: `
+
+
+ AES256
+ arn:aws:kms:us-east-1:1234/5678example
+
+
+ `,
+ expectedErr: errors.New("MasterKeyID is allowed with aws:kms only"),
+ shouldPass: false,
+ },
+ // 5. Invalid XML - master key ID not found in aws:kms algorithm
+ {
+ inputXML: `
+
+
+ aws:kms
+
+
+ `,
+ expectedErr: errors.New("MasterKeyID is missing"),
+ shouldPass: false,
+ },
+ // 6. Invalid Algorithm
+ {
+ inputXML: `
+
+
+ InvalidAlgorithm
+
+
+ `,
+ expectedErr: errors.New("Unknown SSE algorithm"),
+ shouldPass: false,
+ },
+ // 7. Allow missing namespace
+ {
+ inputXML: `
+
+
+ AES256
+
+
+ `,
+ expectedErr: nil,
+ shouldPass: true,
+ },
+ }
+
+ actualConfig := &BucketSSEConfig{
+ XMLName: xml.Name{
+ Local: "ServerSideEncryptionConfiguration",
+ },
+ Rules: []SSERule{
+ {
+ DefaultEncryptionAction: EncryptionAction{
+ Algorithm: AES256,
+ },
+ },
+ },
+ }
+
+ for i, tc := range testCases {
+ _, err := ParseBucketSSEConfig(bytes.NewReader([]byte(tc.inputXML)))
+ if tc.shouldPass && err != nil {
+ t.Fatalf("Test case %d: Expected to succeed but got %s", i+1, err)
+ }
+
+ if !tc.shouldPass {
+ if err == nil || err != nil && err.Error() != tc.expectedErr.Error() {
+ t.Fatalf("Test case %d: Expected %s but got %s", i+1, tc.expectedErr, err)
+ }
+ }
+
+ if !tc.shouldPass {
+ continue
+ }
+
+ if actualXML, err := xml.Marshal(actualConfig); err != nil && bytes.Equal(actualXML, []byte(tc.inputXML)) {
+ t.Fatalf("Test case %d: Expected config %s but got %s", i+1, string(actualXML), tc.inputXML)
+ }
+ }
+}
diff --git a/pkg/bucket/policy/action.go b/pkg/bucket/policy/action.go
index 5e51e280d..33933a9d7 100644
--- a/pkg/bucket/policy/action.go
+++ b/pkg/bucket/policy/action.go
@@ -113,6 +113,11 @@ const (
PutObjectTaggingAction = "s3:PutObjectTagging"
// DeleteObjectTaggingAction - Delete Object Tags API action
DeleteObjectTaggingAction = "s3:DeleteObjectTagging"
+
+ // PutBucketEncryptionAction - PutBucketEncryption REST API action
+ PutBucketEncryptionAction = "s3:PutEncryptionConfiguration"
+ // GetBucketEncryptionAction - GetBucketEncryption REST API action
+ GetBucketEncryptionAction = "s3:GetEncryptionConfiguration"
)
// isObjectAction - returns whether action is object type or not.