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:
parent
f7e2147655
commit
28cee57ef5
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue