From d78f57bed96f137c1c54217162182f8bdf4d5e74 Mon Sep 17 00:00:00 2001 From: Haywood Shannon <5781935+haywoodsh@users.noreply.github.com> Date: Thu, 22 Sep 2022 17:15:13 +0100 Subject: [PATCH] Add command line argument to manually disable IPV6 listeners for unsupported clusters (#3040) * Add command line argument to disable IPV6 listeners * update helm chart and documentation * Add automated test * update documentation * fix import Co-authored-by: Venktesh --- cmd/nginx-ingress/flags.go | 3 + cmd/nginx-ingress/main.go | 2 + deployments/helm-chart/README.md | 1 + .../templates/controller-daemonset.yaml | 1 + .../templates/controller-deployment.yaml | 1 + deployments/helm-chart/values.yaml | 3 + .../command-line-arguments.md | 8 ++ .../installation/installation-with-helm.md | 1 + internal/configs/config_params.go | 1 + internal/configs/configmaps.go | 1 + internal/configs/transportserver.go | 2 + internal/configs/transportserver_test.go | 89 ++++++++++++ internal/configs/version1/config.go | 4 + .../configs/version1/nginx-plus.ingress.tmpl | 6 +- internal/configs/version1/nginx-plus.tmpl | 10 +- internal/configs/version1/nginx.ingress.tmpl | 4 +- internal/configs/version1/nginx.tmpl | 8 +- internal/configs/version2/http.go | 1 + .../version2/nginx-plus.transportserver.tmpl | 2 +- .../version2/nginx-plus.virtualserver.tmpl | 4 +- .../version2/nginx.transportserver.tmpl | 2 +- .../configs/version2/nginx.virtualserver.tmpl | 4 +- internal/configs/version2/stream.go | 2 + internal/configs/virtualserver.go | 3 + internal/configs/virtualserver_test.go | 135 ++++++++++++++++++ internal/k8s/configuration.go | 3 + internal/k8s/configuration_test.go | 2 + internal/k8s/controller.go | 6 + tests/suite/test_disable_ipv6.py | 58 ++++++++ 29 files changed, 347 insertions(+), 20 deletions(-) create mode 100644 tests/suite/test_disable_ipv6.py diff --git a/cmd/nginx-ingress/flags.go b/cmd/nginx-ingress/flags.go index 23f26841db..239aae2624 100644 --- a/cmd/nginx-ingress/flags.go +++ b/cmd/nginx-ingress/flags.go @@ -180,6 +180,9 @@ var ( includeYearInLogs = flag.Bool("include-year", false, "Option to include the year in the log header") + disableIPV6 = flag.Bool("disable-ipv6", false, + `Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack`) + startupCheckFn func() error ) diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index a6a4ed9530..83a68f7b04 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -75,6 +75,7 @@ func main() { cfgParams = processConfigMaps(kubeClient, cfgParams, nginxManager, templateExecutor) staticCfgParams := &configs.StaticConfigParams{ + DisableIPV6: *disableIPV6, HealthStatus: *healthStatus, HealthStatusURI: *healthStatusURI, NginxStatus: *nginxStatus, @@ -149,6 +150,7 @@ func main() { SnippetsEnabled: *enableSnippets, CertManagerEnabled: *enableCertManager, ExternalDNSEnabled: *enableExternalDNS, + IsIPV6Disabled: *disableIPV6, } lbc := k8s.NewLoadBalancerController(lbcInput) diff --git a/deployments/helm-chart/README.md b/deployments/helm-chart/README.md index 268bf5a09b..bd294f4a33 100644 --- a/deployments/helm-chart/README.md +++ b/deployments/helm-chart/README.md @@ -245,6 +245,7 @@ Parameter | Description | Default `controller.enableLatencyMetrics` | Enable collection of latency metrics for upstreams. Requires `prometheus.create`. | false `controller.minReadySeconds` | Specifies the minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available. [docs](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#min-ready-seconds) | 0 `controller.strategy` | Specifies the strategy used to replace old Pods by new ones. [docs](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | {} +`controller.disableIPV6` | Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. | false `rbac.create` | Configures RBAC. | true `prometheus.create` | Expose NGINX or NGINX Plus metrics in the Prometheus format. | false `prometheus.port` | Configures the port to scrape the metrics. | 9113 diff --git a/deployments/helm-chart/templates/controller-daemonset.yaml b/deployments/helm-chart/templates/controller-daemonset.yaml index c95466d6ab..6ae11b7bf3 100644 --- a/deployments/helm-chart/templates/controller-daemonset.yaml +++ b/deployments/helm-chart/templates/controller-daemonset.yaml @@ -192,6 +192,7 @@ spec: - -enable-custom-resources={{ .Values.controller.enableCustomResources }} - -enable-snippets={{ .Values.controller.enableSnippets }} - -include-year={{ .Values.controller.includeYear }} + - -disable-ipv6={{ .Values.controller.disableIPV6 }} {{- if .Values.controller.enableCustomResources }} - -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} - -enable-preview-policies={{ .Values.controller.enablePreviewPolicies }} diff --git a/deployments/helm-chart/templates/controller-deployment.yaml b/deployments/helm-chart/templates/controller-deployment.yaml index 717644e376..fc9cb0ca55 100644 --- a/deployments/helm-chart/templates/controller-deployment.yaml +++ b/deployments/helm-chart/templates/controller-deployment.yaml @@ -195,6 +195,7 @@ spec: - -enable-custom-resources={{ .Values.controller.enableCustomResources }} - -enable-snippets={{ .Values.controller.enableSnippets }} - -include-year={{ .Values.controller.includeYear }} + - -disable-ipv6={{ .Values.controller.disableIPV6 }} {{- if .Values.controller.enableCustomResources }} - -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} - -enable-preview-policies={{ .Values.controller.enablePreviewPolicies }} diff --git a/deployments/helm-chart/values.yaml b/deployments/helm-chart/values.yaml index 60a49f8023..53e8c68f04 100644 --- a/deployments/helm-chart/values.yaml +++ b/deployments/helm-chart/values.yaml @@ -382,6 +382,9 @@ controller: ## Enable collection of latency metrics for upstreams. Requires prometheus.create. enableLatencyMetrics: false + ## Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. + disableIPV6: false + rbac: ## Configures RBAC. create: true diff --git a/docs/content/configuration/global-configuration/command-line-arguments.md b/docs/content/configuration/global-configuration/command-line-arguments.md index e313461bd8..b30def42b5 100644 --- a/docs/content/configuration/global-configuration/command-line-arguments.md +++ b/docs/content/configuration/global-configuration/command-line-arguments.md @@ -459,3 +459,11 @@ The HTTP port for the readiness endpoint. Format: `[1024 - 65535]` (default `8081`)   + +### -disable-ipv6 + +Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. + +Default `false`. +  + \ No newline at end of file diff --git a/docs/content/installation/installation-with-helm.md b/docs/content/installation/installation-with-helm.md index 0863720b0c..717bbf184f 100644 --- a/docs/content/installation/installation-with-helm.md +++ b/docs/content/installation/installation-with-helm.md @@ -237,6 +237,7 @@ The following tables lists the configurable parameters of the NGINX Ingress Cont |``controller.enableLatencyMetrics`` | Enable collection of latency metrics for upstreams. Requires ``prometheus.create``. | false | |``controller.minReadySeconds`` | Specifies the minimum number of seconds for which a newly created Pod should be ready, without any of its containers crashing, for it to be considered available. [docs](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#min-ready-seconds) | 0 | |``controller.strategy`` | Specifies the strategy used to replace old Pods with new ones. [docs](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | {} | +| `controller.disableIPV6` | Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. | false | |``rbac.create`` | Configures RBAC. | true | |``prometheus.create`` | Expose NGINX or NGINX Plus metrics in the Prometheus format. | false | |``prometheus.port`` | Configures the port to scrape the metrics. | 9113 | diff --git a/internal/configs/config_params.go b/internal/configs/config_params.go index 07901f3ad6..1ba37991ae 100644 --- a/internal/configs/config_params.go +++ b/internal/configs/config_params.go @@ -111,6 +111,7 @@ type ConfigParams struct { // StaticConfigParams holds immutable NGINX configuration parameters that affect the main NGINX config. type StaticConfigParams struct { + DisableIPV6 bool HealthStatus bool HealthStatusURI string NginxStatus bool diff --git a/internal/configs/configmaps.go b/internal/configs/configmaps.go index 3b7cf8a73e..5250efd36c 100644 --- a/internal/configs/configmaps.go +++ b/internal/configs/configmaps.go @@ -553,6 +553,7 @@ func GenerateNginxMainConfig(staticCfgParams *StaticConfigParams, config *Config AccessLogOff: config.MainAccessLogOff, DefaultServerAccessLogOff: config.DefaultServerAccessLogOff, DefaultServerReturn: config.DefaultServerReturn, + DisableIPV6: staticCfgParams.DisableIPV6, ErrorLogLevel: config.MainErrorLogLevel, HealthStatus: staticCfgParams.HealthStatus, HealthStatusURI: staticCfgParams.HealthStatusURI, diff --git a/internal/configs/transportserver.go b/internal/configs/transportserver.go index e0cb1ea5d1..4ec31f00d5 100644 --- a/internal/configs/transportserver.go +++ b/internal/configs/transportserver.go @@ -16,6 +16,7 @@ type TransportServerEx struct { TransportServer *conf_v1alpha1.TransportServer Endpoints map[string][]string PodsByIP map[string]string + DisableIPV6 bool } func (tsEx *TransportServerEx) String() string { @@ -90,6 +91,7 @@ func generateTransportServerConfig(transportServerEx *TransportServerEx, listene ProxyNextUpstreamTries: nextUpstreamTries, HealthCheck: healthCheck, ServerSnippets: serverSnippets, + DisableIPV6: transportServerEx.DisableIPV6, }, Match: match, Upstreams: upstreams, diff --git a/internal/configs/transportserver_test.go b/internal/configs/transportserver_test.go index 7232e88527..6b140d5ec3 100644 --- a/internal/configs/transportserver_test.go +++ b/internal/configs/transportserver_test.go @@ -95,6 +95,7 @@ func TestGenerateTransportServerConfigForTCPSnippets(t *testing.T) { "10.0.0.20:5001", }, }, + DisableIPV6: false, } listenerPort := 2020 @@ -132,6 +133,7 @@ func TestGenerateTransportServerConfigForTCPSnippets(t *testing.T) { ProxyNextUpstreamTimeout: "0s", ProxyTimeout: "10m", HealthCheck: nil, + DisableIPV6: false, ServerSnippets: []string{"deny 192.168.1.1;", "allow 192.168.1.0/24;"}, }, StreamSnippets: []string{"limit_conn_zone $binary_remote_addr zone=addr:10m;"}, @@ -143,6 +145,86 @@ func TestGenerateTransportServerConfigForTCPSnippets(t *testing.T) { } } +func TestGenerateTransportServerConfigForIPV6Disabled(t *testing.T) { + t.Parallel() + transportServerEx := TransportServerEx{ + TransportServer: &conf_v1alpha1.TransportServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "tcp-server", + Namespace: "default", + }, + Spec: conf_v1alpha1.TransportServerSpec{ + Listener: conf_v1alpha1.TransportServerListener{ + Name: "tcp-listener", + Protocol: "TCP", + }, + Upstreams: []conf_v1alpha1.Upstream{ + { + Name: "tcp-app", + Service: "tcp-app-svc", + Port: 5001, + }, + }, + Action: &conf_v1alpha1.Action{ + Pass: "tcp-app", + }, + }, + }, + Endpoints: map[string][]string{ + "default/tcp-app-svc:5001": { + "10.0.0.20:5001", + }, + }, + DisableIPV6: true, + } + + listenerPort := 2020 + + expected := &version2.TransportServerConfig{ + Upstreams: []version2.StreamUpstream{ + { + Name: "ts_default_tcp-server_tcp-app", + Servers: []version2.StreamUpstreamServer{ + { + Address: "10.0.0.20:5001", + MaxFails: 1, + FailTimeout: "10s", + }, + }, + UpstreamLabels: version2.UpstreamLabels{ + ResourceName: "tcp-server", + ResourceType: "transportserver", + ResourceNamespace: "default", + Service: "tcp-app-svc", + }, + LoadBalancingMethod: "random two least_conn", + }, + }, + Server: version2.StreamServer{ + Port: listenerPort, + UDP: false, + StatusZone: "tcp-listener", + ProxyPass: "ts_default_tcp-server_tcp-app", + Name: "tcp-server", + Namespace: "default", + ProxyConnectTimeout: "60s", + ProxyNextUpstream: false, + ProxyNextUpstreamTries: 0, + ProxyNextUpstreamTimeout: "0s", + ProxyTimeout: "10m", + HealthCheck: nil, + ServerSnippets: []string{}, + DisableIPV6: true, + }, + StreamSnippets: []string{}, + } + + result := generateTransportServerConfig(&transportServerEx, listenerPort, true) + if diff := cmp.Diff(expected, result); diff != "" { + t.Errorf("generateTransportServerConfigForIPV6Disabled() mismatch (-want +got):\n%s", diff) + } +} + func TestGenerateTransportServerConfigForTCP(t *testing.T) { t.Parallel() transportServerEx := TransportServerEx{ @@ -182,6 +264,7 @@ func TestGenerateTransportServerConfigForTCP(t *testing.T) { "10.0.0.20:5001", }, }, + DisableIPV6: false, } listenerPort := 2020 @@ -270,6 +353,7 @@ func TestGenerateTransportServerConfigForTCPMaxConnections(t *testing.T) { "10.0.0.20:5001", }, }, + DisableIPV6: false, } listenerPort := 2020 @@ -308,6 +392,7 @@ func TestGenerateTransportServerConfigForTCPMaxConnections(t *testing.T) { ProxyNextUpstreamTimeout: "0s", ProxyTimeout: "50s", HealthCheck: nil, + DisableIPV6: false, ServerSnippets: []string{}, }, StreamSnippets: []string{}, @@ -356,6 +441,7 @@ func TestGenerateTransportServerConfigForTLSPassthrough(t *testing.T) { "10.0.0.20:5001", }, }, + DisableIPV6: false, } listenerPort := 2020 @@ -397,6 +483,7 @@ func TestGenerateTransportServerConfigForTLSPassthrough(t *testing.T) { HealthCheck: nil, ServerSnippets: []string{}, }, + DisableIPV6: false, StreamSnippets: []string{}, } @@ -448,6 +535,7 @@ func TestGenerateTransportServerConfigForUDP(t *testing.T) { "10.0.0.20:5001", }, }, + DisableIPV6: false, } listenerPort := 2020 @@ -487,6 +575,7 @@ func TestGenerateTransportServerConfigForUDP(t *testing.T) { ProxyNextUpstreamTries: 0, ProxyTimeout: "10m", HealthCheck: nil, + DisableIPV6: false, ServerSnippets: []string{}, }, StreamSnippets: []string{}, diff --git a/internal/configs/version1/config.go b/internal/configs/version1/config.go index 7990619bb7..fb7bfdd6d6 100644 --- a/internal/configs/version1/config.go +++ b/internal/configs/version1/config.go @@ -15,6 +15,7 @@ type IngressNginxConfig struct { Keepalive string Ingress Ingress SpiffeClientCerts bool + DisableIPV6 bool } // Ingress holds information about an Ingress resource. @@ -110,6 +111,8 @@ type Server struct { AppProtectDosAccessLogDst string SpiffeCerts bool + + DisableIPV6 bool } // JWTRedirectLocation describes a location for redirecting client requests to a login URL for JWT Authentication. @@ -162,6 +165,7 @@ type MainConfig struct { AccessLogOff bool DefaultServerAccessLogOff bool DefaultServerReturn string + DisableIPV6 bool ErrorLogLevel string HealthStatus bool HealthStatusURI string diff --git a/internal/configs/version1/nginx-plus.ingress.tmpl b/internal/configs/version1/nginx-plus.ingress.tmpl index 94c692a67c..60b61acc05 100644 --- a/internal/configs/version1/nginx-plus.ingress.tmpl +++ b/internal/configs/version1/nginx-plus.ingress.tmpl @@ -22,14 +22,14 @@ upstream {{$upstream.Name}} { server { {{if $server.SpiffeCerts}} listen 443 ssl; - listen [::]:443 ssl; + {{if not $server.DisableIPV6}}listen [::]:443 ssl;{{end}} ssl_certificate /etc/nginx/secrets/spiffe_cert.pem; ssl_certificate_key /etc/nginx/secrets/spiffe_key.pem; {{else}} {{if not $server.GRPCOnly}} {{range $port := $server.Ports}} listen {{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:{{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; + {{if not $server.DisableIPV6}}listen [::]:{{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}};{{end}} {{- end}} {{end}} @@ -41,7 +41,7 @@ server { {{else}} {{- range $port := $server.SSLPorts}} listen {{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:{{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; + {{if not $server.DisableIPV6}}listen [::]:{{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}};{{end}} {{- end}} {{end}} {{if $server.SSLRejectHandshake}} diff --git a/internal/configs/version1/nginx-plus.tmpl b/internal/configs/version1/nginx-plus.tmpl index 814b8ac778..162a3bb20d 100644 --- a/internal/configs/version1/nginx-plus.tmpl +++ b/internal/configs/version1/nginx-plus.tmpl @@ -153,7 +153,7 @@ http { set $service ""; listen 80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}}; + {{if not .DisableIPV6}}listen [::]:80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}};{{end}} {{if .TLSPassthrough}} listen unix:/var/lib/nginx/passthrough-https.sock ssl default_server{{if .HTTP2}} http2{{end}} proxy_protocol; @@ -161,7 +161,7 @@ http { real_ip_header proxy_protocol; {{else}} listen 443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}}; + {{if not .DisableIPV6}}listen [::]:443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}};{{end}} {{end}} {{if .SSLRejectHandshake}} @@ -202,7 +202,7 @@ http { # NGINX Plus APIs server { listen {{.NginxStatusPort}}; - listen [::]:{{.NginxStatusPort}}; + {{if not .DisableIPV6}}listen [::]:{{.NginxStatusPort}};{{end}} root /usr/share/nginx/html; @@ -262,7 +262,7 @@ http { {{if .InternalRouteServer}} server { listen 443 ssl; - listen [::]:443 ssl; + {{if not .DisableIPV6}}listen [::]:443 ssl;{{end}} server_name {{.InternalRouteServerName}}; ssl_certificate /etc/nginx/secrets/spiffe_cert.pem; ssl_certificate_key /etc/nginx/secrets/spiffe_key.pem; @@ -298,7 +298,7 @@ stream { server { listen 443{{if .ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:443{{if .ProxyProtocol}} proxy_protocol{{end}}; + {{if not .DisableIPV6}}listen [::]:443{{if .ProxyProtocol}} proxy_protocol{{end}};{{end}} ssl_preread on; diff --git a/internal/configs/version1/nginx.ingress.tmpl b/internal/configs/version1/nginx.ingress.tmpl index 93f25cc53b..77772c06f2 100644 --- a/internal/configs/version1/nginx.ingress.tmpl +++ b/internal/configs/version1/nginx.ingress.tmpl @@ -13,7 +13,7 @@ server { {{if not $server.GRPCOnly}} {{range $port := $server.Ports}} listen {{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:{{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; + {{if not $server.DisableIPV6}}listen [::]:{{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}};{{end}} {{- end}} {{end}} @@ -25,7 +25,7 @@ server { {{else}} {{- range $port := $server.SSLPorts}} listen {{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:{{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}}; + {{if not $server.DisableIPV6}}listen [::]:{{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}};{{end}} {{- end}} {{end}} {{if $server.SSLRejectHandshake}} diff --git a/internal/configs/version1/nginx.tmpl b/internal/configs/version1/nginx.tmpl index bde822b1f9..20dfcd273b 100644 --- a/internal/configs/version1/nginx.tmpl +++ b/internal/configs/version1/nginx.tmpl @@ -107,7 +107,7 @@ http { set $service ""; listen 80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}}; + {{if not .DisableIPV6}}listen [::]:80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}};{{end}} {{if .TLSPassthrough}} listen unix:/var/lib/nginx/passthrough-https.sock ssl default_server{{if .HTTP2}} http2{{end}} proxy_protocol; @@ -115,7 +115,7 @@ http { real_ip_header proxy_protocol; {{else}} listen 443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}}; + {{if not .DisableIPV6}}listen [::]:443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}};{{end}} {{end}} {{if .SSLRejectHandshake}} @@ -156,7 +156,7 @@ http { # stub_status server { listen {{.NginxStatusPort}}; - listen [::]:{{.NginxStatusPort}}; + {{if not .DisableIPV6}}listen [::]:{{.NginxStatusPort}};{{end}} {{range $value := .NginxStatusAllowCIDRs}} allow {{$value}};{{end}} @@ -237,7 +237,7 @@ stream { server { listen 443{{if .ProxyProtocol}} proxy_protocol{{end}}; - listen [::]:443{{if .ProxyProtocol}} proxy_protocol{{end}}; + {{if not .DisableIPV6}}listen [::]:443{{if .ProxyProtocol}} proxy_protocol{{end}};{{end}} ssl_preread on; diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index ca410ee184..9faf6634be 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -77,6 +77,7 @@ type Server struct { PoliciesErrorReturn *Return VSNamespace string VSName string + DisableIPV6 bool } // SSL defines SSL configuration for a server. diff --git a/internal/configs/version2/nginx-plus.transportserver.tmpl b/internal/configs/version2/nginx-plus.transportserver.tmpl index 755ff445a5..f886e349c7 100644 --- a/internal/configs/version2/nginx-plus.transportserver.tmpl +++ b/internal/configs/version2/nginx-plus.transportserver.tmpl @@ -35,7 +35,7 @@ server { set_real_ip_from unix:; {{ else }} listen {{ $s.Port }}{{ if $s.UDP }} udp{{ end }}; - listen [::]:{{ $s.Port }}{{ if $s.UDP }} udp{{ end }}; + {{ if not $s.DisableIPV6 }}listen [::]:{{ $s.Port }}{{ if $s.UDP }} udp{{ end }};{{ end }} {{ end }} status_zone {{ $s.StatusZone }}; diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index 62fbe645b1..5349454303 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -59,7 +59,7 @@ match {{ $m.Name }} { {{ $s := .Server }} server { listen 80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; - listen [::]:80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; + {{ if not $s.DisableIPV6 }}listen [::]:80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }};{{ end }} server_name {{ $s.ServerName }}; status_zone {{ $s.StatusZone }}; @@ -91,7 +91,7 @@ server { real_ip_header proxy_protocol; {{ else }} listen 443 ssl{{ if $ssl.HTTP2 }} http2{{ end }}{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; - listen [::]:443 ssl{{ if $ssl.HTTP2 }} http2{{ end }}{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; + {{ if not $s.DisableIPV6 }}listen [::]:443 ssl{{ if $ssl.HTTP2 }} http2{{ end }}{{ if $s.ProxyProtocol }} proxy_protocol{{ end }};{{ end }} {{ end }} {{ if $ssl.RejectHandshake }} diff --git a/internal/configs/version2/nginx.transportserver.tmpl b/internal/configs/version2/nginx.transportserver.tmpl index b3d485fbee..c725967157 100644 --- a/internal/configs/version2/nginx.transportserver.tmpl +++ b/internal/configs/version2/nginx.transportserver.tmpl @@ -23,7 +23,7 @@ server { set_real_ip_from unix:; {{ else }} listen {{ $s.Port }}{{ if $s.UDP }} udp{{ end }}; - listen [::]:{{ $s.Port }}{{ if $s.UDP }} udp{{ end }}; + {{if not $s.DisableIPV6}}listen [::]:{{ $s.Port }}{{ if $s.UDP }} udp{{ end }};{{end}} {{ end }} {{ if $s.ProxyRequests }} diff --git a/internal/configs/version2/nginx.virtualserver.tmpl b/internal/configs/version2/nginx.virtualserver.tmpl index 692e25686a..b5c9a8b1c5 100644 --- a/internal/configs/version2/nginx.virtualserver.tmpl +++ b/internal/configs/version2/nginx.virtualserver.tmpl @@ -41,7 +41,7 @@ limit_req_zone {{ $z.Key }} zone={{ $z.ZoneName }}:{{ $z.ZoneSize }} rate={{ $z. {{ $s := .Server }} server { listen 80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; - listen [::]:80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; + {{ if not $s.DisableIPV6 }}listen [::]:80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }};{{ end }} server_name {{ $s.ServerName }}; @@ -57,7 +57,7 @@ server { real_ip_header proxy_protocol; {{ else }} listen 443 ssl{{ if $ssl.HTTP2 }} http2{{ end }}{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; - listen [::]:443 ssl{{ if $ssl.HTTP2 }} http2{{ end }}{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; + {{ if not $s.DisableIPV6 }}listen [::]:443 ssl{{ if $ssl.HTTP2 }} http2{{ end }}{{ if $s.ProxyProtocol }} proxy_protocol{{ end }};{{ end }} {{ end }} {{ if $ssl.RejectHandshake }} diff --git a/internal/configs/version2/stream.go b/internal/configs/version2/stream.go index 3ef93c5f8c..70b3e88738 100644 --- a/internal/configs/version2/stream.go +++ b/internal/configs/version2/stream.go @@ -6,6 +6,7 @@ type TransportServerConfig struct { Upstreams []StreamUpstream StreamSnippets []string Match *Match + DisableIPV6 bool } // StreamUpstream defines a stream upstream. @@ -43,6 +44,7 @@ type StreamServer struct { ProxyNextUpstreamTries int HealthCheck *StreamHealthCheck ServerSnippets []string + DisableIPV6 bool } // StreamHealthCheck defines a health check for a StreamUpstream in a StreamServer. diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index bece3e1353..8390f54162 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -236,6 +236,7 @@ type virtualServerConfigurator struct { warnings Warnings spiffeCerts bool oidcPolCfg *oidcPolicyCfg + isIPV6Disabled bool } type oidcPolicyCfg struct { @@ -275,6 +276,7 @@ func newVirtualServerConfigurator( warnings: make(map[runtime.Object][]string), spiffeCerts: staticParams.NginxServiceMesh, oidcPolCfg: &oidcPolicyCfg{}, + isIPV6Disabled: staticParams.DisableIPV6, } } @@ -675,6 +677,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( PoliciesErrorReturn: policiesCfg.ErrorReturn, VSNamespace: vsEx.VirtualServer.Namespace, VSName: vsEx.VirtualServer.Name, + DisableIPV6: vsc.isIPV6Disabled, }, SpiffeCerts: vsc.spiffeCerts, } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index 93fb9bb857..413417e3f2 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -664,6 +664,141 @@ func TestGenerateVirtualServerConfig(t *testing.T) { } } +func TestGenerateVirtualServerConfigIPV6Disabled(t *testing.T) { + t.Parallel() + virtualServerEx := VirtualServerEx{ + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "cafe", + Namespace: "default", + }, + Spec: conf_v1.VirtualServerSpec{ + Host: "cafe.example.com", + Upstreams: []conf_v1.Upstream{ + { + Name: "tea", + Service: "tea-svc", + Port: 80, + }, + { + Name: "coffee", + Service: "coffee-svc", + Port: 80, + }, + }, + Routes: []conf_v1.Route{ + { + Path: "/tea", + Action: &conf_v1.Action{ + Pass: "tea", + }, + }, + { + Path: "/coffee", + Action: &conf_v1.Action{ + Pass: "coffee", + }, + }, + }, + }, + }, + Endpoints: map[string][]string{ + "default/tea-svc:80": { + "10.0.0.20:80", + }, + "default/coffee-svc:80": { + "10.0.0.40:80", + }, + }, + } + + baseCfgParams := ConfigParams{} + + expected := version2.VirtualServerConfig{ + Upstreams: []version2.Upstream{ + { + UpstreamLabels: version2.UpstreamLabels{ + Service: "tea-svc", + ResourceType: "virtualserver", + ResourceName: "cafe", + ResourceNamespace: "default", + }, + Name: "vs_default_cafe_tea", + Servers: []version2.UpstreamServer{ + { + Address: "10.0.0.20:80", + }, + }, + }, + { + UpstreamLabels: version2.UpstreamLabels{ + Service: "coffee-svc", + ResourceType: "virtualserver", + ResourceName: "cafe", + ResourceNamespace: "default", + }, + Name: "vs_default_cafe_coffee", + Servers: []version2.UpstreamServer{ + { + Address: "10.0.0.40:80", + }, + }, + }, + }, + HTTPSnippets: []string{}, + LimitReqZones: []version2.LimitReqZone{}, + Server: version2.Server{ + ServerName: "cafe.example.com", + StatusZone: "cafe.example.com", + VSNamespace: "default", + VSName: "cafe", + DisableIPV6: true, + Locations: []version2.Location{ + { + Path: "/tea", + ProxyPass: "http://vs_default_cafe_tea", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxySSLName: "tea-svc.default.svc", + ProxyPassRequestHeaders: true, + ProxySetHeaders: []version2.Header{{Name: "Host", Value: "$host"}}, + ServiceName: "tea-svc", + }, + { + Path: "/coffee", + ProxyPass: "http://vs_default_cafe_coffee", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxySSLName: "coffee-svc.default.svc", + ProxyPassRequestHeaders: true, + ProxySetHeaders: []version2.Header{{Name: "Host", Value: "$host"}}, + ServiceName: "coffee-svc", + }, + }, + }, + } + + isPlus := false + isResolverConfigured := false + isWildcardEnabled := false + vsc := newVirtualServerConfigurator( + &baseCfgParams, + isPlus, + isResolverConfigured, + &StaticConfigParams{DisableIPV6: true}, + isWildcardEnabled, + ) + + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, nil, nil) + if diff := cmp.Diff(expected, result); diff != "" { + t.Errorf("GenerateVirtualServerConfig() mismatch (-want +got):\n%s", diff) + } + + if len(warnings) != 0 { + t.Errorf("GenerateVirtualServerConfig returned warnings: %v", vsc.warnings) + } +} + func TestGenerateVirtualServerConfigGrpcErrorPageWarning(t *testing.T) { t.Parallel() virtualServerEx := VirtualServerEx{ diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 46aee6fefc..fd4c78bebc 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -359,6 +359,7 @@ type Configuration struct { isTLSPassthroughEnabled bool snippetsEnabled bool isCertManagerEnabled bool + isIPV6Disabled bool lock sync.RWMutex } @@ -376,6 +377,7 @@ func NewConfiguration( isTLSPassthroughEnabled bool, snippetsEnabled bool, isCertManagerEnabled bool, + isIPV6Disabled bool, ) *Configuration { return &Configuration{ hosts: make(map[string]Resource), @@ -403,6 +405,7 @@ func NewConfiguration( isTLSPassthroughEnabled: isTLSPassthroughEnabled, snippetsEnabled: snippetsEnabled, isCertManagerEnabled: isCertManagerEnabled, + isIPV6Disabled: isIPV6Disabled, } } diff --git a/internal/k8s/configuration_test.go b/internal/k8s/configuration_test.go index 76bfbc466a..48e1e11bc8 100644 --- a/internal/k8s/configuration_test.go +++ b/internal/k8s/configuration_test.go @@ -24,6 +24,7 @@ func createTestConfiguration() *Configuration { isTLSPassthroughEnabled := true certManagerEnabled := true snippetsEnabled := true + isIPV6Disabled := false return NewConfiguration( lbc.HasCorrectIngressClass, isPlus, @@ -39,6 +40,7 @@ func createTestConfiguration() *Configuration { isTLSPassthroughEnabled, snippetsEnabled, certManagerEnabled, + isIPV6Disabled, ) } diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 5b39932f91..838d2b90c3 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -167,6 +167,7 @@ type LoadBalancerController struct { certManagerController *cm_controller.CmController externalDNSController *ed_controller.ExtDNSController batchSyncEnabled bool + isIPV6Disabled bool } var keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc @@ -208,6 +209,7 @@ type NewLoadBalancerControllerInput struct { SnippetsEnabled bool CertManagerEnabled bool ExternalDNSEnabled bool + IsIPV6Disabled bool } // NewLoadBalancerController creates a controller @@ -238,6 +240,7 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc internalRoutesEnabled: input.InternalRoutesEnabled, isPrometheusEnabled: input.IsPrometheusEnabled, isLatencyMetricsEnabled: input.IsLatencyMetricsEnabled, + isIPV6Disabled: input.IsIPV6Disabled, } eventBroadcaster := record.NewBroadcaster() @@ -357,6 +360,7 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc input.IsTLSPassthroughEnabled, input.SnippetsEnabled, input.CertManagerEnabled, + input.IsIPV6Disabled, ) lbc.appProtectConfiguration = appprotect.NewConfiguration() @@ -3162,6 +3166,7 @@ func isMatchingResourceRef(ownerNs, resRef, key string) bool { func (lbc *LoadBalancerController) createTransportServerEx(transportServer *conf_v1alpha1.TransportServer, listenerPort int) *configs.TransportServerEx { endpoints := make(map[string][]string) podsByIP := make(map[string]string) + disableIPV6 := lbc.configuration.isIPV6Disabled for _, u := range transportServer.Spec.Upstreams { podEndps, external, err := lbc.getEndpointsForUpstream(transportServer.Namespace, u.Service, uint16(u.Port)) @@ -3191,6 +3196,7 @@ func (lbc *LoadBalancerController) createTransportServerEx(transportServer *conf TransportServer: transportServer, Endpoints: endpoints, PodsByIP: podsByIP, + DisableIPV6: disableIPV6, } } diff --git a/tests/suite/test_disable_ipv6.py b/tests/suite/test_disable_ipv6.py new file mode 100644 index 0000000000..31d83bb0c9 --- /dev/null +++ b/tests/suite/test_disable_ipv6.py @@ -0,0 +1,58 @@ +import pytest +from suite.resources_utils import ( + get_first_pod_name, + get_nginx_template_conf, + get_ts_nginx_template_conf, + wait_before_test, +) +from suite.vs_vsr_resources_utils import get_vs_nginx_template_conf + + +@pytest.mark.vs +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup, transport_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-global-configuration=nginx-ingress/nginx-configuration", + "-disable-ipv6=true", + ], + }, + {"example": "virtual-server-status", "app_type": "simple"}, + {"example": "transport-server-status", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestDisableIpv6: + def test_ipv6_is_disabled( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + transport_server_setup, + ): + wait_before_test() + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + nginx_config = get_nginx_template_conf(kube_apis.v1, ingress_controller_prerequisites.namespace) + ts_config = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + vs_config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + assert "listen [::]:" not in nginx_config + assert "listen [::]:" not in vs_config + assert "listen [::]:" not in ts_config