From ad17cf55a0fa69bf865abc01e7464457864f4190 Mon Sep 17 00:00:00 2001 From: Robin Walsh Date: Fri, 19 Jun 2015 11:33:03 -0700 Subject: [PATCH] Allowing at-rest encryption when using S3 This change allows the user to specify `-backend-config="encrypt=1"` to tell S3 to encrypt the data that's in the bucket when using S3 for remote config storage. The encryption uses "Amazon S3-managed encryption keys" so it should not require any further user intervention. A line was added to the unit test just for coverage. The acceptance test was modified to: a) Use encryption b) Push some test data up to the bucket created to ensure that Amazon accepts the header. --- state/remote/s3.go | 31 +++++++++++++++++++++++-------- state/remote/s3_test.go | 10 ++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/state/remote/s3.go b/state/remote/s3.go index 67f96f39b1..c12aafef79 100644 --- a/state/remote/s3.go +++ b/state/remote/s3.go @@ -32,6 +32,12 @@ func s3Factory(conf map[string]string) (Client, error) { } } + serverSideEncryption := false + _, ok = conf["encrypt"] + if ok { + serverSideEncryption = true + } + accessKeyId := conf["access_key"] secretAccessKey := conf["secret_key"] @@ -60,16 +66,18 @@ func s3Factory(conf map[string]string) (Client, error) { nativeClient := s3.New(awsConfig) return &S3Client{ - nativeClient: nativeClient, - bucketName: bucketName, - keyName: keyName, + nativeClient: nativeClient, + bucketName: bucketName, + keyName: keyName, + serverSideEncryption: serverSideEncryption, }, nil } type S3Client struct { - nativeClient *s3.S3 - bucketName string - keyName string + nativeClient *s3.S3 + bucketName string + keyName string + serverSideEncryption bool } func (c *S3Client) Get() (*Payload, error) { @@ -113,13 +121,20 @@ func (c *S3Client) Put(data []byte) error { contentType := "application/octet-stream" contentLength := int64(len(data)) - _, err := c.nativeClient.PutObject(&s3.PutObjectInput{ + i := &s3.PutObjectInput{ ContentType: &contentType, ContentLength: &contentLength, Body: bytes.NewReader(data), Bucket: &c.bucketName, Key: &c.keyName, - }) + } + + if c.serverSideEncryption { + e := "AES256" + i.ServerSideEncryption = &e + } + + _, err := c.nativeClient.PutObject(i) if err == nil { return nil diff --git a/state/remote/s3_test.go b/state/remote/s3_test.go index 172ed3a0aa..530036eeb7 100644 --- a/state/remote/s3_test.go +++ b/state/remote/s3_test.go @@ -28,6 +28,7 @@ func TestS3Factory(t *testing.T) { config["region"] = "us-west-1" config["bucket"] = "foo" config["key"] = "bar" + config["encrypt"] = "1" // For this test we'll provide the credentials as config. The // acceptance tests implicitly test passing credentials as // environment variables. @@ -80,11 +81,13 @@ func TestS3Client(t *testing.T) { bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) keyName := "testState" + testData := []byte(`testing data`) config := make(map[string]string) config["region"] = regionName config["bucket"] = bucketName config["key"] = keyName + config["encrypt"] = "1" client, err := s3Factory(config) if err != nil { @@ -105,6 +108,13 @@ func TestS3Client(t *testing.T) { if err != nil { t.Skipf("Failed to create test S3 bucket, so skipping") } + + // Ensure we can perform a PUT request with the encryption header + err = s3Client.Put(testData) + if err != nil { + t.Logf("WARNING: Failed to send test data to S3 bucket. (error was %s)", err) + } + defer func() { deleteBucketReq := &s3.DeleteBucketInput{ Bucket: &bucketName,