Skip to content

Commit

Permalink
KMS: add google_kms_crypto_key_latest_version data source (Google…
Browse files Browse the repository at this point in the history
  • Loading branch information
BBBmau authored Aug 23, 2024
1 parent 4c3e8a6 commit cf7fa9d
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
"google_kms_crypto_key": kms.DataSourceGoogleKmsCryptoKey(),
"google_kms_crypto_keys": kms.DataSourceGoogleKmsCryptoKeys(),
"google_kms_crypto_key_version": kms.DataSourceGoogleKmsCryptoKeyVersion(),
"google_kms_crypto_key_latest_version": kms.DataSourceGoogleKmsLatestCryptoKeyVersion(),
"google_kms_crypto_key_versions": kms.DataSourceGoogleKmsCryptoKeyVersions(),
"google_kms_key_ring": kms.DataSourceGoogleKmsKeyRing(),
"google_kms_key_rings": kms.DataSourceGoogleKmsKeyRings(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package kms

import (
"fmt"
"log"

"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func DataSourceGoogleKmsLatestCryptoKeyVersion() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleKmsLatestCryptoKeyVersionRead,
Schema: map[string]*schema.Schema{
"crypto_key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
"version": {
Type: schema.TypeInt,
Computed: true,
},
"algorithm": {
Type: schema.TypeString,
Computed: true,
},
"protection_level": {
Type: schema.TypeString,
Computed: true,
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"filter": {
Type: schema.TypeString,
Optional: true,
Description: `
The filter argument is used to add a filter query parameter that limits which type of cryptoKeyVersion is retrieved as the latest by the data source: ?filter={{filter}}. When no value is provided there is no filtering.
Example filter values if filtering on state.
* "state:ENABLED" will retrieve the latest cryptoKeyVersion that has the state "ENABLED".
[See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering)
`,
},
"public_key": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"algorithm": {
Type: schema.TypeString,
Computed: true,
},
"pem": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}

func dataSourceGoogleKmsLatestCryptoKeyVersionRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*transport_tpg.Config)

userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}

cryptoKeyId, err := ParseKmsCryptoKeyId(d.Get("crypto_key").(string), config)
if err != nil {
return err
}

id := fmt.Sprintf("%s/latestCryptoKeyVersion", cryptoKeyId.CryptoKeyId())
if filter, ok := d.GetOk("filter"); ok {
id += "/filter=" + filter.(string)
}
d.SetId(id)

versions, err := dataSourceKMSCryptoKeyVersionsList(d, meta, cryptoKeyId.CryptoKeyId(), userAgent)
if err != nil {
return err
}

// grab latest version
lv := len(versions) - 1
if lv < 0 {
return fmt.Errorf("No CryptoVersions found in crypto key %s", cryptoKeyId.CryptoKeyId())
}

latestVersion := versions[lv].(map[string]interface{})

// The google_kms_crypto_key resource and dataset set
// id as the value of name (projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{name}})
// and set name is set as just {{name}}.

if err := d.Set("name", flattenKmsCryptoKeyVersionName(latestVersion["name"], d)); err != nil {
return fmt.Errorf("Error setting LatestCryptoKeyVersion: %s", err)
}
if err := d.Set("version", flattenKmsCryptoKeyVersionVersion(latestVersion["name"], d)); err != nil {
return fmt.Errorf("Error setting CryptoKeyVersion: %s", err)
}
if err := d.Set("state", flattenKmsCryptoKeyVersionState(latestVersion["state"], d)); err != nil {
return fmt.Errorf("Error setting LatestCryptoKeyVersion: %s", err)
}
if err := d.Set("protection_level", flattenKmsCryptoKeyVersionProtectionLevel(latestVersion["protectionLevel"], d)); err != nil {
return fmt.Errorf("Error setting LatestCryptoKeyVersion: %s", err)
}
if err := d.Set("algorithm", flattenKmsCryptoKeyVersionAlgorithm(latestVersion["algorithm"], d)); err != nil {
return fmt.Errorf("Error setting LatestCryptoKeyVersion: %s", err)
}

url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}/cryptoKeyVersions/{{version}}")
if err != nil {
return err
}

log.Printf("[DEBUG] Getting attributes for CryptoKeyVersion: %#v", url)

url, err = tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}")
if err != nil {
return err
}

log.Printf("[DEBUG] Getting purpose of CryptoKey: %#v", url)
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: cryptoKeyId.KeyRingId.Project,
RawURL: url,
UserAgent: userAgent,
})
if err != nil {
return transport_tpg.HandleDataSourceNotFoundError(err, d, fmt.Sprintf("KmsCryptoKey %q", d.Id()), url)
}

if res["purpose"] == "ASYMMETRIC_SIGN" || res["purpose"] == "ASYMMETRIC_DECRYPT" {
url, err = tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}/cryptoKeyVersions/{{version}}/publicKey")
if err != nil {
return err
}
log.Printf("[DEBUG] Getting public key of CryptoKeyVersion: %#v", url)

res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: cryptoKeyId.KeyRingId.Project,
RawURL: url,
UserAgent: userAgent,
Timeout: d.Timeout(schema.TimeoutRead),
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsCryptoKeyVersionsPendingGeneration},
})

if err != nil {
log.Printf("Error generating public key: %s", err)
return err
}

if err := d.Set("public_key", flattenKmsCryptoKeyVersionPublicKey(res, d)); err != nil {
return fmt.Errorf("Error setting CryptoKeyVersion public key: %s", err)
}
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package kms_test

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-provider-google/google/acctest"
)

func TestAccDataSourceGoogleKmsCryptoKeyLatestVersion_basic(t *testing.T) {
asymSignKey := acctest.BootstrapKMSKeyWithPurpose(t, "ASYMMETRIC_SIGN")

id := asymSignKey.CryptoKey.Name + "/latestCryptoKeyVersion"

randomString := acctest.RandString(t, 10)
filterNameFindsNoLatestCryptoKeyVersion := fmt.Sprintf("name:%s", randomString)
filterNameFindEnabledLatestCryptoKeyVersion := "state:enabled"

findsNoLatestCryptoKeyVersionId := fmt.Sprintf("%s/filter=%s", id, filterNameFindsNoLatestCryptoKeyVersion)
findsEnabledLatestCryptoKeyVersionId := fmt.Sprintf("%s/filter=%s", id, filterNameFindEnabledLatestCryptoKeyVersion)

context := map[string]interface{}{
"crypto_key": asymSignKey.CryptoKey.Name,
"filter": "", // Can be overridden using 2nd argument to config funcs
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccDataSourceGoogleKmsCryptoKeyLatestVersion_basic(context, ""),
// Test will attempt to get the latest version from the list of cryptoKeyVersions, if the latest is not enabled it will return an error
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "id", id),
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "crypto_key", asymSignKey.CryptoKey.Name),
resource.TestMatchResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "version", regexp.MustCompile("[1-9]+[0-9]*")),
),
ExpectError: regexp.MustCompile("Error: googleapi: Error 400:"),
},
{
Config: testAccDataSourceGoogleKmsCryptoKeyLatestVersion_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindEnabledLatestCryptoKeyVersion)),
Check: resource.ComposeTestCheckFunc(
// This filter should retrieve the latest ENABLED cryptoKeyVersion in the bootstrapped KMS crypto key used by the test
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "id", findsEnabledLatestCryptoKeyVersionId),
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "crypto_key", asymSignKey.CryptoKey.Name),
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "state", "ENABLED"),
),
},
{
Config: testAccDataSourceGoogleKmsCryptoKeyLatestVersion_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindsNoLatestCryptoKeyVersion)),
Check: resource.ComposeTestCheckFunc(
// This filter should retrieve no latest version
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "id", findsNoLatestCryptoKeyVersionId),
resource.TestCheckResourceAttr("data.google_kms_crypto_key_latest_version.latest_version", "crypto_key", asymSignKey.CryptoKey.Name),
),
ExpectError: regexp.MustCompile("Error: No CryptoVersions found in crypto key"),
},
},
})
}

func testAccDataSourceGoogleKmsCryptoKeyLatestVersion_basic(context map[string]interface{}, filter string) string {
context["filter"] = filter

return acctest.Nprintf(`
data "google_kms_crypto_key_latest_version" "latest_version" {
crypto_key = "%{crypto_key}"
%{filter}
}
`, context)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package kms

import (
Expand Down Expand Up @@ -161,7 +159,7 @@ func dataSourceGoogleKmsCryptoKeyVersionsRead(d *schema.ResourceData, meta inter
func dataSourceKMSCryptoKeyVersionsList(d *schema.ResourceData, meta interface{}, cryptoKeyId string, userAgent string) ([]interface{}, error) {
config := meta.(*transport_tpg.Config)

url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}/cryptoKeyVersions?filter=state=ENABLED")
url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}/cryptoKeyVersions")
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
subcategory: "Cloud Key Management Service"
description: |-
Provides access to the latest KMS key version data with Google Cloud KMS.
---

# google_kms_crypto_key_latest_version

Provides access to the latest Google Cloud Platform KMS CryptoKeyVersion in a CryptoKey. For more information see
[the official documentation](https://cloud.google.com/kms/docs/object-hierarchy#key_version)
and
[API](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions).

## Example Usage

```hcl
data "google_kms_key_ring" "my_key_ring" {
name = "my-key-ring"
location = "us-central1"
}
data "google_kms_crypto_key" "my_crypto_key" {
name = "my-crypto-key"
key_ring = data.google_kms_key_ring.my_key_ring.id
}
data "google_kms_crypto_key_latest_version" "my_crypto_key_latest_version" {
crypto_key = data.google_kms_crypto_key.my_key.id
}
```

## Argument Reference

The following arguments are supported:

* `crypto_key` - (Required) The `id` of the Google Cloud Platform CryptoKey to which the key version belongs. This is also the `id` field of the
`google_kms_crypto_key` resource/datasource.

* `filter` - (Optional) The filter argument is used to add a filter query parameter that limits which type of cryptoKeyVersion is retrieved as the latest by the data source: ?filter={{filter}}. When no value is provided there is no filtering.

Example filter values if filtering on state.

* `"state:ENABLED"` will retrieve the latest cryptoKeyVersion that has the state "ENABLED".

[See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering)

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are
exported:

* `state` - The current state of the latest CryptoKeyVersion. See the [state reference](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions#CryptoKeyVersion.CryptoKeyVersionState) for possible outputs.

* `protection_level` - The ProtectionLevel describing how crypto operations are performed with this CryptoKeyVersion. See the [protection_level reference](https://cloud.google.com/kms/docs/reference/rest/v1/ProtectionLevel) for possible outputs.

* `algorithm` - The CryptoKeyVersionAlgorithm that this CryptoKeyVersion supports. See the [algorithm reference](https://cloud.google.com/kms/docs/reference/rest/v1/CryptoKeyVersionAlgorithm) for possible outputs.

* `public_key` - If the enclosing CryptoKey has purpose `ASYMMETRIC_SIGN` or `ASYMMETRIC_DECRYPT`, this block contains details about the public key associated to this CryptoKeyVersion. Structure is [documented below](#nested_public_key).

<a name="nested_public_key"></a>The `public_key` block, if present, contains:

* `pem` - The public key, encoded in PEM format. For more information, see the RFC 7468 sections for General Considerations and Textual Encoding of Subject Public Key Info.

* `algorithm` - The CryptoKeyVersionAlgorithm that this CryptoKeyVersion supports.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ The following arguments are supported:
* `crypto_key` - (Required) The `id` of the Google Cloud Platform CryptoKey to which the key version belongs. This is also the `id` field of the
`google_kms_crypto_key` resource/datasource.

* `filter` - (Optional) The filter argument is used to add a filter query parameter that limits which versions are retrieved by the data source: ?filter={{filter}}. When no value is provided there is no filtering.

Example filter values if filtering on name. Note: names take the form projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{cryptoKey}}/cryptoKeyVersions.

* `"name:my-key-"` will retrieve cryptoKeyVersions that contain "my-key-" anywhere in their name.
* `"name=projects/my-project/locations/global/keyRings/my-key-ring/cryptoKeys/my-key-1/cryptoKeyVersions/my-version-1"` will only retrieve a key with that exact name.

[See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering)

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are exported:
Expand Down

0 comments on commit cf7fa9d

Please sign in to comment.