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

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

View file

@ -120,6 +120,7 @@ func TestPrepareIAM(t *testing.T) {
Region: "europe-west1",
Zone: "europe-west1-a",
ServiceAccountID: "const-test-case",
NamePrefix: "test_iam",
}
azureVars := &AzureIAMVariables{
Location: "westus",
@ -509,6 +510,9 @@ func TestCreateIAM(t *testing.T) {
"service_account_key": {
Value: "12345678_abcdefg",
},
"service_account_mail_vm": {
Value: "test_iam_service_account_vm",
},
"subscription_id": {
Value: "test_subscription_id",
},
@ -581,7 +585,7 @@ func TestCreateIAM(t *testing.T) {
vars: gcpVars,
tf: &stubTerraform{showState: newTestState()},
fs: afero.NewMemMapFs(),
want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}},
want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg", ServiceAccountVMMailAddress: "test_iam_service_account_vm"}},
},
"gcp init fails": {
pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
@ -614,7 +618,25 @@ func TestCreateIAM(t *testing.T) {
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{},
Outputs: map[string]*tfjson.StateOutput{
"service_account_mail_vm": {Value: "test_iam_service_account_vm"},
},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp no service_account_mail_vm": {
pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{
"service_account_key": {Value: "12345678_abcdefg"},
},
},
},
},
@ -1129,7 +1151,8 @@ func TestShowIAM(t *testing.T) {
"GCP success": {
tf: &stubTerraform{
showState: getTfjsonState(map[string]any{
"service_account_key": "key",
"service_account_key": "key",
"service_account_mail_vm": "example@example.com",
}),
},
csp: cloudprovider.GCP,
@ -1137,7 +1160,8 @@ func TestShowIAM(t *testing.T) {
"GCP wrong data type": {
tf: &stubTerraform{
showState: getTfjsonState(map[string]any{
"service_account_key": map[string]any{},
"service_account_key": map[string]any{},
"service_account_mail_vm": "example@example.com",
}),
},
csp: cloudprovider.GCP,
@ -1145,7 +1169,9 @@ func TestShowIAM(t *testing.T) {
},
"GCP missing key": {
tf: &stubTerraform{
showState: getTfjsonState(map[string]any{}),
showState: getTfjsonState(map[string]any{
"service_account_mail_vm": "example@example.com",
}),
},
csp: cloudprovider.GCP,
wantErr: true,

View file

@ -141,6 +141,8 @@ type GCPClusterVariables struct {
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 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.
AdditionalLabels cloudprovider.Tags `hcl:"additional_labels" cty:"additional_labels"`
}
@ -182,6 +184,9 @@ 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.
// 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.

View file

@ -122,8 +122,9 @@ func TestGCPClusterVariables(t *testing.T) {
DiskType: "pd-ssd",
},
},
CustomEndpoint: "example.com",
CCTechnology: "SEV_SNP",
CustomEndpoint: "example.com",
CCTechnology: "SEV_SNP",
IAMServiceAccountVM: "example@example.com",
}
// test that the variables are correctly rendered
@ -151,10 +152,11 @@ node_groups = {
zone = "eu-central-1b"
}
}
custom_endpoint = "example.com"
internal_load_balancer = false
cc_technology = "SEV_SNP"
additional_labels = null
custom_endpoint = "example.com"
internal_load_balancer = false
cc_technology = "SEV_SNP"
iam_service_account_vm = "example@example.com"
additional_labels = null
`
got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
@ -173,9 +175,27 @@ func TestGCPIAMVariables(t *testing.T) {
region = "eu-central-1"
zone = "eu-central-1a"
service_account_id = "my-service-account"
name_prefix = ""
`
got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
vars = GCPIAMVariables{
Project: "my-project",
Region: "eu-central-1",
Zone: "eu-central-1a",
NamePrefix: "my-prefix",
}
// test that the variables are correctly rendered
want = `project_id = "my-project"
region = "eu-central-1"
zone = "eu-central-1a"
service_account_id = ""
name_prefix = "my-prefix"
`
got = vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
}
func TestAzureClusterVariables(t *testing.T) {