diff --git a/google-beta/data_source_google_kms_secret_ciphertext.go b/google-beta/data_source_google_kms_secret_ciphertext.go new file mode 100644 index 0000000000..6a63a5079d --- /dev/null +++ b/google-beta/data_source_google_kms_secret_ciphertext.go @@ -0,0 +1,61 @@ +package google + +import ( + "google.golang.org/api/cloudkms/v1" + + "encoding/base64" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "log" + "time" +) + +func dataSourceGoogleKmsSecretCiphertext() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGoogleKmsSecretCiphertextRead, + Schema: map[string]*schema.Schema{ + "crypto_key": { + Type: schema.TypeString, + Required: true, + }, + "ciphertext": { + Type: schema.TypeString, + Computed: true, + }, + "plaintext": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + }, + } +} + +func dataSourceGoogleKmsSecretCiphertextRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + cryptoKeyId, err := parseKmsCryptoKeyId(d.Get("crypto_key").(string), config) + + if err != nil { + return err + } + + plaintext := base64.StdEncoding.EncodeToString([]byte(d.Get("plaintext").(string))) + + kmsEncryptRequest := &cloudkms.EncryptRequest{ + Plaintext: plaintext, + } + + encryptResponse, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.Encrypt(cryptoKeyId.cryptoKeyId(), kmsEncryptRequest).Do() + + if err != nil { + return fmt.Errorf("Error encrypting plaintext: %s", err) + } + + log.Printf("[INFO] Successfully encrypted plaintext") + + d.Set("ciphertext", encryptResponse.Ciphertext) + d.SetId(time.Now().UTC().String()) + + return nil +} diff --git a/google-beta/data_source_google_kms_secret_ciphertext_test.go b/google-beta/data_source_google_kms_secret_ciphertext_test.go new file mode 100644 index 0000000000..5199da434e --- /dev/null +++ b/google-beta/data_source_google_kms_secret_ciphertext_test.go @@ -0,0 +1,118 @@ +package google + +import ( + "encoding/base64" + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/cloudkms/v1" +) + +func TestAccKmsSecretCiphertext_basic(t *testing.T) { + t.Parallel() + + projectOrg := getTestOrgFromEnv(t) + projectBillingAccount := getTestBillingAccountFromEnv(t) + + projectId := "terraform-" + acctest.RandString(10) + keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + plaintext := fmt.Sprintf("secret-%s", acctest.RandString(10)) + + // The first test creates resources needed to encrypt plaintext and produce ciphertext + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testGoogleKmsCryptoKey_basic(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName), + Check: func(s *terraform.State) error { + cryptoKeyId, err := getCryptoKeyId(s, "google_kms_crypto_key.crypto_key") + + if err != nil { + return err + } + + // The second test asserts that the data source created a ciphertext that can be decrypted to the correct plaintext + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testGoogleKmsSecretCiphertext_datasource(cryptoKeyId.terraformId(), plaintext), + Check: func(s *terraform.State) error { + plaintext, err := testAccDecryptSecretDataWithCryptoKey(s, cryptoKeyId, "data.google_kms_secret_ciphertext.acceptance") + + if err != nil { + return err + } + + return resource.TestCheckResourceAttr("data.google_kms_secret_ciphertext.acceptance", "plaintext", plaintext)(s) + }, + }, + }, + }) + + return nil + }, + }, + }, + }) +} + +func getCryptoKeyId(s *terraform.State, cryptoKeyResourceName string) (*kmsCryptoKeyId, error) { + config := testAccProvider.Meta().(*Config) + rs, ok := s.RootModule().Resources[cryptoKeyResourceName] + if !ok { + return nil, fmt.Errorf("Resource not found: %s", cryptoKeyResourceName) + } + + return parseKmsCryptoKeyId(rs.Primary.Attributes["id"], config) +} + +func testAccDecryptSecretDataWithCryptoKey(s *terraform.State, cryptoKeyId *kmsCryptoKeyId, secretCiphertextResourceName string) (string, error) { + config := testAccProvider.Meta().(*Config) + rs, ok := s.RootModule().Resources[secretCiphertextResourceName] + if !ok { + return "", fmt.Errorf("Resource not found: %s", secretCiphertextResourceName) + } + ciphertext, ok := rs.Primary.Attributes["ciphertext"] + if !ok { + return "", fmt.Errorf("Attribute 'ciphertext' not found in resource '%s'", secretCiphertextResourceName) + } + + kmsDecryptRequest := &cloudkms.DecryptRequest{ + Ciphertext: ciphertext, + } + + decryptResponse, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.Decrypt(cryptoKeyId.cryptoKeyId(), kmsDecryptRequest).Do() + + if err != nil { + return "", fmt.Errorf("Error decrypting ciphertext: %s", err) + } + + plaintextBytes, err := base64.StdEncoding.DecodeString(decryptResponse.Plaintext) + + if err != nil { + return "", err + } + + plaintext := string(plaintextBytes) + log.Printf("[INFO] Successfully decrypted ciphertext and got plaintext: %s", plaintext) + + return plaintext, nil +} + +func testGoogleKmsSecretCiphertext_datasource(cryptoKeyTerraformId, plaintext string) string { + return fmt.Sprintf(` +data "google_kms_secret_ciphertext" "acceptance" { + crypto_key = "%s" + plaintext = "%s" +} + `, cryptoKeyTerraformId, plaintext) +} diff --git a/google-beta/provider.go b/google-beta/provider.go index ef932622a2..c731acc63d 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -397,6 +397,7 @@ func Provider() terraform.ResourceProvider { "google_iam_policy": dataSourceGoogleIamPolicy(), "google_iam_role": dataSourceGoogleIamRole(), "google_kms_secret": dataSourceGoogleKmsSecret(), + "google_kms_secret_ciphertext": dataSourceGoogleKmsSecretCiphertext(), "google_kms_key_ring": dataSourceGoogleKmsKeyRing(), "google_kms_crypto_key": dataSourceGoogleKmsCryptoKey(), "google_kms_crypto_key_version": dataSourceGoogleKmsCryptoKeyVersion(), diff --git a/website/docs/d/google_kms_secret_ciphertext.html.markdown b/website/docs/d/google_kms_secret_ciphertext.html.markdown new file mode 100644 index 0000000000..686112285e --- /dev/null +++ b/website/docs/d/google_kms_secret_ciphertext.html.markdown @@ -0,0 +1,98 @@ +--- +layout: "google" +page_title: "Google: google_kms_secret_ciphertext" +sidebar_current: "docs-google-kms-secret-ciphertext" +description: |- + Encrypts secret data with Google Cloud KMS and provides access to the ciphertext +--- + +# google\_kms\_secret\_ciphertext + +This data source allows you to encrypt data with Google Cloud KMS and use the +ciphertext within your resource definitions. + +For more information see +[the official documentation](https://cloud.google.com/kms/docs/encrypt-decrypt). + +~> **NOTE**: Using this data source will allow you to conceal secret data within your +resource definitions, but it does not take care of protecting that data in the +logging output, plan output, or state output. Please take care to secure your secret +data outside of resource definitions. + +## Example Usage + +First, create a KMS KeyRing and CryptoKey using the resource definitions: + +```hcl +resource "google_kms_key_ring" "my_key_ring" { + project = "my-project" + name = "my-key-ring" + location = "us-central1" +} + +resource "google_kms_crypto_key" "my_crypto_key" { + name = "my-crypto-key" + key_ring = "${google_kms_key_ring.my_key_ring.id}" +} +``` + +Next, encrypt some sensitive information and use the encrypted data in your resource definitions: + +```hcl +data "google_kms_secret_ciphertext" "my_password" { + crypto_key = "${google_kms_crypto_key.my_crypto_key.id}" + plaintext = "my-secret-password" +} + +resource "google_compute_instance" "instance" { + name = "test" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + network = "default" + + access_config { + } + } + + metadata = { + password = "${data.google_kms_secret_ciphertext.my_password.ciphertext}" + } +} +``` + +The resulting instance can then access the encrypted password from its metadata +and decrypt it, e.g. using the [Cloud SDK](https://cloud.google.com/sdk/gcloud/reference/kms/decrypt)): +```bash +$ curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/attributes/password \ +> | base64 -d | gcloud kms decrypt \ +> --project my-project \ +> --location us-central1 \ +> --keyring my-key-ring \ +> --key my-crypto-key \ +> --plaintext-file - \ +> --ciphertext-file - \ +my-secret-password +``` + +## Argument Reference + +The following arguments are supported: + +* `plaintext` (Required) - The plaintext to be encrypted +* `crypto_key` (Required) - The id of the CryptoKey that will be used to + encrypt the provided plaintext. This is represented by the format + `{projectId}/{location}/{keyRingName}/{cryptoKeyName}`. + +## Attributes Reference + +The following attribute is exported: + +* `ciphertext` - Contains the result of encrypting the provided plaintext, encoded in base64. diff --git a/website/google.erb b/website/google.erb index 05e326f9fb..95c9bd4108 100644 --- a/website/google.erb +++ b/website/google.erb @@ -150,6 +150,9 @@