Skip to content

Commit

Permalink
terraform/iam: create additional service account for VMs
Browse files Browse the repository at this point in the history
This service account is used in the following commits and is attached to the VMs
  • Loading branch information
3u13r committed Mar 10, 2025
1 parent eb29f9f commit 824bfe7
Show file tree
Hide file tree
Showing 17 changed files with 224 additions and 89 deletions.
1 change: 1 addition & 0 deletions .github/actions/constellation_iam_create/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
8 changes: 6 additions & 2 deletions cli/internal/cloudcmd/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type GCPIAMConfig struct {
Zone string
ProjectID string
ServiceAccountID string
NamePrefix string
}

// AzureIAMConfig holds the necessary values for Azure IAM configuration.
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions cli/internal/cloudcmd/iamupgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions cli/internal/cloudcmd/tfvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
140 changes: 75 additions & 65 deletions cli/internal/cmd/iamcreate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ func TestIAMCreateGCP(t *testing.T) {
creator *stubIAMCreator
zoneFlag string
serviceAccountIDFlag string
namePrefixFlag string
projectIDFlag string
yesFlag bool
updateConfigFlag bool
Expand All @@ -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",
Expand All @@ -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,
},
}

Expand Down Expand Up @@ -590,6 +599,7 @@ func TestIAMCreateGCP(t *testing.T) {
flags: gcpIAMCreateFlags{
zone: tc.zoneFlag,
serviceAccountID: tc.serviceAccountIDFlag,
namePrefix: tc.serviceAccountIDFlag,
projectID: tc.projectIDFlag,
},
},
Expand Down
21 changes: 17 additions & 4 deletions cli/internal/cmd/iamcreategcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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)
Expand Down
14 changes: 12 additions & 2 deletions cli/internal/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 824bfe7

Please sign in to comment.