-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added data source google kms secret asymmetric (#4609)
* Added data source google_kms_secret_asymmetric * typo in the name * added missing reference to google_kms_crypto_key_version * processed lint errors * remove superfluous brackets * make it explicit that the crc32 is calculated using castagnoli * Removed duplicative beta-only imports Co-authored-by: Mark van Holsteijn <[email protected]>
- Loading branch information
1 parent
18229a6
commit d332482
Showing
5 changed files
with
467 additions
and
1 deletion.
There are no files selected for viewing
141 changes: 141 additions & 0 deletions
141
mmv1/third_party/terraform/data_sources/data_source_google_kms_secret_asymmetric.go
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,141 @@ | ||
package google | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"fmt" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" | ||
"google.golang.org/protobuf/types/known/wrapperspb" | ||
"hash/crc32" | ||
"regexp" | ||
"strconv" | ||
) | ||
|
||
var ( | ||
cryptoKeyVersionRegexp = regexp.MustCompile(`^(//[^/]*/[^/]*/)?(projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+/cryptoKeyVersions/[^/]+)$`) | ||
) | ||
|
||
func dataSourceGoogleKmsSecretAsymmetric() *schema.Resource { | ||
return &schema.Resource{ | ||
ReadContext: dataSourceGoogleKmsSecretAsymmetricReadContext, | ||
Schema: map[string]*schema.Schema{ | ||
"crypto_key_version": { | ||
Type: schema.TypeString, | ||
Description: "The fully qualified KMS crypto key version name", | ||
ValidateFunc: validateRegexp(cryptoKeyVersionRegexp.String()), | ||
Required: true, | ||
}, | ||
"ciphertext": { | ||
Type: schema.TypeString, | ||
Description: "The public key encrypted ciphertext in base64 encoding", | ||
ValidateFunc: validateBase64WithWhitespaces, | ||
Required: true, | ||
}, | ||
"crc32": { | ||
Type: schema.TypeString, | ||
Description: "The crc32 checksum of the ciphertext, hexadecimal encoding", | ||
ValidateFunc: validateHexadecimalUint32, | ||
Optional: true, | ||
}, | ||
"plaintext": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Sensitive: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func dataSourceGoogleKmsSecretAsymmetricReadContext(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
var diags diag.Diagnostics | ||
|
||
err := dataSourceGoogleKmsSecretAsymmetricRead(ctx, d, meta) | ||
if err != nil { | ||
diags = diag.FromErr(err) | ||
} | ||
return diags | ||
} | ||
|
||
func dataSourceGoogleKmsSecretAsymmetricRead(ctx context.Context, d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
userAgent, err := generateUserAgentString(d, config.userAgent) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// `google_kms_crypto_key_version` returns an id with the prefix | ||
// //cloudkms.googleapis.com/v1, which is an invalid name. To allow for the most elegant | ||
// configuration, we will allow it as an input. | ||
keyVersion := cryptoKeyVersionRegexp.FindStringSubmatch(d.Get("crypto_key_version").(string)) | ||
cryptoKeyVersion := keyVersion[len(keyVersion)-1] | ||
|
||
base64CipherText := removeWhiteSpaceFromString(d.Get("ciphertext").(string)) | ||
ciphertext, err := base64.StdEncoding.DecodeString(base64CipherText) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
crc32c := func(data []byte) uint32 { | ||
t := crc32.MakeTable(crc32.Castagnoli) | ||
return crc32.Checksum(data, t) | ||
} | ||
|
||
ciphertextCRC32C := crc32c(ciphertext) | ||
if s, ok := d.Get("crc32").(string); ok && s != "" { | ||
u, err := strconv.ParseUint(s, 16, 32) | ||
if err != nil { | ||
return fmt.Errorf("failed to convert crc32 into uint32, %s", err) | ||
} | ||
ciphertextCRC32C = uint32(u) | ||
} else { | ||
if err := d.Set("crc32", fmt.Sprintf("%x", ciphertextCRC32C)); err != nil { | ||
return fmt.Errorf("failed to set crc32, %s", err) | ||
} | ||
} | ||
|
||
req := &kmspb.AsymmetricDecryptRequest{ | ||
Name: cryptoKeyVersion, | ||
Ciphertext: ciphertext, | ||
CiphertextCrc32C: wrapperspb.Int64(int64(ciphertextCRC32C)), | ||
} | ||
|
||
client := config.NewKeyManagementClient(ctx, userAgent) | ||
result, err := client.AsymmetricDecrypt(ctx, req) | ||
if err != nil { | ||
return fmt.Errorf("failed to decrypt ciphertext: %v", err) | ||
} | ||
|
||
if !result.VerifiedCiphertextCrc32C || int64(crc32c(result.Plaintext)) != result.PlaintextCrc32C.Value { | ||
return fmt.Errorf("asymmetricDecrypt request corrupted in-transit") | ||
} | ||
|
||
if err := d.Set("plaintext", string(result.Plaintext)); err != nil { | ||
return fmt.Errorf("error setting plaintext: %s", err) | ||
} | ||
|
||
d.SetId(fmt.Sprintf("%s:%x:%s", cryptoKeyVersion, ciphertextCRC32C, base64CipherText)) | ||
return nil | ||
} | ||
|
||
func removeWhiteSpaceFromString(s string) string { | ||
whitespaceRegexp := regexp.MustCompile(`(?m)[\s]+`) | ||
return whitespaceRegexp.ReplaceAllString(s, "") | ||
} | ||
|
||
func validateBase64WithWhitespaces(i interface{}, val string) ([]string, []error) { | ||
_, err := base64.StdEncoding.DecodeString(removeWhiteSpaceFromString(i.(string))) | ||
if err != nil { | ||
return nil, []error{fmt.Errorf("could not decode %q as a valid base64 value. Please use the terraform base64 functions such as base64encode() or filebase64() to supply a valid base64 string", val)} | ||
} | ||
return nil, nil | ||
} | ||
|
||
func validateHexadecimalUint32(i interface{}, val string) ([]string, []error) { | ||
_, err := strconv.ParseUint(i.(string), 16, 32) | ||
if err != nil { | ||
return nil, []error{fmt.Errorf("could not decode %q as a unsigned 32 bit hexadecimal integer", val)} | ||
} | ||
return nil, nil | ||
} |
153 changes: 153 additions & 0 deletions
153
mmv1/third_party/terraform/tests/data_source_google_kms_secret_asymmetric_test.go
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,153 @@ | ||
package google | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/pem" | ||
"fmt" | ||
"hash/crc32" | ||
"log" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform" | ||
) | ||
|
||
func TestAccKmsSecretAsymmetricBasic(t *testing.T) { | ||
// Nested tests confuse VCR | ||
skipIfVcr(t) | ||
t.Parallel() | ||
|
||
projectOrg := getTestOrgFromEnv(t) | ||
projectBillingAccount := getTestBillingAccountFromEnv(t) | ||
|
||
projectID := "terraform-" + randString(t, 10) | ||
keyRingName := fmt.Sprintf("tf-test-%s", randString(t, 10)) | ||
cryptoKeyName := fmt.Sprintf("tf-test-%s", randString(t, 10)) | ||
|
||
plaintext := fmt.Sprintf("secret-%s", randString(t, 10)) | ||
|
||
// The first test creates resources needed to encrypt plaintext and produce ciphertext | ||
vcrTest(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: kmsCryptoKeyAsymmetricDecryptBasic(projectID, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName), | ||
Check: func(s *terraform.State) error { | ||
ciphertext, cryptoKeyVersionID, crc, err := testAccEncryptSecretDataAsymmetricWithPublicKey(t, s, "data.google_kms_crypto_key_version.crypto_key", plaintext) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// The second test asserts that the data source has the correct plaintext, given the created ciphertext | ||
vcrTest(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: googleKmsSecretAsymmetricDatasource(cryptoKeyVersionID, ciphertext), | ||
Check: resource.TestCheckResourceAttr("data.google_kms_secret_asymmetric.acceptance", "plaintext", plaintext), | ||
}, | ||
{ | ||
Config: googleKmsSecretAsymmetricDatasourceWithCrc(cryptoKeyVersionID, ciphertext, crc), | ||
Check: resource.TestCheckResourceAttr("data.google_kms_secret_asymmetric.acceptance_with_crc", "plaintext", plaintext), | ||
}, | ||
}, | ||
}) | ||
|
||
return nil | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccEncryptSecretDataAsymmetricWithPublicKey(t *testing.T, s *terraform.State, cryptoKeyResourceName, plaintext string) (string, string, uint32, error) { | ||
rs, ok := s.RootModule().Resources[cryptoKeyResourceName] | ||
if !ok { | ||
return "", "", 0, fmt.Errorf("resource not found: %s", cryptoKeyResourceName) | ||
} | ||
|
||
cryptoKeyVersionID := rs.Primary.Attributes["id"] | ||
|
||
block, _ := pem.Decode([]byte(rs.Primary.Attributes["public_key.0.pem"])) | ||
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
if err != nil { | ||
return "", "", 0, fmt.Errorf("failed to parse public key: %v", err) | ||
} | ||
rsaKey, ok := publicKey.(*rsa.PublicKey) | ||
if !ok { | ||
return "", "", 0, fmt.Errorf("public key is not rsa") | ||
} | ||
|
||
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaKey, []byte(plaintext), nil) | ||
if err != nil { | ||
return "", "", 0, fmt.Errorf("rsa.EncryptOAEP: %v", err) | ||
} | ||
|
||
crc := crc32.Checksum(ciphertext, crc32.MakeTable(crc32.Castagnoli)) | ||
|
||
result := base64.StdEncoding.EncodeToString(ciphertext) | ||
log.Printf("[INFO] Successfully encrypted plaintext and got ciphertext: %s", result) | ||
|
||
return result, cryptoKeyVersionID, crc, nil | ||
} | ||
|
||
func googleKmsSecretAsymmetricDatasource(cryptoKeyTerraformID, ciphertext string) string { | ||
return fmt.Sprintf(` | ||
data "google_kms_secret_asymmetric" "acceptance" { | ||
crypto_key_version = "%s" | ||
ciphertext = "%s" | ||
} | ||
`, cryptoKeyTerraformID, ciphertext) | ||
} | ||
|
||
func googleKmsSecretAsymmetricDatasourceWithCrc(cryptoKeyTerraformID, ciphertext string, crc uint32) string { | ||
return fmt.Sprintf(` | ||
data "google_kms_secret_asymmetric" "acceptance_with_crc" { | ||
crypto_key_version = "%s" | ||
ciphertext = "%s" | ||
crc32 = "%x" | ||
} | ||
`, cryptoKeyTerraformID, ciphertext, crc) | ||
} | ||
|
||
func kmsCryptoKeyAsymmetricDecryptBasic(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_service" "acceptance" { | ||
project = google_project.acceptance.project_id | ||
service = "cloudkms.googleapis.com" | ||
} | ||
resource "google_kms_key_ring" "key_ring" { | ||
project = google_project_service.acceptance.project | ||
name = "%s" | ||
location = "us-central1" | ||
depends_on = [google_project_service.acceptance] | ||
} | ||
resource "google_kms_crypto_key" "crypto_key" { | ||
name = "%s" | ||
key_ring = google_kms_key_ring.key_ring.self_link | ||
purpose = "ASYMMETRIC_DECRYPT" | ||
version_template { | ||
algorithm = "RSA_DECRYPT_OAEP_4096_SHA256" | ||
} | ||
} | ||
data "google_kms_crypto_key_version" "crypto_key" { | ||
crypto_key = google_kms_crypto_key.crypto_key.id | ||
} | ||
`, projectID, projectID, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName) | ||
} |
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
Oops, something went wrong.