diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index 13581bf014..cf0ebc88ca 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -6315,11 +6315,15 @@ spec: - name type: object firewallRulesManagement: - default: Managed description: |- - FirewallRulesManagement specifies the management policy for the cluster. Managed indicates that - the firewall rules will be created and destroyed by the cluster. Unmanaged indicates that the - user should create and destroy the firewall rules. + FirewallRulesManagement specifies the management policy for the cluster. "Managed" indicates that + the firewall rules will be created and destroyed by the cluster. "Unmanaged" indicates that the + user should create and destroy the firewall rules. For Shared VPC installation, if the installer + credential doesn't have firewall rules management permissions, the "firewallRulesManagement" settings + can be absent or set to "Unmanaged" explicitly. For non-Shared VPC installation, if the installer + credential doesn't have firewall rules management permissions, the "firewallRulesManagement" settings + must be set to "Unmanaged" explicitly. And in this case, the user needs to pre-configure the VPC network + and the firewall rules before the installation. enum: - Managed - Unmanaged diff --git a/pkg/asset/installconfig/gcp/permissions.go b/pkg/asset/installconfig/gcp/permissions.go new file mode 100644 index 0000000000..5716fd85d1 --- /dev/null +++ b/pkg/asset/installconfig/gcp/permissions.go @@ -0,0 +1,44 @@ +package gcp + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + + gcptypes "github.com/openshift/installer/pkg/types/gcp" +) + +const ( + // CreateFirewallPermission is the permission to create firewall rules in the google cloud provider. + CreateFirewallPermission = "compute.firewalls.create" // required to create GCP firewall rules + + // DeleteFirewallPermission is the permission to delete firewall rules in the google cloud provider. + DeleteFirewallPermission = "compute.firewalls.delete" + + // UpdateNetworksPermission is the permission to update networks and the network resources in the google cloud provider. + UpdateNetworksPermission = "compute.networks.updatePolicy" +) + +// HasPermission determines if the permission exists for the service account in the project. +func HasPermission(ctx context.Context, projectID string, permissions []string, endpoint *gcptypes.PSCEndpoint) (bool, error) { + client, err := NewClient(ctx, endpoint) + if err != nil { + return false, fmt.Errorf("failed to create client permission check: %w", err) + } + + foundPermissions, err := client.GetProjectPermissions(ctx, projectID, permissions) + if err != nil { + return false, fmt.Errorf("failed to find project permissions: %w", err) + } + + permissionsValid := true + for _, permission := range permissions { + if hasPermission := foundPermissions.Has(permission); !hasPermission { + logrus.Debugf("permission %s not found", permission) + permissionsValid = false + } + } + + return permissionsValid, nil +} diff --git a/pkg/asset/installconfig/gcp/validation.go b/pkg/asset/installconfig/gcp/validation.go index b0e3c249cc..30644dc76f 100644 --- a/pkg/asset/installconfig/gcp/validation.go +++ b/pkg/asset/installconfig/gcp/validation.go @@ -494,6 +494,38 @@ func ValidateForProvisioning(ic *types.InstallConfig) error { allErrs := field.ErrorList{} + if ic.GCP.FirewallRulesManagement == gcp.UnmanagedFirewallRules && ic.GCP.Network == "" { + // this is usually a static check, however it is validated here after the + // create install-config process to ensure that the create install-config + // does not fail in cases where the firewall rules management is set to + // unmanaged when the permissions do not exist. + allErrs = append(allErrs, field.Required( + field.NewPath("platform").Child("gcp").Child("network"), + "a network must be specified when firewall rules are unmanaged"), + ) + } else if ic.GCP.FirewallRulesManagement == gcp.ManagedFirewallRules { + projectID := ic.GCP.ProjectID + configField := "projectID" + if ic.GCP.NetworkProjectID != "" { + projectID = ic.GCP.NetworkProjectID + configField = "networkProjectID" + } + + hasPermissions, err := HasPermission(context.TODO(), projectID, []string{ + CreateFirewallPermission, + DeleteFirewallPermission, + UpdateNetworksPermission, + }, ic.GCP.Endpoint) + if err != nil { + allErrs = append(allErrs, field.InternalError(field.NewPath("platform").Child("gcp").Child(configField), err)) + } else if !hasPermissions { + allErrs = append(allErrs, field.Invalid( + field.NewPath("platform").Child("gcp").Child("firewallRulesManagement"), + ic.GCP.FirewallRulesManagement, + "firewall permissions are required when firewall rules management is set to Managed")) + } + } + return allErrs.ToAggregate() } diff --git a/pkg/asset/installconfig/installconfig.go b/pkg/asset/installconfig/installconfig.go index 153e79f28e..00fd2ce00c 100644 --- a/pkg/asset/installconfig/installconfig.go +++ b/pkg/asset/installconfig/installconfig.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/pkg/errors" + "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" @@ -21,6 +22,7 @@ import ( icvsphere "github.com/openshift/installer/pkg/asset/installconfig/vsphere" "github.com/openshift/installer/pkg/types" "github.com/openshift/installer/pkg/types/defaults" + "github.com/openshift/installer/pkg/types/gcp" "github.com/openshift/installer/pkg/types/validation" ) @@ -133,6 +135,33 @@ func (a *InstallConfig) finishGCP() error { } a.Config.Platform.GCP.Endpoint.ClusterUseOnly = &defaultClusterUseOnly } + + project := a.Config.GCP.ProjectID + if a.Config.Platform.GCP.NetworkProjectID != "" { + project = a.Config.GCP.NetworkProjectID + } + + if a.Config.GCP.FirewallRulesManagement == "" { + firewallPermissions, err := icgcp.HasPermission(context.TODO(), project, []string{ + icgcp.CreateFirewallPermission, + icgcp.DeleteFirewallPermission, + icgcp.UpdateNetworksPermission, + }, a.Config.GCP.Endpoint) + if err != nil { + return err + } + + a.Config.GCP.FirewallRulesManagement = gcp.ManagedFirewallRules + if !firewallPermissions { + if a.Config.GCP.NetworkProjectID != "" { + logrus.Debugf("missing firewall permissions, setting rule management to Unmanaged") + a.Config.GCP.FirewallRulesManagement = gcp.UnmanagedFirewallRules + } else { + logrus.Warnf("missing firewall permissions, add the permissions or set firewall rules management to Unmanaged and create firewall rules") + } + } + } + return nil } diff --git a/pkg/types/gcp/defaults/platform.go b/pkg/types/gcp/defaults/platform.go index 5cd41a1313..dfe85e81e5 100644 --- a/pkg/types/gcp/defaults/platform.go +++ b/pkg/types/gcp/defaults/platform.go @@ -8,10 +8,6 @@ func SetPlatformDefaults(p *gcp.Platform) { return } - if p.FirewallRulesManagement == "" { - p.FirewallRulesManagement = gcp.ManagedFirewallRules - } - if gcpDmp := p.DefaultMachinePlatform; gcpDmp != nil { if ek := gcpDmp.EncryptionKey; ek != nil { if kms := ek.KMSKey; kms != nil { diff --git a/pkg/types/gcp/platform.go b/pkg/types/gcp/platform.go index 5e2e18dc00..656352df44 100644 --- a/pkg/types/gcp/platform.go +++ b/pkg/types/gcp/platform.go @@ -121,10 +121,14 @@ type Platform struct { // +optional DNS *DNS `json:"dns,omitempty"` - // FirewallRulesManagement specifies the management policy for the cluster. Managed indicates that - // the firewall rules will be created and destroyed by the cluster. Unmanaged indicates that the - // user should create and destroy the firewall rules. - // +default="Managed" + // FirewallRulesManagement specifies the management policy for the cluster. "Managed" indicates that + // the firewall rules will be created and destroyed by the cluster. "Unmanaged" indicates that the + // user should create and destroy the firewall rules. For Shared VPC installation, if the installer + // credential doesn't have firewall rules management permissions, the "firewallRulesManagement" settings + // can be absent or set to "Unmanaged" explicitly. For non-Shared VPC installation, if the installer + // credential doesn't have firewall rules management permissions, the "firewallRulesManagement" settings + // must be set to "Unmanaged" explicitly. And in this case, the user needs to pre-configure the VPC network + // and the firewall rules before the installation. // +optional FirewallRulesManagement FirewallRulesManagementPolicy `json:"firewallRulesManagement,omitempty"` } diff --git a/pkg/types/gcp/validation/platform.go b/pkg/types/gcp/validation/platform.go index 87f290734b..6ad20026b6 100644 --- a/pkg/types/gcp/validation/platform.go +++ b/pkg/types/gcp/validation/platform.go @@ -150,10 +150,6 @@ func ValidatePlatform(p *gcp.Platform, fldPath *field.Path, ic *types.InstallCon if !supportedFirewallRulePolicies.Has(p.FirewallRulesManagement) { allErrs = append(allErrs, field.NotSupported(fldPath.Child("firewallRulesManagement"), p.FirewallRulesManagement, sets.List(supportedFirewallRulePolicies))) } - - if p.FirewallRulesManagement == gcp.UnmanagedFirewallRules && p.Network == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("network"), "a network must be specified when firewall rules are unmanaged")) - } } return allErrs diff --git a/pkg/types/gcp/validation/platform_test.go b/pkg/types/gcp/validation/platform_test.go index 55481f67fc..589812c511 100644 --- a/pkg/types/gcp/validation/platform_test.go +++ b/pkg/types/gcp/validation/platform_test.go @@ -232,16 +232,6 @@ func TestValidatePlatform(t *testing.T) { }, valid: false, }, - { - name: "invalid firewall management configuration", - platform: &gcp.Platform{ - UserProvisionedDNS: dns.UserProvisionedDNSEnabled, - FirewallRulesManagement: gcp.UnmanagedFirewallRules, - Region: "us-east1", - ProjectID: "valid-project", - }, - valid: false, - }, { name: "invalid firewall management", platform: &gcp.Platform{