Skip to content

Commit

Permalink
allow resources with an indirect project to use user project ov… (#1422)
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician authored and danawillow committed Nov 22, 2019
1 parent 7e36bef commit 082e38a
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 7 deletions.
20 changes: 16 additions & 4 deletions google-beta/kms_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,23 @@ func parseKmsCryptoKeyId(id string, config *Config) (*kmsCryptoKeyId, error) {
func clearCryptoKeyVersions(cryptoKeyId *kmsCryptoKeyId, config *Config) error {
versionsClient := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions

versionsResponse, err := versionsClient.List(cryptoKeyId.cryptoKeyId()).Do()
listCall := versionsClient.List(cryptoKeyId.cryptoKeyId())
if config.UserProjectOverride {
listCall.Header().Set("X-Goog-User-Project", cryptoKeyId.KeyRingId.Project)
}
versionsResponse, err := listCall.Do()

if err != nil {
return err
}

for _, version := range versionsResponse.CryptoKeyVersions {
request := &cloudkms.DestroyCryptoKeyVersionRequest{}
_, err = versionsClient.Destroy(version.Name, request).Do()
destroyCall := versionsClient.Destroy(version.Name, request)
if config.UserProjectOverride {
destroyCall.Header().Set("X-Goog-User-Project", cryptoKeyId.KeyRingId.Project)
}
_, err = destroyCall.Do()

if err != nil {
return err
Expand All @@ -197,10 +205,14 @@ func clearCryptoKeyVersions(cryptoKeyId *kmsCryptoKeyId, config *Config) error {

func disableCryptoKeyRotation(cryptoKeyId *kmsCryptoKeyId, config *Config) error {
keyClient := config.clientKms.Projects.Locations.KeyRings.CryptoKeys
_, err := keyClient.Patch(cryptoKeyId.cryptoKeyId(), &cloudkms.CryptoKey{
patchCall := keyClient.Patch(cryptoKeyId.cryptoKeyId(), &cloudkms.CryptoKey{
NullFields: []string{"rotationPeriod", "nextRotationTime"},
}).
UpdateMask("rotationPeriod,nextRotationTime").Do()
UpdateMask("rotationPeriod,nextRotationTime")
if config.UserProjectOverride {
patchCall.Header().Set("X-Goog-User-Project", cryptoKeyId.KeyRingId.Project)
}
_, err := patchCall.Do()

return err
}
146 changes: 146 additions & 0 deletions google-beta/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,49 @@ func TestAccProviderUserProjectOverride(t *testing.T) {
})
}

// Do the same thing as TestAccProviderUserProjectOverride, but using a resource that gets its project via
// a reference to a different resource instead of a project field.
func TestAccProviderIndirectUserProjectOverride(t *testing.T) {
t.Parallel()

org := getTestOrgFromEnv(t)
billing := getTestBillingAccountFromEnv(t)
pid := "terraform-" + acctest.RandString(10)
sa := "terraform-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
// No TestDestroy since that's not really the point of this test
Steps: []resource.TestStep{
{
Config: testAccProviderIndirectUserProjectOverride(pid, pname, org, billing, sa),
Check: func(s *terraform.State) error {
// The token creator IAM API call returns success long before the policy is
// actually usable. Wait a solid 2 minutes to ensure we can use it.
time.Sleep(2 * time.Minute)
return nil
},
},
{
Config: testAccProviderIndirectUserProjectOverride_step2(pid, pname, org, billing, sa, false),
ExpectError: regexp.MustCompile(`Cloud Key Management Service \(KMS\) API has not been used`),
},
{
Config: testAccProviderIndirectUserProjectOverride_step2(pid, pname, org, billing, sa, true),
},
{
ResourceName: "google_kms_crypto_key.project-2-key",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccProviderIndirectUserProjectOverride_step3(pid, pname, org, billing, sa, true),
},
},
})
}

func testAccProviderBasePath_setBasePath(endpoint, name string) string {
return fmt.Sprintf(`
provider "google" {
Expand Down Expand Up @@ -348,6 +391,109 @@ provider "google" {
`, testAccProviderUserProjectOverride(pid, name, org, billing, sa), override)
}

// Set up two projects. Project 1 has a service account that is used to create a
// kms crypto key in project 2. The kms API is only enabled in project 2,
// which causes the create to fail unless user_project_override is set to true.
func testAccProviderIndirectUserProjectOverride(pid, name, org, billing, sa string) string {
return fmt.Sprintf(`
resource "google_project" "project-1" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_service_account" "project-1" {
project = google_project.project-1.project_id
account_id = "%s"
}
resource "google_project" "project-2" {
project_id = "%s-2"
name = "%s-2"
org_id = "%s"
billing_account = "%s"
}
resource "google_project_service" "project-2-kms" {
project = google_project.project-2.project_id
service = "cloudkms.googleapis.com"
}
// Permission needed for user_project_override
resource "google_project_iam_member" "project-2-serviceusage" {
project = google_project.project-2.project_id
role = "roles/serviceusage.serviceUsageConsumer"
member = "serviceAccount:${google_service_account.project-1.email}"
}
resource "google_project_iam_member" "project-2-kms" {
project = google_project.project-2.project_id
role = "roles/cloudkms.admin"
member = "serviceAccount:${google_service_account.project-1.email}"
}
data "google_client_openid_userinfo" "me" {}
// Enable the test runner to get an access token on behalf of
// the project 1 service account
resource "google_service_account_iam_member" "token-creator-iam" {
service_account_id = google_service_account.project-1.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${data.google_client_openid_userinfo.me.email}"
}
`, pid, name, org, billing, sa, pid, name, org, billing)
}

func testAccProviderIndirectUserProjectOverride_step2(pid, name, org, billing, sa string, override bool) string {
return fmt.Sprintf(`
// See step 3 below, which is really step 2 minus the kms resources.
// Step 3 exists because provider configurations can't be removed while objects
// created by that provider still exist in state. Step 3 will remove the
// kms resources so the whole config can be deleted.
%s
resource "google_kms_key_ring" "project-2-keyring" {
provider = google.project-1-token
project = google_project.project-2.project_id
name = "%s"
location = "us-central1"
}
resource "google_kms_crypto_key" "project-2-key" {
provider = google.project-1-token
name = "%s"
key_ring = google_kms_key_ring.project-2-keyring.self_link
}
`, testAccProviderIndirectUserProjectOverride_step3(pid, name, org, billing, sa, override), pid, pid)
}

func testAccProviderIndirectUserProjectOverride_step3(pid, name, org, billing, sa string, override bool) string {
return fmt.Sprintf(`
%s
data "google_service_account_access_token" "project-1-token" {
// This data source would have a depends_on to
// google_service_account_iam_binding.token-creator-iam, but depends_on
// in data sources makes them always have a diff in apply:
// https://www.terraform.io/docs/configuration/data-sources.html#data-resource-dependencies
// Instead, rely on the other test step completing before this one.
target_service_account = google_service_account.project-1.email
scopes = ["userinfo-email", "https://www.googleapis.com/auth/cloud-platform"]
lifetime = "300s"
}
provider "google" {
alias = "project-1-token"
access_token = data.google_service_account_access_token.project-1-token.access_token
user_project_override = %v
}
`, testAccProviderIndirectUserProjectOverride(pid, name, org, billing, sa), override)
}

// getTestRegion has the same logic as the provider's getRegion, to be used in tests.
func getTestRegion(is *terraform.InstanceState, config *Config) (string, error) {
if res, ok := is.Attributes["region"]; ok {
Expand Down
19 changes: 16 additions & 3 deletions google-beta/resource_kms_crypto_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"log"
"reflect"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -164,7 +165,11 @@ func resourceKMSCryptoKeyCreate(d *schema.ResourceData, meta interface{}) error
}

log.Printf("[DEBUG] Creating new CryptoKey: %#v", obj)
res, err := sendRequestWithTimeout(config, "POST", "", url, obj, d.Timeout(schema.TimeoutCreate))
var project string
if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
project = parts[1]
}
res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate))
if err != nil {
return fmt.Errorf("Error creating CryptoKey: %s", err)
}
Expand All @@ -189,7 +194,11 @@ func resourceKMSCryptoKeyRead(d *schema.ResourceData, meta interface{}) error {
return err
}

res, err := sendRequest(config, "GET", "", url, nil)
var project string
if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
project = parts[1]
}
res, err := sendRequest(config, "GET", project, url, nil)
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("KMSCryptoKey %q", d.Id()))
}
Expand Down Expand Up @@ -275,7 +284,11 @@ func resourceKMSCryptoKeyUpdate(d *schema.ResourceData, meta interface{}) error
if err != nil {
return err
}
_, err = sendRequestWithTimeout(config, "PATCH", "", url, obj, d.Timeout(schema.TimeoutUpdate))
var project string
if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
project = parts[1]
}
_, err = sendRequestWithTimeout(config, "PATCH", project, url, obj, d.Timeout(schema.TimeoutUpdate))

if err != nil {
return fmt.Errorf("Error updating CryptoKey %q: %s", d.Id(), err)
Expand Down
4 changes: 4 additions & 0 deletions website/docs/r/kms_crypto_key.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,7 @@ $ terraform import google_kms_crypto_key.default {{key_ring}}/{{name}}

-> If you're importing a resource with beta features, make sure to include `-provider=google-beta`
as an argument so that Terraform uses the correct provider to import your resource.

## User Project Overrides

This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override).

0 comments on commit 082e38a

Please sign in to comment.