Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 6 additions & 0 deletions .github/actions/e2e_test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ runs:
run: |
uuid=$(uuidgen | tr "[:upper:]" "[:lower:]")
uuid=${uuid%%-*}

# GCP has a 6 character limit the additional uuid prefix since the full prefix length has a maximum of 24
if [[ ${{ inputs.cloudProvider }} == 'gcp' ]]; then
uuid=${uuid:0:6}
fi

echo "uuid=${uuid}" | tee -a $GITHUB_OUTPUT
echo "prefix=e2e-${{ github.run_id }}-${{ github.run_attempt }}-${uuid}" | tee -a $GITHUB_OUTPUT

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
3 changes: 3 additions & 0 deletions cli/internal/cloudcmd/iamupgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
// UpgradeRequiresIAMMigration returns true if the given cloud provider requires an IAM migration.
func UpgradeRequiresIAMMigration(provider cloudprovider.Provider) bool {
switch provider {
case cloudprovider.GCP:
// TODO(@3u13r): remove this case after the v2.22.0 release
return true
default:
return false
}
Expand Down
2 changes: 2 additions & 0 deletions cli/internal/cloudcmd/tfvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ func gcpTerraformVars(conf *config.Config, imageRef string) *terraform.GCPCluste
InternalLoadBalancer: conf.InternalLoadBalancer,
CCTechnology: ccTech,
AdditionalLabels: conf.Tags,
IAMServiceAccountVM: conf.Provider.GCP.IAMServiceAccountVM,
}
}

Expand All @@ -240,6 +241,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
1 change: 1 addition & 0 deletions cli/internal/cmd/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ func TestValidateInputs(t *testing.T) {
ClientX509CertURL: "client_cert",
}))
cfg.Provider.GCP.ServiceAccountKeyPath = "saKey.json"
cfg.Provider.GCP.IAMServiceAccountVM = "example@example.com"
}

require.NoError(fh.WriteYAML(constants.ConfigFilename, cfg))
Expand Down
3 changes: 3 additions & 0 deletions cli/internal/cmd/iamcreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ var (
regionRegex = regexp.MustCompile(`^\w+-\w+[0-9]$`)
// Source: https://cloud.google.com/resource-manager/reference/rest/v1/projects.
gcpIDRegex = regexp.MustCompile(`^[a-z][-a-z0-9]{4,28}[a-z0-9]$`)

// We currently append 6 characters to the prefix, therefore we remove 6 characters from the gcpIDRegex.
gcpPrefixRegex = regexp.MustCompile(`^[a-z][-a-z0-9]{4,22}[a-z0-9]$`)
)

// newIAMCreateCmd returns a new cobra.Command for the iam create parent command. It needs another verb, and does nothing on its own.
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
35 changes: 29 additions & 6 deletions cli/internal/cmd/iamcreategcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ 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"}...)
must(cmd.Flags().MarkDeprecated("serviceAccountID", "use --prefix instead"))

return cmd
}

Expand All @@ -53,6 +59,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 +98,18 @@ 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)
}
if f.namePrefix != "" && !gcpPrefixRegex.MatchString(f.namePrefix) {
return fmt.Errorf("prefix %q doesn't match %s", f.namePrefix, gcpIDRegex)
}

return nil
}

Expand All @@ -109,13 +125,19 @@ 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("Service Account ID:\t%s\n", c.flags.serviceAccountID)
if c.flags.namePrefix != "" {
cmd.Printf("Name Prefix:\t\t%s\n", c.flags.namePrefix)
}
if c.flags.serviceAccountID != "" {
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 All @@ -127,11 +149,12 @@ func (c *gcpIAMCreator) printOutputValues(cmd *cobra.Command, _ cloudcmd.IAMOutp
cmd.Printf("serviceAccountKeyPath:\t%s\n\n", c.flags.pathPrefixer.PrefixPrintablePath(constants.GCPServiceAccountKeyFilename))
}

func (c *gcpIAMCreator) writeOutputValuesToConfig(conf *config.Config, _ cloudcmd.IAMOutput) {
func (c *gcpIAMCreator) writeOutputValuesToConfig(conf *config.Config, out cloudcmd.IAMOutput) {
conf.Provider.GCP.Project = c.flags.projectID
conf.Provider.GCP.ServiceAccountKeyPath = constants.GCPServiceAccountKeyFilename // File was created in workspace, so only the filename is needed.
conf.Provider.GCP.Region = c.flags.region
conf.Provider.GCP.Zone = c.flags.zone
conf.Provider.GCP.IAMServiceAccountVM = out.GCPOutput.IAMServiceAccountVM
for groupName, group := range conf.NodeGroups {
group.Zone = c.flags.zone
conf.NodeGroups[groupName] = group
Expand Down
1 change: 1 addition & 0 deletions cli/internal/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
conf.Provider.GCP.Project = "test-project"
conf.Provider.GCP.Zone = "test-zone"
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
conf.Provider.GCP.IAMServiceAccountVM = "example@example.com"
conf.Attestation.GCPSEVSNP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce, measurements.PCRMeasurementLength)
conf.Attestation.GCPSEVSNP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce, measurements.PCRMeasurementLength)
conf.Attestation.GCPSEVSNP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce, measurements.PCRMeasurementLength)
Expand Down
Loading