From 570ae46e720dd056bfa4b7e44984533c1cb58ab7 Mon Sep 17 00:00:00 2001 From: Tristan Newmann Date: Tue, 21 Nov 2023 16:50:22 +1100 Subject: [PATCH 1/6] Support S3 server side encryption headers for Write and Copy Adds support to s3blob for setting AWS S3 server side encryption headers when making requests that require such headers. The additional settings can be specified with the `ssetype` and `kmskeyid` URL params, similar to the other configuration settings --- aws/aws.go | 4 +++ blob/s3blob/s3blob.go | 51 ++++++++++++++++++++++++++++++++++---- blob/s3blob/s3blob_test.go | 2 ++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index 55dc38644d..4a7a81e355 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -103,6 +103,8 @@ func ConfigFromURLParams(q url.Values) (*aws.Config, error) { } cfg.S3ForcePathStyle = aws.Bool(b) case "awssdk": + case "ssetype": + case "kmskeyid": // ignore, should be handled before this default: return nil, fmt.Errorf("unknown query parameter %q", param) @@ -198,6 +200,8 @@ func V2ConfigFromURLParams(ctx context.Context, q url.Values) (awsv2.Config, err case "profile": opts = append(opts, awsv2cfg.WithSharedConfigProfile(value)) case "awssdk": + case "ssetype": + case "kmskeyid": // ignore, should be handled before this default: return awsv2.Config{}, fmt.Errorf("unknown query parameter %q", param) diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go index c7a5bd3c41..ece2000006 100644 --- a/blob/s3blob/s3blob.go +++ b/blob/s3blob/s3blob.go @@ -152,6 +152,10 @@ func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket return nil, fmt.Errorf("open bucket %v: %v", u, err) } clientV2 := s3v2.NewFromConfig(cfg) + + o.Options.EncryptionType = u.Query().Get("ssetype") + o.Options.KMSEncryptionId = u.Query().Get("kmskeyid") + return OpenBucketV2(ctx, clientV2, u.Host, &o.Options) } configProvider := &gcaws.ConfigOverrider{ @@ -162,6 +166,10 @@ func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket return nil, fmt.Errorf("open bucket %v: %v", u, err) } configProvider.Configs = append(configProvider.Configs, overrideCfg) + + o.Options.EncryptionType = u.Query().Get("ssetype") + o.Options.KMSEncryptionId = u.Query().Get("kmskeyid") + return OpenBucket(ctx, configProvider, u.Host, &o.Options) } @@ -171,6 +179,10 @@ type Options struct { // Some S3-compatible services (like CEPH) do not currently support // ListObjectsV2. UseLegacyList bool + + EncryptionType string + + KMSEncryptionId string } // openBucket returns an S3 Bucket. @@ -193,11 +205,13 @@ func openBucket(ctx context.Context, useV2 bool, sess client.ConfigProvider, cli client = s3.New(sess) } return &bucket{ - useV2: useV2, - name: bucketName, - client: client, - clientV2: clientV2, - useLegacyList: opts.UseLegacyList, + useV2: useV2, + name: bucketName, + client: client, + clientV2: clientV2, + useLegacyList: opts.UseLegacyList, + kmsKeyId: opts.KMSEncryptionId, + encryptionType: opts.EncryptionType, }, nil } @@ -365,6 +379,9 @@ type bucket struct { client *s3.S3 clientV2 *s3v2.Client useLegacyList bool + + encryptionType string + kmsKeyId string } func (b *bucket) Close() error { @@ -973,6 +990,12 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str if len(opts.ContentMD5) > 0 { reqV2.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString(opts.ContentMD5)) } + if b.encryptionType != "" { + reqV2.ServerSideEncryption = typesv2.ServerSideEncryption(b.encryptionType) + } + if b.kmsKeyId != "" { + reqV2.SSEKMSKeyId = aws.String(b.kmsKeyId) + } if opts.BeforeWrite != nil { asFunc := func(i interface{}) bool { // Note that since the Go CDK Blob @@ -1046,6 +1069,12 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str if len(opts.ContentMD5) > 0 { req.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString(opts.ContentMD5)) } + if b.encryptionType != "" { + req.ServerSideEncryption = aws.String(b.encryptionType) + } + if b.kmsKeyId != "" { + req.SSEKMSKeyId = aws.String(b.kmsKeyId) + } if opts.BeforeWrite != nil { asFunc := func(i interface{}) bool { pu, ok := i.(**s3manager.Uploader) @@ -1083,6 +1112,12 @@ func (b *bucket) Copy(ctx context.Context, dstKey, srcKey string, opts *driver.C CopySource: aws.String(b.name + "/" + srcKey), Key: aws.String(dstKey), } + if b.encryptionType != "" { + input.ServerSideEncryption = typesv2.ServerSideEncryption(b.encryptionType) + } + if b.kmsKeyId != "" { + input.SSEKMSKeyId = aws.String(b.kmsKeyId) + } if opts.BeforeCopy != nil { asFunc := func(i interface{}) bool { switch v := i.(type) { @@ -1104,6 +1139,12 @@ func (b *bucket) Copy(ctx context.Context, dstKey, srcKey string, opts *driver.C CopySource: aws.String(b.name + "/" + srcKey), Key: aws.String(dstKey), } + if b.encryptionType != "" { + input.ServerSideEncryption = aws.String(b.encryptionType) + } + if b.kmsKeyId != "" { + input.SSEKMSKeyId = aws.String(b.kmsKeyId) + } if opts.BeforeCopy != nil { asFunc := func(i interface{}) bool { switch v := i.(type) { diff --git a/blob/s3blob/s3blob_test.go b/blob/s3blob/s3blob_test.go index 865d920139..8793cfa7a9 100644 --- a/blob/s3blob/s3blob_test.go +++ b/blob/s3blob/s3blob_test.go @@ -466,6 +466,8 @@ func TestOpenBucketFromURL(t *testing.T) { {"s3://mybucket?profile=main®ion=us-west-1", false}, // OK, use V2. {"s3://mybucket?awssdk=v2", false}, + // OK, use KMS Server Side Encryption + {"s3://mybucket?ssetype=aws:kms&kmskeyid=arn:aws:us-east-1:12345:key/1-a-2-b", false}, // Invalid parameter together with a valid one. {"s3://mybucket?profile=main¶m=value", true}, // Invalid parameter. From 2017a81d38243e061a89fb2837d8ce0ac208f6d6 Mon Sep 17 00:00:00 2001 From: Tristan Newmann Date: Tue, 21 Nov 2023 17:04:27 +1100 Subject: [PATCH 2/6] Add comments for encryption settings to s3blob options --- blob/s3blob/s3blob.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go index ece2000006..1b9e4dae2a 100644 --- a/blob/s3blob/s3blob.go +++ b/blob/s3blob/s3blob.go @@ -154,7 +154,7 @@ func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket clientV2 := s3v2.NewFromConfig(cfg) o.Options.EncryptionType = u.Query().Get("ssetype") - o.Options.KMSEncryptionId = u.Query().Get("kmskeyid") + o.Options.KMSEncryptionID = u.Query().Get("kmskeyid") return OpenBucketV2(ctx, clientV2, u.Host, &o.Options) } @@ -168,7 +168,7 @@ func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket configProvider.Configs = append(configProvider.Configs, overrideCfg) o.Options.EncryptionType = u.Query().Get("ssetype") - o.Options.KMSEncryptionId = u.Query().Get("kmskeyid") + o.Options.KMSEncryptionID = u.Query().Get("kmskeyid") return OpenBucket(ctx, configProvider, u.Host, &o.Options) } @@ -180,9 +180,15 @@ type Options struct { // ListObjectsV2. UseLegacyList bool + // EncryptionType sets the encryption type headers when making write or + // copy calls. This is required if the bucket has a restrictive bucket + // policy that enforces a specific encryption type EncryptionType string - KMSEncryptionId string + // KMSEncryptionID sets the kms key id header for write or copy calls. + // This is required when a bucket policy enforces the use of a specific + // KMS key for uploads + KMSEncryptionID string } // openBucket returns an S3 Bucket. @@ -210,7 +216,7 @@ func openBucket(ctx context.Context, useV2 bool, sess client.ConfigProvider, cli client: client, clientV2: clientV2, useLegacyList: opts.UseLegacyList, - kmsKeyId: opts.KMSEncryptionId, + kmsKeyId: opts.KMSEncryptionID, encryptionType: opts.EncryptionType, }, nil } From b4dae37727d31abe3d3292005c8e17f4fe177dad Mon Sep 17 00:00:00 2001 From: Tristan Newmann Date: Thu, 23 Nov 2023 16:37:31 +1100 Subject: [PATCH 3/6] blob/s3blob: Add additional safety when specifying the encryption type Updates the EncryptionType field of s3blob.Options to use the AWS type. An error is thrown at initialisation time if an invalid value is provided for the ssetype parameter --- aws/aws.go | 4 --- blob/s3blob/s3blob.go | 67 ++++++++++++++++++++++++++------------ blob/s3blob/s3blob_test.go | 2 ++ 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index 4a7a81e355..55dc38644d 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -103,8 +103,6 @@ func ConfigFromURLParams(q url.Values) (*aws.Config, error) { } cfg.S3ForcePathStyle = aws.Bool(b) case "awssdk": - case "ssetype": - case "kmskeyid": // ignore, should be handled before this default: return nil, fmt.Errorf("unknown query parameter %q", param) @@ -200,8 +198,6 @@ func V2ConfigFromURLParams(ctx context.Context, q url.Values) (awsv2.Config, err case "profile": opts = append(opts, awsv2cfg.WithSharedConfigProfile(value)) case "awssdk": - case "ssetype": - case "kmskeyid": // ignore, should be handled before this default: return awsv2.Config{}, fmt.Errorf("unknown query parameter %q", param) diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go index 1b9e4dae2a..fad0e129b7 100644 --- a/blob/s3blob/s3blob.go +++ b/blob/s3blob/s3blob.go @@ -64,13 +64,6 @@ import ( "encoding/hex" "errors" "fmt" - "io" - "net/http" - "net/url" - "sort" - "strconv" - "strings" - s3managerv2 "github.com/aws/aws-sdk-go-v2/feature/s3/manager" s3v2 "github.com/aws/aws-sdk-go-v2/service/s3" typesv2 "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -88,6 +81,12 @@ import ( "gocloud.dev/gcerrors" "gocloud.dev/internal/escape" "gocloud.dev/internal/gcerr" + "io" + "net/http" + "net/url" + "sort" + "strconv" + "strings" ) const defaultPageSize = 1000 @@ -144,32 +143,58 @@ type URLOpener struct { Options Options } +const ( + SSETypeParamKey = "ssetype" + KMSKeyIdParamKey = "kmskeyid" +) + +func IsValidServerSideEncryptionType(value string) bool { + // Surely there is a better way to get the values + for _, v := range typesv2.ServerSideEncryptionAes256.Values() { + if string(v) == value { + return true + } + } + return false +} + // OpenBucketURL opens a blob.Bucket based on u. func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket, error) { + q := u.Query() + + if sseType := q.Get(SSETypeParamKey); sseType != "" { + q.Del(SSETypeParamKey) + + if !IsValidServerSideEncryptionType(sseType) { + return nil, fmt.Errorf("ssetype invalid %v", sseType) + } + + o.Options.EncryptionType = typesv2.ServerSideEncryption(sseType) + } + + if kmsKeyID := u.Query().Get(KMSKeyIdParamKey); kmsKeyID != "" { + q.Del(KMSKeyIdParamKey) + o.Options.KMSEncryptionID = kmsKeyID + } + if o.UseV2 { - cfg, err := gcaws.V2ConfigFromURLParams(ctx, u.Query()) + cfg, err := gcaws.V2ConfigFromURLParams(ctx, q) if err != nil { return nil, fmt.Errorf("open bucket %v: %v", u, err) } clientV2 := s3v2.NewFromConfig(cfg) - o.Options.EncryptionType = u.Query().Get("ssetype") - o.Options.KMSEncryptionID = u.Query().Get("kmskeyid") - return OpenBucketV2(ctx, clientV2, u.Host, &o.Options) } configProvider := &gcaws.ConfigOverrider{ Base: o.ConfigProvider, } - overrideCfg, err := gcaws.ConfigFromURLParams(u.Query()) + overrideCfg, err := gcaws.ConfigFromURLParams(q) if err != nil { return nil, fmt.Errorf("open bucket %v: %v", u, err) } configProvider.Configs = append(configProvider.Configs, overrideCfg) - o.Options.EncryptionType = u.Query().Get("ssetype") - o.Options.KMSEncryptionID = u.Query().Get("kmskeyid") - return OpenBucket(ctx, configProvider, u.Host, &o.Options) } @@ -183,7 +208,7 @@ type Options struct { // EncryptionType sets the encryption type headers when making write or // copy calls. This is required if the bucket has a restrictive bucket // policy that enforces a specific encryption type - EncryptionType string + EncryptionType typesv2.ServerSideEncryption // KMSEncryptionID sets the kms key id header for write or copy calls. // This is required when a bucket policy enforces the use of a specific @@ -386,7 +411,7 @@ type bucket struct { clientV2 *s3v2.Client useLegacyList bool - encryptionType string + encryptionType typesv2.ServerSideEncryption kmsKeyId string } @@ -997,7 +1022,7 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str reqV2.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString(opts.ContentMD5)) } if b.encryptionType != "" { - reqV2.ServerSideEncryption = typesv2.ServerSideEncryption(b.encryptionType) + reqV2.ServerSideEncryption = b.encryptionType } if b.kmsKeyId != "" { reqV2.SSEKMSKeyId = aws.String(b.kmsKeyId) @@ -1076,7 +1101,7 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str req.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString(opts.ContentMD5)) } if b.encryptionType != "" { - req.ServerSideEncryption = aws.String(b.encryptionType) + req.ServerSideEncryption = aws.String(string(b.encryptionType)) } if b.kmsKeyId != "" { req.SSEKMSKeyId = aws.String(b.kmsKeyId) @@ -1119,7 +1144,7 @@ func (b *bucket) Copy(ctx context.Context, dstKey, srcKey string, opts *driver.C Key: aws.String(dstKey), } if b.encryptionType != "" { - input.ServerSideEncryption = typesv2.ServerSideEncryption(b.encryptionType) + input.ServerSideEncryption = b.encryptionType } if b.kmsKeyId != "" { input.SSEKMSKeyId = aws.String(b.kmsKeyId) @@ -1146,7 +1171,7 @@ func (b *bucket) Copy(ctx context.Context, dstKey, srcKey string, opts *driver.C Key: aws.String(dstKey), } if b.encryptionType != "" { - input.ServerSideEncryption = aws.String(b.encryptionType) + input.ServerSideEncryption = aws.String(string(b.encryptionType)) } if b.kmsKeyId != "" { input.SSEKMSKeyId = aws.String(b.kmsKeyId) diff --git a/blob/s3blob/s3blob_test.go b/blob/s3blob/s3blob_test.go index 8793cfa7a9..2dba57227a 100644 --- a/blob/s3blob/s3blob_test.go +++ b/blob/s3blob/s3blob_test.go @@ -468,6 +468,8 @@ func TestOpenBucketFromURL(t *testing.T) { {"s3://mybucket?awssdk=v2", false}, // OK, use KMS Server Side Encryption {"s3://mybucket?ssetype=aws:kms&kmskeyid=arn:aws:us-east-1:12345:key/1-a-2-b", false}, + // Invalid ssetype + {"s3://mybucket?ssetype=aws:notkmsoraes&kmskeyid=arn:aws:us-east-1:12345:key/1-a-2-b", true}, // Invalid parameter together with a valid one. {"s3://mybucket?profile=main¶m=value", true}, // Invalid parameter. From a7a2899c67095cf6fbb17e652c2eef9ad2ee0fd0 Mon Sep 17 00:00:00 2001 From: Tristan Newmann Date: Thu, 23 Nov 2023 16:45:44 +1100 Subject: [PATCH 4/6] blob/s3blob: Unexport S3 specific constants --- blob/s3blob/s3blob.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go index fad0e129b7..3b2e568c36 100644 --- a/blob/s3blob/s3blob.go +++ b/blob/s3blob/s3blob.go @@ -144,8 +144,8 @@ type URLOpener struct { } const ( - SSETypeParamKey = "ssetype" - KMSKeyIdParamKey = "kmskeyid" + sseTypeParamKey = "ssetype" + kmsKeyIdParamKey = "kmskeyid" ) func IsValidServerSideEncryptionType(value string) bool { @@ -162,8 +162,8 @@ func IsValidServerSideEncryptionType(value string) bool { func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket, error) { q := u.Query() - if sseType := q.Get(SSETypeParamKey); sseType != "" { - q.Del(SSETypeParamKey) + if sseType := q.Get(sseTypeParamKey); sseType != "" { + q.Del(sseTypeParamKey) if !IsValidServerSideEncryptionType(sseType) { return nil, fmt.Errorf("ssetype invalid %v", sseType) @@ -172,8 +172,8 @@ func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket o.Options.EncryptionType = typesv2.ServerSideEncryption(sseType) } - if kmsKeyID := u.Query().Get(KMSKeyIdParamKey); kmsKeyID != "" { - q.Del(KMSKeyIdParamKey) + if kmsKeyID := u.Query().Get(kmsKeyIdParamKey); kmsKeyID != "" { + q.Del(kmsKeyIdParamKey) o.Options.KMSEncryptionID = kmsKeyID } From 868b27ddfc0a88027a7033fec3cbc178a2a4d1fe Mon Sep 17 00:00:00 2001 From: Tristan Newmann Date: Thu, 15 Feb 2024 15:16:24 +1100 Subject: [PATCH 5/6] blob/s3blob: Refactor IsValidServerSideEncryption to toServerSideEncryption. Add tests --- blob/s3blob/s3blob.go | 33 +++++++++++++++++---------------- blob/s3blob/s3blob_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go index 3b2e568c36..3563fb7894 100644 --- a/blob/s3blob/s3blob.go +++ b/blob/s3blob/s3blob.go @@ -64,6 +64,13 @@ import ( "encoding/hex" "errors" "fmt" + "io" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + s3managerv2 "github.com/aws/aws-sdk-go-v2/feature/s3/manager" s3v2 "github.com/aws/aws-sdk-go-v2/service/s3" typesv2 "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -81,12 +88,6 @@ import ( "gocloud.dev/gcerrors" "gocloud.dev/internal/escape" "gocloud.dev/internal/gcerr" - "io" - "net/http" - "net/url" - "sort" - "strconv" - "strings" ) const defaultPageSize = 1000 @@ -148,28 +149,28 @@ const ( kmsKeyIdParamKey = "kmskeyid" ) -func IsValidServerSideEncryptionType(value string) bool { - // Surely there is a better way to get the values - for _, v := range typesv2.ServerSideEncryptionAes256.Values() { - if string(v) == value { - return true +func toServerSideEncryptionType(value string) (typesv2.ServerSideEncryption, error) { + for _, sseType := range typesv2.ServerSideEncryptionAes256.Values() { + if strings.ToLower(string(sseType)) == strings.ToLower(value) { + return sseType, nil } } - return false + return "", fmt.Errorf("'%s' is not a valid value for '%s'", value, sseTypeParamKey) } // OpenBucketURL opens a blob.Bucket based on u. func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket, error) { q := u.Query() - if sseType := q.Get(sseTypeParamKey); sseType != "" { + if sseTypeParam := q.Get(sseTypeParamKey); sseTypeParam != "" { q.Del(sseTypeParamKey) - if !IsValidServerSideEncryptionType(sseType) { - return nil, fmt.Errorf("ssetype invalid %v", sseType) + sseType, err := toServerSideEncryptionType(sseTypeParam) + if err != nil { + return nil, err } - o.Options.EncryptionType = typesv2.ServerSideEncryption(sseType) + o.Options.EncryptionType = sseType } if kmsKeyID := u.Query().Get(kmsKeyIdParamKey); kmsKeyID != "" { diff --git a/blob/s3blob/s3blob_test.go b/blob/s3blob/s3blob_test.go index 2dba57227a..b8e5ccfac6 100644 --- a/blob/s3blob/s3blob_test.go +++ b/blob/s3blob/s3blob_test.go @@ -487,3 +487,32 @@ func TestOpenBucketFromURL(t *testing.T) { } } } + +func TestToServerSideEncryptionType(t *testing.T) { + tests := []struct { + value string + sseType typesv2.ServerSideEncryption + expectedError error + }{ + // OK. + {"AES256", typesv2.ServerSideEncryptionAes256, nil}, + // OK, KMS + {"aws:kms", typesv2.ServerSideEncryptionAwsKms, nil}, + // OK, KMS + {"aws:kms:dsse", typesv2.ServerSideEncryptionAwsKmsDsse, nil}, + // OK, AES256 mixed case + {"Aes256", typesv2.ServerSideEncryptionAes256, nil}, + // Invalid SSE type + {"invalid", "", fmt.Errorf("'invalid' is not a valid value for '%s'", sseTypeParamKey)}, + } + + for _, test := range tests { + sseType, err := toServerSideEncryptionType(test.value) + if ((err != nil) != (test.expectedError != nil)) && err.Error() != test.expectedError.Error() { + t.Errorf("%s: got error \"%v\", want error \"%v\"", test.value, err, test.expectedError) + } + if sseType != test.sseType { + t.Errorf("%s: got type %v, want type %v", test.value, sseType, test.sseType) + } + } +} From 1cb7fd0550917d57f47cbce3c9126e1b47ffc672 Mon Sep 17 00:00:00 2001 From: Tristan Newmann Date: Mon, 19 Feb 2024 13:09:08 +1100 Subject: [PATCH 6/6] blob/s3blob: Use query variable for parameter extraction in OpenBucketURL --- blob/s3blob/s3blob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go index 3563fb7894..b85e4dfa4e 100644 --- a/blob/s3blob/s3blob.go +++ b/blob/s3blob/s3blob.go @@ -173,7 +173,7 @@ func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket o.Options.EncryptionType = sseType } - if kmsKeyID := u.Query().Get(kmsKeyIdParamKey); kmsKeyID != "" { + if kmsKeyID := q.Get(kmsKeyIdParamKey); kmsKeyID != "" { q.Del(kmsKeyIdParamKey) o.Options.KMSEncryptionID = kmsKeyID }