From bd65ad36976b92779db2338b67a06a2a8cdcb217 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 | 24 ++- cli/internal/cmd/iamcreate_test.go | 184 ++++++++++-------- cli/internal/cmd/iamcreategcp.go | 30 ++- cli/internal/terraform/terraform.go | 14 +- cli/internal/terraform/terraform_test.go | 44 ++++- cli/internal/terraform/variables.go | 2 + docs/docs/getting-started/first-steps.md | 2 +- docs/docs/reference/cli.md | 16 +- docs/docs/workflows/config.md | 2 +- .../examples/full/gcp/main.tf | 10 +- terraform/infrastructure/iam/gcp/main.tf | 30 +++ terraform/infrastructure/iam/gcp/outputs.tf | 6 + terraform/infrastructure/iam/gcp/variables.tf | 5 + 14 files changed, 241 insertions(+), 129 deletions(-) diff --git a/.github/actions/constellation_iam_create/action.yml b/.github/actions/constellation_iam_create/action.yml index 91282a927..95e607c36 100644 --- a/.github/actions/constellation_iam_create/action.yml +++ b/.github/actions/constellation_iam_create/action.yml @@ -110,6 +110,7 @@ runs: --projectID="${{ inputs.gcpProjectID }}" \ --zone="${{ inputs.gcpZone }}" \ --serviceAccountID="${{ inputs.namePrefix }}-sa" \ + --serviceAccountVMID="${{ inputs.namePrefix }}-vm-sa" \ --update-config \ --tf-log=DEBUG \ --yes diff --git a/cli/internal/cloudcmd/iam.go b/cli/internal/cloudcmd/iam.go index e73f2854d..8205347b1 100644 --- a/cli/internal/cloudcmd/iam.go +++ b/cli/internal/cloudcmd/iam.go @@ -87,10 +87,11 @@ type IAMConfigOptions struct { // GCPIAMConfig holds the necessary values for GCP IAM configuration. type GCPIAMConfig struct { - Region string - Zone string - ProjectID string - ServiceAccountID string + Region string + Zone string + ProjectID string + ServiceAccountID string + ServiceAccountVMID string } // AzureIAMConfig holds the necessary values for Azure IAM configuration. @@ -140,10 +141,11 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMCon defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel) vars := terraform.GCPIAMVariables{ - ServiceAccountID: opts.GCP.ServiceAccountID, - Project: opts.GCP.ProjectID, - Region: opts.GCP.Region, - Zone: opts.GCP.Zone, + IAMServiceAccountVM: opts.GCP.ServiceAccountID, + ServiceAccountID: opts.GCP.ServiceAccountVMID, + Project: opts.GCP.ProjectID, + Region: opts.GCP.Region, + Zone: opts.GCP.Zone, } if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil { @@ -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/cmd/iamcreate_test.go b/cli/internal/cmd/iamcreate_test.go index 3a9c83051..17d9ccdd4 100644 --- a/cli/internal/cmd/iamcreate_test.go +++ b/cli/internal/cmd/iamcreate_test.go @@ -452,113 +452,124 @@ func TestIAMCreateGCP(t *testing.T) { } testCases := map[string]struct { - setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingConfigFiles []string, existingDirs []string) afero.Fs - creator *stubIAMCreator - zoneFlag string - serviceAccountIDFlag string - projectIDFlag string - yesFlag bool - updateConfigFlag bool - existingConfigFiles []string - existingDirs []string - stdin string - wantAbort bool - wantErr bool + setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingConfigFiles []string, existingDirs []string) afero.Fs + creator *stubIAMCreator + zoneFlag string + serviceAccountIDFlag string + serviceAccountVMIDFlag string + projectIDFlag string + yesFlag bool + updateConfigFlag bool + existingConfigFiles []string + existingDirs []string + stdin string + wantAbort bool + wantErr bool }{ "iam create gcp": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - yesFlag: true, + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + zoneFlag: "europe-west1-a", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + projectIDFlag: "constell-1234", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + 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", + serviceAccountIDFlag: "constell-test", + serviceAccountVMIDFlag: "constell-test-vm", + projectIDFlag: "constell-1234", + yesFlag: true, + updateConfigFlag: true, + wantErr: true, }, } @@ -588,9 +599,10 @@ func TestIAMCreateGCP(t *testing.T) { }, providerCreator: &gcpIAMCreator{ flags: gcpIAMCreateFlags{ - zone: tc.zoneFlag, - serviceAccountID: tc.serviceAccountIDFlag, - projectID: tc.projectIDFlag, + zone: tc.zoneFlag, + serviceAccountID: tc.serviceAccountIDFlag, + serviceAccountVMID: tc.serviceAccountVMIDFlag, + projectID: tc.projectIDFlag, }, }, } diff --git a/cli/internal/cmd/iamcreategcp.go b/cli/internal/cmd/iamcreategcp.go index b6c55e5d1..f7ff3f62d 100644 --- a/cli/internal/cmd/iamcreategcp.go +++ b/cli/internal/cmd/iamcreategcp.go @@ -34,6 +34,9 @@ func newIAMCreateGCPCmd() *cobra.Command { 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("serviceAccountVMID", "", "ID for the VM service account that will be created (required)\n"+ + "Must be 6 to 30 lowercase letters, digits, or hyphens.") + must(cobra.MarkFlagRequired(cmd.Flags(), "serviceAccountVMID")) 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")) @@ -52,10 +55,11 @@ func runIAMCreateGCP(cmd *cobra.Command, _ []string) error { // gcpIAMCreateFlags contains the parsed flags of the iam create gcp command. type gcpIAMCreateFlags struct { rootFlags - serviceAccountID string - zone string - region string - projectID string + serviceAccountID string + serviceAccountVMID string + zone string + region string + projectID string } func (f *gcpIAMCreateFlags) parse(flags *pflag.FlagSet) error { @@ -94,6 +98,14 @@ func (f *gcpIAMCreateFlags) parse(flags *pflag.FlagSet) error { if !gcpIDRegex.MatchString(f.serviceAccountID) { return fmt.Errorf("serviceAccountID %q doesn't match %s", f.serviceAccountID, gcpIDRegex) } + + f.serviceAccountVMID, err = flags.GetString("serviceAccountVMID") + if err != nil { + return fmt.Errorf("getting 'serviceAccountVMID' flag: %w", err) + } + if !gcpIDRegex.MatchString(f.serviceAccountVMID) { + return fmt.Errorf("serviceAccountVMID %q doesn't match %s", f.serviceAccountVMID, gcpIDRegex) + } return nil } @@ -105,10 +117,11 @@ type gcpIAMCreator struct { func (c *gcpIAMCreator) getIAMConfigOptions() *cloudcmd.IAMConfigOptions { return &cloudcmd.IAMConfigOptions{ GCP: cloudcmd.GCPIAMConfig{ - Zone: c.flags.zone, - Region: c.flags.region, - ProjectID: c.flags.projectID, - ServiceAccountID: c.flags.serviceAccountID, + Zone: c.flags.zone, + Region: c.flags.region, + ProjectID: c.flags.projectID, + ServiceAccountID: c.flags.serviceAccountID, + ServiceAccountVMID: c.flags.serviceAccountVMID, }, } } @@ -116,6 +129,7 @@ func (c *gcpIAMCreator) getIAMConfigOptions() *cloudcmd.IAMConfigOptions { func (c *gcpIAMCreator) printConfirmValues(cmd *cobra.Command) { cmd.Printf("Project ID:\t\t%s\n", c.flags.projectID) cmd.Printf("Service Account ID:\t%s\n", c.flags.serviceAccountID) + cmd.Printf("Service Account VM ID:\t%s\n", c.flags.serviceAccountVMID) 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 f48d36e02..f12de0fbc 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 103f0e959..fd2c9fdc7 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -116,10 +116,11 @@ func TestPrepareCluster(t *testing.T) { func TestPrepareIAM(t *testing.T) { gcpVars := &GCPIAMVariables{ - Project: "const-1234", - Region: "europe-west1", - Zone: "europe-west1-a", - ServiceAccountID: "const-test-case", + Project: "const-1234", + Region: "europe-west1", + Zone: "europe-west1-a", + ServiceAccountID: "const-test-case", + IAMServiceAccountVM: "test_iam_service_account_vm", } 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 86af569e0..84ffcab5f 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. + IAMServiceAccountVM string `hcl:"service_account_id_vm" cty:"service_account_id_vm"` } // String returns a string representation of the IAM-specific variables, formatted as Terraform variables. diff --git a/docs/docs/getting-started/first-steps.md b/docs/docs/getting-started/first-steps.md index b197f659d..bd07198d4 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 06e823e6e..1f98a65b9 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -685,13 +685,15 @@ constellation iam create gcp [flags] ### Options ``` - -h, --help help for gcp - --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. - --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 + -h, --help help for gcp + --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. + --serviceAccountVMID string ID for the VM service account that will be created (required) + Must be 6 to 30 lowercase letters, digits, or hyphens. + --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 ``` ### Options inherited from parent commands diff --git a/docs/docs/workflows/config.md b/docs/docs/workflows/config.md index 95f791acd..2f96fb2e2 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 --serviceAccountVMID=constell-test-vm --serviceAccountID=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 aa05d9b0e..7a9175af0 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 + project_id = local.project_id + service_account_id = "${local.name}-sa" + service_account_id_vm = "${local.name}-sa-vm" + 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 9ed3759b3..fc9e3d15e 100644 --- a/terraform/infrastructure/iam/gcp/main.tf +++ b/terraform/infrastructure/iam/gcp/main.tf @@ -13,6 +13,12 @@ provider "google" { zone = var.zone } +resource "google_service_account" "service_account_vm" { + account_id = var.service_account_id_vm + 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 display_name = "Constellation service account" @@ -65,6 +71,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("${var.service_account_id}-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 437261bb8..cd76b1b9a 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 19c25d787..b4317dd64 100644 --- a/terraform/infrastructure/iam/gcp/variables.tf +++ b/terraform/infrastructure/iam/gcp/variables.tf @@ -8,6 +8,11 @@ variable "service_account_id" { description = "ID for the service account being created. Must match ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$." } +variable "service_account_id_vm" { + type = string + description = "ID for the service account being created. Must match ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$." +} + variable "region" { type = string description = "GCP region the cluster should reside in. Needs to have the N2D machine type available."