From 824bfe706230323cf2786f92a2fb50a0c71c365f Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Thu, 20 Feb 2025 20:09:30 +0100 Subject: [PATCH] terraform/iam: create additional service account for VMs This service account is used in the following commits and is attached to the VMs --- .../constellation_iam_create/action.yml | 1 + cli/internal/cloudcmd/iam.go | 8 +- cli/internal/cloudcmd/iamupgrade.go | 2 + cli/internal/cloudcmd/tfvars.go | 1 + cli/internal/cmd/iamcreate_test.go | 140 ++++++++++-------- cli/internal/cmd/iamcreategcp.go | 21 ++- cli/internal/terraform/terraform.go | 14 +- cli/internal/terraform/terraform_test.go | 36 ++++- cli/internal/terraform/variables.go | 2 + cli/internal/terraform/variables_test.go | 18 +++ docs/docs/getting-started/first-steps.md | 2 +- docs/docs/reference/cli.md | 6 +- docs/docs/workflows/config.md | 2 +- .../examples/full/gcp/main.tf | 10 +- terraform/infrastructure/iam/gcp/main.tf | 37 ++++- terraform/infrastructure/iam/gcp/outputs.tf | 6 + terraform/infrastructure/iam/gcp/variables.tf | 7 +- 17 files changed, 224 insertions(+), 89 deletions(-) diff --git a/.github/actions/constellation_iam_create/action.yml b/.github/actions/constellation_iam_create/action.yml index 91282a927e..6df998872d 100644 --- a/.github/actions/constellation_iam_create/action.yml +++ b/.github/actions/constellation_iam_create/action.yml @@ -102,6 +102,7 @@ runs: --tf-log=DEBUG \ --yes ${extraFlags} + # TODO(@3u13r): Replace deprecated --serviceAccountID with --prefix - name: Constellation iam create gcp shell: bash if: inputs.cloudProvider == 'gcp' diff --git a/cli/internal/cloudcmd/iam.go b/cli/internal/cloudcmd/iam.go index e73f2854d0..0046c27e39 100644 --- a/cli/internal/cloudcmd/iam.go +++ b/cli/internal/cloudcmd/iam.go @@ -91,6 +91,7 @@ type GCPIAMConfig struct { Zone string ProjectID string ServiceAccountID string + NamePrefix string } // AzureIAMConfig holds the necessary values for Azure IAM configuration. @@ -141,6 +142,7 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMCon vars := terraform.GCPIAMVariables{ ServiceAccountID: opts.GCP.ServiceAccountID, + NamePrefix: opts.GCP.NamePrefix, Project: opts.GCP.ProjectID, Region: opts.GCP.Region, Zone: opts.GCP.Zone, @@ -158,7 +160,8 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMCon return IAMOutput{ CloudProvider: cloudprovider.GCP, GCPOutput: GCPIAMOutput{ - ServiceAccountKey: iamOutput.GCP.SaKey, + ServiceAccountKey: iamOutput.GCP.SaKey, + IAMServiceAccountVM: iamOutput.GCP.ServiceAccountVMMailAddress, }, }, nil } @@ -232,7 +235,8 @@ type IAMOutput struct { // GCPIAMOutput contains the output information of a GCP IAM configuration. type GCPIAMOutput struct { - ServiceAccountKey string `json:"serviceAccountID,omitempty"` + ServiceAccountKey string `json:"serviceAccountID,omitempty"` + IAMServiceAccountVM string `json:"iamServiceAccountVM,omitempty"` } // AzureIAMOutput contains the output information of a Microsoft Azure IAM configuration. diff --git a/cli/internal/cloudcmd/iamupgrade.go b/cli/internal/cloudcmd/iamupgrade.go index 729af5d29d..6e7b569900 100644 --- a/cli/internal/cloudcmd/iamupgrade.go +++ b/cli/internal/cloudcmd/iamupgrade.go @@ -22,6 +22,8 @@ import ( // UpgradeRequiresIAMMigration returns true if the given cloud provider requires an IAM migration. func UpgradeRequiresIAMMigration(provider cloudprovider.Provider) bool { switch provider { + case cloudprovider.GCP: + return true default: return false } diff --git a/cli/internal/cloudcmd/tfvars.go b/cli/internal/cloudcmd/tfvars.go index aab752aca5..e95bb32265 100644 --- a/cli/internal/cloudcmd/tfvars.go +++ b/cli/internal/cloudcmd/tfvars.go @@ -240,6 +240,7 @@ func gcpTerraformIAMVars(conf *config.Config, oldVars terraform.GCPIAMVariables) Region: conf.Provider.GCP.Region, Zone: conf.Provider.GCP.Zone, ServiceAccountID: oldVars.ServiceAccountID, + NamePrefix: oldVars.NamePrefix, } } diff --git a/cli/internal/cmd/iamcreate_test.go b/cli/internal/cmd/iamcreate_test.go index 3a9c830512..b7d33c6c7c 100644 --- a/cli/internal/cmd/iamcreate_test.go +++ b/cli/internal/cmd/iamcreate_test.go @@ -456,6 +456,7 @@ func TestIAMCreateGCP(t *testing.T) { creator *stubIAMCreator zoneFlag string serviceAccountIDFlag string + namePrefixFlag string projectIDFlag string yesFlag bool updateConfigFlag bool @@ -466,6 +467,14 @@ func TestIAMCreateGCP(t *testing.T) { wantErr bool }{ "iam create gcp": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + yesFlag: true, + }, + "iam create gcp with deprecated serice account flag": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, zoneFlag: "europe-west1-a", @@ -474,91 +483,91 @@ func TestIAMCreateGCP(t *testing.T) { yesFlag: true, }, "iam create gcp with existing config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - yesFlag: true, - existingConfigFiles: []string{constants.ConfigFilename}, + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + yesFlag: true, + existingConfigFiles: []string{constants.ConfigFilename}, }, "iam create gcp --update-config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - updateConfigFlag: true, - yesFlag: true, - existingConfigFiles: []string{constants.ConfigFilename}, + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + updateConfigFlag: true, + yesFlag: true, + existingConfigFiles: []string{constants.ConfigFilename}, }, "iam create gcp existing terraform dir": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", existingDirs: []string{constants.TerraformIAMWorkingDir}, yesFlag: true, wantErr: true, }, "iam create gcp invalid b64": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: invalidIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - yesFlag: true, - wantErr: true, + setupFs: defaultFs, + creator: &stubIAMCreator{id: invalidIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + yesFlag: true, + wantErr: true, }, "interactive": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - stdin: "yes\n", + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + stdin: "yes\n", }, "interactive update config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - stdin: "yes\n", - updateConfigFlag: true, - existingConfigFiles: []string{constants.ConfigFilename}, + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + stdin: "yes\n", + updateConfigFlag: true, + existingConfigFiles: []string{constants.ConfigFilename}, }, "interactive abort": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - stdin: "no\n", - wantAbort: true, + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + stdin: "no\n", + wantAbort: true, }, "interactive abort update config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - stdin: "no\n", - wantAbort: true, - updateConfigFlag: true, - existingConfigFiles: []string{constants.ConfigFilename}, + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + stdin: "no\n", + wantAbort: true, + updateConfigFlag: true, + existingConfigFiles: []string{constants.ConfigFilename}, }, "unwritable fs": { - setupFs: readOnlyFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - yesFlag: true, - updateConfigFlag: true, - wantErr: true, + setupFs: readOnlyFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + namePrefixFlag: "constell-test", + projectIDFlag: "constell-1234", + yesFlag: true, + updateConfigFlag: true, + wantErr: true, }, } @@ -590,6 +599,7 @@ func TestIAMCreateGCP(t *testing.T) { flags: gcpIAMCreateFlags{ zone: tc.zoneFlag, serviceAccountID: tc.serviceAccountIDFlag, + namePrefix: tc.serviceAccountIDFlag, projectID: tc.projectIDFlag, }, }, diff --git a/cli/internal/cmd/iamcreategcp.go b/cli/internal/cmd/iamcreategcp.go index b6c55e5d1f..c0c081ad76 100644 --- a/cli/internal/cmd/iamcreategcp.go +++ b/cli/internal/cmd/iamcreategcp.go @@ -31,13 +31,18 @@ func newIAMCreateGCPCmd() *cobra.Command { cmd.Flags().String("zone", "", "GCP zone the cluster will be deployed in (required)\n"+ "Find a list of available zones here: https://cloud.google.com/compute/docs/regions-zones#available") must(cobra.MarkFlagRequired(cmd.Flags(), "zone")) - cmd.Flags().String("serviceAccountID", "", "ID for the service account that will be created (required)\n"+ - "Must be 6 to 30 lowercase letters, digits, or hyphens.") - must(cobra.MarkFlagRequired(cmd.Flags(), "serviceAccountID")) + + cmd.Flags().String("serviceAccountID", "", "[Deprecated use \"--prefix\"]ID for the service account that will be created (required)\n"+ + "Must be 6 to 30 lowercase letters, digits, or hyphens. This flag is mutually exclusive with --prefix.") + cmd.Flags().String("prefix", "", "Prefix for the service account ID and VM ID that will be created (required)\n"+ + "Must be letters, digits, or hyphens.") + cmd.Flags().String("projectID", "", "ID of the GCP project the configuration will be created in (required)\n"+ "Find it on the welcome screen of your project: https://console.cloud.google.com/welcome") must(cobra.MarkFlagRequired(cmd.Flags(), "projectID")) + cmd.MarkFlagsMutuallyExclusive([]string{"prefix", "serviceAccountID"}...) + return cmd } @@ -53,6 +58,7 @@ func runIAMCreateGCP(cmd *cobra.Command, _ []string) error { type gcpIAMCreateFlags struct { rootFlags serviceAccountID string + namePrefix string zone string region string projectID string @@ -91,9 +97,14 @@ func (f *gcpIAMCreateFlags) parse(flags *pflag.FlagSet) error { if err != nil { return fmt.Errorf("getting 'serviceAccountID' flag: %w", err) } - if !gcpIDRegex.MatchString(f.serviceAccountID) { + if f.serviceAccountID != "" && !gcpIDRegex.MatchString(f.serviceAccountID) { return fmt.Errorf("serviceAccountID %q doesn't match %s", f.serviceAccountID, gcpIDRegex) } + + f.namePrefix, err = flags.GetString("prefix") + if err != nil { + return fmt.Errorf("getting 'prefix' flag: %w", err) + } return nil } @@ -109,12 +120,14 @@ func (c *gcpIAMCreator) getIAMConfigOptions() *cloudcmd.IAMConfigOptions { Region: c.flags.region, ProjectID: c.flags.projectID, ServiceAccountID: c.flags.serviceAccountID, + NamePrefix: c.flags.namePrefix, }, } } func (c *gcpIAMCreator) printConfirmValues(cmd *cobra.Command) { cmd.Printf("Project ID:\t\t%s\n", c.flags.projectID) + cmd.Printf("Name Prefix:\t\t%s\n", c.flags.namePrefix) cmd.Printf("Service Account ID:\t%s\n", c.flags.serviceAccountID) cmd.Printf("Region:\t\t\t%s\n", c.flags.region) cmd.Printf("Zone:\t\t\t%s\n\n", c.flags.zone) diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index f48d36e029..f12de0fbc0 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -103,9 +103,18 @@ func (c *Client) ShowIAM(ctx context.Context, provider cloudprovider.Provider) ( if !ok { return IAMOutput{}, errors.New("invalid type in service_account_key output: not a string") } + IAMServiceAccountVMOutputRaw, ok := tfState.Values.Outputs["service_account_mail_vm"] + if !ok { + return IAMOutput{}, errors.New("no service_account_mail_vm output found") + } + IAMServiceAccountVMOutput, ok := IAMServiceAccountVMOutputRaw.Value.(string) + if !ok { + return IAMOutput{}, errors.New("invalid type in service_account_mail_vm output: not a string") + } return IAMOutput{ GCP: GCPIAMOutput{ - SaKey: saKeyOutput, + SaKey: saKeyOutput, + ServiceAccountVMMailAddress: IAMServiceAccountVMOutput, }, }, nil case cloudprovider.Azure: @@ -539,7 +548,8 @@ type IAMOutput struct { // GCPIAMOutput contains the output information of the Terraform IAM operation on GCP. type GCPIAMOutput struct { - SaKey string + SaKey string + ServiceAccountVMMailAddress string } // AzureIAMOutput contains the output information of the Terraform IAM operation on Microsoft Azure. diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index 103f0e959f..41236a4f0b 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -120,6 +120,7 @@ func TestPrepareIAM(t *testing.T) { Region: "europe-west1", Zone: "europe-west1-a", ServiceAccountID: "const-test-case", + NamePrefix: "test_iam", } azureVars := &AzureIAMVariables{ Location: "westus", @@ -509,6 +510,9 @@ func TestCreateIAM(t *testing.T) { "service_account_key": { Value: "12345678_abcdefg", }, + "service_account_mail_vm": { + Value: "test_iam_service_account_vm", + }, "subscription_id": { Value: "test_subscription_id", }, @@ -581,7 +585,7 @@ func TestCreateIAM(t *testing.T) { vars: gcpVars, tf: &stubTerraform{showState: newTestState()}, fs: afero.NewMemMapFs(), - want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}}, + want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg", ServiceAccountVMMailAddress: "test_iam_service_account_vm"}}, }, "gcp init fails": { pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), @@ -614,7 +618,25 @@ func TestCreateIAM(t *testing.T) { tf: &stubTerraform{ showState: &tfjson.State{ Values: &tfjson.StateValues{ - Outputs: map[string]*tfjson.StateOutput{}, + Outputs: map[string]*tfjson.StateOutput{ + "service_account_mail_vm": {Value: "test_iam_service_account_vm"}, + }, + }, + }, + }, + fs: afero.NewMemMapFs(), + wantErr: true, + }, + "gcp no service_account_mail_vm": { + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), + provider: cloudprovider.GCP, + vars: gcpVars, + tf: &stubTerraform{ + showState: &tfjson.State{ + Values: &tfjson.StateValues{ + Outputs: map[string]*tfjson.StateOutput{ + "service_account_key": {Value: "12345678_abcdefg"}, + }, }, }, }, @@ -1129,7 +1151,8 @@ func TestShowIAM(t *testing.T) { "GCP success": { tf: &stubTerraform{ showState: getTfjsonState(map[string]any{ - "service_account_key": "key", + "service_account_key": "key", + "service_account_mail_vm": "example@example.com", }), }, csp: cloudprovider.GCP, @@ -1137,7 +1160,8 @@ func TestShowIAM(t *testing.T) { "GCP wrong data type": { tf: &stubTerraform{ showState: getTfjsonState(map[string]any{ - "service_account_key": map[string]any{}, + "service_account_key": map[string]any{}, + "service_account_mail_vm": "example@example.com", }), }, csp: cloudprovider.GCP, @@ -1145,7 +1169,9 @@ func TestShowIAM(t *testing.T) { }, "GCP missing key": { tf: &stubTerraform{ - showState: getTfjsonState(map[string]any{}), + showState: getTfjsonState(map[string]any{ + "service_account_mail_vm": "example@example.com", + }), }, csp: cloudprovider.GCP, wantErr: true, diff --git a/cli/internal/terraform/variables.go b/cli/internal/terraform/variables.go index 86af569e08..3b9d8b8d2a 100644 --- a/cli/internal/terraform/variables.go +++ b/cli/internal/terraform/variables.go @@ -182,6 +182,8 @@ type GCPIAMVariables struct { Zone string `hcl:"zone" cty:"zone"` // ServiceAccountID is the ID of the service account to use. ServiceAccountID string `hcl:"service_account_id" cty:"service_account_id"` + // IAMServiceAccountVM is the ID of the service account to attach to VMs. + NamePrefix string `hcl:"name_prefix" cty:"name_prefix"` } // String returns a string representation of the IAM-specific variables, formatted as Terraform variables. diff --git a/cli/internal/terraform/variables_test.go b/cli/internal/terraform/variables_test.go index 02567c314c..19f2ba0b93 100644 --- a/cli/internal/terraform/variables_test.go +++ b/cli/internal/terraform/variables_test.go @@ -173,9 +173,27 @@ func TestGCPIAMVariables(t *testing.T) { region = "eu-central-1" zone = "eu-central-1a" service_account_id = "my-service-account" +name_prefix = "" ` got := vars.String() assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences + + vars = GCPIAMVariables{ + Project: "my-project", + Region: "eu-central-1", + Zone: "eu-central-1a", + NamePrefix: "my-prefix", + } + + // test that the variables are correctly rendered + want = `project_id = "my-project" +region = "eu-central-1" +zone = "eu-central-1a" +service_account_id = "" +name_prefix = "my-prefix" +` + got = vars.String() + assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences } func TestAzureClusterVariables(t *testing.T) { diff --git a/docs/docs/getting-started/first-steps.md b/docs/docs/getting-started/first-steps.md index b197f659dd..bd07198d4a 100644 --- a/docs/docs/getting-started/first-steps.md +++ b/docs/docs/getting-started/first-steps.md @@ -102,7 +102,7 @@ If you encounter any problem with the following steps, make sure to use the [lat ```bash - constellation iam create gcp --projectID=yourproject-12345 --zone=europe-west2-a --serviceAccountID=constell-test --update-config + constellation iam create gcp --projectID=yourproject-12345 --zone=europe-west2-a --serviceAccountVMID=constell-test-vm --serviceAccountID=constell-test --update-config ``` This command creates IAM configuration in the GCP project `yourproject-12345` on the GCP zone `europe-west2-a` creating a new service account `constell-test`. It also updates the configuration file `constellation-conf.yaml` in your current directory with the IAM values filled in. diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 7bb4d5b404..9f22428b00 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -686,10 +686,12 @@ constellation iam create gcp [flags] ``` -h, --help help for gcp + --prefix string Prefix for the service account ID and VM ID that will be created (required) + Must be letters, digits, or hyphens. --projectID string ID of the GCP project the configuration will be created in (required) Find it on the welcome screen of your project: https://console.cloud.google.com/welcome - --serviceAccountID string ID for the service account that will be created (required) - Must be 6 to 30 lowercase letters, digits, or hyphens. + --serviceAccountID string [Deprecated use "--prefix"]ID for the service account that will be created (required) + Must be 6 to 30 lowercase letters, digits, or hyphens. This flag is mutually exclusive with --prefix. --zone string GCP zone the cluster will be deployed in (required) Find a list of available zones here: https://cloud.google.com/compute/docs/regions-zones#available ``` diff --git a/docs/docs/workflows/config.md b/docs/docs/workflows/config.md index 95f791acd7..35ed983028 100644 --- a/docs/docs/workflows/config.md +++ b/docs/docs/workflows/config.md @@ -210,7 +210,7 @@ Paste the output into the corresponding fields of the `constellation-conf.yaml` You must be authenticated with the [GCP CLI](https://cloud.google.com/sdk/gcloud) in the shell session with a user that has the [required permissions for IAM creation](../getting-started/install.md#set-up-cloud-credentials). ```bash -constellation iam create gcp --projectID=yourproject-12345 --zone=europe-west2-a --serviceAccountID=constell-test +constellation iam create gcp --projectID=yourproject-12345 --zone=europe-west2-a --prefix=constell-test ``` This command creates IAM configuration in the GCP project `yourproject-12345` on the GCP zone `europe-west2-a` creating a new service account `constell-test`. diff --git a/terraform-provider-constellation/examples/full/gcp/main.tf b/terraform-provider-constellation/examples/full/gcp/main.tf index d685785b06..ef19c4ab7e 100644 --- a/terraform-provider-constellation/examples/full/gcp/main.tf +++ b/terraform-provider-constellation/examples/full/gcp/main.tf @@ -45,11 +45,11 @@ resource "random_bytes" "measurement_salt" { module "gcp_iam" { // replace $VERSION with the Constellation version you want to use, e.g., v2.14.0 - source = "https://github.com/edgelesssys/constellation/releases/download/$VERSION/terraform-module.zip//terraform-module/iam/gcp" - project_id = local.project_id - service_account_id = "${local.name}-sa" - zone = local.zone - region = local.region + source = "https://github.com/edgelesssys/constellation/releases/download/$VERSION/terraform-module.zip//terraform-module/iam/gcp" + sproject_id = local.project_id + name_prefix = local.name + zone = local.zone + region = local.region } module "gcp_infrastructure" { diff --git a/terraform/infrastructure/iam/gcp/main.tf b/terraform/infrastructure/iam/gcp/main.tf index dc27c5f6cf..48b9c477ab 100644 --- a/terraform/infrastructure/iam/gcp/main.tf +++ b/terraform/infrastructure/iam/gcp/main.tf @@ -13,8 +13,19 @@ provider "google" { zone = var.zone } +locals { + sa_name = var.name_prefix == "" ? var.service_account_id : "${var.name_prefix}-sa" + sa_vm_name = var.name_prefix == "" ? "${var.service_account_id}-vm" : "${var.name_prefix}-sa-vm" +} + +resource "google_service_account" "service_account_vm" { + account_id = local.sa_vm_name + display_name = "Constellation service account for VMs" + description = "Service account used by the VMs" +} + resource "google_service_account" "service_account" { - account_id = var.service_account_id + account_id = local.sa_name display_name = "Constellation service account" description = "Service account used inside Constellation" } @@ -65,6 +76,30 @@ resource "google_project_iam_member" "iam_service_account_user_role" { depends_on = [null_resource.delay] } +resource "google_project_iam_custom_role" "iam_custom_role_vm" { + # role_id must not contain dashes + role_id = replace("${local.sa_vm_name}-role", "-", "_") + title = "Constellation IAM role for VMs" + description = "Constellation IAM role for VMs" + permissions = [ + "compute.instances.get", + "compute.instances.list", + "compute.subnetworks.get", + "compute.globalForwardingRules.list", + "compute.zones.list", + ] +} + +resource "google_project_iam_binding" "iam_binding_custom_role_vm_to_service_account_vm" { + project = var.project_id + role = "projects/${var.project_id}/roles/${google_project_iam_custom_role.iam_custom_role_vm.role_id}" + + members = [ + "serviceAccount:${google_service_account.service_account_vm.email}", + ] + depends_on = [null_resource.delay] +} + resource "google_service_account_key" "service_account_key" { service_account_id = google_service_account.service_account.name depends_on = [null_resource.delay] diff --git a/terraform/infrastructure/iam/gcp/outputs.tf b/terraform/infrastructure/iam/gcp/outputs.tf index 437261bb84..cd76b1b9ae 100644 --- a/terraform/infrastructure/iam/gcp/outputs.tf +++ b/terraform/infrastructure/iam/gcp/outputs.tf @@ -3,3 +3,9 @@ output "service_account_key" { description = "Private key of the service account." sensitive = true } + +output "service_account_mail_vm" { + value = google_service_account.service_account_vm.email + description = "Mail address of the service account to be attached to the VMs" + sensitive = false +} diff --git a/terraform/infrastructure/iam/gcp/variables.tf b/terraform/infrastructure/iam/gcp/variables.tf index 19c25d787b..b1895f0fca 100644 --- a/terraform/infrastructure/iam/gcp/variables.tf +++ b/terraform/infrastructure/iam/gcp/variables.tf @@ -5,7 +5,12 @@ variable "project_id" { variable "service_account_id" { type = string - description = "ID for the service account being created. Must match ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$." + description = "[DEPRECATED use var.name_prefix] ID for the service account being created. Must match ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$." +} + +variable "name_prefix" { + type = string + description = "Prefix to be used for all resources created by this module." } variable "region" {