From cc9690cf459991e935a9fd8adb60a2365a15903c Mon Sep 17 00:00:00 2001 From: Roberto Jung Drebes Date: Fri, 4 Oct 2019 22:10:14 +0000 Subject: [PATCH] BigQuery Dataset default CMEK encryption Signed-off-by: Modular Magician --- google/resource_big_query_dataset.go | 68 +++++++++++++++++++ google/resource_big_query_dataset_test.go | 51 ++++++++++++++ website/docs/r/bigquery_dataset.html.markdown | 40 +++++++++++ 3 files changed, 159 insertions(+) diff --git a/google/resource_big_query_dataset.go b/google/resource_big_query_dataset.go index 763c4e5bc00..b927f6fa839 100644 --- a/google/resource_big_query_dataset.go +++ b/google/resource_big_query_dataset.go @@ -82,6 +82,19 @@ func resourceBigQueryDataset() *schema.Resource { Elem: bigqueryDatasetAccessSchema(), // Default schema.HashSchema is used. }, + "default_encryption_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kms_key_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, "default_partition_expiration_ms": { Type: schema.TypeInt, Optional: true, @@ -241,6 +254,12 @@ func resourceBigQueryDatasetCreate(d *schema.ResourceData, meta interface{}) err } else if v, ok := d.GetOkExists("location"); !isEmptyValue(reflect.ValueOf(locationProp)) && (ok || !reflect.DeepEqual(v, locationProp)) { obj["location"] = locationProp } + defaultEncryptionConfigurationProp, err := expandBigQueryDatasetDefaultEncryptionConfiguration(d.Get("default_encryption_configuration"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("default_encryption_configuration"); !isEmptyValue(reflect.ValueOf(defaultEncryptionConfigurationProp)) && (ok || !reflect.DeepEqual(v, defaultEncryptionConfigurationProp)) { + obj["defaultEncryptionConfiguration"] = defaultEncryptionConfigurationProp + } url, err := replaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets") if err != nil { @@ -335,6 +354,9 @@ func resourceBigQueryDatasetRead(d *schema.ResourceData, meta interface{}) error if err := d.Set("location", flattenBigQueryDatasetLocation(res["location"], d)); err != nil { return fmt.Errorf("Error reading Dataset: %s", err) } + if err := d.Set("default_encryption_configuration", flattenBigQueryDatasetDefaultEncryptionConfiguration(res["defaultEncryptionConfiguration"], d)); err != nil { + return fmt.Errorf("Error reading Dataset: %s", err) + } if err := d.Set("self_link", ConvertSelfLinkToV1(res["selfLink"].(string))); err != nil { return fmt.Errorf("Error reading Dataset: %s", err) } @@ -399,6 +421,12 @@ func resourceBigQueryDatasetUpdate(d *schema.ResourceData, meta interface{}) err } else if v, ok := d.GetOkExists("location"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, locationProp)) { obj["location"] = locationProp } + defaultEncryptionConfigurationProp, err := expandBigQueryDatasetDefaultEncryptionConfiguration(d.Get("default_encryption_configuration"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("default_encryption_configuration"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, defaultEncryptionConfigurationProp)) { + obj["defaultEncryptionConfiguration"] = defaultEncryptionConfigurationProp + } url, err := replaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}") if err != nil { @@ -618,6 +646,23 @@ func flattenBigQueryDatasetLocation(v interface{}, d *schema.ResourceData) inter return v } +func flattenBigQueryDatasetDefaultEncryptionConfiguration(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["kms_key_name"] = + flattenBigQueryDatasetDefaultEncryptionConfigurationKmsKeyName(original["kmsKeyName"], d) + return []interface{}{transformed} +} +func flattenBigQueryDatasetDefaultEncryptionConfigurationKmsKeyName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + func expandBigQueryDatasetAccess(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { v = v.(*schema.Set).List() l := v.([]interface{}) @@ -787,3 +832,26 @@ func expandBigQueryDatasetLabels(v interface{}, d TerraformResourceData, config func expandBigQueryDatasetLocation(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } + +func expandBigQueryDatasetDefaultEncryptionConfiguration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedKmsKeyName, err := expandBigQueryDatasetDefaultEncryptionConfigurationKmsKeyName(original["kms_key_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedKmsKeyName); val.IsValid() && !isEmptyValue(val) { + transformed["kmsKeyName"] = transformedKmsKeyName + } + + return transformed, nil +} + +func expandBigQueryDatasetDefaultEncryptionConfigurationKmsKeyName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_big_query_dataset_test.go b/google/resource_big_query_dataset_test.go index c22ff7214a2..ac3ffe802c9 100644 --- a/google/resource_big_query_dataset_test.go +++ b/google/resource_big_query_dataset_test.go @@ -135,6 +135,29 @@ func TestAccBigQueryDataset_regionalLocation(t *testing.T) { }) } +func TestAccBigQueryDataset_cmek(t *testing.T) { + t.Parallel() + + kms := BootstrapKMSKeyInLocation(t, "us") + pid := getTestProjectFromEnv() + datasetID1 := fmt.Sprintf("tf_test_%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccBigQueryDataset_cmek(pid, datasetID1, kms.CryptoKey.Name), + }, + { + ResourceName: "google_bigquery_dataset.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccAddTable(datasetID string, tableID string) resource.TestCheckFunc { // Not actually a check, but adds a table independently of terraform return func(s *terraform.State) error { @@ -303,3 +326,31 @@ resource "google_bigquery_dataset" "access_test" { } }`, otherDatasetID, otherTableID, datasetID) } + +func testAccBigQueryDataset_cmek(pid, datasetID, kmsKey string) string { + return fmt.Sprintf(` +data "google_project" "project" { + project_id = "%s" +} + +resource "google_project_iam_member" "kms-project-binding" { + project = "${data.google_project.project.project_id}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:bq-${data.google_project.project.number}@bigquery-encryption.iam.gserviceaccount.com" +} + +resource "google_bigquery_dataset" "test" { + dataset_id = "%s" + friendly_name = "test" + description = "This is a test description" + location = "US" + default_table_expiration_ms = 3600000 + + default_encryption_configuration { + kms_key_name = "%s" + } + + project = "${google_project_iam_member.kms-project-binding.project}" +} +`, pid, datasetID, kmsKey) +} diff --git a/website/docs/r/bigquery_dataset.html.markdown b/website/docs/r/bigquery_dataset.html.markdown index 1014450ae6b..5c50dbfd816 100644 --- a/website/docs/r/bigquery_dataset.html.markdown +++ b/website/docs/r/bigquery_dataset.html.markdown @@ -55,6 +55,32 @@ resource "google_bigquery_dataset" "dataset" { } } ``` +## Example Usage - Bigquery Dataset Cmek + + +```hcl +resource "google_bigquery_dataset" "dataset" { + dataset_id = "example_dataset" + friendly_name = "test" + description = "This is a test description" + location = "US" + default_table_expiration_ms = 3600000 + + default_encryption_configuration { + kms_key_name = "${google_kms_crypto_key.crypto_key.self_link}" + } +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "example-key" + key_ring = "${google_kms_key_ring.key_ring.self_link}" +} + +resource "google_kms_key_ring" "key_ring" { + name = "example-keyring" + location = "us" +} +``` ## Argument Reference @@ -139,6 +165,12 @@ The following arguments are supported: The default value is multi-regional location `US`. Changing this forces a new resource to be created. +* `default_encryption_configuration` - + (Optional) + The default encryption key for all tables in the dataset. Once this property is set, + all newly-created partitioned tables in the dataset will have encryption key set to + this value, unless table creation request (or query) overrides the key. Structure is documented below. + * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. @@ -210,6 +242,14 @@ The `view` block supports: A-Z), numbers (0-9), or underscores (_). The maximum length is 1,024 characters. +The `default_encryption_configuration` block supports: + +* `kms_key_name` - + (Required) + Describes the Cloud KMS encryption key that will be used to protect destination + BigQuery table. The BigQuery Service Account associated with your project requires + access to this encryption key. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: