Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blob/s3blob: Support S3 server side encryption headers for Write and Copy #3340

Merged
merged 6 commits into from
Feb 25, 2024
100 changes: 86 additions & 14 deletions blob/s3blob/s3blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,6 @@
"encoding/hex"
"errors"
"fmt"
"io"
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
"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"
Expand All @@ -88,6 +81,12 @@
"gocloud.dev/gcerrors"
"gocloud.dev/internal/escape"
"gocloud.dev/internal/gcerr"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
)

const defaultPageSize = 1000
Expand Down Expand Up @@ -144,24 +143,58 @@
Options Options
}

const (
sseTypeParamKey = "ssetype"
kmsKeyIdParamKey = "kmskeyid"
)

func IsValidServerSideEncryptionType(value string) bool {
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
// Surely there is a better way to get the values
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
for _, v := range typesv2.ServerSideEncryptionAes256.Values() {
if string(v) == value {
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
return true
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
}
}
return false
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
}

// 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)
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
}

o.Options.EncryptionType = typesv2.ServerSideEncryption(sseType)
}

if kmsKeyID := u.Query().Get(kmsKeyIdParamKey); kmsKeyID != "" {
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
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)

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)

return OpenBucket(ctx, configProvider, u.Host, &o.Options)
}

Expand All @@ -171,6 +204,16 @@
// Some S3-compatible services (like CEPH) do not currently support
// 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 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
// KMS key for uploads
KMSEncryptionID string
}

// openBucket returns an S3 Bucket.
Expand All @@ -193,11 +236,13 @@
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
}

Expand Down Expand Up @@ -365,6 +410,9 @@
client *s3.S3
clientV2 *s3v2.Client
useLegacyList bool

encryptionType typesv2.ServerSideEncryption
kmsKeyId string
}

func (b *bucket) Close() error {
Expand Down Expand Up @@ -973,6 +1021,12 @@
if len(opts.ContentMD5) > 0 {
reqV2.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString(opts.ContentMD5))
}
if b.encryptionType != "" {
reqV2.ServerSideEncryption = b.encryptionType

Check warning on line 1025 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1025

Added line #L1025 was not covered by tests
}
if b.kmsKeyId != "" {
reqV2.SSEKMSKeyId = aws.String(b.kmsKeyId)

Check warning on line 1028 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1028

Added line #L1028 was not covered by tests
}
if opts.BeforeWrite != nil {
asFunc := func(i interface{}) bool {
// Note that since the Go CDK Blob
Expand Down Expand Up @@ -1046,6 +1100,12 @@
if len(opts.ContentMD5) > 0 {
req.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString(opts.ContentMD5))
}
if b.encryptionType != "" {
req.ServerSideEncryption = aws.String(string(b.encryptionType))

Check warning on line 1104 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1104

Added line #L1104 was not covered by tests
}
if b.kmsKeyId != "" {
req.SSEKMSKeyId = aws.String(b.kmsKeyId)

Check warning on line 1107 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1107

Added line #L1107 was not covered by tests
}
if opts.BeforeWrite != nil {
asFunc := func(i interface{}) bool {
pu, ok := i.(**s3manager.Uploader)
Expand Down Expand Up @@ -1083,6 +1143,12 @@
CopySource: aws.String(b.name + "/" + srcKey),
Key: aws.String(dstKey),
}
if b.encryptionType != "" {
input.ServerSideEncryption = b.encryptionType

Check warning on line 1147 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1147

Added line #L1147 was not covered by tests
}
if b.kmsKeyId != "" {
input.SSEKMSKeyId = aws.String(b.kmsKeyId)

Check warning on line 1150 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1150

Added line #L1150 was not covered by tests
}
if opts.BeforeCopy != nil {
asFunc := func(i interface{}) bool {
switch v := i.(type) {
Expand All @@ -1104,6 +1170,12 @@
CopySource: aws.String(b.name + "/" + srcKey),
Key: aws.String(dstKey),
}
if b.encryptionType != "" {
input.ServerSideEncryption = aws.String(string(b.encryptionType))

Check warning on line 1174 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1174

Added line #L1174 was not covered by tests
}
if b.kmsKeyId != "" {
input.SSEKMSKeyId = aws.String(b.kmsKeyId)

Check warning on line 1177 in blob/s3blob/s3blob.go

View check run for this annotation

Codecov / codecov/patch

blob/s3blob/s3blob.go#L1177

Added line #L1177 was not covered by tests
}
if opts.BeforeCopy != nil {
asFunc := func(i interface{}) bool {
switch v := i.(type) {
Expand Down
4 changes: 4 additions & 0 deletions blob/s3blob/s3blob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,10 @@ func TestOpenBucketFromURL(t *testing.T) {
{"s3://mybucket?profile=main&region=us-west-1", false},
// OK, use V2.
{"s3://mybucket?awssdk=v2", false},
// OK, use KMS Server Side Encryption
tristan-newmann marked this conversation as resolved.
Show resolved Hide resolved
{"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&param=value", true},
// Invalid parameter.
Expand Down
Loading