Skip to content

Commit

Permalink
Bootstrapping PSA Roles for Healthcare (GoogleCloudPlatform#7376)
Browse files Browse the repository at this point in the history
* Add a new bootstrap function for assigning roles to service accounts.

* Call the bootstrap function

* Call the bootstrap function

* Use an options struct, remove iam members

* Bootstrap roles in the dicom store test

* Grant roles in composer environment tests

* Refactor bootstrap function

* Delete unused options struct

* Fix comments

* Improve the interface

* Remove extra newline

* Fix variable names

* Move composer environment test changes to separate PR
  • Loading branch information
trodge authored and ericayyliu committed Jul 26, 2023
1 parent 68f7592 commit 18813ff
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 20 deletions.
4 changes: 4 additions & 0 deletions mmv1/products/healthcare/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides
fhir_store_name: "example-fhir-store"
pubsub_topic: "fhir-notifications"
bq_dataset_name: "bq_example_dataset"
test_vars_overrides:
policyChanged: 'BootstrapPSARoles(t, "gsp-sa-healthcare", []string{"roles/bigquery.dataEditor", "roles/bigquery.jobUser"})'
- !ruby/object:Provider::Terraform::Examples
name: "healthcare_fhir_store_notification_config"
primary_resource_id: "default"
Expand Down Expand Up @@ -107,6 +109,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides
pubsub_topic: "dicom-notifications"
bq_dataset_name: "dicom_bq_ds"
bq_table_name: "dicom_bq_tb"
test_vars_overrides:
policyChanged: 'BootstrapPSARoles(t, "gsp-sa-healthcare", []string{"roles/bigquery.dataEditor", "roles/bigquery.jobUser"})'
properties:
creationTime: !ruby/object:Overrides::Terraform::PropertyOverride
exclude: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,6 @@ resource "google_healthcare_fhir_store" "default" {
}
}
}

depends_on = [
google_project_iam_member.bigquery_editor,
google_project_iam_member.bigquery_job_user
]
}

data "google_project" "project" {}

resource "google_project_iam_member" "bigquery_editor" {
project = data.google_project.project.project_id
role = "roles/bigquery.dataEditor"
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-healthcare.iam.gserviceaccount.com"
}

resource "google_project_iam_member" "bigquery_job_user" {
project = data.google_project.project.project_id
role = "roles/bigquery.jobUser"
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-healthcare.iam.gserviceaccount.com"
}

resource "google_pubsub_topic" "topic" {
Expand All @@ -57,4 +38,4 @@ resource "google_bigquery_dataset" "bq_dataset" {
description = "This is a test description"
location = "US"
delete_contents_on_destroy = true
}
}
76 changes: 76 additions & 0 deletions mmv1/third_party/terraform/utils/bootstrap_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,82 @@ func BootstrapProject(t *testing.T, projectID, billingAccount string, services [
return project
}

// BootstrapAllPSARoles ensures that the given project's IAM
// policy grants the given service agents the given roles.
// This is important to bootstrap because using iam policy resources means that
// deleting them removes permissions for concurrent tests.
// Return whether the policy changed.
func BootstrapAllPSARoles(t *testing.T, agentNames, roles []string) bool {
config := BootstrapConfig(t)
if config == nil {
t.Fatal("Could not bootstrap a config for BootstrapAllPSARoles.")
return false
}
client := config.NewResourceManagerClient(config.userAgent)

// Get the project since we need its number, id, and policy.
project, err := client.Projects.Get(getTestProjectFromEnv()).Do()
if err != nil {
t.Fatalf("Error getting project with id %q: %s", project.ProjectId, err)
return false
}

getPolicyRequest := &cloudresourcemanager.GetIamPolicyRequest{}
policy, err := client.Projects.GetIamPolicy(project.ProjectId, getPolicyRequest).Do()
if err != nil {
t.Fatalf("Error getting project iam policy: %v", err)
return false
}

var members []string
for _, agentName := range agentNames {
member := fmt.Sprintf("serviceAccount:service-%d@%s.iam.gserviceaccount.com", project.ProjectNumber, agentName)
members = append(members, member)
}

// Create the bindings we need to add to the policy.
var newBindings []*cloudresourcemanager.Binding
for _, role := range roles {
newBindings = append(newBindings, &cloudresourcemanager.Binding{
Role: role,
Members: members,
})
}

mergedBindings := mergeBindings(append(policy.Bindings, newBindings...))

if !compareBindings(policy.Bindings, mergedBindings) {
// The policy must change.
setPolicyRequest := &cloudresourcemanager.SetIamPolicyRequest{Policy: policy}
policy, err = client.Projects.SetIamPolicy(project.ProjectId, setPolicyRequest).Do()
if err != nil {
t.Fatalf("Error setting project iam policy: %v", err)
return false
}
return true
}

return false
}

// BootstrapAllPSARole is a version of BootstrapAllPSARoles for granting a
// single role to multiple service agents.
func BootstrapAllPSARole(t *testing.T, agentNames []string, role string) bool {
return BootstrapAllPSARoles(t, agentNames, []string{role})
}

// BootstrapPSARoles is a version of BootstrapAllPSARoles for granting roles to
// a single service agent.
func BootstrapPSARoles(t *testing.T, agentName string, roles []string) bool {
return BootstrapAllPSARoles(t, []string{agentName}, roles)
}

// BootstrapPSARole is a simplified version of BootstrapPSARoles for granting a
// single role to a single service agent.
func BootstrapPSARole(t *testing.T, agentName, role string) bool {
return BootstrapPSARoles(t, agentName, []string{role})
}

func BootstrapConfig(t *testing.T) *Config {
if v := os.Getenv("TF_ACC"); v == "" {
t.Skip("Acceptance tests and bootstrapping skipped unless env 'TF_ACC' set")
Expand Down

0 comments on commit 18813ff

Please sign in to comment.