gcp: support projects with no default permissions (#3656)

* helm/gcp: use service account in operator and joinservice

* helm: format operator testdata

* terraform/iam: create additional service account for VMs

This service account is used in the following commits and is attached to the VMs

* config: pass VM service account from iam create to cluster create via config

* cli/iamcreate: limit name prefix length

* docs: add minimal gcp IAM permissions
This commit is contained in:
Leonard Cohnen 2025-03-25 14:13:38 +01:00 committed by GitHub
parent 83e08e3e37
commit 66815a4a47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 771 additions and 466 deletions

View file

@ -102,6 +102,7 @@ runs:
--tf-log=DEBUG \ --tf-log=DEBUG \
--yes ${extraFlags} --yes ${extraFlags}
# TODO(@3u13r): Replace deprecated --serviceAccountID with --prefix
- name: Constellation iam create gcp - name: Constellation iam create gcp
shell: bash shell: bash
if: inputs.cloudProvider == 'gcp' if: inputs.cloudProvider == 'gcp'

View file

@ -258,6 +258,12 @@ runs:
run: | run: |
uuid=$(uuidgen | tr "[:upper:]" "[:lower:]") uuid=$(uuidgen | tr "[:upper:]" "[:lower:]")
uuid=${uuid%%-*} 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 "uuid=${uuid}" | tee -a $GITHUB_OUTPUT
echo "prefix=e2e-${{ github.run_id }}-${{ github.run_attempt }}-${uuid}" | tee -a $GITHUB_OUTPUT echo "prefix=e2e-${{ github.run_id }}-${{ github.run_attempt }}-${uuid}" | tee -a $GITHUB_OUTPUT

View file

@ -91,6 +91,7 @@ type GCPIAMConfig struct {
Zone string Zone string
ProjectID string ProjectID string
ServiceAccountID string ServiceAccountID string
NamePrefix string
} }
// AzureIAMConfig holds the necessary values for Azure IAM configuration. // 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{ vars := terraform.GCPIAMVariables{
ServiceAccountID: opts.GCP.ServiceAccountID, ServiceAccountID: opts.GCP.ServiceAccountID,
NamePrefix: opts.GCP.NamePrefix,
Project: opts.GCP.ProjectID, Project: opts.GCP.ProjectID,
Region: opts.GCP.Region, Region: opts.GCP.Region,
Zone: opts.GCP.Zone, Zone: opts.GCP.Zone,
@ -159,6 +161,7 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMCon
CloudProvider: cloudprovider.GCP, CloudProvider: cloudprovider.GCP,
GCPOutput: GCPIAMOutput{ GCPOutput: GCPIAMOutput{
ServiceAccountKey: iamOutput.GCP.SaKey, ServiceAccountKey: iamOutput.GCP.SaKey,
IAMServiceAccountVM: iamOutput.GCP.ServiceAccountVMMailAddress,
}, },
}, nil }, nil
} }
@ -233,6 +236,7 @@ type IAMOutput struct {
// GCPIAMOutput contains the output information of a GCP IAM configuration. // GCPIAMOutput contains the output information of a GCP IAM configuration.
type GCPIAMOutput struct { 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. // AzureIAMOutput contains the output information of a Microsoft Azure IAM configuration.

View file

@ -22,6 +22,9 @@ import (
// UpgradeRequiresIAMMigration returns true if the given cloud provider requires an IAM migration. // UpgradeRequiresIAMMigration returns true if the given cloud provider requires an IAM migration.
func UpgradeRequiresIAMMigration(provider cloudprovider.Provider) bool { func UpgradeRequiresIAMMigration(provider cloudprovider.Provider) bool {
switch provider { switch provider {
case cloudprovider.GCP:
// TODO(@3u13r): remove this case after the v2.22.0 release
return true
default: default:
return false return false
} }

View file

@ -231,6 +231,7 @@ func gcpTerraformVars(conf *config.Config, imageRef string) *terraform.GCPCluste
InternalLoadBalancer: conf.InternalLoadBalancer, InternalLoadBalancer: conf.InternalLoadBalancer,
CCTechnology: ccTech, CCTechnology: ccTech,
AdditionalLabels: conf.Tags, AdditionalLabels: conf.Tags,
IAMServiceAccountVM: conf.Provider.GCP.IAMServiceAccountVM,
} }
} }
@ -240,6 +241,7 @@ func gcpTerraformIAMVars(conf *config.Config, oldVars terraform.GCPIAMVariables)
Region: conf.Provider.GCP.Region, Region: conf.Provider.GCP.Region,
Zone: conf.Provider.GCP.Zone, Zone: conf.Provider.GCP.Zone,
ServiceAccountID: oldVars.ServiceAccountID, ServiceAccountID: oldVars.ServiceAccountID,
NamePrefix: oldVars.NamePrefix,
} }
} }

View file

@ -256,6 +256,7 @@ func TestValidateInputs(t *testing.T) {
ClientX509CertURL: "client_cert", ClientX509CertURL: "client_cert",
})) }))
cfg.Provider.GCP.ServiceAccountKeyPath = "saKey.json" cfg.Provider.GCP.ServiceAccountKeyPath = "saKey.json"
cfg.Provider.GCP.IAMServiceAccountVM = "example@example.com"
} }
require.NoError(fh.WriteYAML(constants.ConfigFilename, cfg)) require.NoError(fh.WriteYAML(constants.ConfigFilename, cfg))

View file

@ -29,6 +29,9 @@ var (
regionRegex = regexp.MustCompile(`^\w+-\w+[0-9]$`) regionRegex = regexp.MustCompile(`^\w+-\w+[0-9]$`)
// Source: https://cloud.google.com/resource-manager/reference/rest/v1/projects. // Source: https://cloud.google.com/resource-manager/reference/rest/v1/projects.
gcpIDRegex = regexp.MustCompile(`^[a-z][-a-z0-9]{4,28}[a-z0-9]$`) 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. // newIAMCreateCmd returns a new cobra.Command for the iam create parent command. It needs another verb, and does nothing on its own.

View file

@ -456,6 +456,7 @@ func TestIAMCreateGCP(t *testing.T) {
creator *stubIAMCreator creator *stubIAMCreator
zoneFlag string zoneFlag string
serviceAccountIDFlag string serviceAccountIDFlag string
namePrefixFlag string
projectIDFlag string projectIDFlag string
yesFlag bool yesFlag bool
updateConfigFlag bool updateConfigFlag bool
@ -466,6 +467,14 @@ func TestIAMCreateGCP(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"iam create gcp": { "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, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
@ -477,7 +486,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
yesFlag: true, yesFlag: true,
existingConfigFiles: []string{constants.ConfigFilename}, existingConfigFiles: []string{constants.ConfigFilename},
@ -486,7 +495,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
updateConfigFlag: true, updateConfigFlag: true,
yesFlag: true, yesFlag: true,
@ -496,7 +505,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
existingDirs: []string{constants.TerraformIAMWorkingDir}, existingDirs: []string{constants.TerraformIAMWorkingDir},
@ -507,7 +516,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: invalidIAMIDFile}, creator: &stubIAMCreator{id: invalidIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
yesFlag: true, yesFlag: true,
wantErr: true, wantErr: true,
@ -516,7 +525,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
stdin: "yes\n", stdin: "yes\n",
}, },
@ -524,7 +533,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
stdin: "yes\n", stdin: "yes\n",
updateConfigFlag: true, updateConfigFlag: true,
@ -534,7 +543,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
stdin: "no\n", stdin: "no\n",
wantAbort: true, wantAbort: true,
@ -543,7 +552,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
stdin: "no\n", stdin: "no\n",
wantAbort: true, wantAbort: true,
@ -554,7 +563,7 @@ func TestIAMCreateGCP(t *testing.T) {
setupFs: readOnlyFs, setupFs: readOnlyFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
zoneFlag: "europe-west1-a", zoneFlag: "europe-west1-a",
serviceAccountIDFlag: "constell-test", namePrefixFlag: "constell-test",
projectIDFlag: "constell-1234", projectIDFlag: "constell-1234",
yesFlag: true, yesFlag: true,
updateConfigFlag: true, updateConfigFlag: true,
@ -590,6 +599,7 @@ func TestIAMCreateGCP(t *testing.T) {
flags: gcpIAMCreateFlags{ flags: gcpIAMCreateFlags{
zone: tc.zoneFlag, zone: tc.zoneFlag,
serviceAccountID: tc.serviceAccountIDFlag, serviceAccountID: tc.serviceAccountIDFlag,
namePrefix: tc.serviceAccountIDFlag,
projectID: tc.projectIDFlag, projectID: tc.projectIDFlag,
}, },
}, },

View file

@ -31,13 +31,19 @@ func newIAMCreateGCPCmd() *cobra.Command {
cmd.Flags().String("zone", "", "GCP zone the cluster will be deployed in (required)\n"+ 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") "Find a list of available zones here: https://cloud.google.com/compute/docs/regions-zones#available")
must(cobra.MarkFlagRequired(cmd.Flags(), "zone")) 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.") cmd.Flags().String("serviceAccountID", "", "[Deprecated use \"--prefix\"]ID for the service account that will be created (required)\n"+
must(cobra.MarkFlagRequired(cmd.Flags(), "serviceAccountID")) "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"+ 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") "Find it on the welcome screen of your project: https://console.cloud.google.com/welcome")
must(cobra.MarkFlagRequired(cmd.Flags(), "projectID")) must(cobra.MarkFlagRequired(cmd.Flags(), "projectID"))
cmd.MarkFlagsMutuallyExclusive([]string{"prefix", "serviceAccountID"}...)
must(cmd.Flags().MarkDeprecated("serviceAccountID", "use --prefix instead"))
return cmd return cmd
} }
@ -53,6 +59,7 @@ func runIAMCreateGCP(cmd *cobra.Command, _ []string) error {
type gcpIAMCreateFlags struct { type gcpIAMCreateFlags struct {
rootFlags rootFlags
serviceAccountID string serviceAccountID string
namePrefix string
zone string zone string
region string region string
projectID string projectID string
@ -91,9 +98,18 @@ func (f *gcpIAMCreateFlags) parse(flags *pflag.FlagSet) error {
if err != nil { if err != nil {
return fmt.Errorf("getting 'serviceAccountID' flag: %w", err) 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) 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 return nil
} }
@ -109,13 +125,19 @@ func (c *gcpIAMCreator) getIAMConfigOptions() *cloudcmd.IAMConfigOptions {
Region: c.flags.region, Region: c.flags.region,
ProjectID: c.flags.projectID, ProjectID: c.flags.projectID,
ServiceAccountID: c.flags.serviceAccountID, ServiceAccountID: c.flags.serviceAccountID,
NamePrefix: c.flags.namePrefix,
}, },
} }
} }
func (c *gcpIAMCreator) printConfirmValues(cmd *cobra.Command) { func (c *gcpIAMCreator) printConfirmValues(cmd *cobra.Command) {
cmd.Printf("Project ID:\t\t%s\n", c.flags.projectID) cmd.Printf("Project ID:\t\t%s\n", c.flags.projectID)
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("Service Account ID:\t%s\n", c.flags.serviceAccountID)
}
cmd.Printf("Region:\t\t\t%s\n", c.flags.region) cmd.Printf("Region:\t\t\t%s\n", c.flags.region)
cmd.Printf("Zone:\t\t\t%s\n\n", c.flags.zone) cmd.Printf("Zone:\t\t\t%s\n\n", c.flags.zone)
} }
@ -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)) 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.Project = c.flags.projectID
conf.Provider.GCP.ServiceAccountKeyPath = constants.GCPServiceAccountKeyFilename // File was created in workspace, so only the filename is needed. 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.Region = c.flags.region
conf.Provider.GCP.Zone = c.flags.zone conf.Provider.GCP.Zone = c.flags.zone
conf.Provider.GCP.IAMServiceAccountVM = out.GCPOutput.IAMServiceAccountVM
for groupName, group := range conf.NodeGroups { for groupName, group := range conf.NodeGroups {
group.Zone = c.flags.zone group.Zone = c.flags.zone
conf.NodeGroups[groupName] = group conf.NodeGroups[groupName] = group

View file

@ -539,6 +539,7 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
conf.Provider.GCP.Project = "test-project" conf.Provider.GCP.Project = "test-project"
conf.Provider.GCP.Zone = "test-zone" conf.Provider.GCP.Zone = "test-zone"
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path" 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[4] = measurements.WithAllBytes(0x44, measurements.Enforce, measurements.PCRMeasurementLength)
conf.Attestation.GCPSEVSNP.Measurements[9] = measurements.WithAllBytes(0x11, 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) conf.Attestation.GCPSEVSNP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce, measurements.PCRMeasurementLength)

View file

@ -103,9 +103,18 @@ func (c *Client) ShowIAM(ctx context.Context, provider cloudprovider.Provider) (
if !ok { if !ok {
return IAMOutput{}, errors.New("invalid type in service_account_key output: not a string") 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{ return IAMOutput{
GCP: GCPIAMOutput{ GCP: GCPIAMOutput{
SaKey: saKeyOutput, SaKey: saKeyOutput,
ServiceAccountVMMailAddress: IAMServiceAccountVMOutput,
}, },
}, nil }, nil
case cloudprovider.Azure: case cloudprovider.Azure:
@ -540,6 +549,7 @@ type IAMOutput struct {
// GCPIAMOutput contains the output information of the Terraform IAM operation on GCP. // GCPIAMOutput contains the output information of the Terraform IAM operation on GCP.
type GCPIAMOutput struct { type GCPIAMOutput struct {
SaKey string SaKey string
ServiceAccountVMMailAddress string
} }
// AzureIAMOutput contains the output information of the Terraform IAM operation on Microsoft Azure. // AzureIAMOutput contains the output information of the Terraform IAM operation on Microsoft Azure.

View file

@ -120,6 +120,7 @@ func TestPrepareIAM(t *testing.T) {
Region: "europe-west1", Region: "europe-west1",
Zone: "europe-west1-a", Zone: "europe-west1-a",
ServiceAccountID: "const-test-case", ServiceAccountID: "const-test-case",
NamePrefix: "test_iam",
} }
azureVars := &AzureIAMVariables{ azureVars := &AzureIAMVariables{
Location: "westus", Location: "westus",
@ -509,6 +510,9 @@ func TestCreateIAM(t *testing.T) {
"service_account_key": { "service_account_key": {
Value: "12345678_abcdefg", Value: "12345678_abcdefg",
}, },
"service_account_mail_vm": {
Value: "test_iam_service_account_vm",
},
"subscription_id": { "subscription_id": {
Value: "test_subscription_id", Value: "test_subscription_id",
}, },
@ -581,7 +585,7 @@ func TestCreateIAM(t *testing.T) {
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{showState: newTestState()}, tf: &stubTerraform{showState: newTestState()},
fs: afero.NewMemMapFs(), 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": { "gcp init fails": {
pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
@ -614,7 +618,25 @@ func TestCreateIAM(t *testing.T) {
tf: &stubTerraform{ tf: &stubTerraform{
showState: &tfjson.State{ showState: &tfjson.State{
Values: &tfjson.StateValues{ 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"},
},
}, },
}, },
}, },
@ -1130,6 +1152,7 @@ func TestShowIAM(t *testing.T) {
tf: &stubTerraform{ tf: &stubTerraform{
showState: getTfjsonState(map[string]any{ showState: getTfjsonState(map[string]any{
"service_account_key": "key", "service_account_key": "key",
"service_account_mail_vm": "example@example.com",
}), }),
}, },
csp: cloudprovider.GCP, csp: cloudprovider.GCP,
@ -1138,6 +1161,7 @@ func TestShowIAM(t *testing.T) {
tf: &stubTerraform{ tf: &stubTerraform{
showState: getTfjsonState(map[string]any{ 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, csp: cloudprovider.GCP,
@ -1145,7 +1169,9 @@ func TestShowIAM(t *testing.T) {
}, },
"GCP missing key": { "GCP missing key": {
tf: &stubTerraform{ tf: &stubTerraform{
showState: getTfjsonState(map[string]any{}), showState: getTfjsonState(map[string]any{
"service_account_mail_vm": "example@example.com",
}),
}, },
csp: cloudprovider.GCP, csp: cloudprovider.GCP,
wantErr: true, wantErr: true,

View file

@ -141,6 +141,8 @@ type GCPClusterVariables struct {
InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"` InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"`
// CCTechnology is the confidential computing technology to use on the VMs. (`SEV` or `SEV_SNP`) // CCTechnology is the confidential computing technology to use on the VMs. (`SEV` or `SEV_SNP`)
CCTechnology string `hcl:"cc_technology" cty:"cc_technology"` CCTechnology string `hcl:"cc_technology" cty:"cc_technology"`
// IAMServiceAccountControlPlane is the IAM service account mail address to attach to VMs.
IAMServiceAccountVM string `hcl:"iam_service_account_vm" cty:"iam_service_account_vm"`
// AdditionalLables are (optional) additional labels that should be applied to created resources. // AdditionalLables are (optional) additional labels that should be applied to created resources.
AdditionalLabels cloudprovider.Tags `hcl:"additional_labels" cty:"additional_labels"` AdditionalLabels cloudprovider.Tags `hcl:"additional_labels" cty:"additional_labels"`
} }
@ -182,6 +184,9 @@ type GCPIAMVariables struct {
Zone string `hcl:"zone" cty:"zone"` Zone string `hcl:"zone" cty:"zone"`
// ServiceAccountID is the ID of the service account to use. // ServiceAccountID is the ID of the service account to use.
ServiceAccountID string `hcl:"service_account_id" cty:"service_account_id"` ServiceAccountID string `hcl:"service_account_id" cty:"service_account_id"`
// IAMServiceAccountVM is the ID of the service account to attach to VMs.
// TODO(@3u13r): Eventually remove this field after v2.22 has been released.
NamePrefix string `hcl:"name_prefix,optional" cty:"name_prefix"`
} }
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables. // String returns a string representation of the IAM-specific variables, formatted as Terraform variables.

View file

@ -124,6 +124,7 @@ func TestGCPClusterVariables(t *testing.T) {
}, },
CustomEndpoint: "example.com", CustomEndpoint: "example.com",
CCTechnology: "SEV_SNP", CCTechnology: "SEV_SNP",
IAMServiceAccountVM: "example@example.com",
} }
// test that the variables are correctly rendered // test that the variables are correctly rendered
@ -154,6 +155,7 @@ node_groups = {
custom_endpoint = "example.com" custom_endpoint = "example.com"
internal_load_balancer = false internal_load_balancer = false
cc_technology = "SEV_SNP" cc_technology = "SEV_SNP"
iam_service_account_vm = "example@example.com"
additional_labels = null additional_labels = null
` `
got := vars.String() got := vars.String()
@ -173,9 +175,27 @@ func TestGCPIAMVariables(t *testing.T) {
region = "eu-central-1" region = "eu-central-1"
zone = "eu-central-1a" zone = "eu-central-1a"
service_account_id = "my-service-account" service_account_id = "my-service-account"
name_prefix = ""
` `
got := vars.String() got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences 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) { func TestAzureClusterVariables(t *testing.T) {

View file

@ -102,7 +102,7 @@ If you encounter any problem with the following steps, make sure to use the [lat
<TabItem value="gcp" label="GCP"> <TabItem value="gcp" label="GCP">
```bash ```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 --prefix=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. 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.

View file

@ -234,6 +234,9 @@ Enable the [Compute Engine API](https://console.cloud.google.com/apis/library/co
To [create the IAM configuration](../workflows/config.md#creating-an-iam-configuration) for Constellation, you need the following permissions: To [create the IAM configuration](../workflows/config.md#creating-an-iam-configuration) for Constellation, you need the following permissions:
* `iam.roles.create`
* `iam.roles.delete`
* `iam.roles.get`
* `iam.serviceAccountKeys.create` * `iam.serviceAccountKeys.create`
* `iam.serviceAccountKeys.delete` * `iam.serviceAccountKeys.delete`
* `iam.serviceAccountKeys.get` * `iam.serviceAccountKeys.get`

View file

@ -686,10 +686,10 @@ constellation iam create gcp [flags]
``` ```
-h, --help help for gcp -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) --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 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) --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 Find a list of available zones here: https://cloud.google.com/compute/docs/regions-zones#available
``` ```

View file

@ -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). 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 ```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`. 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`.

View file

@ -188,6 +188,9 @@ type GCPConfig struct {
// Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization // Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization
ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"` ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"`
// description: | // description: |
// GCP service account mail address. This is being attached to the VMs for authorization.
IAMServiceAccountVM string `yaml:"IAMServiceAccountVM"`
// description: |
// Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage // Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"` DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
// description: | // description: |
@ -349,6 +352,7 @@ func Default() *Config {
Region: "", Region: "",
Zone: "", Zone: "",
ServiceAccountKeyPath: "", ServiceAccountKeyPath: "",
IAMServiceAccountVM: "",
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
UseMarketplaceImage: toPtr(false), UseMarketplaceImage: toPtr(false),
}, },

View file

@ -241,7 +241,7 @@ func init() {
FieldName: "gcp", FieldName: "gcp",
}, },
} }
GCPConfigDoc.Fields = make([]encoder.Doc, 6) GCPConfigDoc.Fields = make([]encoder.Doc, 7)
GCPConfigDoc.Fields[0].Name = "project" GCPConfigDoc.Fields[0].Name = "project"
GCPConfigDoc.Fields[0].Type = "string" GCPConfigDoc.Fields[0].Type = "string"
GCPConfigDoc.Fields[0].Note = "" GCPConfigDoc.Fields[0].Note = ""
@ -262,16 +262,21 @@ func init() {
GCPConfigDoc.Fields[3].Note = "" GCPConfigDoc.Fields[3].Note = ""
GCPConfigDoc.Fields[3].Description = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization" GCPConfigDoc.Fields[3].Description = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization"
GCPConfigDoc.Fields[3].Comments[encoder.LineComment] = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization" GCPConfigDoc.Fields[3].Comments[encoder.LineComment] = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization"
GCPConfigDoc.Fields[4].Name = "deployCSIDriver" GCPConfigDoc.Fields[4].Name = "IAMServiceAccountVM"
GCPConfigDoc.Fields[4].Type = "bool" GCPConfigDoc.Fields[4].Type = "string"
GCPConfigDoc.Fields[4].Note = "" GCPConfigDoc.Fields[4].Note = ""
GCPConfigDoc.Fields[4].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" GCPConfigDoc.Fields[4].Description = "GCP service account mail address. This is being attached to the VMs for authorization."
GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "GCP service account mail address. This is being attached to the VMs for authorization."
GCPConfigDoc.Fields[5].Name = "useMarketplaceImage" GCPConfigDoc.Fields[5].Name = "deployCSIDriver"
GCPConfigDoc.Fields[5].Type = "bool" GCPConfigDoc.Fields[5].Type = "bool"
GCPConfigDoc.Fields[5].Note = "" GCPConfigDoc.Fields[5].Note = ""
GCPConfigDoc.Fields[5].Description = "Use the specified GCP Marketplace image offering." GCPConfigDoc.Fields[5].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Use the specified GCP Marketplace image offering." GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
GCPConfigDoc.Fields[6].Name = "useMarketplaceImage"
GCPConfigDoc.Fields[6].Type = "bool"
GCPConfigDoc.Fields[6].Note = ""
GCPConfigDoc.Fields[6].Description = "Use the specified GCP Marketplace image offering."
GCPConfigDoc.Fields[6].Comments[encoder.LineComment] = "Use the specified GCP Marketplace image offering."
OpenStackConfigDoc.Type = "OpenStackConfig" OpenStackConfigDoc.Type = "OpenStackConfig"
OpenStackConfigDoc.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments." OpenStackConfigDoc.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments."

View file

@ -464,6 +464,7 @@ func TestValidate(t *testing.T) {
gcp.Project = "test-project" gcp.Project = "test-project"
gcp.Zone = "test-zone" gcp.Zone = "test-zone"
gcp.ServiceAccountKeyPath = "test-key-path" gcp.ServiceAccountKeyPath = "test-key-path"
gcp.IAMServiceAccountVM = "example@example.com"
cnf.Provider = ProviderConfig{} cnf.Provider = ProviderConfig{}
cnf.Provider.GCP = gcp cnf.Provider.GCP = gcp
cnf.Attestation.GCPSEVSNP.Measurements = measurements.M{ cnf.Attestation.GCPSEVSNP.Measurements = measurements.M{

View file

@ -40,6 +40,9 @@ spec:
- --cloud-provider={{ .Values.csp }} - --cloud-provider={{ .Values.csp }}
- --key-service-endpoint=key-service.{{ .Release.Namespace }}:{{ .Values.global.keyServicePort }} - --key-service-endpoint=key-service.{{ .Release.Namespace }}:{{ .Values.global.keyServicePort }}
- --attestation-variant={{ .Values.attestationVariant }} - --attestation-variant={{ .Values.attestationVariant }}
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts: volumeMounts:
- mountPath: {{ .Values.global.serviceBasePath | quote }} - mountPath: {{ .Values.global.serviceBasePath | quote }}
name: config name: config
@ -47,6 +50,9 @@ spec:
- mountPath: /etc/kubernetes - mountPath: /etc/kubernetes
name: kubeadm name: kubeadm
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
ports: ports:
- containerPort: {{ .Values.joinServicePort }} - containerPort: {{ .Values.joinServicePort }}
name: tcp name: tcp
@ -54,6 +60,10 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
volumes: volumes:
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: config - name: config
projected: projected:
sources: sources:

View file

@ -42,6 +42,8 @@ spec:
value: {{ .Values.csp | quote }} value: {{ .Values.csp | quote }}
- name: constellation-uid - name: constellation-uid
value: {{ .Values.constellationUID | quote }} value: {{ .Values.constellationUID | quote }}
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: {{ .Values.controllerManager.manager.image | quote }} image: {{ .Values.controllerManager.manager.image | quote }}
livenessProbe: livenessProbe:
httpGet: httpGet:
@ -72,6 +74,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -109,6 +114,10 @@ spec:
name: gceconf name: gceconf
optional: true optional: true
name: gceconf name: gceconf
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -50,6 +50,8 @@ spec:
value: GCP value: GCP
- name: constellation-uid - name: constellation-uid
value: "42424242424242" value: "42424242424242"
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: constellationOperatorImage image: constellationOperatorImage
livenessProbe: livenessProbe:
httpGet: httpGet:
@ -86,6 +88,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -123,6 +128,10 @@ spec:
name: gceconf name: gceconf
optional: true optional: true
name: gceconf name: gceconf
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -40,6 +40,9 @@ spec:
- --cloud-provider=AWS - --cloud-provider=AWS
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=aws-nitro-tpm - --attestation-variant=aws-nitro-tpm
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config
@ -47,6 +50,9 @@ spec:
- mountPath: /etc/kubernetes - mountPath: /etc/kubernetes
name: kubeadm name: kubeadm
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
ports: ports:
- containerPort: 9090 - containerPort: 9090
name: tcp name: tcp
@ -54,6 +60,10 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
volumes: volumes:
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: config - name: config
projected: projected:
sources: sources:

View file

@ -50,6 +50,8 @@ spec:
value: Azure value: Azure
- name: constellation-uid - name: constellation-uid
value: "42424242424242" value: "42424242424242"
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: constellationOperatorImage image: constellationOperatorImage
livenessProbe: livenessProbe:
httpGet: httpGet:
@ -86,6 +88,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -123,6 +128,10 @@ spec:
name: gceconf name: gceconf
optional: true optional: true
name: gceconf name: gceconf
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -40,6 +40,9 @@ spec:
- --cloud-provider=Azure - --cloud-provider=Azure
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=azure-sev-snp - --attestation-variant=azure-sev-snp
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config
@ -47,6 +50,9 @@ spec:
- mountPath: /etc/kubernetes - mountPath: /etc/kubernetes
name: kubeadm name: kubeadm
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
ports: ports:
- containerPort: 9090 - containerPort: 9090
name: tcp name: tcp
@ -54,6 +60,10 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
volumes: volumes:
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: config - name: config
projected: projected:
sources: sources:

View file

@ -50,6 +50,8 @@ spec:
value: GCP value: GCP
- name: constellation-uid - name: constellation-uid
value: "42424242424242" value: "42424242424242"
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: constellationOperatorImage image: constellationOperatorImage
livenessProbe: livenessProbe:
httpGet: httpGet:
@ -86,6 +88,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -123,6 +128,10 @@ spec:
name: gceconf name: gceconf
optional: true optional: true
name: gceconf name: gceconf
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -40,6 +40,9 @@ spec:
- --cloud-provider=GCP - --cloud-provider=GCP
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=gcp-sev-es - --attestation-variant=gcp-sev-es
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config
@ -47,6 +50,9 @@ spec:
- mountPath: /etc/kubernetes - mountPath: /etc/kubernetes
name: kubeadm name: kubeadm
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
ports: ports:
- containerPort: 9090 - containerPort: 9090
name: tcp name: tcp
@ -54,6 +60,10 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
volumes: volumes:
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: config - name: config
projected: projected:
sources: sources:

View file

@ -50,6 +50,8 @@ spec:
value: GCP value: GCP
- name: constellation-uid - name: constellation-uid
value: "42424242424242" value: "42424242424242"
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: constellationOperatorImage image: constellationOperatorImage
livenessProbe: livenessProbe:
httpGet: httpGet:
@ -86,6 +88,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -123,6 +128,10 @@ spec:
name: gceconf name: gceconf
optional: true optional: true
name: gceconf name: gceconf
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -40,6 +40,9 @@ spec:
- --cloud-provider=OpenStack - --cloud-provider=OpenStack
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=qemu-vtpm - --attestation-variant=qemu-vtpm
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config
@ -47,6 +50,9 @@ spec:
- mountPath: /etc/kubernetes - mountPath: /etc/kubernetes
name: kubeadm name: kubeadm
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
ports: ports:
- containerPort: 9090 - containerPort: 9090
name: tcp name: tcp
@ -54,6 +60,10 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
volumes: volumes:
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: config - name: config
projected: projected:
sources: sources:

View file

@ -50,6 +50,8 @@ spec:
value: QEMU value: QEMU
- name: constellation-uid - name: constellation-uid
value: "42424242424242" value: "42424242424242"
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: constellationOperatorImage image: constellationOperatorImage
livenessProbe: livenessProbe:
httpGet: httpGet:
@ -86,6 +88,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -123,6 +128,10 @@ spec:
name: gceconf name: gceconf
optional: true optional: true
name: gceconf name: gceconf
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -40,6 +40,9 @@ spec:
- --cloud-provider=QEMU - --cloud-provider=QEMU
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=qemu-vtpm - --attestation-variant=qemu-vtpm
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config
@ -47,6 +50,9 @@ spec:
- mountPath: /etc/kubernetes - mountPath: /etc/kubernetes
name: kubeadm name: kubeadm
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
ports: ports:
- containerPort: 9090 - containerPort: 9090
name: tcp name: tcp
@ -54,6 +60,10 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
volumes: volumes:
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: config - name: config
projected: projected:
sources: sources:

View file

@ -31,6 +31,9 @@ spec:
- /manager - /manager
args: args:
- --leader-elect - --leader-elect
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
image: controller:latest image: controller:latest
name: manager name: manager
securityContext: securityContext:
@ -60,6 +63,9 @@ spec:
- mountPath: /etc/gce - mountPath: /etc/gce
name: gceconf name: gceconf
readOnly: true readOnly: true
- mountPath: /var/secrets/google
name: gcekey
readOnly: true
- mountPath: /etc/constellation-upgrade-agent.sock - mountPath: /etc/constellation-upgrade-agent.sock
name: upgrade-agent-socket name: upgrade-agent-socket
readOnly: true readOnly: true
@ -91,6 +97,10 @@ spec:
configMap: configMap:
name: gceconf name: gceconf
optional: true optional: true
- name: gcekey
secret:
secretName: gcekey
optional: true
- name: upgrade-agent-socket - name: upgrade-agent-socket
hostPath: hostPath:
path: /run/constellation-upgrade-agent.sock path: /run/constellation-upgrade-agent.sock

View file

@ -47,7 +47,7 @@ module "gcp_iam" {
// replace $VERSION with the Constellation version you want to use, e.g., v2.14.0 // 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" source = "https://github.com/edgelesssys/constellation/releases/download/$VERSION/terraform-module.zip//terraform-module/iam/gcp"
project_id = local.project_id project_id = local.project_id
service_account_id = "${local.name}-sa" name_prefix = local.name
zone = local.zone zone = local.zone
region = local.region region = local.region
} }
@ -81,6 +81,7 @@ module "gcp_infrastructure" {
project = local.project_id project = local.project_id
internal_load_balancer = false internal_load_balancer = false
cc_technology = local.cc_technology cc_technology = local.cc_technology
iam_service_account_vm = module.gcp_iam.service_account_mail_vm
} }
data "constellation_attestation" "foo" { data "constellation_attestation" "foo" {

View file

@ -183,6 +183,7 @@ module "instance_group" {
init_secret_hash = local.init_secret_hash init_secret_hash = local.init_secret_hash
custom_endpoint = var.custom_endpoint custom_endpoint = var.custom_endpoint
cc_technology = var.cc_technology cc_technology = var.cc_technology
iam_service_account_vm = var.iam_service_account_vm
} }
resource "google_compute_address" "loadbalancer_ip_internal" { resource "google_compute_address" "loadbalancer_ip_internal" {

View file

@ -77,17 +77,11 @@ resource "google_compute_instance_template" "template" {
on_host_maintenance = "TERMINATE" on_host_maintenance = "TERMINATE"
} }
# Define all IAM access via the service account and not via scopes:
# See: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance_template#nested_service_account
service_account { service_account {
scopes = [ email = var.iam_service_account_vm
"https://www.googleapis.com/auth/compute", scopes = ["cloud-platform"]
"https://www.googleapis.com/auth/servicecontrol",
"https://www.googleapis.com/auth/service.management.readonly",
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring.write",
"https://www.googleapis.com/auth/trace.append",
"https://www.googleapis.com/auth/cloud-platform",
]
} }
shielded_instance_config { shielded_instance_config {

View file

@ -108,3 +108,9 @@ variable "cc_technology" {
error_message = "The confidential computing technology has to be 'SEV' or 'SEV_SNP'." error_message = "The confidential computing technology has to be 'SEV' or 'SEV_SNP'."
} }
} }
variable "iam_service_account_vm" {
type = string
default = ""
description = "IAM service account used for the VMs"
}

View file

@ -76,6 +76,11 @@ variable "additional_labels" {
description = "Additional labels that should be given to created recources." description = "Additional labels that should be given to created recources."
} }
variable "iam_service_account_vm" {
type = string
default = ""
description = "IAM service account used for the VMs"
}
variable "emergency_ssh" { variable "emergency_ssh" {
type = bool type = bool
default = false default = false

View file

@ -13,8 +13,19 @@ provider "google" {
zone = var.zone 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" "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" { resource "google_service_account" "service_account" {
account_id = var.service_account_id account_id = local.sa_name
display_name = "Constellation service account" display_name = "Constellation service account"
description = "Service account used inside Constellation" 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] depends_on = [null_resource.delay]
} }
resource "google_project_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" "custom_role_vm_to_service_account_vm" {
project = var.project_id
role = "projects/${var.project_id}/roles/${google_project_iam_custom_role.vm.role_id}"
members = [
"serviceAccount:${google_service_account.vm.email}",
]
depends_on = [null_resource.delay]
}
resource "google_service_account_key" "service_account_key" { resource "google_service_account_key" "service_account_key" {
service_account_id = google_service_account.service_account.name service_account_id = google_service_account.service_account.name
depends_on = [null_resource.delay] depends_on = [null_resource.delay]

View file

@ -3,3 +3,9 @@ output "service_account_key" {
description = "Private key of the service account." description = "Private key of the service account."
sensitive = true sensitive = true
} }
output "service_account_mail_vm" {
value = google_service_account.vm.email
description = "Mail address of the service account to be attached to the VMs"
sensitive = false
}

View file

@ -5,7 +5,12 @@ variable "project_id" {
variable "service_account_id" { variable "service_account_id" {
type = string 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" { variable "region" {