S3 Bucket Object Sever Side Encryption (#11261)

* added server_side_encryption to s3_bucket_object resource including associated acceptance test and documentation.

* got acceptance tests passing.

* made server_side_encryption a computed attribute and only set kms_key_id attribute if an S3 non-default master key is in use.

* ensured kms api is only interrogated if required.
This commit is contained in:
Matthew 2017-01-31 20:20:48 +11:00 committed by Radek Simko
parent f7e2147655
commit 28cee57ef5
3 changed files with 145 additions and 3 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/aws/aws-sdk-go/service/s3"
)
@ -89,6 +90,13 @@ func resourceAwsS3BucketObject() *schema.Resource {
ValidateFunc: validateS3BucketObjectStorageClassType,
},
"server_side_encryption": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateS3BucketObjectServerSideEncryption,
Computed: true,
},
"kms_key_id": {
Type: schema.TypeString,
Optional: true,
@ -102,7 +110,7 @@ func resourceAwsS3BucketObject() *schema.Resource {
// See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html
Optional: true,
Computed: true,
ConflictsWith: []string{"kms_key_id"},
ConflictsWith: []string{"kms_key_id", "server_side_encryption"},
},
"version_id": {
@ -171,9 +179,13 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro
putInput.ContentDisposition = aws.String(v.(string))
}
if v, ok := d.GetOk("server_side_encryption"); ok {
putInput.ServerSideEncryption = aws.String(v.(string))
}
if v, ok := d.GetOk("kms_key_id"); ok {
putInput.SSEKMSKeyId = aws.String(v.(string))
putInput.ServerSideEncryption = aws.String("aws:kms")
putInput.ServerSideEncryption = aws.String(s3.ServerSideEncryptionAwsKms)
}
resp, err := s3conn.PutObject(putInput)
@ -218,7 +230,24 @@ func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) err
d.Set("content_language", resp.ContentLanguage)
d.Set("content_type", resp.ContentType)
d.Set("version_id", resp.VersionId)
d.Set("kms_key_id", resp.SSEKMSKeyId)
d.Set("server_side_encryption", resp.ServerSideEncryption)
// Only set non-default KMS key ID (one that doesn't match default)
if resp.SSEKMSKeyId != nil {
// retrieve S3 KMS Default Master Key
kmsconn := meta.(*AWSClient).kmsconn
kmsresp, err := kmsconn.DescribeKey(&kms.DescribeKeyInput{
KeyId: aws.String("alias/aws/s3"),
})
if err != nil {
return fmt.Errorf("Failed to describe default S3 KMS key (alias/aws/s3): %s", err)
}
if *resp.SSEKMSKeyId != *kmsresp.KeyMetadata.Arn {
log.Printf("[DEBUG] S3 object is encrypted using a non-default KMS Key ID: %s", *resp.SSEKMSKeyId)
d.Set("kms_key_id", resp.SSEKMSKeyId)
}
}
d.Set("etag", strings.Trim(*resp.ETag, `"`))
// The "STANDARD" (which is also the default) storage
@ -328,3 +357,19 @@ func validateS3BucketObjectStorageClassType(v interface{}, k string) (ws []strin
}
return
}
func validateS3BucketObjectServerSideEncryption(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
serverSideEncryption := map[string]bool{
s3.ServerSideEncryptionAes256: true,
s3.ServerSideEncryptionAwsKms: true,
}
if _, ok := serverSideEncryption[value]; !ok {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Server Side Encryption value %q. Valid values are %q and %q",
k, value, s3.ServerSideEncryptionAes256, s3.ServerSideEncryptionAwsKms))
}
return
}

View File

@ -267,6 +267,43 @@ func TestAccAWSS3BucketObject_kms(t *testing.T) {
})
}
func TestAccAWSS3BucketObject_sse(t *testing.T) {
tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-source-sse")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpFile.Name())
// first write some data to the tempfile just so it's not 0 bytes.
err = ioutil.WriteFile(tmpFile.Name(), []byte("{anything will do}"), 0644)
if err != nil {
t.Fatal(err)
}
rInt := acctest.RandInt()
var obj s3.GetObjectOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketObjectDestroy,
Steps: []resource.TestStep{
resource.TestStep{
PreConfig: func() {},
Config: testAccAWSS3BucketObjectConfig_withSSE(rInt, tmpFile.Name()),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketObjectExists(
"aws_s3_bucket_object.object",
&obj),
testAccCheckAWSS3BucketObjectSSE(
"aws_s3_bucket_object.object",
"aws:kms"),
),
},
},
})
}
func TestAccAWSS3BucketObject_acl(t *testing.T) {
rInt := acctest.RandInt()
var obj s3.GetObjectOutput
@ -467,6 +504,34 @@ func testAccCheckAWSS3BucketObjectStorageClass(n, expectedClass string) resource
}
}
func testAccCheckAWSS3BucketObjectSSE(n, expectedSSE string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n]
s3conn := testAccProvider.Meta().(*AWSClient).s3conn
out, err := s3conn.HeadObject(&s3.HeadObjectInput{
Bucket: aws.String(rs.Primary.Attributes["bucket"]),
Key: aws.String(rs.Primary.Attributes["key"]),
})
if err != nil {
return fmt.Errorf("HeadObject error: %v", err)
}
if out.ServerSideEncryption == nil {
return fmt.Errorf("Expected a non %v Server Side Encryption.", out.ServerSideEncryption)
}
sse := *out.ServerSideEncryption
if sse != expectedSSE {
return fmt.Errorf("Expected Server Side Encryption %v, got %v.",
expectedSSE, sse)
}
return nil
}
}
func testAccAWSS3BucketObjectConfigSource(randInt int, source string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "object_bucket" {
@ -561,6 +626,21 @@ resource "aws_s3_bucket_object" "object" {
`, randInt)
}
func testAccAWSS3BucketObjectConfig_withSSE(randInt int, source string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "object_bucket" {
bucket = "tf-object-test-bucket-%d"
}
resource "aws_s3_bucket_object" "object" {
bucket = "${aws_s3_bucket.object_bucket.bucket}"
key = "test-key"
source = "%s"
server_side_encryption = "aws:kms"
}
`, randInt, source)
}
func testAccAWSS3BucketObjectConfig_acl(randInt int, acl string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "object_bucket" {

View File

@ -44,6 +44,22 @@ resource "aws_s3_bucket_object" "examplebucket_object" {
}
```
### Server Side Encryption with S3 Default Master Key
```
resource "aws_s3_bucket" "examplebucket" {
bucket = "examplebuckettftest"
acl = "private"
}
resource "aws_s3_bucket_object" "examplebucket_object" {
key = "someobject"
bucket = "${aws_s3_bucket.examplebucket.bucket}"
source = "index.html"
server_side_encryption = "aws:kms"
}
```
## Argument Reference
The following arguments are supported:
@ -62,6 +78,7 @@ The following arguments are supported:
for the object. Can be either "`STANDARD`", "`REDUCED_REDUNDANCY`", or "`STANDARD_IA`". Defaults to "`STANDARD`".
* `etag` - (Optional) Used to trigger updates. The only meaningful value is `${md5(file("path/to/file"))}`.
This attribute is not compatible with `kms_key_id`.
* `server_side_encryption` - (Optional) Specifies server-side encryption of the object in S3. Valid values are "`AES256`" and "`aws:kms`".
* `kms_key_id` - (Optional) Specifies the AWS KMS Key ARN to use for object encryption.
This value is a fully qualified **ARN** of the KMS Key. If using `aws_kms_key`,
use the exported `arn` attribute: