Skip to content
Closed
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
21 changes: 21 additions & 0 deletions pkg/operator/controller/ingress/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ const (
RouterServiceHTTPSPort = "ROUTER_SERVICE_HTTPS_PORT"
StatsPort = "STATS_PORT"

// EndpointAddressValidation is the environment variable that enables the validation
// of endpoints/endpointslices addresses used by router. When enabled, Router will
// drop endpoint updates containing FQDN-typed EndpointSlices or addresses that
// resolve to restricted IPs (loopback, link-local, unspecified, etc.).
EndpointAddressValidation = "ENDPOINT_ADDRESS_VALIDATION"

HTTPPortName = "http"
HTTPSPortName = "https"
StatsPortName = "metrics"
Expand Down Expand Up @@ -826,6 +832,21 @@ func desiredRouterDeployment(ci *operatorv1.IngressController, config *Config, i
)

if ci.Status.EndpointPublishingStrategy.Type == operatorv1.HostNetworkStrategyType {
// Enable endpoint address validation on user-deployed ingress controllers
// running with HostNetwork strategy on cloud platforms. The default
// ingress controller is excluded because its lifecycle and network
// exposure are managed by the platform installer.
if infraStatus := infraConfig.Status.PlatformStatus; infraStatus != nil && ci.Name != manifests.DefaultIngressControllerName {
switch infraStatus.Type {
case configv1.AWSPlatformType, configv1.AzurePlatformType, configv1.GCPPlatformType,
configv1.AlibabaCloudPlatformType, configv1.IBMCloudPlatformType, configv1.PowerVSPlatformType, configv1.OpenStackPlatformType:
env = append(env, corev1.EnvVar{
Name: EndpointAddressValidation,
Value: "true",
})
}
}

// Expose ports 80, 443, and 1936 on the host to provide
// endpoints for the user's HA solution.
deployment.Spec.Template.Spec.HostNetwork = true
Expand Down
169 changes: 169 additions & 0 deletions pkg/operator/controller/ingress/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,7 @@ func TestDesiredRouterDeploymentVariety(t *testing.T) {
{"STATS_PORT", true, "9146"},
{"ROUTER_SERVICE_HTTP_PORT", true, "8080"},
{"ROUTER_SERVICE_HTTPS_PORT", true, "8443"},
{"ENDPOINT_ADDRESS_VALIDATION", false, ""},
}
if err := checkDeploymentEnvironment(t, deployment, tests); err != nil {
t.Error(err)
Expand All @@ -1143,6 +1144,164 @@ func TestDesiredRouterDeploymentVariety(t *testing.T) {
checkContainerPort(t, deployment, "metrics", 9146)
}

func TestDesiredRouterDeploymentEndpointAddressValidation(t *testing.T) {
tests := []struct {
name string
icName string
strategyType operatorv1.EndpointPublishingStrategyType
platformType configv1.PlatformType
nilPlatform bool
expectEnvVar bool
}{
{
name: "user IC with HostNetwork on AWS enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.AWSPlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on Azure enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.AzurePlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on GCP enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.GCPPlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on IBMCloud enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.IBMCloudPlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on PowerVS enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.PowerVSPlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on AlibabaCloud enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.AlibabaCloudPlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on OpenStack enables validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.OpenStackPlatformType,
expectEnvVar: true,
},
{
name: "user IC with HostNetwork on BareMetal does not enable validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.BareMetalPlatformType,
expectEnvVar: false,
},
{
name: "user IC with HostNetwork on None does not enable validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.NonePlatformType,
expectEnvVar: false,
},
{
name: "user IC with LoadBalancerService on AWS does not enable validation",
icName: "custom",
strategyType: operatorv1.LoadBalancerServiceStrategyType,
platformType: configv1.AWSPlatformType,
expectEnvVar: false,
},
{
name: "user IC with NodePortService on GCP does not enable validation",
icName: "custom",
strategyType: operatorv1.NodePortServiceStrategyType,
platformType: configv1.GCPPlatformType,
expectEnvVar: false,
},
{
name: "default IC with HostNetwork on AWS does not enable validation",
icName: "default",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.AWSPlatformType,
expectEnvVar: false,
},
{
name: "default IC with HostNetwork on GCP does not enable validation",
icName: "default",
strategyType: operatorv1.HostNetworkStrategyType,
platformType: configv1.GCPPlatformType,
expectEnvVar: false,
},
{
name: "user IC with Private strategy on AWS does not enable validation",
icName: "custom",
strategyType: operatorv1.PrivateStrategyType,
platformType: configv1.AWSPlatformType,
expectEnvVar: false,
},
{
name: "user IC with HostNetwork and nil PlatformStatus does not enable validation",
icName: "custom",
strategyType: operatorv1.HostNetworkStrategyType,
nilPlatform: true,
expectEnvVar: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ic, ingressConfig, infraConfig, apiConfig, networkConfig, _, clusterProxyConfig := getRouterDeploymentComponents(t)
ic.Name = tc.icName
if tc.nilPlatform {
infraConfig.Status.PlatformStatus = nil
} else {
infraConfig.Status.PlatformStatus.Type = tc.platformType
}
ic.Status.EndpointPublishingStrategy.Type = tc.strategyType
if tc.strategyType == operatorv1.HostNetworkStrategyType {
ic.Status.EndpointPublishingStrategy.HostNetwork = &operatorv1.HostNetworkStrategy{
Protocol: operatorv1.TCPProtocol,
HTTPPort: 80,
HTTPSPort: 443,
StatsPort: 1936,
}
}
var proxyNeeded bool
if infraConfig.Status.PlatformStatus != nil {
var err error
proxyNeeded, err = IsProxyProtocolNeeded(ic, infraConfig.Status.PlatformStatus, nil)
if err != nil {
t.Fatalf("failed to determine proxy protocol need: %v", err)
}
}
deployment, err := desiredRouterDeployment(ic, &Config{IngressControllerImage: ingressControllerImage}, ingressConfig, infraConfig, apiConfig, networkConfig, nil, proxyNeeded, false, nil, clusterProxyConfig)
if err != nil {
t.Fatalf("invalid router Deployment: %v", err)
}

envTests := []envData{
{"ENDPOINT_ADDRESS_VALIDATION", tc.expectEnvVar, "true"},
}
if err := checkDeploymentEnvironment(t, deployment, envTests); err != nil {
t.Error(err)
}
checkDeploymentHasEnvSorted(t, deployment)
})
}
}

// TestDesiredRouterDeploymentFIPS verifies that on a FIPS-enabled cluster,
// TLS_CHACHA20_POLY1305_SHA256 is removed from ROUTER_CIPHERSUITES while
// FIPS-compliant TLS 1.3 ciphers are kept (OCPBUGS-3917).
Expand Down Expand Up @@ -2156,6 +2315,16 @@ func Test_deploymentConfigChanged(t *testing.T) {
},
expect: true,
},
{
description: "if ENDPOINT_ADDRESS_VALIDATION is added",
mutate: func(deployment *appsv1.Deployment) {
deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
Name: "ENDPOINT_ADDRESS_VALIDATION",
Value: "true",
})
},
expect: true,
},
{
// This test case can be removed after OpenShift 4.13.
// See <https://issues.redhat.com/browse/OCPBUGS-4703>.
Expand Down
1 change: 1 addition & 0 deletions test/e2e/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func TestAll(t *testing.T) {
t.Run("TestHTTPHeaderCapture", TestHTTPHeaderCapture)
t.Run("TestHeaderNameCaseAdjustment", TestHeaderNameCaseAdjustment)
t.Run("TestHealthCheckIntervalIngressController", TestHealthCheckIntervalIngressController)
t.Run("TestHostNetworkEndpointAddressValidation", TestHostNetworkEndpointAddressValidation)
t.Run("TestHostNetworkEndpointPublishingStrategy", TestHostNetworkEndpointPublishingStrategy)
t.Run("TestIngressControllerScale", TestIngressControllerScale)
t.Run("TestIngressControllerServiceNameCollision", TestIngressControllerServiceNameCollision)
Expand Down
65 changes: 65 additions & 0 deletions test/e2e/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,71 @@ func TestHostNetworkEndpointPublishingStrategy(t *testing.T) {
}
}

// TestHostNetworkEndpointAddressValidation creates a user-deployed
// ingresscontroller with the "HostNetwork" endpoint publishing strategy on a
// cloud platform and verifies that the ENDPOINT_ADDRESS_VALIDATION env var is
// set to "true" on the router deployment.
func TestHostNetworkEndpointAddressValidation(t *testing.T) {
t.Parallel()
if infraConfig.Status.PlatformStatus == nil {
t.Skip("test skipped on nil platform")
}
supportedPlatforms := map[configv1.PlatformType]struct{}{
configv1.AWSPlatformType: {},
configv1.AzurePlatformType: {},
configv1.GCPPlatformType: {},
configv1.AlibabaCloudPlatformType: {},
configv1.IBMCloudPlatformType: {},
configv1.PowerVSPlatformType: {},
configv1.OpenStackPlatformType: {},
}
if _, supported := supportedPlatforms[infraConfig.Status.PlatformStatus.Type]; !supported {
t.Skipf("test skipped on platform %q", infraConfig.Status.PlatformStatus.Type)
}

icName := types.NamespacedName{
Namespace: operatorNamespace,
Name: names.SimpleNameGenerator.GenerateName("hostnet-addrval-"),
}
domain := fmt.Sprintf("%s.%s", icName.Name, dnsConfig.Spec.BaseDomain)
ing := newHostNetworkController(icName, domain)
if err := createWithRetryOnError(t, t.Context(), ing, DefaultRetryTimeout); err != nil {
t.Fatalf("failed to create ingresscontroller: %v", err)
}
t.Cleanup(func() { assertIngressControllerDeleted(t, kclient, ing) })

if err := waitForIngressControllerCondition(t, kclient, 5*time.Minute, icName, availableConditionsForIngressControllerWithHostNetwork...); err != nil {
t.Fatalf("failed to observe expected conditions: %v", err)
}

deploymentName := controller.RouterDeploymentName(ing)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: deploymentName.Namespace,
Name: deploymentName.Name,
},
}
if err := waitForDeploymentEnvVar(t, deployment, 1*time.Minute, "ENDPOINT_ADDRESS_VALIDATION", "true"); err != nil {
t.Fatalf("expected deployment to have ENDPOINT_ADDRESS_VALIDATION=true: %v", err)
}

// Verify the default ingress controller does NOT have the env var set.
defaultIC := &operatorv1.IngressController{}
if err := kclient.Get(t.Context(), defaultName, defaultIC); err != nil {
t.Fatalf("failed to get default ingresscontroller: %v", err)
}
defaultDeploymentName := controller.RouterDeploymentName(defaultIC)
defaultDeployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: defaultDeploymentName.Namespace,
Name: defaultDeploymentName.Name,
},
}
if err := waitForDeploymentEnvVar(t, defaultDeployment, 1*time.Minute, "ENDPOINT_ADDRESS_VALIDATION", ""); err != nil {
t.Fatalf("expected default ingresscontroller deployment not to have ENDPOINT_ADDRESS_VALIDATION: %v", err)
}
}

// TestHostNetworkPortBinding creates two ingresscontrollers on the same node
// with different port bindings and verifies that both routers are available.
func TestHostNetworkPortBinding(t *testing.T) {
Expand Down