-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Added data source google kms secret asymmetric #4609
Merged
melinath
merged 7 commits into
GoogleCloudPlatform:master
from
melinath:upstream-tpgb-3052
Mar 22, 2021
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
8301dd9
Added data source google_kms_secret_asymmetric
mvanholsteijn c325bec
typo in the name
mvanholsteijn 684ecb7
added missing reference to google_kms_crypto_key_version
mvanholsteijn 3e43401
processed lint errors
mvanholsteijn 73cb434
remove superfluous brackets
mvanholsteijn 5086fa2
make it explicit that the crc32 is calculated using castagnoli
mvanholsteijn abf157c
Removed duplicative beta-only imports
melinath 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
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) | ||
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. As a side note, this will also need to use |
||
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.
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.
I think this is the first datasource/resource in TPG to use the new context-aware CRUD ops.