-
Notifications
You must be signed in to change notification settings - Fork 1
/
s3_encrypted_service.go
121 lines (101 loc) · 3.77 KB
/
s3_encrypted_service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package s3server
// Copyright (C) 2022 by RStudio, PBC
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3crypto"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
type encryptedS3Service struct {
defaultS3Wrapper
keyID string
}
func (s *encryptedS3Service) encryptionClient() (*s3crypto.EncryptionClientV2, error) {
kmsClient := kms.New(s.session)
// Create the KeyProvider
var matdesc s3crypto.MaterialDescription
handler := s3crypto.NewKMSContextKeyGenerator(kmsClient, s.keyID, matdesc)
// Create an encryption and decryption client
// We need to pass the session here so S3 can use it. In addition, any decryption that
// occurs will use the KMS client.
svc, err := s3crypto.NewEncryptionClientV2(s.session, s3crypto.AESGCMContentCipherBuilderV2(handler))
if err != nil {
return nil, err
}
return svc, err
}
func (s *encryptedS3Service) decryptionClient() (*s3crypto.DecryptionClientV2, error) {
kmsClient := kms.New(s.session)
cr := s3crypto.NewCryptoRegistry()
if err := s3crypto.RegisterAESGCMContentCipher(cr); err != nil {
return nil, err
}
if err := s3crypto.RegisterKMSContextWrapWithAnyCMK(cr, kmsClient); err != nil {
return nil, err
}
// Create a decryption client to decrypt artifacts
svc, err := s3crypto.NewDecryptionClientV2(s.session, cr)
if err != nil {
return nil, err
}
return svc, err
}
// Upload takes the same input as the defaultS3Service *s3manager.UploadInput, and converts it to a PutObjectInput
// since the EncryptionClient does not have an equivalent to s3manager today. This means it will potentially upload
// content more slowly, and in an unoptimized format.
func (s *encryptedS3Service) Upload(input *s3manager.UploadInput, ctx context.Context, options ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) {
svc, err := s.encryptionClient()
if err != nil {
return nil, err
}
// The s3crypto V2 client requires an io.ReadSeeker for signing and content-length purposes, but we use an
// io.PipeReader under the hood here. As a first pass for this functionality, we'll have to add two caveats to
// the docs:
// 1. Higher memory requirements and
// 2. May be slower as the AWS SDK doesn't currently support the same optimizations for encrypted payloads
b, err := ioutil.ReadAll(input.Body)
if err != nil {
return nil, fmt.Errorf("encryptedS3Service.Upload: Failed to read body %w", err)
}
putObjectInput := &s3.PutObjectInput{
Key: input.Key,
Bucket: input.Bucket,
Body: bytes.NewReader(b),
}
output, err := svc.PutObjectWithContext(ctx, putObjectInput)
if err != nil {
return nil, fmt.Errorf("encryptedS3Service.Upload: Something went wrong uploading to S3. You may want to check your configuration, error: %s", err.Error())
}
// Pass the supported fields from the PutObjectOutput to the UploadOutput format.
s3managerOutput := &s3manager.UploadOutput{
VersionID: output.VersionId,
ETag: output.ETag,
}
return s3managerOutput, nil
}
// GetObject downloads an encrypted file from S3 and returns the plaintext value using client-side KMS decryption
func (s *encryptedS3Service) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
svc, err := s.decryptionClient()
if err != nil {
return nil, err
}
out, err := svc.GetObject(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == s3.ErrCodeNoSuchKey {
return nil, err
} else {
return nil, fmt.Errorf("encryptedS3Service.GetObject: Something went wrong getting an object from S3. You may want to check your configuration, error: %s", err.Error())
}
}
}
return out, err
}
func (s *encryptedS3Service) KmsEncrypted() bool {
return true
}