Add support for bucket encryption (#1876)

* Support encryption in Storage bucket.
* Add self links to crypto keys and key ring ids
* Update ID documentation to talk about self link instead.
This commit is contained in:
Nathan McKinley 2018-08-17 14:33:27 -07:00 committed by GitHub
parent 503e029f4f
commit d77c0adbb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 15 deletions

View File

@ -39,6 +39,10 @@ func resourceKmsCryptoKey() *schema.Resource {
Optional: true,
ValidateFunc: validateKmsCryptoKeyRotationPeriod,
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
@ -163,6 +167,7 @@ func resourceKmsCryptoKeyRead(d *schema.ResourceData, meta interface{}) error {
d.Set("key_ring", cryptoKeyId.KeyRingId.terraformId())
d.Set("name", cryptoKeyId.Name)
d.Set("rotation_period", cryptoKey.RotationPeriod)
d.Set("self_link", cryptoKey.Name)
d.SetId(cryptoKeyId.cryptoKeyId())

View File

@ -345,7 +345,7 @@ resource "google_kms_key_ring" "key_ring" {
resource "google_kms_crypto_key" "crypto_key" {
name = "%s"
key_ring = "${google_kms_key_ring.key_ring.id}"
key_ring = "${google_kms_key_ring.key_ring.self_link}"
rotation_period = "1000000s"
}
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName)
@ -376,7 +376,7 @@ resource "google_kms_key_ring" "key_ring" {
resource "google_kms_crypto_key" "crypto_key" {
name = "%s"
key_ring = "${google_kms_key_ring.key_ring.id}"
key_ring = "${google_kms_key_ring.key_ring.self_link}"
rotation_period = "%s"
}
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, rotationPeriod)
@ -407,7 +407,7 @@ resource "google_kms_key_ring" "key_ring" {
resource "google_kms_crypto_key" "crypto_key" {
name = "%s"
key_ring = "${google_kms_key_ring.key_ring.id}"
key_ring = "${google_kms_key_ring.key_ring.self_link}"
}
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName)
}

View File

@ -5,6 +5,7 @@ import (
"log"
"regexp"
"strings"
"time"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudkms/v1"
@ -36,6 +37,10 @@ func resourceKmsKeyRing() *schema.Resource {
Computed: true,
ForceNew: true,
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
@ -72,16 +77,27 @@ func resourceKmsKeyRingCreate(d *schema.ResourceData, meta interface{}) error {
Name: d.Get("name").(string),
}
keyRing, err := config.clientKms.Projects.Locations.KeyRings.Create(keyRingId.parentId(), &cloudkms.KeyRing{}).KeyRingId(keyRingId.Name).Do()
// This resource is often created just after a project, and requires
// billing support, which is eventually consistent. We attempt to
// wait on billing support in the project resource, but we can't
// always get it right - this retry fixes a lot of flaky tests we were
// noticing.
err = retryTimeDuration(func() error {
keyRing, err := config.clientKms.Projects.Locations.KeyRings.Create(keyRingId.parentId(), &cloudkms.KeyRing{}).KeyRingId(keyRingId.Name).Do()
if err != nil {
return fmt.Errorf("Error creating KeyRing: %s", err)
}
log.Printf("[DEBUG] Created KeyRing %s", keyRing.Name)
d.SetId(keyRingId.keyRingId())
return nil
}, time.Duration(30*time.Second))
if err != nil {
return fmt.Errorf("Error creating KeyRing: %s", err)
return err
}
log.Printf("[DEBUG] Created KeyRing %s", keyRing.Name)
d.SetId(keyRingId.keyRingId())
return resourceKmsKeyRingRead(d, meta)
}
@ -100,13 +116,14 @@ func resourceKmsKeyRingRead(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Executing read for KMS KeyRing %s", keyRingId.keyRingId())
_, err = config.clientKms.Projects.Locations.KeyRings.Get(keyRingId.keyRingId()).Do()
keyRing, err := config.clientKms.Projects.Locations.KeyRings.Get(keyRingId.keyRingId()).Do()
if err != nil {
return fmt.Errorf("Error reading KeyRing: %s", err)
}
d.Set("project", project)
d.Set("self_link", keyRing.Name)
return nil
}

View File

@ -34,6 +34,20 @@ func resourceStorageBucket() *schema.Resource {
ForceNew: true,
},
"encryption": &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_kms_key_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},
"force_destroy": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
@ -244,11 +258,11 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error
return err
}
// Get the bucket and acl
// Get the bucket and location
bucket := d.Get("name").(string)
location := d.Get("location").(string)
// Create a bucket, setting the acl, location and name.
// Create a bucket, setting the labels, location and name.
sb := &storage.Bucket{
Name: bucket,
Labels: expandLabels(d),
@ -295,6 +309,10 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error
sb.Logging = expandBucketLogging(v.([]interface{}))
}
if v, ok := d.GetOk("encryption"); ok {
sb.Encryption = expandBucketEncryption(v.([]interface{}))
}
var res *storage.Bucket
err = retry(func() error {
@ -373,6 +391,14 @@ func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error
}
}
if d.HasChange("encryption") {
if v, ok := d.GetOk("encryption"); ok {
sb.Encryption = expandBucketEncryption(v.([]interface{}))
} else {
sb.NullFields = append(sb.NullFields, "Encryption")
}
}
if d.HasChange("labels") {
sb.Labels = expandLabels(d)
if len(sb.Labels) == 0 {
@ -438,6 +464,7 @@ func resourceStorageBucketRead(d *schema.ResourceData, meta interface{}) error {
d.Set("self_link", res.SelfLink)
d.Set("url", fmt.Sprintf("gs://%s", bucket))
d.Set("storage_class", res.StorageClass)
d.Set("encryption", flattenBucketEncryption(res.Encryption))
d.Set("location", res.Location)
d.Set("cors", flattenCors(res.Cors))
d.Set("logging", flattenBucketLogging(res.Logging))
@ -542,6 +569,29 @@ func flattenCors(corsRules []*storage.BucketCors) []map[string]interface{} {
return corsRulesSchema
}
func expandBucketEncryption(configured interface{}) *storage.BucketEncryption {
encs := configured.([]interface{})
enc := encs[0].(map[string]interface{})
bucketenc := &storage.BucketEncryption{
DefaultKmsKeyName: enc["default_kms_key_name"].(string),
}
return bucketenc
}
func flattenBucketEncryption(enc *storage.BucketEncryption) []map[string]interface{} {
encryption := make([]map[string]interface{}, 0, 1)
if enc == nil {
return encryption
}
encryption = append(encryption, map[string]interface{}{
"default_kms_key_name": enc.DefaultKmsKeyName,
})
return encryption
}
func expandBucketLogging(configured interface{}) *storage.BucketLogging {
loggings := configured.([]interface{})
logging := loggings[0].(map[string]interface{})

View File

@ -547,6 +547,33 @@ func TestAccStorageBucket_cors(t *testing.T) {
}
}
func TestAccStorageBucket_encryption(t *testing.T) {
t.Parallel()
projectId := "terraform-" + acctest.RandString(10)
projectOrg := getTestOrgFromEnv(t)
projectBillingAccount := getTestBillingAccountFromEnv(t)
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
bucketName := fmt.Sprintf("tf-test-crypto-bucket-%d", acctest.RandInt())
var bucket storage.Bucket
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccStorageBucket_encryption(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, bucketName),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleKmsCryptoKeyExists("google_kms_crypto_key.crypto_key"),
testAccCheckStorageBucketExists(
"google_storage_bucket.bucket", bucketName, &bucket),
),
},
},
})
}
func TestAccStorageBucket_labels(t *testing.T) {
t.Parallel()
@ -902,6 +929,44 @@ resource "google_storage_bucket" "bucket" {
`, bucketName)
}
func testAccStorageBucket_encryption(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, bucketName string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
name = "%s"
project_id = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_project_services" "acceptance" {
project = "${google_project.acceptance.project_id}"
services = [
"cloudkms.googleapis.com",
]
}
resource "google_kms_key_ring" "key_ring" {
project = "${google_project_services.acceptance.project}"
name = "%s"
location = "us"
}
resource "google_kms_crypto_key" "crypto_key" {
name = "%s"
key_ring = "${google_kms_key_ring.key_ring.id}"
rotation_period = "1000000s"
}
resource "google_storage_bucket" "bucket" {
name = "%s"
encryption {
default_kms_key_name = "${google_kms_crypto_key.crypto_key.self_link}"
}
}
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, bucketName)
}
func testAccStorageBucket_updateLabels(bucketName string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {

View File

@ -30,7 +30,7 @@ resource "google_kms_key_ring" "my_key_ring" {
resource "google_kms_crypto_key" "my_crypto_key" {
name = "my-crypto-key"
key_ring = "${google_kms_key_ring.my_key_ring.id}"
key_ring = "${google_kms_key_ring.my_key_ring.self_link}"
rotation_period = "100000s"
}
```
@ -56,7 +56,7 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are
exported:
* `id` - The ID of the created CryptoKey. Its format is `{projectId}/{location}/{keyRingName}/{cryptoKeyName}`.
* `self_link` - The self link of the created CryptoKey. Its format is `projects/{projectId}/locations/{location}/keyRings/{keyRingName}/cryptoKeys/{cryptoKeyName}`.
## Import

View File

@ -48,7 +48,7 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are
exported:
* `id` - The ID of the created KeyRing. Its format is `{projectId}/{location}/{keyRingName}`.
* `self_link` - The self link of the created KeyRing. Its format is `projects/{projectId}/locations/{location}/keyRings/{keyRingName}`.
## Import

View File

@ -64,6 +64,8 @@ The following arguments are supported:
* `logging` - (Optional) The bucket's [Access & Storage Logs](https://cloud.google.com/storage/docs/access-logs) configuration.
* `encryption` - (Optional) The bucket's encryption configuration.
The `lifecycle_rule` block supports:
* `action` - (Required) The Lifecycle Rule's action configuration. A single block of this type is supported. Structure is documented below.
@ -117,6 +119,12 @@ The `logging` block supports:
* `log_object_prefix` - (Optional, Computed) The object prefix for log objects. If it's not provided,
by default GCS sets this to the log_bucket's name.
The `encryption` block supports:
* `default_kms_key_name`: A Cloud KMS key that will be used to encrypt objects inserted into this bucket, if no encryption method is specified.
You must pay attention to whether the crypto key is available in the location that this bucket is created in.
See [the docs](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) for more details.
## Attributes Reference
In addition to the arguments listed above, the following computed attributes are