-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds support for creating KMS CryptoKeys resources #692
Merged
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
eb497da
Adds support for creating KMS CryptoKeys resources
amfarrell b493bac
Destroy extant CryptoKeyVersions on CryptoKey destroy
9de9eff
Inherit project, location etc from KeyRing in CryptoKey
7ac65dc
Add function to calculate next rotation
0aa931e
Implement RotationPeriod parameter on CryptoKey
1450129
Import CryptoKey state
58ada43
Uncommit my local acceptance test hacks
5da8276
Docs for google_kms_crypto_key
4d5f9bf
Clear id at the end of CryptoKey deletion
42f6f24
Fix parseCryptoKeyId error messages
e21d734
Use correct naming in CryptoKeyIdParsing test
1ff44c3
Check RotationPeriod is present in acceptance test
7faf003
Rename variable in test function for consistency
d9ebafa
Fix wrong resource name in cryptokey docs
cde0e24
Add KeyRing to CryptoKey doc example
0b49730
Run test CryptoKey configs through terraform fmt
41eca4b
Don't set CryptoKey purpose in terraform state on import
3ea4b8a
Fix indentation in CryptoKey test
44ec6d6
Parallelise CryptoKey tests
d361401
Set rotation_key on CryptoKey read
adb4d54
Move RotationPeriod validation to planning phase
56bb79d
Use import state passthrough for CryptoKey
f78800b
Correct casing issues in test case names
dfe7413
Remove redundant CheckDestroy calls in CryptoKey tests
9f1857a
Add explanatory comment about extra test steps
18b4791
More explicit error handling in CryptoKey tests
90a04a4
Explicit dependency on project services in test keyring configs
345e299
Clean up comments in cryptokey resource
d2e13b2
Do not repeat in cryptokey id regexes
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this comment has mismatched tabs/spaces for indentation, mind choosing one or the other? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
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}.`") | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add
t.Parallel()
(and then a newline) to the beginning of all tests?