mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-07-03 08:42:39 +00:00
Adds support for creating KMS CryptoKeys resources (#692)
* Adds support for creating KMS CryptoKeys resources * Destroy extant CryptoKeyVersions on CryptoKey destroy * Inherit project, location etc from KeyRing in CryptoKey * Add function to calculate next rotation * Implement RotationPeriod parameter on CryptoKey * Import CryptoKey state * Uncommit my local acceptance test hacks * Docs for google_kms_crypto_key * Clear id at the end of CryptoKey deletion Also add more detail to warning message. * Fix parseCryptoKeyId error messages * Use correct naming in CryptoKeyIdParsing test * Check RotationPeriod is present in acceptance test * Rename variable in test function for consistency * Fix wrong resource name in cryptokey docs * Add KeyRing to CryptoKey doc example * Run test CryptoKey configs through terraform fmt * Don't set CryptoKey purpose in terraform state on import * Fix indentation in CryptoKey test * Parallelise CryptoKey tests * Set rotation_key on CryptoKey read * Move RotationPeriod validation to planning phase * Use import state passthrough for CryptoKey * Correct casing issues in test case names * Remove redundant CheckDestroy calls in CryptoKey tests * Add explanatory comment about extra test steps * More explicit error handling in CryptoKey tests * Explicit dependency on project services in test keyring configs * Clean up comments in cryptokey resource * Do not repeat in cryptokey id regexes
This commit is contained in:
parent
a6d50b0c87
commit
56d633a8d4
0
google/encryptor-pod.yml
Normal file
0
google/encryptor-pod.yml
Normal file
45
google/import_kms_crypto_key_test.go
Normal file
45
google/import_kms_crypto_key_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"os"
|
||||
)
|
||||
|
||||
func TestAccGoogleKmsCryptoKey_importBasic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
resourceName := "google_kms_crypto_key.crypto_key"
|
||||
|
||||
projectId := "terraform-" + acctest.RandString(10)
|
||||
projectOrg := os.Getenv("GOOGLE_ORG")
|
||||
projectBillingAccount := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testGoogleKmsCryptoKey_basic(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -118,6 +118,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"google_logging_folder_sink": resourceLoggingFolderSink(),
|
||||
"google_logging_project_sink": resourceLoggingProjectSink(),
|
||||
"google_kms_key_ring": resourceKmsKeyRing(),
|
||||
"google_kms_crypto_key": resourceKmsCryptoKey(),
|
||||
"google_sourcerepo_repository": resourceSourceRepoRepository(),
|
||||
"google_spanner_instance": resourceSpannerInstance(),
|
||||
"google_spanner_database": resourceSpannerDatabase(),
|
||||
|
|
248
google/resource_kms_crypto_key.go
Normal file
248
google/resource_kms_crypto_key.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"google.golang.org/api/cloudkms/v1"
|
||||
)
|
||||
|
||||
func resourceKmsCryptoKey() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceKmsCryptoKeyCreate,
|
||||
Read: resourceKmsCryptoKeyRead,
|
||||
Delete: resourceKmsCryptoKeyDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"key_ring": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"rotation_period": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateKmsCryptoKeyRotationPeriod,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type kmsCryptoKeyId struct {
|
||||
KeyRingId kmsKeyRingId
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *kmsCryptoKeyId) cryptoKeyId() string {
|
||||
return fmt.Sprintf("%s/cryptoKeys/%s", s.KeyRingId.keyRingId(), s.Name)
|
||||
}
|
||||
|
||||
func (s *kmsCryptoKeyId) parentId() string {
|
||||
return s.KeyRingId.keyRingId()
|
||||
}
|
||||
|
||||
func (s *kmsCryptoKeyId) terraformId() string {
|
||||
return fmt.Sprintf("%s/%s", s.KeyRingId.terraformId(), s.Name)
|
||||
}
|
||||
|
||||
func resourceKmsCryptoKeyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
keyRingId, err := parseKmsKeyRingId(d.Get("key_ring").(string), config)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cryptoKeyId := &kmsCryptoKeyId{
|
||||
KeyRingId: *keyRingId,
|
||||
Name: d.Get("name").(string),
|
||||
}
|
||||
|
||||
key := cloudkms.CryptoKey{Purpose: "ENCRYPT_DECRYPT"}
|
||||
|
||||
if d.Get("rotation_period") != "" {
|
||||
rotationPeriod := d.Get("rotation_period").(string)
|
||||
nextRotation, err := kmsCryptoKeyNextRotation(time.Now(), rotationPeriod)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error setting CryptoKey rotation period: %s", err.Error())
|
||||
}
|
||||
|
||||
key.NextRotationTime = nextRotation
|
||||
key.RotationPeriod = rotationPeriod
|
||||
}
|
||||
|
||||
cryptoKey, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.Create(cryptoKeyId.KeyRingId.keyRingId(), &key).CryptoKeyId(cryptoKeyId.Name).Do()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating CryptoKey: %s", err.Error())
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Created CryptoKey %s", cryptoKey.Name)
|
||||
|
||||
d.SetId(cryptoKeyId.terraformId())
|
||||
|
||||
return resourceKmsCryptoKeyRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceKmsCryptoKeyRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
cryptoKeyId, err := parseKmsCryptoKeyId(d.Id(), config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Executing read for KMS CryptoKey %s", cryptoKeyId.cryptoKeyId())
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading CryptoKey: %s", err)
|
||||
}
|
||||
d.Set("key_ring", cryptoKeyId.KeyRingId.terraformId())
|
||||
d.Set("name", cryptoKeyId.Name)
|
||||
d.Set("rotation_period", d.Get("rotation_period"))
|
||||
|
||||
d.SetId(cryptoKeyId.terraformId())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearCryptoKeyVersions(cryptoKeyId *kmsCryptoKeyId, config *Config) error {
|
||||
versionsClient := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions
|
||||
|
||||
versionsResponse, err := versionsClient.List(cryptoKeyId.cryptoKeyId()).Do()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, version := range versionsResponse.CryptoKeyVersions {
|
||||
request := &cloudkms.DestroyCryptoKeyVersionRequest{}
|
||||
_, err = versionsClient.Destroy(version.Name, request).Do()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Because KMS CryptoKey resources cannot be deleted on GCP, we are only going to remove it from state
|
||||
and destroy all its versions, rendering the key useless for encryption and decryption of data.
|
||||
Re-creation of this resource through Terraform will produce an error.
|
||||
*/
|
||||
|
||||
func resourceKmsCryptoKeyDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
cryptoKeyId, err := parseKmsCryptoKeyId(d.Id(), config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf(`
|
||||
[WARNING] KMS CryptoKey resources cannot be deleted from GCP. The CryptoKey %s will be removed from Terraform state,
|
||||
and all its CryptoKeyVersions will be destroyed, but it will still be present on the server.`, cryptoKeyId.cryptoKeyId())
|
||||
|
||||
err = clearCryptoKeyVersions(cryptoKeyId, config)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateKmsCryptoKeyRotationPeriod(value interface{}, _ string) (ws []string, errors []error) {
|
||||
period := value.(string)
|
||||
pattern := regexp.MustCompile("^([0-9.]*\\d)s$")
|
||||
match := pattern.FindStringSubmatch(period)
|
||||
|
||||
if len(match) == 0 {
|
||||
errors = append(errors, fmt.Errorf("Invalid period format: %s", period))
|
||||
}
|
||||
|
||||
number := match[1]
|
||||
seconds, err := strconv.ParseFloat(number, 64)
|
||||
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
} else {
|
||||
if seconds < 86400.0 {
|
||||
errors = append(errors, fmt.Errorf("Rotation period must be greater than one day"))
|
||||
}
|
||||
|
||||
parts := strings.Split(number, ".")
|
||||
|
||||
if len(parts) > 1 && len(parts[1]) > 9 {
|
||||
errors = append(errors, fmt.Errorf("Rotation period cannot have more than 9 fractional digits"))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func kmsCryptoKeyNextRotation(now time.Time, period string) (result string, err error) {
|
||||
var duration time.Duration
|
||||
|
||||
duration, err = time.ParseDuration(period)
|
||||
|
||||
if err == nil {
|
||||
result = now.UTC().Add(duration).Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseKmsCryptoKeyId(id string, config *Config) (*kmsCryptoKeyId, error) {
|
||||
parts := strings.Split(id, "/")
|
||||
|
||||
cryptoKeyIdRegex := regexp.MustCompile("^([a-z0-9-]+)/([a-z0-9-])+/([a-zA-Z0-9_-]{1,63})/([a-zA-Z0-9_-]{1,63})$")
|
||||
cryptoKeyIdWithoutProjectRegex := regexp.MustCompile("^([a-z0-9-])+/([a-zA-Z0-9_-]{1,63})/([a-zA-Z0-9_-]{1,63})$")
|
||||
|
||||
if cryptoKeyIdRegex.MatchString(id) {
|
||||
return &kmsCryptoKeyId{
|
||||
KeyRingId: kmsKeyRingId{
|
||||
Project: parts[0],
|
||||
Location: parts[1],
|
||||
Name: parts[2],
|
||||
},
|
||||
Name: parts[3],
|
||||
}, nil
|
||||
}
|
||||
|
||||
if cryptoKeyIdWithoutProjectRegex.MatchString(id) {
|
||||
if config.Project == "" {
|
||||
return nil, fmt.Errorf("The default project for the provider must be set when using the `{location}/{keyRingName}/{cryptoKeyName}` id format.")
|
||||
}
|
||||
|
||||
return &kmsCryptoKeyId{
|
||||
KeyRingId: kmsKeyRingId{
|
||||
Project: config.Project,
|
||||
Location: parts[0],
|
||||
Name: parts[1],
|
||||
},
|
||||
Name: parts[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Invalid CryptoKey id format, expecting `{projectId}/{locationId}/{KeyringName}/{cryptoKeyName}` or `{locationId}/{keyRingName}/{cryptoKeyName}.`")
|
||||
}
|
397
google/resource_kms_crypto_key_test.go
Normal file
397
google/resource_kms_crypto_key_test.go
Normal file
|
@ -0,0 +1,397 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestCryptoKeyIdParsing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[string]struct {
|
||||
ImportId string
|
||||
ExpectedError bool
|
||||
ExpectedTerraformId string
|
||||
ExpectedCryptoKeyId string
|
||||
Config *Config
|
||||
}{
|
||||
"id is in project/location/keyRingName/cryptoKeyName format": {
|
||||
ImportId: "test-project/us-central1/test-key-ring/test-key-name",
|
||||
ExpectedError: false,
|
||||
ExpectedTerraformId: "test-project/us-central1/test-key-ring/test-key-name",
|
||||
ExpectedCryptoKeyId: "projects/test-project/locations/us-central1/keyRings/test-key-ring/cryptoKeys/test-key-name",
|
||||
},
|
||||
"id contains name that is longer than 63 characters": {
|
||||
ImportId: "test-project/us-central1/test-key-ring/can-you-believe-that-this-cryptokey-name-is-this-extravagantly-long",
|
||||
ExpectedError: true,
|
||||
},
|
||||
"id is in location/keyRingName/cryptoKeyName format": {
|
||||
ImportId: "us-central1/test-key-ring/test-key-name",
|
||||
ExpectedError: false,
|
||||
ExpectedTerraformId: "test-project/us-central1/test-key-ring/test-key-name",
|
||||
ExpectedCryptoKeyId: "projects/test-project/locations/us-central1/keyRings/test-key-ring/cryptoKeys/test-key-name",
|
||||
Config: &Config{Project: "test-project"},
|
||||
},
|
||||
"id is in location/keyRingName/cryptoKeyName format without project in config": {
|
||||
ImportId: "us-central1/test-key-ring/test-key-name",
|
||||
ExpectedError: true,
|
||||
Config: &Config{Project: ""},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range cases {
|
||||
cryptoKeyId, err := parseKmsCryptoKeyId(tc.ImportId, tc.Config)
|
||||
|
||||
if tc.ExpectedError && err == nil {
|
||||
t.Fatalf("bad: %s, expected an error", tn)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if tc.ExpectedError {
|
||||
continue
|
||||
}
|
||||
t.Fatalf("bad: %s, err: %#v", tn, err)
|
||||
}
|
||||
|
||||
if cryptoKeyId.terraformId() != tc.ExpectedTerraformId {
|
||||
t.Fatalf("bad: %s, expected Terraform ID to be `%s` but is `%s`", tn, tc.ExpectedTerraformId, cryptoKeyId.terraformId())
|
||||
}
|
||||
|
||||
if cryptoKeyId.cryptoKeyId() != tc.ExpectedCryptoKeyId {
|
||||
t.Fatalf("bad: %s, expected CryptoKey ID to be `%s` but is `%s`", tn, tc.ExpectedCryptoKeyId, cryptoKeyId.cryptoKeyId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptoKeyNextRotationCalculation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().UTC()
|
||||
period, _ := time.ParseDuration("1000000s")
|
||||
|
||||
expected := now.Add(period).Format(time.RFC3339Nano)
|
||||
|
||||
timestamp, err := kmsCryptoKeyNextRotation(now, "1000000s")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected failure parsing time %s and duration 1000s: %s", now, err.Error())
|
||||
}
|
||||
|
||||
if expected != timestamp {
|
||||
t.Fatalf("expected %s to equal %s", timestamp, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptoKeyNextRotationCalculation_validation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, errs := validateKmsCryptoKeyRotationPeriod("86399s", "rotation_period")
|
||||
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("Periods of less than a day should be invalid")
|
||||
}
|
||||
|
||||
_, errs = validateKmsCryptoKeyRotationPeriod("100000.0000000001s", "rotation_period")
|
||||
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("Numbers with more than 9 fractional digits are invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccGoogleKmsCryptoKey_basic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
projectId := "terraform-" + acctest.RandString(10)
|
||||
projectOrg := os.Getenv("GOOGLE_ORG")
|
||||
location := os.Getenv("GOOGLE_REGION")
|
||||
projectBillingAccount := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testGoogleKmsCryptoKey_basic(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckGoogleKmsCryptoKeyExists("google_kms_crypto_key.crypto_key"),
|
||||
),
|
||||
},
|
||||
// Use a separate TestStep rather than a CheckDestroy because we need the project to still exist.
|
||||
resource.TestStep{
|
||||
Config: testGoogleKmsCryptoKey_removed(projectId, projectOrg, projectBillingAccount, keyRingName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckGoogleKmsCryptoKeyWasRemovedFromState("google_kms_crypto_key.crypto_key"),
|
||||
testAccCheckGoogleKmsCryptoKeyVersionsDestroyed(projectId, location, keyRingName, cryptoKeyName),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccGoogleKmsCryptoKey_rotation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
projectId := "terraform-" + acctest.RandString(10)
|
||||
projectOrg := os.Getenv("GOOGLE_ORG")
|
||||
location := os.Getenv("GOOGLE_REGION")
|
||||
projectBillingAccount := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
rotationPeriod := "100000s"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testGoogleKmsCryptoKey_rotation(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, rotationPeriod),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckGoogleKmsCryptoKeyExists("google_kms_crypto_key.crypto_key"),
|
||||
testAccCheckGoogleKmsCryptoKeyHasRotationParams(rotationPeriod, "google_kms_crypto_key.crypto_key"),
|
||||
),
|
||||
},
|
||||
// Use a separate TestStep rather than a CheckDestroy because we need the project to still exist.
|
||||
resource.TestStep{
|
||||
Config: testGoogleKmsCryptoKey_removed(projectId, projectOrg, projectBillingAccount, keyRingName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckGoogleKmsCryptoKeyWasRemovedFromState("google_kms_crypto_key.crypto_key"),
|
||||
testAccCheckGoogleKmsCryptoKeyVersionsDestroyed(projectId, location, keyRingName, cryptoKeyName),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckGoogleKmsCryptoKeyExists(resourceName string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
rs, ok := s.RootModule().Resources[resourceName]
|
||||
if !ok {
|
||||
return fmt.Errorf("Resource not found: %s", resourceName)
|
||||
}
|
||||
|
||||
keyRingId, err := parseKmsKeyRingId(rs.Primary.Attributes["key_ring"], config)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cryptoKeyId := &kmsCryptoKeyId{
|
||||
KeyRingId: *keyRingId,
|
||||
Name: rs.Primary.Attributes["name"],
|
||||
}
|
||||
|
||||
listCryptoKeysResponse, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.List(cryptoKeyId.parentId()).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error listing KeyRings: %s", err)
|
||||
}
|
||||
|
||||
for _, cryptoKey := range listCryptoKeysResponse.CryptoKeys {
|
||||
log.Printf("[DEBUG] Found CryptoKey: %s", cryptoKey.Name)
|
||||
|
||||
if cryptoKey.Name == cryptoKeyId.cryptoKeyId() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("CryptoKey not found: %s", cryptoKeyId.cryptoKeyId())
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckGoogleKmsCryptoKeyHasRotationParams(rotationPeriod, resourceName string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
rs, ok := s.RootModule().Resources[resourceName]
|
||||
if !ok {
|
||||
return fmt.Errorf("Resource not found: %s", resourceName)
|
||||
}
|
||||
|
||||
keyRingId, err := parseKmsKeyRingId(rs.Primary.Attributes["key_ring"], config)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cryptoKeyId := &kmsCryptoKeyId{
|
||||
KeyRingId: *keyRingId,
|
||||
Name: rs.Primary.Attributes["name"],
|
||||
}
|
||||
|
||||
getCryptoKeyResponse, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.Get(cryptoKeyId.cryptoKeyId()).Do()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rotationPeriod != getCryptoKeyResponse.RotationPeriod {
|
||||
return fmt.Errorf("Expected rotation period %s to match input %s", getCryptoKeyResponse.RotationPeriod, rotationPeriod)
|
||||
}
|
||||
|
||||
_, err = time.Parse(time.RFC3339Nano, getCryptoKeyResponse.NextRotationTime)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse NextRotationTime timestamp: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
KMS KeyRings cannot be deleted. This ensures that the CryptoKey resource was removed from state,
|
||||
even though the server-side resource was not removed.
|
||||
*/
|
||||
func testAccCheckGoogleKmsCryptoKeyWasRemovedFromState(resourceName string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
_, ok := s.RootModule().Resources[resourceName]
|
||||
|
||||
if ok {
|
||||
return fmt.Errorf("Resource was not removed from state: %s", resourceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
KMS KeyRings cannot be deleted. This ensures that the CryptoKey resource's CryptoKeyVersion
|
||||
sub-resources were scheduled to be destroyed, rendering the key itself inoperable.
|
||||
*/
|
||||
|
||||
func testAccCheckGoogleKmsCryptoKeyVersionsDestroyed(projectId, location, keyRingName, cryptoKeyName string) resource.TestCheckFunc {
|
||||
return func(_ *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
gcpResourceUri := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", projectId, location, keyRingName, cryptoKeyName)
|
||||
|
||||
response, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions.List(gcpResourceUri).Do()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unexpected failure to list versions: %s", err)
|
||||
}
|
||||
|
||||
versions := response.CryptoKeyVersions
|
||||
|
||||
for _, v := range versions {
|
||||
if v.State != "DESTROY_SCHEDULED" && v.State != "DESTROYED" {
|
||||
return fmt.Errorf("CryptoKey %s should have no versions, but version %s has state %s", cryptoKeyName, v.Name, v.State)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This test runs in its own project, otherwise the test project would start to get filled
|
||||
with undeletable resources
|
||||
*/
|
||||
func testGoogleKmsCryptoKey_basic(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName 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-central1"
|
||||
}
|
||||
|
||||
resource "google_kms_crypto_key" "crypto_key" {
|
||||
name = "%s"
|
||||
key_ring = "${google_kms_key_ring.key_ring.id}"
|
||||
}
|
||||
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName)
|
||||
}
|
||||
|
||||
func testGoogleKmsCryptoKey_rotation(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, rotationPeriod 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-central1"
|
||||
}
|
||||
|
||||
resource "google_kms_crypto_key" "crypto_key" {
|
||||
name = "%s"
|
||||
key_ring = "${google_kms_key_ring.key_ring.id}"
|
||||
rotation_period = "%s"
|
||||
}
|
||||
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, rotationPeriod)
|
||||
}
|
||||
|
||||
func testGoogleKmsCryptoKey_removed(projectId, projectOrg, projectBillingAccount, keyRingName 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-central1"
|
||||
}
|
||||
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName)
|
||||
}
|
69
website/docs/r/google_kms_crypto_key.html.markdown
Normal file
69
website/docs/r/google_kms_crypto_key.html.markdown
Normal file
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
layout: "google"
|
||||
page_title: "Google: google_kms_crypto_key"
|
||||
sidebar_current: "docs-google-kms-crypto-key"
|
||||
description: |-
|
||||
Allows creation of a Google Cloud Platform KMS CryptoKey.
|
||||
---
|
||||
|
||||
# google\_kms\_crypto\_key
|
||||
|
||||
Allows creation of a Google Cloud Platform KMS CryptoKey. For more information see
|
||||
[the official documentation](https://cloud.google.com/kms/docs/object-hierarchy#cryptokey)
|
||||
and
|
||||
[API](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys).
|
||||
|
||||
A CryptoKey is an interface to key material which can be used to encrypt and decrypt data. A CryptoKey belongs to a
|
||||
Google Cloud KMS KeyRing.
|
||||
|
||||
~> Note: CryptoKeys cannot be deleted from Google Cloud Platform. Destroying a Terraform-managed CryptoKey will remove it
|
||||
from state and delete all CryptoKeyVersions, rendering the key unusable, but **will not delete the resource on the server**.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```hcl
|
||||
resource "google_kms_key_ring" "my_key_ring" {
|
||||
name = "my-key-ring"
|
||||
project = "my-project"
|
||||
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}"
|
||||
rotation_period = "100000s"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The CryptoKey's name.
|
||||
A CryptoKey’s name must be unique within a location and match the regular expression `[a-zA-Z0-9_-]{1,63}`
|
||||
|
||||
* `key_ring` - (Required) The id of the Google Cloud Platform KeyRing to which the key shall belong.
|
||||
|
||||
- - -
|
||||
|
||||
* `rotation_period` - (Optional) Every time this period passes, generate a new CryptoKeyVersion and set it as
|
||||
the primary. The first rotation will take place after the specified period. The rotation period has the format
|
||||
of a decimal number with up to 9 fractional digits, followed by the letter s (seconds). It must be greater than
|
||||
a day (ie, 83400).
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
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}`.
|
||||
|
||||
## Import
|
||||
|
||||
CryptoKeys can be imported using the CryptoKey autogenerated `id`, e.g.
|
||||
|
||||
```
|
||||
$ terraform import google_kms_crypto_key.my_crypto_key my-gcp-project/us-central1/my-key-ring/my-crypto-key
|
||||
|
||||
$ terraform import google_kms_crypto_key.my_crypto_key us-central1/my-key-ring/my-crypto-key
|
||||
```
|
|
@ -80,6 +80,9 @@
|
|||
<li<%= sidebar_current("docs-google-kms-key-ring") %>>
|
||||
<a href="/docs/providers/google/r/google_kms_key_ring.html">google_kms_key_ring</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-google-kms-crypto-key") %>>
|
||||
<a href="/docs/providers/google/r/google_kms_crypto_key.html">google_kms_crypto_key</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-google-organization-policy") %>>
|
||||
<a href="/docs/providers/google/r/google_organization_policy.html">google_organization_policy</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue
Block a user