From 18a87fb665bc0e2d5a2458788ec7945e0df992d1 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 17 Jul 2024 16:07:33 +0100 Subject: [PATCH 01/43] add ip listener input to virtual server (not working) --- internal/configs/version2/http.go | 1 + internal/configs/virtualserver.go | 1 + internal/k8s/configuration.go | 18 ++++++++++++++++++ internal/k8s/controller.go | 1 + pkg/apis/configuration/v1/types.go | 2 ++ 5 files changed, 23 insertions(+) diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 2ee1884688..25b0ac6266 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -57,6 +57,7 @@ type Server struct { ServerName string StatusZone string CustomListeners bool + IP string HTTPPort int HTTPSPort int ProxyProtocol bool diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 32d561def6..e2a50a996b 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -87,6 +87,7 @@ type VirtualServerEx struct { VirtualServer *conf_v1.VirtualServer HTTPPort int HTTPSPort int + IP string Endpoints map[string][]string VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index f4d9bb05a3..43c2090a02 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -200,6 +200,7 @@ type VirtualServerConfiguration struct { Warnings []string HTTPPort int HTTPSPort int + IP string } // NewVirtualServerConfiguration creates a VirtualServerConfiguration. @@ -825,6 +826,10 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi vsc.HTTPSPort = gcListener.Port } } + + if gcListener, ok := c.listenerMap[vs.Spec.Listener.IP]; ok { + vsc.IP = gcListener.IP + } } } @@ -1170,6 +1175,15 @@ func (c *Configuration) addWarningsForVirtualServersWithMissConfiguredListeners( continue } } + + if vsc.VirtualServer.Spec.Listener.IP != "" { + if _, exists := c.listenerMap[vsc.VirtualServer.Spec.Listener.IP]; !exists { + warningMsg := fmt.Sprintf("Listener %s is not defined in GlobalConfiguration", + vsc.VirtualServer.Spec.Listener.IP) + c.hosts[vsc.VirtualServer.Spec.Host].AddWarning(warningMsg) + continue + } + } } } } @@ -1783,6 +1797,10 @@ func detectChangesInHosts(oldHosts map[string]Resource, newHosts map[string]Reso if newVsc.HTTPPort != oldVsc.HTTPPort || newVsc.HTTPSPort != oldVsc.HTTPSPort { updatedHosts = append(updatedHosts, h) } + + if newVsc.IP != oldVsc.IP { + updatedHosts = append(updatedHosts, h) + } } return removedHosts, updatedHosts, addedHosts diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index b88cf6b687..46d355bc6c 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -3213,6 +3213,7 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. if vsc, ok := resource.(*VirtualServerConfiguration); ok { virtualServerEx.HTTPPort = vsc.HTTPPort virtualServerEx.HTTPSPort = vsc.HTTPSPort + virtualServerEx.IP = vsc.IP } if virtualServer.Spec.TLS != nil && virtualServer.Spec.TLS.Secret != "" { diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 0ad315fad7..ba830105eb 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -62,6 +62,7 @@ type VirtualServerSpec struct { type VirtualServerListener struct { HTTP string `json:"http"` HTTPS string `json:"https"` + IP string `json:"ip"` } // ExternalDNS defines externaldns sub-resource of a virtual server. @@ -411,6 +412,7 @@ type GlobalConfigurationSpec struct { type Listener struct { Name string `json:"name"` Port int `json:"port"` + IP string `json:"ip"` Protocol string `json:"protocol"` Ssl bool `json:"ssl"` } From acb8a3b0df585e04d6fbe029b00b52db040d047d Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Thu, 18 Jul 2024 08:50:46 +0100 Subject: [PATCH 02/43] Initial tests for validating IP field in listeners --- .../validation/globalconfiguration_test.go | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pkg/apis/configuration/validation/globalconfiguration_test.go b/pkg/apis/configuration/validation/globalconfiguration_test.go index de3a3af458..9ea7d20924 100644 --- a/pkg/apis/configuration/validation/globalconfiguration_test.go +++ b/pkg/apis/configuration/validation/globalconfiguration_test.go @@ -74,6 +74,12 @@ func TestValidateListeners(t *testing.T) { Port: 53, Protocol: "UDP", }, + { + Name: "test-listener-ip", + IP: "127.0.0.1", + Port: 8080, + Protocol: "HTTP", + }, } gcv := createGlobalConfigurationValidator() @@ -84,6 +90,34 @@ func TestValidateListeners(t *testing.T) { } } +func TestValidateListeners_FailsOnInvalidIP(t *testing.T) { + t.Parallel() + + // todo: implement tests for invalid IP addresses, hint use k8s validators for IP used in the `externaldns` pkg + // implement IP validation in the getalidListeners + listeners := []conf_v1.Listener{ + { + Name: "test-listener-1", + IP: "267.0.0.1", // invalid IP + Port: 8080, + Protocol: "HTTP", + }, + { + Name: "test-listener-2", + IP: "127.0.0", // invalid IP, missing octet + Port: 8080, + Protocol: "HTTP", + }, + } + + gcv := createGlobalConfigurationValidator() + + _, allErrs := gcv.getValidListeners(listeners, field.NewPath("listeners")) + if len(allErrs) == 0 { + t.Errorf("validateListeners() returned no errors %v for invalid input", allErrs) + } +} + func TestValidateListenersFails(t *testing.T) { t.Parallel() tests := []struct { From 386600a7ac9878c5eb79f4e6d3ff16251f299867 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 23 Jul 2024 14:15:43 +0100 Subject: [PATCH 03/43] Refactor listener validator for IP --- .../validation/globalconfiguration.go | 112 ++++++++++++------ 1 file changed, 76 insertions(+), 36 deletions(-) diff --git a/pkg/apis/configuration/validation/globalconfiguration.go b/pkg/apis/configuration/validation/globalconfiguration.go index daf7579320..876a4e91fb 100644 --- a/pkg/apis/configuration/validation/globalconfiguration.go +++ b/pkg/apis/configuration/validation/globalconfiguration.go @@ -45,54 +45,94 @@ func (gcv *GlobalConfigurationValidator) getValidListeners(listeners []conf_v1.L allErrs := field.ErrorList{} listenerNames := sets.Set[string]{} - portProtocolCombinations := sets.Set[string]{} - - portProtocolMap := make(map[int]string) + ipPortProtocolCombinations := make(map[string]map[int]string) // map[IP]map[Port]Protocol var validListeners []conf_v1.Listener for i, l := range listeners { idxPath := fieldPath.Index(i) - portProtocolKey := generatePortProtocolKey(l.Port, l.Protocol) listenerErrs := gcv.validateListener(l, idxPath) if len(listenerErrs) > 0 { allErrs = append(allErrs, listenerErrs...) - } else if listenerNames.Has(l.Name) { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), l.Name)) - } else if portProtocolCombinations.Has(portProtocolKey) { - msg := fmt.Sprintf("Listener %s: Duplicated port/protocol combination %s", l.Name, portProtocolKey) - allErrs = append(allErrs, field.Duplicate(fieldPath, msg)) - } else if protocol, ok := portProtocolMap[l.Port]; ok { - var msg string - switch protocol { - case "HTTP": - if l.Protocol == "TCP" || l.Protocol == "UDP" { - msg = fmt.Sprintf( - "Listener %s with protocol %s can't use port %d. Port is taken by an HTTP listener", - l.Name, l.Protocol, l.Port) - allErrs = append(allErrs, field.Forbidden(fieldPath, msg)) - } else { - validListeners = append(validListeners, l) - } - case "TCP", "UDP": - if l.Protocol == "HTTP" { - msg = fmt.Sprintf( - "Listener %s with protocol %s can't use port %d. Port is taken by TCP or UDP listener", - l.Name, l.Protocol, l.Port) - allErrs = append(allErrs, field.Forbidden(fieldPath, msg)) - } else { - validListeners = append(validListeners, l) - } - } - } else { - listenerNames.Insert(l.Name) - portProtocolCombinations.Insert(portProtocolKey) - portProtocolMap[l.Port] = l.Protocol - validListeners = append(validListeners, l) + continue + } + + if err := gcv.validateIP(l, idxPath); err != nil { + allErrs = append(allErrs, err) + continue + } + + if err := gcv.checkForDuplicateName(listenerNames, l, idxPath); err != nil { + allErrs = append(allErrs, err) + continue + } + + if err := gcv.checkPortProtocolConflicts(ipPortProtocolCombinations, l, fieldPath); err != nil { + allErrs = append(allErrs, err) + continue } + + gcv.updatePortProtocolCombinations(ipPortProtocolCombinations, l) + validListeners = append(validListeners, l) } return validListeners, allErrs } +// validateIP checks if the provided IP address of the listener is valid. +func (gcv *GlobalConfigurationValidator) validateIP(listener conf_v1.Listener, idxPath *field.Path) *field.Error { + if listener.IP != "" { + ipPath := idxPath.Child("IP") + if len(validation.IsValidIP(ipPath, listener.IP)) > 0 { + return field.Invalid(ipPath, listener.IP, "invalid IP address") + } + } + return nil +} + +// checkForDuplicateName checks if the listener name is unique. +func (gcv *GlobalConfigurationValidator) checkForDuplicateName(listenerNames sets.Set[string], listener conf_v1.Listener, idxPath *field.Path) *field.Error { + if listenerNames.Has(listener.Name) { + return field.Duplicate(idxPath.Child("name"), listener.Name) + } + listenerNames.Insert(listener.Name) + return nil +} + +// checkPortProtocolConflicts ensures no duplicate or conflicting port/protocol combinations exist. +func (gcv *GlobalConfigurationValidator) checkPortProtocolConflicts(combinations map[string]map[int]string, listener conf_v1.Listener, fieldPath *field.Path) *field.Error { + ip := listener.IP + if ip == "" { + ip = "" + } + + if combinations[ip] == nil { + combinations[ip] = make(map[int]string) + } + + existingProtocol, exists := combinations[ip][listener.Port] + if exists { + if existingProtocol == listener.Protocol { + return field.Duplicate(fieldPath, fmt.Sprintf("Listener %s: Duplicated port/protocol combination %d/%s", listener.Name, listener.Port, listener.Protocol)) + } else if listener.IP != "" || (listener.Protocol == "HTTP" && (existingProtocol == "UDP" || existingProtocol == "TCP")) || ((listener.Protocol == "UDP" || listener.Protocol == "TCP") && existingProtocol == "HTTP") { + return field.Invalid(fieldPath.Child("port"), listener.Port, fmt.Sprintf("Listener %s: Port %d is used with a different protocol (current: %s, new: %s)", listener.Name, listener.Port, existingProtocol, listener.Protocol)) + } + } + return nil +} + +// updatePortProtocolCombinations updates the port/protocol combinations map with the given listener's details. +func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinations map[string]map[int]string, listener conf_v1.Listener) { + ip := listener.IP + if ip == "" { + ip = "" + } + + if combinations[ip] == nil { + combinations[ip] = make(map[int]string) + } + + combinations[ip][listener.Port] = listener.Protocol +} + func generatePortProtocolKey(port int, protocol string) string { return fmt.Sprintf("%d/%s", port, protocol) } From c20235c5118454c5e58b492c24c2e86118d55de4 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 23 Jul 2024 14:54:32 +0100 Subject: [PATCH 04/43] Remove accidental code and update CRDs --- .../k8s.nginx.org_globalconfigurations.yaml | 2 ++ deploy/crds.yaml | 2 ++ internal/configs/version2/http.go | 1 - internal/configs/virtualserver.go | 1 - internal/k8s/configuration.go | 18 ------------------ internal/k8s/controller.go | 1 - pkg/apis/configuration/v1/types.go | 1 - 7 files changed, 4 insertions(+), 22 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index 51fce137cd..da2de980c7 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -46,6 +46,8 @@ spec: items: description: Listener defines a listener. properties: + ip: + type: string name: type: string port: diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 7b49247e15..637672852c 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -142,6 +142,8 @@ spec: items: description: Listener defines a listener. properties: + ip: + type: string name: type: string port: diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 25b0ac6266..2ee1884688 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -57,7 +57,6 @@ type Server struct { ServerName string StatusZone string CustomListeners bool - IP string HTTPPort int HTTPSPort int ProxyProtocol bool diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index e2a50a996b..32d561def6 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -87,7 +87,6 @@ type VirtualServerEx struct { VirtualServer *conf_v1.VirtualServer HTTPPort int HTTPSPort int - IP string Endpoints map[string][]string VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 43c2090a02..f4d9bb05a3 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -200,7 +200,6 @@ type VirtualServerConfiguration struct { Warnings []string HTTPPort int HTTPSPort int - IP string } // NewVirtualServerConfiguration creates a VirtualServerConfiguration. @@ -826,10 +825,6 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi vsc.HTTPSPort = gcListener.Port } } - - if gcListener, ok := c.listenerMap[vs.Spec.Listener.IP]; ok { - vsc.IP = gcListener.IP - } } } @@ -1175,15 +1170,6 @@ func (c *Configuration) addWarningsForVirtualServersWithMissConfiguredListeners( continue } } - - if vsc.VirtualServer.Spec.Listener.IP != "" { - if _, exists := c.listenerMap[vsc.VirtualServer.Spec.Listener.IP]; !exists { - warningMsg := fmt.Sprintf("Listener %s is not defined in GlobalConfiguration", - vsc.VirtualServer.Spec.Listener.IP) - c.hosts[vsc.VirtualServer.Spec.Host].AddWarning(warningMsg) - continue - } - } } } } @@ -1797,10 +1783,6 @@ func detectChangesInHosts(oldHosts map[string]Resource, newHosts map[string]Reso if newVsc.HTTPPort != oldVsc.HTTPPort || newVsc.HTTPSPort != oldVsc.HTTPSPort { updatedHosts = append(updatedHosts, h) } - - if newVsc.IP != oldVsc.IP { - updatedHosts = append(updatedHosts, h) - } } return removedHosts, updatedHosts, addedHosts diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 46d355bc6c..b88cf6b687 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -3213,7 +3213,6 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. if vsc, ok := resource.(*VirtualServerConfiguration); ok { virtualServerEx.HTTPPort = vsc.HTTPPort virtualServerEx.HTTPSPort = vsc.HTTPSPort - virtualServerEx.IP = vsc.IP } if virtualServer.Spec.TLS != nil && virtualServer.Spec.TLS.Secret != "" { diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index ba830105eb..17b08b8410 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -62,7 +62,6 @@ type VirtualServerSpec struct { type VirtualServerListener struct { HTTP string `json:"http"` HTTPS string `json:"https"` - IP string `json:"ip"` } // ExternalDNS defines externaldns sub-resource of a virtual server. From d53fdfd0490715ef70b0748af70273a53aa53b7f Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 24 Jul 2024 14:20:50 +0100 Subject: [PATCH 05/43] Add tests and fix port conflict --- .../validation/globalconfiguration.go | 9 +- .../validation/globalconfiguration_test.go | 140 ++++++++++++++++-- 2 files changed, 130 insertions(+), 19 deletions(-) diff --git a/pkg/apis/configuration/validation/globalconfiguration.go b/pkg/apis/configuration/validation/globalconfiguration.go index 876a4e91fb..2d32764b3d 100644 --- a/pkg/apis/configuration/validation/globalconfiguration.go +++ b/pkg/apis/configuration/validation/globalconfiguration.go @@ -100,9 +100,6 @@ func (gcv *GlobalConfigurationValidator) checkForDuplicateName(listenerNames set // checkPortProtocolConflicts ensures no duplicate or conflicting port/protocol combinations exist. func (gcv *GlobalConfigurationValidator) checkPortProtocolConflicts(combinations map[string]map[int]string, listener conf_v1.Listener, fieldPath *field.Path) *field.Error { ip := listener.IP - if ip == "" { - ip = "" - } if combinations[ip] == nil { combinations[ip] = make(map[int]string) @@ -112,19 +109,17 @@ func (gcv *GlobalConfigurationValidator) checkPortProtocolConflicts(combinations if exists { if existingProtocol == listener.Protocol { return field.Duplicate(fieldPath, fmt.Sprintf("Listener %s: Duplicated port/protocol combination %d/%s", listener.Name, listener.Port, listener.Protocol)) - } else if listener.IP != "" || (listener.Protocol == "HTTP" && (existingProtocol == "UDP" || existingProtocol == "TCP")) || ((listener.Protocol == "UDP" || listener.Protocol == "TCP") && existingProtocol == "HTTP") { + } else if listener.Protocol == "HTTP" || existingProtocol == "HTTP" { return field.Invalid(fieldPath.Child("port"), listener.Port, fmt.Sprintf("Listener %s: Port %d is used with a different protocol (current: %s, new: %s)", listener.Name, listener.Port, existingProtocol, listener.Protocol)) } } + return nil } // updatePortProtocolCombinations updates the port/protocol combinations map with the given listener's details. func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinations map[string]map[int]string, listener conf_v1.Listener) { ip := listener.IP - if ip == "" { - ip = "" - } if combinations[ip] == nil { combinations[ip] = make(map[int]string) diff --git a/pkg/apis/configuration/validation/globalconfiguration_test.go b/pkg/apis/configuration/validation/globalconfiguration_test.go index 9ea7d20924..3cfb51940b 100644 --- a/pkg/apis/configuration/validation/globalconfiguration_test.go +++ b/pkg/apis/configuration/validation/globalconfiguration_test.go @@ -93,28 +93,144 @@ func TestValidateListeners(t *testing.T) { func TestValidateListeners_FailsOnInvalidIP(t *testing.T) { t.Parallel() - // todo: implement tests for invalid IP addresses, hint use k8s validators for IP used in the `externaldns` pkg - // implement IP validation in the getalidListeners - listeners := []conf_v1.Listener{ + testCases := []struct { + name string + listeners []conf_v1.Listener + }{ { - Name: "test-listener-1", - IP: "267.0.0.1", // invalid IP - Port: 8080, - Protocol: "HTTP", + name: "Invalid IP", + listeners: []conf_v1.Listener{ + {Name: "test-listener-1", IP: "267.0.0.1", Port: 8082, Protocol: "UDP"}, + }, }, { - Name: "test-listener-2", - IP: "127.0.0", // invalid IP, missing octet - Port: 8080, - Protocol: "HTTP", + name: "Invalid IP with missing octet", + listeners: []conf_v1.Listener{ + {Name: "test-listener-2", IP: "127.0.0", Port: 8080, Protocol: "HTTP"}, + }, + }, + { + name: "Valid and invalid IPs", + listeners: []conf_v1.Listener{ + {Name: "test-listener-3", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "test-listener-4", IP: "256.256.256.256", Port: 8081, Protocol: "HTTP"}, + }, }, } gcv := createGlobalConfigurationValidator() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, allErrs := gcv.getValidListeners(tc.listeners, field.NewPath("listeners")) + if len(allErrs) == 0 { + t.Errorf("Expected errors for invalid IPs, but got none") + } else { + for _, err := range allErrs { + t.Logf("Caught expected error: %v", err) + } + } + }) + } +} + +func TestValidateListeners_FailsOnDuplicateNamesDifferentIP(t *testing.T) { + t.Parallel() + + listeners := []conf_v1.Listener{ + {Name: "test-listener", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "test-listener", IP: "192.168.1.2", Port: 8081, Protocol: "HTTP"}, + } + + gcv := createGlobalConfigurationValidator() + _, allErrs := gcv.getValidListeners(listeners, field.NewPath("listeners")) if len(allErrs) == 0 { - t.Errorf("validateListeners() returned no errors %v for invalid input", allErrs) + t.Errorf("validateListeners() returned no errors %v for duplicate names", allErrs) + } +} + +func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + listeners []conf_v1.Listener + }{ + { + name: "Same port used with UDP and HTTP protocols", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "UDP"}, + {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + }, + }, + { + name: "Same port used with the same protocol", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + }, + }, + } + + gcv := createGlobalConfigurationValidator() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, allErrs := gcv.getValidListeners(tc.listeners, field.NewPath("listeners")) + if len(allErrs) > 0 { + t.Logf("Caught expected error(s): %v", allErrs) + } + }) + } +} + +func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + listeners []conf_v1.Listener + }{ + { + name: "Different Ports and IPs", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IP: "192.168.1.2", Port: 9090, Protocol: "HTTP"}, + }, + }, + { + name: "Same IP, Same Protocol and Different Port", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IP: "192.168.1.1", Port: 9090, Protocol: "HTTP"}, + }, + }, + { + name: "Same IP, Different Protocol and Different Port", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "listener-2", IP: "192.168.1.1", Port: 9090, Protocol: "UDP"}, + }, + }, + { + name: "Same IP, Different Protocol and Same Port", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "UDP"}, + {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + }, + }, + } + + gcv := createGlobalConfigurationValidator() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, allErrs := gcv.getValidListeners(tc.listeners, field.NewPath("listeners")) + if len(allErrs) != 0 { + t.Errorf("Unexpected errors for valid listeners: %v", allErrs) + } + }) } } From d044ea42f0cafe636940ac10d9633aa68a1aaaee Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 25 Jul 2024 10:33:27 +0100 Subject: [PATCH 06/43] Add back vs support (not passing into template helper) --- .../bases/k8s.nginx.org_virtualservers.yaml | 2 ++ deploy/crds.yaml | 2 ++ internal/configs/version2/http.go | 1 + internal/configs/version2/template_helper.go | 23 +++++++++++++------ internal/configs/virtualserver.go | 2 ++ internal/k8s/configuration.go | 7 ++++++ internal/k8s/controller.go | 1 + pkg/apis/configuration/v1/types.go | 1 + 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index 774449f8cc..0d3412dd80 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -118,6 +118,8 @@ spec: type: string https: type: string + ip: + type: string type: object policies: items: diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 637672852c..6dee2f46ed 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1741,6 +1741,8 @@ spec: type: string https: type: string + ip: + type: string type: object policies: items: diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 2ee1884688..25b0ac6266 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -57,6 +57,7 @@ type Server struct { ServerName string StatusZone string CustomListeners bool + IP string HTTPPort int HTTPSPort int ProxyProtocol bool diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 1f9356959f..8bee1012ca 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -56,11 +56,11 @@ func buildDefaultListenerDirectives(listenerType protocol, s Server) string { var directives string port := getDefaultPort(listenerType) - directives += buildListenDirective(port, s.ProxyProtocol, ipv4) + directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv4) if !s.DisableIPV6 { directives += spacing - directives += buildListenDirective(port, s.ProxyProtocol, ipv6) + directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv6) } return directives @@ -71,11 +71,11 @@ func buildCustomListenerDirectives(listenerType protocol, s Server) string { if (listenerType == http && s.HTTPPort > 0) || (listenerType == https && s.HTTPSPort > 0) { port := getCustomPort(listenerType, s) - directives += buildListenDirective(port, s.ProxyProtocol, ipv4) + directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv4) if !s.DisableIPV6 { directives += spacing - directives += buildListenDirective(port, s.ProxyProtocol, ipv6) + directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv6) } } @@ -96,14 +96,22 @@ func getCustomPort(listenerType protocol, s Server) string { return strconv.Itoa(s.HTTPSPort) + " ssl" } -func buildListenDirective(port string, proxyProtocol bool, listenType listenerType) string { +func buildListenDirective(ip string, port string, proxyProtocol bool, listenType listenerType) string { base := "listen" var directive string if listenType == ipv6 { - directive = base + " [::]:" + port + if ip == "" { + directive = fmt.Sprintf("%s [::]:%s", base, port) + } else { + directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) + } } else { - directive = base + " " + port + if ip == "" { + directive = fmt.Sprintf("%s %s", base, port) + } else { + directive = fmt.Sprintf("%s %s:%s", base, ip, port) + } } if proxyProtocol { @@ -115,6 +123,7 @@ func buildListenDirective(port string, proxyProtocol bool, listenType listenerTy } func makeHTTPListener(s Server) string { + fmt.Println("DEBUGGGG IP ", s.IP) return makeListener(http, s) } diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 32d561def6..c2fd0b8ac6 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -87,6 +87,7 @@ type VirtualServerEx struct { VirtualServer *conf_v1.VirtualServer HTTPPort int HTTPSPort int + IP string Endpoints map[string][]string VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool @@ -835,6 +836,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( StatusZone: vsEx.VirtualServer.Spec.Host, HTTPPort: vsEx.HTTPPort, HTTPSPort: vsEx.HTTPSPort, + IP: vsEx.IP, CustomListeners: useCustomListeners, ProxyProtocol: vsc.cfgParams.ProxyProtocol, SSL: sslConfig, diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index f4d9bb05a3..362f974389 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -200,6 +200,7 @@ type VirtualServerConfiguration struct { Warnings []string HTTPPort int HTTPSPort int + IP string } // NewVirtualServerConfiguration creates a VirtualServerConfiguration. @@ -817,12 +818,18 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTP]; ok { if gcListener.Protocol == conf_v1.HTTPProtocol && !gcListener.Ssl { vsc.HTTPPort = gcListener.Port + if gcListener.IP == "" { + vsc.IP = gcListener.IP + } } } if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTPS]; ok { if gcListener.Protocol == conf_v1.HTTPProtocol && gcListener.Ssl { vsc.HTTPSPort = gcListener.Port + if gcListener.IP == "" { + vsc.IP = gcListener.IP + } } } } diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index b88cf6b687..46d355bc6c 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -3213,6 +3213,7 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. if vsc, ok := resource.(*VirtualServerConfiguration); ok { virtualServerEx.HTTPPort = vsc.HTTPPort virtualServerEx.HTTPSPort = vsc.HTTPSPort + virtualServerEx.IP = vsc.IP } if virtualServer.Spec.TLS != nil && virtualServer.Spec.TLS.Secret != "" { diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 17b08b8410..ba830105eb 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -62,6 +62,7 @@ type VirtualServerSpec struct { type VirtualServerListener struct { HTTP string `json:"http"` HTTPS string `json:"https"` + IP string `json:"ip"` } // ExternalDNS defines externaldns sub-resource of a virtual server. From 3f458c77902c559d6738b1a75498f9841f3f6314 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 25 Jul 2024 14:00:18 +0100 Subject: [PATCH 07/43] Fix vs implementation - fully working --- config/crd/bases/k8s.nginx.org_virtualservers.yaml | 2 -- internal/configs/version2/template_helper.go | 5 ++--- internal/configs/virtualserver.go | 1 + internal/k8s/configuration.go | 9 +++++++-- pkg/apis/configuration/v1/types.go | 1 - 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index 0d3412dd80..774449f8cc 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -118,8 +118,6 @@ spec: type: string https: type: string - ip: - type: string type: object policies: items: diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 8bee1012ca..8db01c4320 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -100,11 +100,10 @@ func buildListenDirective(ip string, port string, proxyProtocol bool, listenType base := "listen" var directive string + fmt.Printf("Helper on %s\n", ip) if listenType == ipv6 { - if ip == "" { + if ip != "" { directive = fmt.Sprintf("%s [::]:%s", base, port) - } else { - directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) } } else { if ip == "" { diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index c2fd0b8ac6..3359e23ea7 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -823,6 +823,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( return upstreams[i].Name < upstreams[j].Name }) + fmt.Println("DEBUG vsEx IP: ", vsEx.IP) vsCfg := version2.VirtualServerConfig{ Upstreams: upstreams, SplitClients: splitClients, diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 362f974389..b8f0c65c14 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -818,7 +818,7 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTP]; ok { if gcListener.Protocol == conf_v1.HTTPProtocol && !gcListener.Ssl { vsc.HTTPPort = gcListener.Port - if gcListener.IP == "" { + if gcListener.IP != "" { vsc.IP = gcListener.IP } } @@ -827,7 +827,7 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTPS]; ok { if gcListener.Protocol == conf_v1.HTTPProtocol && gcListener.Ssl { vsc.HTTPSPort = gcListener.Port - if gcListener.IP == "" { + if gcListener.IP != "" { vsc.IP = gcListener.IP } } @@ -1790,6 +1790,11 @@ func detectChangesInHosts(oldHosts map[string]Resource, newHosts map[string]Reso if newVsc.HTTPPort != oldVsc.HTTPPort || newVsc.HTTPSPort != oldVsc.HTTPSPort { updatedHosts = append(updatedHosts, h) } + + if newVsc.IP != oldVsc.IP { + updatedHosts = append(updatedHosts, h) + } + } return removedHosts, updatedHosts, addedHosts diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index ba830105eb..17b08b8410 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -62,7 +62,6 @@ type VirtualServerSpec struct { type VirtualServerListener struct { HTTP string `json:"http"` HTTPS string `json:"https"` - IP string `json:"ip"` } // ExternalDNS defines externaldns sub-resource of a virtual server. From e08d9577e721947fd1d5669cdc31bf0dd892d1a0 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 25 Jul 2024 14:31:06 +0100 Subject: [PATCH 08/43] Fix ipv6 nginx break --- internal/configs/version2/template_helper.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 8db01c4320..a0c8efad16 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -102,14 +102,18 @@ func buildListenDirective(ip string, port string, proxyProtocol bool, listenType fmt.Printf("Helper on %s\n", ip) if listenType == ipv6 { - if ip != "" { + if strings.Contains(ip, ":") { + if ip != "" { + directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) + } + } else { directive = fmt.Sprintf("%s [::]:%s", base, port) } } else { - if ip == "" { - directive = fmt.Sprintf("%s %s", base, port) - } else { + if ip != "" { directive = fmt.Sprintf("%s %s:%s", base, ip, port) + } else { + directive = fmt.Sprintf("%s %s", base, port) } } From 92c5444ef46f2eab5aaadbffc727cce89e3256b2 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 25 Jul 2024 16:32:11 +0100 Subject: [PATCH 09/43] Remove debug and fix crd --- deploy/crds.yaml | 2 -- internal/configs/version2/template_helper.go | 2 -- internal/configs/virtualserver.go | 1 - 3 files changed, 5 deletions(-) diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 6dee2f46ed..637672852c 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1741,8 +1741,6 @@ spec: type: string https: type: string - ip: - type: string type: object policies: items: diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index a0c8efad16..3b849661a2 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -100,7 +100,6 @@ func buildListenDirective(ip string, port string, proxyProtocol bool, listenType base := "listen" var directive string - fmt.Printf("Helper on %s\n", ip) if listenType == ipv6 { if strings.Contains(ip, ":") { if ip != "" { @@ -126,7 +125,6 @@ func buildListenDirective(ip string, port string, proxyProtocol bool, listenType } func makeHTTPListener(s Server) string { - fmt.Println("DEBUGGGG IP ", s.IP) return makeListener(http, s) } diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 3359e23ea7..c2fd0b8ac6 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -823,7 +823,6 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( return upstreams[i].Name < upstreams[j].Name }) - fmt.Println("DEBUG vsEx IP: ", vsEx.IP) vsCfg := version2.VirtualServerConfig{ Upstreams: upstreams, SplitClients: splitClients, From d03fd3e54a3f4a6665100d44959a024cd5241779 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 30 Jul 2024 10:29:57 +0100 Subject: [PATCH 10/43] Update warning message and tests --- .../configs/version2/template_helper_test.go | 45 +++++++++++++++++++ internal/k8s/configuration.go | 2 +- internal/k8s/configuration_test.go | 6 +-- .../validation/globalconfiguration_test.go | 25 +---------- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index ed0650ae06..658d28debf 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -268,6 +268,51 @@ func TestMakeHTTPSListener(t *testing.T) { } } +func TestMakeHTTPListenerWithCustomIP(t *testing.T) { + t.Parallel() + + testCases := []struct { + server Server + expected string + }{ + {server: Server{ + CustomListeners: true, + DisableIPV6: true, + ProxyProtocol: false, + HTTPPort: 80, + IP: "192.168.0.2", + }, expected: "listen 192.168.0.2:80;\n"}, + {server: Server{ + CustomListeners: true, + DisableIPV6: false, + ProxyProtocol: false, + HTTPPort: 80, + IP: "192.168.1.2", + }, expected: "listen 192.168.1.2:80;\n listen [::]:80;\n"}, + {server: Server{ + CustomListeners: true, + HTTPPort: 81, + IP: "192.168.0.5", + DisableIPV6: true, + ProxyProtocol: false, + }, expected: "listen 192.168.0.5:81;\n"}, + {server: Server{ + CustomListeners: true, + HTTPPort: 81, + DisableIPV6: false, + ProxyProtocol: false, + IP: "192.168.1.5", + }, expected: "listen 192.168.1.5:81;\n listen [::]:81;\n"}, + } + + for _, tc := range testCases { + got := makeHTTPListener(tc.server) + if got != tc.expected { + t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) + } + } +} + func newContainsTemplate(t *testing.T) *template.Template { t.Helper() tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(`{{contains .InputString .Substring}}`) diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index b8f0c65c14..abeaa6956e 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -1141,7 +1141,7 @@ func (c *Configuration) addWarningsForVirtualServersWithMissConfiguredListeners( } if vsc.VirtualServer.Spec.Listener != nil { if c.globalConfiguration == nil { - warningMsg := "Listeners defined, but no GlobalConfiguration is deployed" + warningMsg := "Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration" c.hosts[vsc.VirtualServer.Spec.Host].AddWarning(warningMsg) continue } diff --git a/internal/k8s/configuration_test.go b/internal/k8s/configuration_test.go index e7674a2ccc..080f696dd1 100644 --- a/internal/k8s/configuration_test.go +++ b/internal/k8s/configuration_test.go @@ -2625,7 +2625,7 @@ func TestAddVirtualServerWithValidCustomListenersFirstThenAddGlobalConfiguration VirtualServer: virtualServer, HTTPPort: 0, HTTPSPort: 0, - Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed"}, + Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration"}, }, }, } @@ -2663,7 +2663,7 @@ func TestAddVirtualServerWithValidCustomListenersAndNoGlobalConfiguration(t *tes VirtualServer: virtualServer, HTTPPort: 0, HTTPSPort: 0, - Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed"}, + Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration"}, }, }, } @@ -2834,7 +2834,7 @@ func TestDeleteGlobalConfigurationWithVirtualServerDeployedWithValidCustomListen VirtualServer: virtualServer, HTTPPort: 0, HTTPSPort: 0, - Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed"}, + Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration"}, }, }, } diff --git a/pkg/apis/configuration/validation/globalconfiguration_test.go b/pkg/apis/configuration/validation/globalconfiguration_test.go index 3cfb51940b..d9df130976 100644 --- a/pkg/apis/configuration/validation/globalconfiguration_test.go +++ b/pkg/apis/configuration/validation/globalconfiguration_test.go @@ -157,18 +157,11 @@ func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { name string listeners []conf_v1.Listener }{ - { - name: "Same port used with UDP and HTTP protocols", - listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "UDP"}, - {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - }, - }, { name: "Same port used with the same protocol", listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, }, }, } @@ -206,20 +199,6 @@ func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { {Name: "listener-2", IP: "192.168.1.1", Port: 9090, Protocol: "HTTP"}, }, }, - { - name: "Same IP, Different Protocol and Different Port", - listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - {Name: "listener-2", IP: "192.168.1.1", Port: 9090, Protocol: "UDP"}, - }, - }, - { - name: "Same IP, Different Protocol and Same Port", - listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "UDP"}, - {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - }, - }, } gcv := createGlobalConfigurationValidator() From a96025bcde3b82a319efec4da784f70113049077 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Fri, 2 Aug 2024 09:11:52 +0100 Subject: [PATCH 11/43] Change ip to ipv4 and ipv6 -- fully working --- .../k8s.nginx.org_globalconfigurations.yaml | 4 +- deploy/crds.yaml | 4 +- .../custom-ip-listeners/README.md | 177 ++++++++++++++++++ .../custom-ip-listeners/cafe-secret.yaml | 8 + .../cafe-virtual-server.yaml | 25 +++ .../custom-ip-listeners/cafe.yaml | 65 +++++++ .../transportserver/global-configuration.yaml | 13 ++ internal/configs/version2/http.go | 5 +- internal/configs/version2/template_helper.go | 44 +++-- .../configs/version2/template_helper_test.go | 8 +- internal/configs/virtualserver.go | 10 +- internal/k8s/configuration.go | 29 ++- internal/k8s/controller.go | 5 +- pkg/apis/configuration/v1/types.go | 3 +- .../validation/globalconfiguration.go | 84 ++++++--- .../validation/globalconfiguration_test.go | 26 +-- 16 files changed, 442 insertions(+), 68 deletions(-) create mode 100644 examples/custom-resources/custom-ip-listeners/README.md create mode 100644 examples/custom-resources/custom-ip-listeners/cafe-secret.yaml create mode 100644 examples/custom-resources/custom-ip-listeners/cafe-virtual-server.yaml create mode 100644 examples/custom-resources/custom-ip-listeners/cafe.yaml create mode 100644 examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index da2de980c7..0aac371799 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -46,7 +46,9 @@ spec: items: description: Listener defines a listener. properties: - ip: + ipv4ip: + type: string + ipv6ip: type: string name: type: string diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 637672852c..6c66591986 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -142,7 +142,9 @@ spec: items: description: Listener defines a listener. properties: - ip: + ipv4ip: + type: string + ipv6ip: type: string name: type: string diff --git a/examples/custom-resources/custom-ip-listeners/README.md b/examples/custom-resources/custom-ip-listeners/README.md new file mode 100644 index 0000000000..70016b5feb --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/README.md @@ -0,0 +1,177 @@ +# Custom IP HTTP Listeners + +In this example, we will configure a VirtualServer resource with a custom IP using HTTP listeners. +This will allow HTTP and/or HTTPs based requests to be made on non-default ports using separate IPs. + +## Prerequisites + +1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) + instructions to deploy the Ingress Controller with custom resources enabled. +2. Ensure the Ingress Controller is configured with the `-global-configuration` argument: + + ```console + args: + - -global-configuration=$(POD_NAMESPACE)/nginx-configuration + ``` + +3. If you have a NodePort or Loadbalancer service deployed, ensure they are updated to include the custom listener ports. +Example YAML for a LoadBalancer: + + ```yaml + apiVersion: v1 + kind: Service + metadata: + name: nginx-ingress + namespace: nginx-ingress + spec: + type: LoadBalancer + ports: + - port: 8083 + targetPort: 8083 + protocol: TCP + name: ip-listener-1-http + - port: 8443 + targetPort: 8443 + protocol: TCP + name: ip-listener-2-https + selector: + app: nginx-ingress + ``` + +## Step 1 - Deploy the GlobalConfiguration resource + +Similar to how listeners are configured in our [custom-listeners](../../custom-resource/custom-listeners) examples, +here we deploy a GlobalConfiguration resource with the listeners we want to use in our VirtualServer. + + ```yaml + apiVersion: k8s.nginx.org/v1alpha1 + kind: GlobalConfiguration + metadata: + name: nginx-configuration + namespace: nginx-ingress + spec: + listeners: + - name: ip-listener-1-http + port: 8083 + protocol: HTTP + ip: 127.0.0.1 + - name: ip-listener-2-https + port: 8443 + protocol: HTTP + ip: 127.0.0.1 + ssl: true + ``` + + ```console + kubectl create -f global-configuration.yaml + ``` + +## Step 2 - Deploy the Cafe Application + +Create the coffee and the tea deployments and services: + + ```console + kubectl create -f cafe.yaml + ``` + +## Step 3 - Deploy the VirtualServer with custom listeners + +The VirtualServer in this example is set to use the listeners defined in the GlobalConfiguration resource +that was deployed in Step 1. Below is the yaml of this example VirtualServer: + + ```yaml + apiVersion: k8s.nginx.org/v1 + kind: VirtualServer + metadata: + name: cafe + spec: + listener: + http: ip-listener-1-http + https: ip-listener-2-https + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee + ``` + +1. Create the secret with the TLS certificate and key: + + ```console + kubectl create -f cafe-secret.yaml + ``` + +2. Create the VirtualServer resource: + + ```console + kubectl create -f cafe-virtual-server.yaml + ``` + +## Step 4 - Test the Configuration + +1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServer: + + ```console + kubectl describe virtualserver cafe + ``` + + Below you will see the events as well as the new `Listeners` field + + ```console + . . . + Spec: + Host: cafe.example.com + Listener: + Http: ip-listener-1-http + Https: ip-listener-2-https + . . . + Routes: + . . . + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal AddedOrUpdated 2s nginx-ingress-controller Configuration for default/cafe was added or updated + ``` + +2. Since the deployed VirtualServer is using ports `8083` and `8443` in this example. you must explicitly specify these ports +when sending requests to the endpoints of this VirtualServer: + + For `/coffee` on `8083`: + + ```console + curl -k http://cafe.example.com:8083/coffee + ``` + + ```text + Server address: 10.32.0.40:8080 + Server name: coffee-7dd75bc79b-qmhmv + ... + URI: /coffee + ... + ``` + + For `/coffee` on `8443`: + + ```console + curl -k https://cafe.example.com:8443/coffee + ``` + + ```text + Server address: 10.32.0.40:8080 + Server name: coffee-7dd75bc79b-qmhmv + ... + URI: /coffee + ... + ``` diff --git a/examples/custom-resources/custom-ip-listeners/cafe-secret.yaml b/examples/custom-resources/custom-ip-listeners/cafe-secret.yaml new file mode 100644 index 0000000000..8f9fd84855 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/cafe-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cafe-secret +type: kubernetes.io/tls +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/examples/custom-resources/custom-ip-listeners/cafe-virtual-server.yaml b/examples/custom-resources/custom-ip-listeners/cafe-virtual-server.yaml new file mode 100644 index 0000000000..08f940d406 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/cafe-virtual-server.yaml @@ -0,0 +1,25 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + listener: + http: ip-listener-1-http + https: ip-listener-2-https + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/custom-ip-listeners/cafe.yaml b/examples/custom-resources/custom-ip-listeners/cafe.yaml new file mode 100644 index 0000000000..eebdd58535 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/cafe.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coffee +spec: + replicas: 2 + selector: + matchLabels: + app: coffee + template: + metadata: + labels: + app: coffee + spec: + containers: + - name: coffee + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: coffee-svc +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: ip + selector: + app: coffee +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tea +spec: + replicas: 1 + selector: + matchLabels: + app: tea + template: + metadata: + labels: + app: tea + spec: + containers: + - name: tea + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: tea-svc +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: tea diff --git a/examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml new file mode 100644 index 0000000000..56353d8586 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml @@ -0,0 +1,13 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 25b0ac6266..37251bce42 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -57,7 +57,10 @@ type Server struct { ServerName string StatusZone string CustomListeners bool - IP string + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string HTTPPort int HTTPSPort int ProxyProtocol bool diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 3b849661a2..25b22fc582 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -16,10 +16,11 @@ const ( https ) -type listenerType int +// IPType is the type of IP, either IPv4 or IPv6 used for printing out listener. +type IPType int const ( - ipv4 listenerType = iota + ipv4 IPType = iota ipv6 ) @@ -55,12 +56,18 @@ func makeListener(listenerType protocol, s Server) string { func buildDefaultListenerDirectives(listenerType protocol, s Server) string { var directives string port := getDefaultPort(listenerType) - - directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv4) - - if !s.DisableIPV6 { - directives += spacing - directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv6) + if listenerType == http { + directives += buildListenDirective(s.HTTPIPv4, port, s.ProxyProtocol, ipv4) + if !s.DisableIPV6 { + directives += spacing + directives += buildListenDirective(s.HTTPIPv6, port, s.ProxyProtocol, ipv6) + } + } else { + directives += buildListenDirective(s.HTTPSIPv4, port, s.ProxyProtocol, ipv4) + if !s.DisableIPV6 { + directives += spacing + directives += buildListenDirective(s.HTTPSIPv6, port, s.ProxyProtocol, ipv6) + } } return directives @@ -71,11 +78,18 @@ func buildCustomListenerDirectives(listenerType protocol, s Server) string { if (listenerType == http && s.HTTPPort > 0) || (listenerType == https && s.HTTPSPort > 0) { port := getCustomPort(listenerType, s) - directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv4) - - if !s.DisableIPV6 { - directives += spacing - directives += buildListenDirective(s.IP, port, s.ProxyProtocol, ipv6) + if listenerType == http { + directives += buildListenDirective(s.HTTPIPv4, port, s.ProxyProtocol, ipv4) + if !s.DisableIPV6 { + directives += spacing + directives += buildListenDirective(s.HTTPIPv6, port, s.ProxyProtocol, ipv6) + } + } else { + directives += buildListenDirective(s.HTTPSIPv4, port, s.ProxyProtocol, ipv4) + if !s.DisableIPV6 { + directives += spacing + directives += buildListenDirective(s.HTTPSIPv6, port, s.ProxyProtocol, ipv6) + } } } @@ -96,11 +110,11 @@ func getCustomPort(listenerType protocol, s Server) string { return strconv.Itoa(s.HTTPSPort) + " ssl" } -func buildListenDirective(ip string, port string, proxyProtocol bool, listenType listenerType) string { +func buildListenDirective(ip string, port string, proxyProtocol bool, ipType IPType) string { base := "listen" var directive string - if listenType == ipv6 { + if ipType == ipv6 { if strings.Contains(ip, ":") { if ip != "" { directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index 658d28debf..561a391748 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -280,19 +280,19 @@ func TestMakeHTTPListenerWithCustomIP(t *testing.T) { DisableIPV6: true, ProxyProtocol: false, HTTPPort: 80, - IP: "192.168.0.2", + HTTPIPv4: "192.168.0.2", }, expected: "listen 192.168.0.2:80;\n"}, {server: Server{ CustomListeners: true, DisableIPV6: false, ProxyProtocol: false, HTTPPort: 80, - IP: "192.168.1.2", + HTTPIPv4: "192.168.1.2", }, expected: "listen 192.168.1.2:80;\n listen [::]:80;\n"}, {server: Server{ CustomListeners: true, HTTPPort: 81, - IP: "192.168.0.5", + HTTPIPv4: "192.168.0.5", DisableIPV6: true, ProxyProtocol: false, }, expected: "listen 192.168.0.5:81;\n"}, @@ -301,7 +301,7 @@ func TestMakeHTTPListenerWithCustomIP(t *testing.T) { HTTPPort: 81, DisableIPV6: false, ProxyProtocol: false, - IP: "192.168.1.5", + HTTPIPv4: "192.168.1.5", }, expected: "listen 192.168.1.5:81;\n listen [::]:81;\n"}, } diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index c2fd0b8ac6..3bfbefa507 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -87,7 +87,10 @@ type VirtualServerEx struct { VirtualServer *conf_v1.VirtualServer HTTPPort int HTTPSPort int - IP string + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string Endpoints map[string][]string VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool @@ -836,7 +839,10 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( StatusZone: vsEx.VirtualServer.Spec.Host, HTTPPort: vsEx.HTTPPort, HTTPSPort: vsEx.HTTPSPort, - IP: vsEx.IP, + HTTPIPv4: vsEx.HTTPIPv4, + HTTPIPv6: vsEx.HTTPIPv6, + HTTPSIPv4: vsEx.HTTPSIPv4, + HTTPSIPv6: vsEx.HTTPSIPv6, CustomListeners: useCustomListeners, ProxyProtocol: vsc.cfgParams.ProxyProtocol, SSL: sslConfig, diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index abeaa6956e..0da0031992 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -200,7 +200,10 @@ type VirtualServerConfiguration struct { Warnings []string HTTPPort int HTTPSPort int - IP string + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string } // NewVirtualServerConfiguration creates a VirtualServerConfiguration. @@ -818,8 +821,11 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTP]; ok { if gcListener.Protocol == conf_v1.HTTPProtocol && !gcListener.Ssl { vsc.HTTPPort = gcListener.Port - if gcListener.IP != "" { - vsc.IP = gcListener.IP + if gcListener.IPv4IP != "" { + vsc.HTTPIPv4 = gcListener.IPv4IP + } + if gcListener.IPv6IP != "" { + vsc.HTTPIPv6 = gcListener.IPv6IP } } } @@ -827,12 +833,19 @@ func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfi if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTPS]; ok { if gcListener.Protocol == conf_v1.HTTPProtocol && gcListener.Ssl { vsc.HTTPSPort = gcListener.Port - if gcListener.IP != "" { - vsc.IP = gcListener.IP + if gcListener.IPv4IP != "" { + vsc.HTTPSIPv4 = gcListener.IPv4IP + } + if gcListener.IPv6IP != "" { + vsc.HTTPSIPv6 = gcListener.IPv6IP } } } } + // vsc.HTTPIPv4 = "0.0.0.1" + // vsc.HTTPSIPv4 = "0.0.0.2" + // vsc.HTTPIPv6 = "f7bd:fda1:366e:7043:e97d:2dba:47ec:1e41" + // vsc.HTTPSIPv6 = "cf8c:563f:c03f:c3bd:935a:fcd2:cfbd:4197" } // GetResources returns all configuration resources. @@ -1791,7 +1804,11 @@ func detectChangesInHosts(oldHosts map[string]Resource, newHosts map[string]Reso updatedHosts = append(updatedHosts, h) } - if newVsc.IP != oldVsc.IP { + if newVsc.HTTPIPv4 != oldVsc.HTTPIPv4 { + updatedHosts = append(updatedHosts, h) + } + + if newVsc.HTTPIPv6 != oldVsc.HTTPIPv6 { updatedHosts = append(updatedHosts, h) } diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 46d355bc6c..9958cfb983 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -3213,7 +3213,10 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. if vsc, ok := resource.(*VirtualServerConfiguration); ok { virtualServerEx.HTTPPort = vsc.HTTPPort virtualServerEx.HTTPSPort = vsc.HTTPSPort - virtualServerEx.IP = vsc.IP + virtualServerEx.HTTPIPv4 = vsc.HTTPIPv4 + virtualServerEx.HTTPIPv6 = vsc.HTTPIPv6 + virtualServerEx.HTTPSIPv4 = vsc.HTTPSIPv4 + virtualServerEx.HTTPSIPv6 = vsc.HTTPSIPv6 } if virtualServer.Spec.TLS != nil && virtualServer.Spec.TLS.Secret != "" { diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 17b08b8410..f3f60834ef 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -411,7 +411,8 @@ type GlobalConfigurationSpec struct { type Listener struct { Name string `json:"name"` Port int `json:"port"` - IP string `json:"ip"` + IPv4IP string `json:"ipv4ip"` + IPv6IP string `json:"ipv6ip"` Protocol string `json:"protocol"` Ssl bool `json:"ssl"` } diff --git a/pkg/apis/configuration/validation/globalconfiguration.go b/pkg/apis/configuration/validation/globalconfiguration.go index 2d32764b3d..af982d2aa1 100644 --- a/pkg/apis/configuration/validation/globalconfiguration.go +++ b/pkg/apis/configuration/validation/globalconfiguration.go @@ -11,6 +11,15 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) +// IPType is the type of IP, either IPv4 or IPv6. +type IPType int + +// IPv4 and IPv6 are used to determine the IPType of the the listener. +const ( + IPv4 IPType = iota + IPv6 +) + var allowedProtocols = map[string]bool{ "TCP": true, "UDP": true, @@ -45,7 +54,8 @@ func (gcv *GlobalConfigurationValidator) getValidListeners(listeners []conf_v1.L allErrs := field.ErrorList{} listenerNames := sets.Set[string]{} - ipPortProtocolCombinations := make(map[string]map[int]string) // map[IP]map[Port]Protocol + ipv4PortProtocolCombinations := make(map[string]map[int]string) // map[IP]map[Port]Protocol + ipv6PortProtocolCombinations := make(map[string]map[int]string) // map[IP]map[Port]Protocol var validListeners []conf_v1.Listener for i, l := range listeners { @@ -56,38 +66,29 @@ func (gcv *GlobalConfigurationValidator) getValidListeners(listeners []conf_v1.L continue } - if err := gcv.validateIP(l, idxPath); err != nil { + if err := gcv.checkForDuplicateName(listenerNames, l, idxPath); err != nil { allErrs = append(allErrs, err) continue } - if err := gcv.checkForDuplicateName(listenerNames, l, idxPath); err != nil { + if err := gcv.checkIPPortProtocolConflicts(ipv4PortProtocolCombinations, IPv4, l, fieldPath); err != nil { allErrs = append(allErrs, err) continue } - if err := gcv.checkPortProtocolConflicts(ipPortProtocolCombinations, l, fieldPath); err != nil { + if err := gcv.checkIPPortProtocolConflicts(ipv6PortProtocolCombinations, IPv6, l, fieldPath); err != nil { allErrs = append(allErrs, err) continue } - gcv.updatePortProtocolCombinations(ipPortProtocolCombinations, l) + gcv.updatePortProtocolCombinations(ipv4PortProtocolCombinations, IPv4, l) + gcv.updatePortProtocolCombinations(ipv6PortProtocolCombinations, IPv6, l) + validListeners = append(validListeners, l) } return validListeners, allErrs } -// validateIP checks if the provided IP address of the listener is valid. -func (gcv *GlobalConfigurationValidator) validateIP(listener conf_v1.Listener, idxPath *field.Path) *field.Error { - if listener.IP != "" { - ipPath := idxPath.Child("IP") - if len(validation.IsValidIP(ipPath, listener.IP)) > 0 { - return field.Invalid(ipPath, listener.IP, "invalid IP address") - } - } - return nil -} - // checkForDuplicateName checks if the listener name is unique. func (gcv *GlobalConfigurationValidator) checkForDuplicateName(listenerNames sets.Set[string], listener conf_v1.Listener, idxPath *field.Path) *field.Error { if listenerNames.Has(listener.Name) { @@ -98,11 +99,22 @@ func (gcv *GlobalConfigurationValidator) checkForDuplicateName(listenerNames set } // checkPortProtocolConflicts ensures no duplicate or conflicting port/protocol combinations exist. -func (gcv *GlobalConfigurationValidator) checkPortProtocolConflicts(combinations map[string]map[int]string, listener conf_v1.Listener, fieldPath *field.Path) *field.Error { - ip := listener.IP +func (gcv *GlobalConfigurationValidator) checkIPPortProtocolConflicts(combinations map[string]map[int]string, ipType IPType, listener conf_v1.Listener, fieldPath *field.Path) *field.Error { + var ip string + if ipType == IPv4 { + ip = listener.IPv4IP + if ip == "" { + ip = "0.0.0.0" + } + } else { + ip = listener.IPv6IP + if ip == "" { + ip = "::" + } + } if combinations[ip] == nil { - combinations[ip] = make(map[int]string) + combinations[ip] = make(map[int]string) // map[ip]map[port]protocol } existingProtocol, exists := combinations[ip][listener.Port] @@ -117,14 +129,24 @@ func (gcv *GlobalConfigurationValidator) checkPortProtocolConflicts(combinations return nil } -// updatePortProtocolCombinations updates the port/protocol combinations map with the given listener's details. -func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinations map[string]map[int]string, listener conf_v1.Listener) { - ip := listener.IP +// updatePortProtocolCombinations updates the port/protocol combinations map with the given listener's details for both IPv4 and IPv6. +func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinations map[string]map[int]string, ipType IPType, listener conf_v1.Listener) { + var ip string + if ipType == IPv4 { + ip = listener.IPv4IP + if ip == "" { + ip = "0.0.0.0" + } + } else { + ip = listener.IPv6IP + if ip == "" { + ip = "::" + } + } if combinations[ip] == nil { combinations[ip] = make(map[int]string) } - combinations[ip][listener.Port] = listener.Protocol } @@ -136,6 +158,8 @@ func (gcv *GlobalConfigurationValidator) validateListener(listener conf_v1.Liste allErrs := validateGlobalConfigurationListenerName(listener.Name, fieldPath.Child("name")) allErrs = append(allErrs, gcv.validateListenerPort(listener.Name, listener.Port, fieldPath.Child("port"))...) allErrs = append(allErrs, validateListenerProtocol(listener.Protocol, fieldPath.Child("protocol"))...) + allErrs = append(allErrs, validateListenerIPv4IP(listener.IPv4IP, fieldPath.Child("ipv4ip"))...) + allErrs = append(allErrs, validateListenerIPv6IP(listener.IPv6IP, fieldPath.Child("ipv6ip"))...) return allErrs } @@ -171,6 +195,20 @@ func validateListenerProtocol(protocol string, fieldPath *field.Path) field.Erro } } +func validateListenerIPv4IP(ipv4ip string, fieldPath *field.Path) field.ErrorList { + if ipv4ip != "" { + return validation.IsValidIPv4Address(fieldPath, ipv4ip) + } + return field.ErrorList{} +} + +func validateListenerIPv6IP(ipv6ip string, fieldPath *field.Path) field.ErrorList { + if ipv6ip != "" { + return validation.IsValidIPv6Address(fieldPath, ipv6ip) + } + return field.ErrorList{} +} + func getProtocolsFromMap(p map[string]bool) []string { var keys []string diff --git a/pkg/apis/configuration/validation/globalconfiguration_test.go b/pkg/apis/configuration/validation/globalconfiguration_test.go index d9df130976..c1df1ef089 100644 --- a/pkg/apis/configuration/validation/globalconfiguration_test.go +++ b/pkg/apis/configuration/validation/globalconfiguration_test.go @@ -76,7 +76,7 @@ func TestValidateListeners(t *testing.T) { }, { Name: "test-listener-ip", - IP: "127.0.0.1", + IPv4IP: "127.0.0.1", Port: 8080, Protocol: "HTTP", }, @@ -100,20 +100,20 @@ func TestValidateListeners_FailsOnInvalidIP(t *testing.T) { { name: "Invalid IP", listeners: []conf_v1.Listener{ - {Name: "test-listener-1", IP: "267.0.0.1", Port: 8082, Protocol: "UDP"}, + {Name: "test-listener-1", IPv4IP: "267.0.0.1", Port: 8082, Protocol: "UDP"}, }, }, { name: "Invalid IP with missing octet", listeners: []conf_v1.Listener{ - {Name: "test-listener-2", IP: "127.0.0", Port: 8080, Protocol: "HTTP"}, + {Name: "test-listener-2", IPv4IP: "127.0.0", Port: 8080, Protocol: "HTTP"}, }, }, { name: "Valid and invalid IPs", listeners: []conf_v1.Listener{ - {Name: "test-listener-3", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - {Name: "test-listener-4", IP: "256.256.256.256", Port: 8081, Protocol: "HTTP"}, + {Name: "test-listener-3", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "test-listener-4", IPv4IP: "256.256.256.256", Port: 8081, Protocol: "HTTP"}, }, }, } @@ -138,8 +138,8 @@ func TestValidateListeners_FailsOnDuplicateNamesDifferentIP(t *testing.T) { t.Parallel() listeners := []conf_v1.Listener{ - {Name: "test-listener", IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - {Name: "test-listener", IP: "192.168.1.2", Port: 8081, Protocol: "HTTP"}, + {Name: "test-listener", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + {Name: "test-listener", IPv4IP: "192.168.1.2", Port: 8081, Protocol: "HTTP"}, } gcv := createGlobalConfigurationValidator() @@ -160,8 +160,8 @@ func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { { name: "Same port used with the same protocol", listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - {Name: "listener-2", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-1", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, }, }, } @@ -188,15 +188,15 @@ func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { { name: "Different Ports and IPs", listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - {Name: "listener-2", IP: "192.168.1.2", Port: 9090, Protocol: "HTTP"}, + {Name: "listener-1", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.2", Port: 9090, Protocol: "HTTP"}, }, }, { name: "Same IP, Same Protocol and Different Port", listeners: []conf_v1.Listener{ - {Name: "listener-1", IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - {Name: "listener-2", IP: "192.168.1.1", Port: 9090, Protocol: "HTTP"}, + {Name: "listener-1", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.1", Port: 9090, Protocol: "HTTP"}, }, }, } From 369ad284e2c4efb084b0092e1a6bd65514ef7e4f Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Fri, 2 Aug 2024 15:32:58 +0100 Subject: [PATCH 12/43] Refactor --- internal/configs/version2/template_helper.go | 51 ++++++--------- internal/k8s/configuration.go | 37 ++++------- .../validation/globalconfiguration.go | 62 ++++++++----------- 3 files changed, 56 insertions(+), 94 deletions(-) diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 25b22fc582..546e41c97d 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -16,11 +16,10 @@ const ( https ) -// IPType is the type of IP, either IPv4 or IPv6 used for printing out listener. -type IPType int +type ipType int const ( - ipv4 IPType = iota + ipv4 ipType = iota ipv6 ) @@ -54,8 +53,21 @@ func makeListener(listenerType protocol, s Server) string { } func buildDefaultListenerDirectives(listenerType protocol, s Server) string { - var directives string port := getDefaultPort(listenerType) + return buildListenerDirectives(listenerType, s, port) +} + +func buildCustomListenerDirectives(listenerType protocol, s Server) string { + if (listenerType == http && s.HTTPPort > 0) || (listenerType == https && s.HTTPSPort > 0) { + port := getCustomPort(listenerType, s) + return buildListenerDirectives(listenerType, s, port) + } + return "" +} + +func buildListenerDirectives(listenerType protocol, s Server, port string) string { + var directives string + if listenerType == http { directives += buildListenDirective(s.HTTPIPv4, port, s.ProxyProtocol, ipv4) if !s.DisableIPV6 { @@ -73,29 +85,6 @@ func buildDefaultListenerDirectives(listenerType protocol, s Server) string { return directives } -func buildCustomListenerDirectives(listenerType protocol, s Server) string { - var directives string - - if (listenerType == http && s.HTTPPort > 0) || (listenerType == https && s.HTTPSPort > 0) { - port := getCustomPort(listenerType, s) - if listenerType == http { - directives += buildListenDirective(s.HTTPIPv4, port, s.ProxyProtocol, ipv4) - if !s.DisableIPV6 { - directives += spacing - directives += buildListenDirective(s.HTTPIPv6, port, s.ProxyProtocol, ipv6) - } - } else { - directives += buildListenDirective(s.HTTPSIPv4, port, s.ProxyProtocol, ipv4) - if !s.DisableIPV6 { - directives += spacing - directives += buildListenDirective(s.HTTPSIPv6, port, s.ProxyProtocol, ipv6) - } - } - } - - return directives -} - func getDefaultPort(listenerType protocol) string { if listenerType == http { return "80" @@ -110,15 +99,13 @@ func getCustomPort(listenerType protocol, s Server) string { return strconv.Itoa(s.HTTPSPort) + " ssl" } -func buildListenDirective(ip string, port string, proxyProtocol bool, ipType IPType) string { +func buildListenDirective(ip string, port string, proxyProtocol bool, ipType ipType) string { base := "listen" var directive string if ipType == ipv6 { - if strings.Contains(ip, ":") { - if ip != "" { - directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) - } + if ip != "" { + directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) } else { directive = fmt.Sprintf("%s [::]:%s", base, port) } diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 0da0031992..34201615c7 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -817,35 +817,20 @@ func (c *Configuration) buildListenersAndTSConfigurations() (newListeners map[st func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfiguration) { vs := vsc.VirtualServer - if vs.Spec.Listener != nil && c.globalConfiguration != nil { - if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTP]; ok { - if gcListener.Protocol == conf_v1.HTTPProtocol && !gcListener.Ssl { - vsc.HTTPPort = gcListener.Port - if gcListener.IPv4IP != "" { - vsc.HTTPIPv4 = gcListener.IPv4IP - } - if gcListener.IPv6IP != "" { - vsc.HTTPIPv6 = gcListener.IPv6IP - } - } - } + if vs.Spec.Listener == nil || c.globalConfiguration == nil { + return + } - if gcListener, ok := c.listenerMap[vs.Spec.Listener.HTTPS]; ok { - if gcListener.Protocol == conf_v1.HTTPProtocol && gcListener.Ssl { - vsc.HTTPSPort = gcListener.Port - if gcListener.IPv4IP != "" { - vsc.HTTPSIPv4 = gcListener.IPv4IP - } - if gcListener.IPv6IP != "" { - vsc.HTTPSIPv6 = gcListener.IPv6IP - } - } + assignListener := func(listenerName string, isSSL bool, port *int, ipv4 *string, ipv6 *string) { + if gcListener, ok := c.listenerMap[listenerName]; ok && gcListener.Protocol == conf_v1.HTTPProtocol && gcListener.Ssl == isSSL { + *port = gcListener.Port + *ipv4 = gcListener.IPv4IP + *ipv6 = gcListener.IPv6IP } } - // vsc.HTTPIPv4 = "0.0.0.1" - // vsc.HTTPSIPv4 = "0.0.0.2" - // vsc.HTTPIPv6 = "f7bd:fda1:366e:7043:e97d:2dba:47ec:1e41" - // vsc.HTTPSIPv6 = "cf8c:563f:c03f:c3bd:935a:fcd2:cfbd:4197" + + assignListener(vs.Spec.Listener.HTTP, false, &vsc.HTTPPort, &vsc.HTTPIPv4, &vsc.HTTPIPv6) + assignListener(vs.Spec.Listener.HTTPS, true, &vsc.HTTPSPort, &vsc.HTTPSIPv4, &vsc.HTTPSIPv6) } // GetResources returns all configuration resources. diff --git a/pkg/apis/configuration/validation/globalconfiguration.go b/pkg/apis/configuration/validation/globalconfiguration.go index af982d2aa1..d5da1329e2 100644 --- a/pkg/apis/configuration/validation/globalconfiguration.go +++ b/pkg/apis/configuration/validation/globalconfiguration.go @@ -11,13 +11,11 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) -// IPType is the type of IP, either IPv4 or IPv6. -type IPType int +type ipType int -// IPv4 and IPv6 are used to determine the IPType of the the listener. const ( - IPv4 IPType = iota - IPv6 + ipv4 ipType = iota + ipv6 ) var allowedProtocols = map[string]bool{ @@ -71,18 +69,18 @@ func (gcv *GlobalConfigurationValidator) getValidListeners(listeners []conf_v1.L continue } - if err := gcv.checkIPPortProtocolConflicts(ipv4PortProtocolCombinations, IPv4, l, fieldPath); err != nil { + if err := gcv.checkIPPortProtocolConflicts(ipv4PortProtocolCombinations, ipv4, l, fieldPath); err != nil { allErrs = append(allErrs, err) continue } - if err := gcv.checkIPPortProtocolConflicts(ipv6PortProtocolCombinations, IPv6, l, fieldPath); err != nil { + if err := gcv.checkIPPortProtocolConflicts(ipv6PortProtocolCombinations, ipv6, l, fieldPath); err != nil { allErrs = append(allErrs, err) continue } - gcv.updatePortProtocolCombinations(ipv4PortProtocolCombinations, IPv4, l) - gcv.updatePortProtocolCombinations(ipv6PortProtocolCombinations, IPv6, l) + gcv.updatePortProtocolCombinations(ipv4PortProtocolCombinations, ipv4, l) + gcv.updatePortProtocolCombinations(ipv6PortProtocolCombinations, ipv6, l) validListeners = append(validListeners, l) } @@ -98,20 +96,9 @@ func (gcv *GlobalConfigurationValidator) checkForDuplicateName(listenerNames set return nil } -// checkPortProtocolConflicts ensures no duplicate or conflicting port/protocol combinations exist. -func (gcv *GlobalConfigurationValidator) checkIPPortProtocolConflicts(combinations map[string]map[int]string, ipType IPType, listener conf_v1.Listener, fieldPath *field.Path) *field.Error { - var ip string - if ipType == IPv4 { - ip = listener.IPv4IP - if ip == "" { - ip = "0.0.0.0" - } - } else { - ip = listener.IPv6IP - if ip == "" { - ip = "::" - } - } +// checkIPPortProtocolConflicts ensures no duplicate or conflicting port/protocol combinations exist. +func (gcv *GlobalConfigurationValidator) checkIPPortProtocolConflicts(combinations map[string]map[int]string, ipType ipType, listener conf_v1.Listener, fieldPath *field.Path) *field.Error { + ip := getIP(ipType, listener) if combinations[ip] == nil { combinations[ip] = make(map[int]string) // map[ip]map[port]protocol @@ -130,19 +117,8 @@ func (gcv *GlobalConfigurationValidator) checkIPPortProtocolConflicts(combinatio } // updatePortProtocolCombinations updates the port/protocol combinations map with the given listener's details for both IPv4 and IPv6. -func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinations map[string]map[int]string, ipType IPType, listener conf_v1.Listener) { - var ip string - if ipType == IPv4 { - ip = listener.IPv4IP - if ip == "" { - ip = "0.0.0.0" - } - } else { - ip = listener.IPv6IP - if ip == "" { - ip = "::" - } - } +func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinations map[string]map[int]string, ipType ipType, listener conf_v1.Listener) { + ip := getIP(ipType, listener) if combinations[ip] == nil { combinations[ip] = make(map[int]string) @@ -150,6 +126,20 @@ func (gcv *GlobalConfigurationValidator) updatePortProtocolCombinations(combinat combinations[ip][listener.Port] = listener.Protocol } +// getIP returns the appropriate IP address for the given ipType and listener. +func getIP(ipType ipType, listener conf_v1.Listener) string { + if ipType == ipv4 { + if listener.IPv4IP == "" { + return "0.0.0.0" + } + return listener.IPv4IP + } + if listener.IPv6IP == "" { + return "::" + } + return listener.IPv6IP +} + func generatePortProtocolKey(port int, protocol string) string { return fmt.Sprintf("%d/%s", port, protocol) } From 159612ba0b37f6dbb506b7ba8a82b995469cedda Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 7 Aug 2024 10:56:16 +0100 Subject: [PATCH 13/43] Update and Add new go and snap tests --- .../__snapshots__/templates_test.snap | 2124 ++++++++++++++++- .../configs/version2/template_helper_test.go | 162 +- internal/configs/version2/templates_test.go | 486 ++++ internal/configs/virtualserver_test.go | 219 ++ .../validation/globalconfiguration_test.go | 124 +- 5 files changed, 3083 insertions(+), 32 deletions(-) diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index 5a57399f9f..0922977120 100644 --- a/internal/configs/version2/__snapshots__/templates_test.snap +++ b/internal/configs/version2/__snapshots__/templates_test.snap @@ -3119,7 +3119,7 @@ server { --- -[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPOnly - 1] +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPIPV4Only - 1] upstream test-upstream { zone test-upstream 256k; @@ -3162,7 +3162,7 @@ map $http_x_version $match_0_0 { limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; server { - listen 8082 proxy_protocol; + listen 127.0.0.1:8082 proxy_protocol; listen [::]:8082 proxy_protocol; @@ -3541,7 +3541,7 @@ server { --- -[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPSOnly - 1] +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPIPV6Only - 1] upstream test-upstream { zone test-upstream 256k; @@ -3584,15 +3584,2127 @@ map $http_x_version $match_0_0 { limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; server { - + listen 8082 proxy_protocol; + listen [::1]:8082 proxy_protocol; + server_name example.com; status_zone example.com; set $resource_type "virtualserver"; set $resource_name ""; set $resource_namespace ""; - listen 8443 ssl proxy_protocol; - listen [::]:8443 ssl proxy_protocol; + + http2 on; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + ssl_client_certificate ingress-mtls-secret; + ssl_verify_client on; + ssl_verify_depth 2; + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + server_tokens "off"; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Real-IP; + real_ip_recursive on; + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req_log_level error; + limit_req_status 503; + limit_req zone=pol_rl_test_test_test burst=5 + delay=10; + auth_jwt "My Api"; + auth_jwt_key_file jwk-secret; + app_protect_enable on; + + app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; + + + + + + app_protect_security_log_enable on; + + app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; + + + + # server snippet + location /split { + rewrite ^ @split_0 last; + } + location /coffee { + rewrite ^ @match last; + } + location @hc-coffee { + + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + proxy_pass http://coffee-v2; + health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; + + } + location @hc-tea { + + grpc_connect_timeout ; + grpc_read_timeout ; + grpc_send_timeout ; + grpc_pass grpc://tea-v3; + health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; + + } + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { + + default_type "application/json"; + + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { + + + add_header Set-Cookie "cookie1=test" always; + + add_header Set-Cookie "cookie2=test; Secure" always; + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + + + location @return_0 { + default_type "text/html"; + + # status code is ignored here, using 0 + return 0 "Hello!"; + } + + + + location / { + set $service ""; + status_zone ""; + internal; + # location snippet + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req zone=loc_pol_rl_test_test_test + ; + + + proxy_ssl_certificate egress-mtls-secret.pem; + proxy_ssl_certificate_key egress-mtls-secret.pem; + + proxy_ssl_trusted_certificate trusted-cert.pem; + proxy_ssl_verify on; + proxy_ssl_verify_depth 1; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_ciphers DEFAULT; + proxy_ssl_session_reuse on; + proxy_ssl_server_name on; + proxy_ssl_name ; + set $default_connection_header close; + rewrite $request_uri $request_uri; + rewrite $request_uri $request_uri; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + proxy_max_temp_file_size 1024m; + + proxy_buffering on; + proxy_buffers 8 4k; + proxy_buffer_size 4k; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Header; + proxy_pass_header Host; + proxy_ignore_headers Cache; + add_header Header-Name "Header Value" always; + proxy_pass http://test-upstream$request_uri; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc0 { + set $service ""; + status_zone ""; + + + error_page 400 500 =200 "@error_page_1"; + error_page 500 "@error_page_2"; + proxy_intercept_errors on; + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc1 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc2 { + set $service ""; + status_zone ""; + + + error_page 400 = @grpc_internal; + error_page 401 = @grpc_unauthenticated; + error_page 403 = @grpc_permission_denied; + error_page 404 = @grpc_unimplemented; + error_page 429 = @grpc_unavailable; + error_page 502 = @grpc_unavailable; + error_page 503 = @grpc_unavailable; + error_page 504 = @grpc_unavailable; + error_page 405 = @grpc_internal; + error_page 408 = @grpc_deadline_exceeded; + error_page 413 = @grpc_resource_exhausted; + error_page 414 = @grpc_resource_exhausted; + error_page 415 = @grpc_internal; + error_page 426 = @grpc_internal; + error_page 495 = @grpc_unauthenticated; + error_page 496 = @grpc_unauthenticated; + error_page 497 = @grpc_internal; + error_page 500 = @grpc_internal; + error_page 501 = @grpc_internal; + set $default_connection_header close; + grpc_connect_timeout 30s; + grpc_read_timeout 31s; + grpc_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + grpc_set_header X-Real-IP $remote_addr; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + grpc_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-Port $server_port; + grpc_set_header X-Forwarded-Proto $scheme; + grpc_pass grpc://coffee-v3; + grpc_next_upstream ; + grpc_next_upstream_timeout ; + grpc_next_upstream_tries 0; + } + location @match_loc_0 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @match_loc_default { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location /return { + set $service ""; + status_zone ""; + + + error_page 418 =200 "@return_0"; + proxy_intercept_errors on; + proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; + set $default_connection_header close; + } + + location @grpc_deadline_exceeded { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 4; + add_header grpc-message 'deadline exceeded'; + return 204; + } + + location @grpc_permission_denied { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 7; + add_header grpc-message 'permission denied'; + return 204; + } + + location @grpc_resource_exhausted { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 8; + add_header grpc-message 'resource exhausted'; + return 204; + } + + location @grpc_unimplemented { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 12; + add_header grpc-message unimplemented; + return 204; + } + + location @grpc_internal { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 13; + add_header grpc-message 'internal error'; + return 204; + } + + location @grpc_unavailable { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 14; + add_header grpc-message unavailable; + return 204; + } + + location @grpc_unauthenticated { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 16; + add_header grpc-message unauthenticated; + return 204; + } + + + +} + +--- + +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPOnly - 1] + +upstream test-upstream { + zone test-upstream 256k; + random; + server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; + keepalive 32; + queue 10 timeout=60s; + sticky cookie test expires=25s path=/tea; + + ntlm; +} + +upstream coffee-v1 { + zone coffee-v1 256k; + server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; + + +} + +upstream coffee-v2 { + zone coffee-v2 256k; + server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; + + +} + +split_clients $request_id $split_0 { + 50% @loc0; + 50% @loc1; +} +map $match_0_0 $match { + ~^1 @match_loc_0; + default @match_loc_default; +} +map $http_x_version $match_0_0 { + v2 1; + default 0; +} +# HTTP snippet +limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; + +server { + listen 8082 proxy_protocol; + listen [::]:8082 proxy_protocol; + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + + http2 on; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + ssl_client_certificate ingress-mtls-secret; + ssl_verify_client on; + ssl_verify_depth 2; + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + server_tokens "off"; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Real-IP; + real_ip_recursive on; + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req_log_level error; + limit_req_status 503; + limit_req zone=pol_rl_test_test_test burst=5 + delay=10; + auth_jwt "My Api"; + auth_jwt_key_file jwk-secret; + app_protect_enable on; + + app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; + + + + + + app_protect_security_log_enable on; + + app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; + + + + # server snippet + location /split { + rewrite ^ @split_0 last; + } + location /coffee { + rewrite ^ @match last; + } + location @hc-coffee { + + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + proxy_pass http://coffee-v2; + health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; + + } + location @hc-tea { + + grpc_connect_timeout ; + grpc_read_timeout ; + grpc_send_timeout ; + grpc_pass grpc://tea-v3; + health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; + + } + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { + + default_type "application/json"; + + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { + + + add_header Set-Cookie "cookie1=test" always; + + add_header Set-Cookie "cookie2=test; Secure" always; + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + + + location @return_0 { + default_type "text/html"; + + # status code is ignored here, using 0 + return 0 "Hello!"; + } + + + + location / { + set $service ""; + status_zone ""; + internal; + # location snippet + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req zone=loc_pol_rl_test_test_test + ; + + + proxy_ssl_certificate egress-mtls-secret.pem; + proxy_ssl_certificate_key egress-mtls-secret.pem; + + proxy_ssl_trusted_certificate trusted-cert.pem; + proxy_ssl_verify on; + proxy_ssl_verify_depth 1; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_ciphers DEFAULT; + proxy_ssl_session_reuse on; + proxy_ssl_server_name on; + proxy_ssl_name ; + set $default_connection_header close; + rewrite $request_uri $request_uri; + rewrite $request_uri $request_uri; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + proxy_max_temp_file_size 1024m; + + proxy_buffering on; + proxy_buffers 8 4k; + proxy_buffer_size 4k; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Header; + proxy_pass_header Host; + proxy_ignore_headers Cache; + add_header Header-Name "Header Value" always; + proxy_pass http://test-upstream$request_uri; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc0 { + set $service ""; + status_zone ""; + + + error_page 400 500 =200 "@error_page_1"; + error_page 500 "@error_page_2"; + proxy_intercept_errors on; + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc1 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc2 { + set $service ""; + status_zone ""; + + + error_page 400 = @grpc_internal; + error_page 401 = @grpc_unauthenticated; + error_page 403 = @grpc_permission_denied; + error_page 404 = @grpc_unimplemented; + error_page 429 = @grpc_unavailable; + error_page 502 = @grpc_unavailable; + error_page 503 = @grpc_unavailable; + error_page 504 = @grpc_unavailable; + error_page 405 = @grpc_internal; + error_page 408 = @grpc_deadline_exceeded; + error_page 413 = @grpc_resource_exhausted; + error_page 414 = @grpc_resource_exhausted; + error_page 415 = @grpc_internal; + error_page 426 = @grpc_internal; + error_page 495 = @grpc_unauthenticated; + error_page 496 = @grpc_unauthenticated; + error_page 497 = @grpc_internal; + error_page 500 = @grpc_internal; + error_page 501 = @grpc_internal; + set $default_connection_header close; + grpc_connect_timeout 30s; + grpc_read_timeout 31s; + grpc_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + grpc_set_header X-Real-IP $remote_addr; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + grpc_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-Port $server_port; + grpc_set_header X-Forwarded-Proto $scheme; + grpc_pass grpc://coffee-v3; + grpc_next_upstream ; + grpc_next_upstream_timeout ; + grpc_next_upstream_tries 0; + } + location @match_loc_0 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @match_loc_default { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location /return { + set $service ""; + status_zone ""; + + + error_page 418 =200 "@return_0"; + proxy_intercept_errors on; + proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; + set $default_connection_header close; + } + + location @grpc_deadline_exceeded { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 4; + add_header grpc-message 'deadline exceeded'; + return 204; + } + + location @grpc_permission_denied { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 7; + add_header grpc-message 'permission denied'; + return 204; + } + + location @grpc_resource_exhausted { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 8; + add_header grpc-message 'resource exhausted'; + return 204; + } + + location @grpc_unimplemented { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 12; + add_header grpc-message unimplemented; + return 204; + } + + location @grpc_internal { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 13; + add_header grpc-message 'internal error'; + return 204; + } + + location @grpc_unavailable { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 14; + add_header grpc-message unavailable; + return 204; + } + + location @grpc_unauthenticated { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 16; + add_header grpc-message unauthenticated; + return 204; + } + + + +} + +--- + +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPSIPV4Only - 1] + +upstream test-upstream { + zone test-upstream 256k; + random; + server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; + keepalive 32; + queue 10 timeout=60s; + sticky cookie test expires=25s path=/tea; + + ntlm; +} + +upstream coffee-v1 { + zone coffee-v1 256k; + server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; + + +} + +upstream coffee-v2 { + zone coffee-v2 256k; + server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; + + +} + +split_clients $request_id $split_0 { + 50% @loc0; + 50% @loc1; +} +map $match_0_0 $match { + ~^1 @match_loc_0; + default @match_loc_default; +} +map $http_x_version $match_0_0 { + v2 1; + default 0; +} +# HTTP snippet +limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; + +server { + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + listen 127.0.0.2:8443 ssl proxy_protocol; + listen [::]:8443 ssl proxy_protocol; + + http2 on; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + ssl_client_certificate ingress-mtls-secret; + ssl_verify_client on; + ssl_verify_depth 2; + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + server_tokens "off"; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Real-IP; + real_ip_recursive on; + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req_log_level error; + limit_req_status 503; + limit_req zone=pol_rl_test_test_test burst=5 + delay=10; + auth_jwt "My Api"; + auth_jwt_key_file jwk-secret; + app_protect_enable on; + + app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; + + + + + + app_protect_security_log_enable on; + + app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; + + + + # server snippet + location /split { + rewrite ^ @split_0 last; + } + location /coffee { + rewrite ^ @match last; + } + location @hc-coffee { + + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + proxy_pass http://coffee-v2; + health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; + + } + location @hc-tea { + + grpc_connect_timeout ; + grpc_read_timeout ; + grpc_send_timeout ; + grpc_pass grpc://tea-v3; + health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; + + } + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { + + default_type "application/json"; + + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { + + + add_header Set-Cookie "cookie1=test" always; + + add_header Set-Cookie "cookie2=test; Secure" always; + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + + + location @return_0 { + default_type "text/html"; + + # status code is ignored here, using 0 + return 0 "Hello!"; + } + + + + location / { + set $service ""; + status_zone ""; + internal; + # location snippet + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req zone=loc_pol_rl_test_test_test + ; + + + proxy_ssl_certificate egress-mtls-secret.pem; + proxy_ssl_certificate_key egress-mtls-secret.pem; + + proxy_ssl_trusted_certificate trusted-cert.pem; + proxy_ssl_verify on; + proxy_ssl_verify_depth 1; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_ciphers DEFAULT; + proxy_ssl_session_reuse on; + proxy_ssl_server_name on; + proxy_ssl_name ; + set $default_connection_header close; + rewrite $request_uri $request_uri; + rewrite $request_uri $request_uri; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + proxy_max_temp_file_size 1024m; + + proxy_buffering on; + proxy_buffers 8 4k; + proxy_buffer_size 4k; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Header; + proxy_pass_header Host; + proxy_ignore_headers Cache; + add_header Header-Name "Header Value" always; + proxy_pass http://test-upstream$request_uri; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc0 { + set $service ""; + status_zone ""; + + + error_page 400 500 =200 "@error_page_1"; + error_page 500 "@error_page_2"; + proxy_intercept_errors on; + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc1 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc2 { + set $service ""; + status_zone ""; + + + error_page 400 = @grpc_internal; + error_page 401 = @grpc_unauthenticated; + error_page 403 = @grpc_permission_denied; + error_page 404 = @grpc_unimplemented; + error_page 429 = @grpc_unavailable; + error_page 502 = @grpc_unavailable; + error_page 503 = @grpc_unavailable; + error_page 504 = @grpc_unavailable; + error_page 405 = @grpc_internal; + error_page 408 = @grpc_deadline_exceeded; + error_page 413 = @grpc_resource_exhausted; + error_page 414 = @grpc_resource_exhausted; + error_page 415 = @grpc_internal; + error_page 426 = @grpc_internal; + error_page 495 = @grpc_unauthenticated; + error_page 496 = @grpc_unauthenticated; + error_page 497 = @grpc_internal; + error_page 500 = @grpc_internal; + error_page 501 = @grpc_internal; + set $default_connection_header close; + grpc_connect_timeout 30s; + grpc_read_timeout 31s; + grpc_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + grpc_set_header X-Real-IP $remote_addr; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + grpc_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-Port $server_port; + grpc_set_header X-Forwarded-Proto $scheme; + grpc_pass grpc://coffee-v3; + grpc_next_upstream ; + grpc_next_upstream_timeout ; + grpc_next_upstream_tries 0; + } + location @match_loc_0 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @match_loc_default { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location /return { + set $service ""; + status_zone ""; + + + error_page 418 =200 "@return_0"; + proxy_intercept_errors on; + proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; + set $default_connection_header close; + } + + location @grpc_deadline_exceeded { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 4; + add_header grpc-message 'deadline exceeded'; + return 204; + } + + location @grpc_permission_denied { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 7; + add_header grpc-message 'permission denied'; + return 204; + } + + location @grpc_resource_exhausted { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 8; + add_header grpc-message 'resource exhausted'; + return 204; + } + + location @grpc_unimplemented { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 12; + add_header grpc-message unimplemented; + return 204; + } + + location @grpc_internal { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 13; + add_header grpc-message 'internal error'; + return 204; + } + + location @grpc_unavailable { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 14; + add_header grpc-message unavailable; + return 204; + } + + location @grpc_unauthenticated { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 16; + add_header grpc-message unauthenticated; + return 204; + } + + + +} + +--- + +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPSIPV6Only - 1] + +upstream test-upstream { + zone test-upstream 256k; + random; + server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; + keepalive 32; + queue 10 timeout=60s; + sticky cookie test expires=25s path=/tea; + + ntlm; +} + +upstream coffee-v1 { + zone coffee-v1 256k; + server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; + + +} + +upstream coffee-v2 { + zone coffee-v2 256k; + server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; + + +} + +split_clients $request_id $split_0 { + 50% @loc0; + 50% @loc1; +} +map $match_0_0 $match { + ~^1 @match_loc_0; + default @match_loc_default; +} +map $http_x_version $match_0_0 { + v2 1; + default 0; +} +# HTTP snippet +limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; + +server { + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + listen 8443 ssl proxy_protocol; + listen [::2]:8443 ssl proxy_protocol; + + http2 on; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + ssl_client_certificate ingress-mtls-secret; + ssl_verify_client on; + ssl_verify_depth 2; + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + server_tokens "off"; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Real-IP; + real_ip_recursive on; + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req_log_level error; + limit_req_status 503; + limit_req zone=pol_rl_test_test_test burst=5 + delay=10; + auth_jwt "My Api"; + auth_jwt_key_file jwk-secret; + app_protect_enable on; + + app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; + + + + + + app_protect_security_log_enable on; + + app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; + + + + # server snippet + location /split { + rewrite ^ @split_0 last; + } + location /coffee { + rewrite ^ @match last; + } + location @hc-coffee { + + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + proxy_pass http://coffee-v2; + health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; + + } + location @hc-tea { + + grpc_connect_timeout ; + grpc_read_timeout ; + grpc_send_timeout ; + grpc_pass grpc://tea-v3; + health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; + + } + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { + + default_type "application/json"; + + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { + + + add_header Set-Cookie "cookie1=test" always; + + add_header Set-Cookie "cookie2=test; Secure" always; + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + + + location @return_0 { + default_type "text/html"; + + # status code is ignored here, using 0 + return 0 "Hello!"; + } + + + + location / { + set $service ""; + status_zone ""; + internal; + # location snippet + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req zone=loc_pol_rl_test_test_test + ; + + + proxy_ssl_certificate egress-mtls-secret.pem; + proxy_ssl_certificate_key egress-mtls-secret.pem; + + proxy_ssl_trusted_certificate trusted-cert.pem; + proxy_ssl_verify on; + proxy_ssl_verify_depth 1; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_ciphers DEFAULT; + proxy_ssl_session_reuse on; + proxy_ssl_server_name on; + proxy_ssl_name ; + set $default_connection_header close; + rewrite $request_uri $request_uri; + rewrite $request_uri $request_uri; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + proxy_max_temp_file_size 1024m; + + proxy_buffering on; + proxy_buffers 8 4k; + proxy_buffer_size 4k; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Header; + proxy_pass_header Host; + proxy_ignore_headers Cache; + add_header Header-Name "Header Value" always; + proxy_pass http://test-upstream$request_uri; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc0 { + set $service ""; + status_zone ""; + + + error_page 400 500 =200 "@error_page_1"; + error_page 500 "@error_page_2"; + proxy_intercept_errors on; + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc1 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc2 { + set $service ""; + status_zone ""; + + + error_page 400 = @grpc_internal; + error_page 401 = @grpc_unauthenticated; + error_page 403 = @grpc_permission_denied; + error_page 404 = @grpc_unimplemented; + error_page 429 = @grpc_unavailable; + error_page 502 = @grpc_unavailable; + error_page 503 = @grpc_unavailable; + error_page 504 = @grpc_unavailable; + error_page 405 = @grpc_internal; + error_page 408 = @grpc_deadline_exceeded; + error_page 413 = @grpc_resource_exhausted; + error_page 414 = @grpc_resource_exhausted; + error_page 415 = @grpc_internal; + error_page 426 = @grpc_internal; + error_page 495 = @grpc_unauthenticated; + error_page 496 = @grpc_unauthenticated; + error_page 497 = @grpc_internal; + error_page 500 = @grpc_internal; + error_page 501 = @grpc_internal; + set $default_connection_header close; + grpc_connect_timeout 30s; + grpc_read_timeout 31s; + grpc_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + grpc_set_header X-Real-IP $remote_addr; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + grpc_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-Port $server_port; + grpc_set_header X-Forwarded-Proto $scheme; + grpc_pass grpc://coffee-v3; + grpc_next_upstream ; + grpc_next_upstream_timeout ; + grpc_next_upstream_tries 0; + } + location @match_loc_0 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @match_loc_default { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location /return { + set $service ""; + status_zone ""; + + + error_page 418 =200 "@return_0"; + proxy_intercept_errors on; + proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; + set $default_connection_header close; + } + + location @grpc_deadline_exceeded { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 4; + add_header grpc-message 'deadline exceeded'; + return 204; + } + + location @grpc_permission_denied { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 7; + add_header grpc-message 'permission denied'; + return 204; + } + + location @grpc_resource_exhausted { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 8; + add_header grpc-message 'resource exhausted'; + return 204; + } + + location @grpc_unimplemented { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 12; + add_header grpc-message unimplemented; + return 204; + } + + location @grpc_internal { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 13; + add_header grpc-message 'internal error'; + return 204; + } + + location @grpc_unavailable { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 14; + add_header grpc-message unavailable; + return 204; + } + + location @grpc_unauthenticated { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 16; + add_header grpc-message unauthenticated; + return 204; + } + + + +} + +--- + +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPSOnly - 1] + +upstream test-upstream { + zone test-upstream 256k; + random; + server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; + keepalive 32; + queue 10 timeout=60s; + sticky cookie test expires=25s path=/tea; + + ntlm; +} + +upstream coffee-v1 { + zone coffee-v1 256k; + server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; + + +} + +upstream coffee-v2 { + zone coffee-v2 256k; + server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; + + +} + +split_clients $request_id $split_0 { + 50% @loc0; + 50% @loc1; +} +map $match_0_0 $match { + ~^1 @match_loc_0; + default @match_loc_default; +} +map $http_x_version $match_0_0 { + v2 1; + default 0; +} +# HTTP snippet +limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; + +server { + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + listen 8443 ssl proxy_protocol; + listen [::]:8443 ssl proxy_protocol; + + http2 on; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + ssl_client_certificate ingress-mtls-secret; + ssl_verify_client on; + ssl_verify_depth 2; + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + server_tokens "off"; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Real-IP; + real_ip_recursive on; + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req_log_level error; + limit_req_status 503; + limit_req zone=pol_rl_test_test_test burst=5 + delay=10; + auth_jwt "My Api"; + auth_jwt_key_file jwk-secret; + app_protect_enable on; + + app_protect_policy_file /etc/nginx/waf/nac-policies/default-dataguard-alarm; + + + + + + app_protect_security_log_enable on; + + app_protect_security_log /etc/nginx/waf/nac-logconfs/default-logconf; + + + + # server snippet + location /split { + rewrite ^ @split_0 last; + } + location /coffee { + rewrite ^ @match last; + } + location @hc-coffee { + + proxy_connect_timeout ; + proxy_read_timeout ; + proxy_send_timeout ; + proxy_pass http://coffee-v2; + health_check uri=/ port=50 interval=5s jitter=0s fails=1 passes=1 mandatory persistent keepalive_time=60s; + + } + location @hc-tea { + + grpc_connect_timeout ; + grpc_read_timeout ; + grpc_send_timeout ; + grpc_pass grpc://tea-v3; + health_check port=50 interval=5s jitter=0s fails=1 passes=1 type=grpc grpc_status=12 grpc_service=tea-servicev2; + + } + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0 { + + default_type "application/json"; + + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + location @vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1 { + + + add_header Set-Cookie "cookie1=test" always; + + add_header Set-Cookie "cookie2=test; Secure" always; + + # status code is ignored here, using 0 + return 0 "Hello World"; + } + + + + location @return_0 { + default_type "text/html"; + + # status code is ignored here, using 0 + return 0 "Hello!"; + } + + + + location / { + set $service ""; + status_zone ""; + internal; + # location snippet + allow 127.0.0.1; + deny all; + deny 127.0.0.1; + allow all; + limit_req zone=loc_pol_rl_test_test_test + ; + + + proxy_ssl_certificate egress-mtls-secret.pem; + proxy_ssl_certificate_key egress-mtls-secret.pem; + + proxy_ssl_trusted_certificate trusted-cert.pem; + proxy_ssl_verify on; + proxy_ssl_verify_depth 1; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_ciphers DEFAULT; + proxy_ssl_session_reuse on; + proxy_ssl_server_name on; + proxy_ssl_name ; + set $default_connection_header close; + rewrite $request_uri $request_uri; + rewrite $request_uri $request_uri; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + proxy_max_temp_file_size 1024m; + + proxy_buffering on; + proxy_buffers 8 4k; + proxy_buffer_size 4k; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Header; + proxy_pass_header Host; + proxy_ignore_headers Cache; + add_header Header-Name "Header Value" always; + proxy_pass http://test-upstream$request_uri; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc0 { + set $service ""; + status_zone ""; + + + error_page 400 500 =200 "@error_page_1"; + error_page 500 "@error_page_2"; + proxy_intercept_errors on; + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc1 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @loc2 { + set $service ""; + status_zone ""; + + + error_page 400 = @grpc_internal; + error_page 401 = @grpc_unauthenticated; + error_page 403 = @grpc_permission_denied; + error_page 404 = @grpc_unimplemented; + error_page 429 = @grpc_unavailable; + error_page 502 = @grpc_unavailable; + error_page 503 = @grpc_unavailable; + error_page 504 = @grpc_unavailable; + error_page 405 = @grpc_internal; + error_page 408 = @grpc_deadline_exceeded; + error_page 413 = @grpc_resource_exhausted; + error_page 414 = @grpc_resource_exhausted; + error_page 415 = @grpc_internal; + error_page 426 = @grpc_internal; + error_page 495 = @grpc_unauthenticated; + error_page 496 = @grpc_unauthenticated; + error_page 497 = @grpc_internal; + error_page 500 = @grpc_internal; + error_page 501 = @grpc_internal; + set $default_connection_header close; + grpc_connect_timeout 30s; + grpc_read_timeout 31s; + grpc_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + grpc_set_header X-Real-IP $remote_addr; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + grpc_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-Port $server_port; + grpc_set_header X-Forwarded-Proto $scheme; + grpc_pass grpc://coffee-v3; + grpc_next_upstream ; + grpc_next_upstream_timeout ; + grpc_next_upstream_tries 0; + } + location @match_loc_0 { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v2; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location @match_loc_default { + set $service ""; + status_zone ""; + + + set $default_connection_header close; + proxy_connect_timeout 30s; + proxy_read_timeout 31s; + proxy_send_timeout 32s; + client_max_body_size 1m; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $vs_connection_header; + proxy_pass_request_headers off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://coffee-v1; + proxy_next_upstream error timeout; + proxy_next_upstream_timeout 5s; + proxy_next_upstream_tries 0; + } + location /return { + set $service ""; + status_zone ""; + + + error_page 418 =200 "@return_0"; + proxy_intercept_errors on; + proxy_pass http://unix:/var/lib/nginx/nginx-418-server.sock; + set $default_connection_header close; + } + + location @grpc_deadline_exceeded { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 4; + add_header grpc-message 'deadline exceeded'; + return 204; + } + + location @grpc_permission_denied { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 7; + add_header grpc-message 'permission denied'; + return 204; + } + + location @grpc_resource_exhausted { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 8; + add_header grpc-message 'resource exhausted'; + return 204; + } + + location @grpc_unimplemented { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 12; + add_header grpc-message unimplemented; + return 204; + } + + location @grpc_internal { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 13; + add_header grpc-message 'internal error'; + return 204; + } + + location @grpc_unavailable { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 14; + add_header grpc-message unavailable; + return 204; + } + + location @grpc_unauthenticated { + default_type application/grpc; + add_header content-type application/grpc; + add_header grpc-status 16; + add_header grpc-message unauthenticated; + return 204; + } + + + +} + +--- + +[TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerIP - 1] + +upstream test-upstream { + zone test-upstream 256k; + random; + server 10.0.0.20:8001 max_fails=4 fail_timeout=10s slow_start=10s max_conns=31; + keepalive 32; + queue 10 timeout=60s; + sticky cookie test expires=25s path=/tea; + + ntlm; +} + +upstream coffee-v1 { + zone coffee-v1 256k; + server 10.0.0.31:8001 max_fails=8 fail_timeout=15s max_conns=2; + + +} + +upstream coffee-v2 { + zone coffee-v2 256k; + server 10.0.0.32:8001 max_fails=12 fail_timeout=20s max_conns=4; + + +} + +split_clients $request_id $split_0 { + 50% @loc0; + 50% @loc1; +} +map $match_0_0 $match { + ~^1 @match_loc_0; + default @match_loc_default; +} +map $http_x_version $match_0_0 { + v2 1; + default 0; +} +# HTTP snippet +limit_req_zone $url zone=pol_rl_test_test_test:10m rate=10r/s; + +server { + listen 127.0.0.1:8082 proxy_protocol; + listen [::1]:8082 proxy_protocol; + + + server_name example.com; + status_zone example.com; + set $resource_type "virtualserver"; + set $resource_name ""; + set $resource_namespace ""; + listen 127.0.0.2:8443 ssl proxy_protocol; + listen [::2]:8443 ssl proxy_protocol; http2 on; ssl_certificate cafe-secret.pem; diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index 561a391748..b0248ff92b 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -268,7 +268,7 @@ func TestMakeHTTPSListener(t *testing.T) { } } -func TestMakeHTTPListenerWithCustomIP(t *testing.T) { +func TestMakeHTTPListenerAndHTTPSListenerWithCustomIPs(t *testing.T) { t.Parallel() testCases := []struct { @@ -313,6 +313,166 @@ func TestMakeHTTPListenerWithCustomIP(t *testing.T) { } } +func TestMakeHTTPListenerWithCustomIPV4(t *testing.T) { + t.Parallel() + + testCases := []struct { + server Server + expected string + }{ + {server: Server{ + CustomListeners: true, + DisableIPV6: false, + ProxyProtocol: false, + HTTPSPort: 0, + HTTPPort: 80, + HTTPIPv4: "192.168.0.2", + }, expected: "listen 192.168.0.2:80;\n listen [::]:80;\n"}, + {server: Server{ + CustomListeners: true, + HTTPSPort: 0, + HTTPPort: 81, + HTTPIPv4: "192.168.0.5", + DisableIPV6: false, + ProxyProtocol: false, + }, expected: "listen 192.168.0.5:81;\n listen [::]:81;\n"}, + {server: Server{ + CustomListeners: true, + DisableIPV6: true, + ProxyProtocol: false, + HTTPPort: 81, + HTTPIPv4: "192.168.0.2", + }, expected: "listen 192.168.0.2:81;\n"}, + {server: Server{ + CustomListeners: true, + HTTPPort: 82, + HTTPIPv4: "192.168.0.5", + DisableIPV6: true, + ProxyProtocol: false, + }, expected: "listen 192.168.0.5:82;\n"}, + } + + for _, tc := range testCases { + got := makeHTTPListener(tc.server) + if got != tc.expected { + t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) + } + } +} + +func TestMakeHTTPSListenerWithCustomIPV4(t *testing.T) { + t.Parallel() + + testCases := []struct { + server Server + expected string + }{ + {server: Server{ + CustomListeners: true, + ProxyProtocol: false, + DisableIPV6: true, + HTTPSPort: 80, + HTTPSIPv4: "192.168.0.2", + }, expected: "listen 192.168.0.2:80 ssl;\n"}, + {server: Server{ + CustomListeners: true, + DisableIPV6: true, + HTTPSPort: 81, + HTTPSIPv4: "192.168.0.5", + ProxyProtocol: false, + }, expected: "listen 192.168.0.5:81 ssl;\n"}, + } + + for _, tc := range testCases { + got := makeHTTPSListener(tc.server) + if got != tc.expected { + t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) + } + } +} + +func TestMakeHTTPListenerWithCustomIPV6(t *testing.T) { + t.Parallel() + + testCases := []struct { + server Server + expected string + }{ + {server: Server{ + CustomListeners: true, + ProxyProtocol: false, + HTTPPort: 80, + HTTPIPv6: "::1", + }, expected: "listen 80;\n listen [::1]:80;\n"}, + {server: Server{ + CustomListeners: true, + ProxyProtocol: false, + HTTPPort: 81, + HTTPIPv6: "::1", + }, expected: "listen 81;\n listen [::1]:81;\n"}, + {server: Server{ + CustomListeners: true, + HTTPPort: 81, + HTTPIPv6: "::2", + ProxyProtocol: false, + }, expected: "listen 81;\n listen [::2]:81;\n"}, + {server: Server{ + CustomListeners: true, + HTTPPort: 81, + ProxyProtocol: false, + HTTPIPv6: "::3", + }, expected: "listen 81;\n listen [::3]:81;\n"}, + } + + for _, tc := range testCases { + got := makeHTTPListener(tc.server) + if got != tc.expected { + t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) + } + } +} + +func TestMakeHTTPSListenerWithCustomIPV6(t *testing.T) { + t.Parallel() + + testCases := []struct { + server Server + expected string + }{ + {server: Server{ + CustomListeners: true, + ProxyProtocol: false, + HTTPSPort: 81, + HTTPSIPv6: "::1", + }, expected: "listen 81 ssl;\n listen [::1]:81 ssl;\n"}, + {server: Server{ + CustomListeners: true, + ProxyProtocol: false, + HTTPSPort: 82, + HTTPSIPv6: "::1", + }, expected: "listen 82 ssl;\n listen [::1]:82 ssl;\n"}, + {server: Server{ + CustomListeners: true, + HTTPSPort: 83, + HTTPSIPv6: "::2", + ProxyProtocol: false, + }, expected: "listen 83 ssl;\n listen [::2]:83 ssl;\n"}, + {server: Server{ + CustomListeners: true, + HTTPSPort: 84, + ProxyProtocol: false, + HTTPSIPv6: "::3", + }, expected: "listen 84 ssl;\n listen [::3]:84 ssl;\n"}, + } + + for _, tc := range testCases { + got := makeHTTPSListener(tc.server) + if got != tc.expected { + t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) + } + } +} + func newContainsTemplate(t *testing.T) *template.Template { t.Helper() tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(`{{contains .InputString .Substring}}`) diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 811bb4c6c4..9b31c52133 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -129,6 +129,136 @@ func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListener(t *testi t.Log(string(got)) } +func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerIP(t *testing.T) { + t.Parallel() + executor := newTmplExecutorNGINXPlus(t) + got, err := executor.ExecuteVirtualServerTemplate(&virtualServerCfgWithCustomListenerIP) + if err != nil { + t.Error(err) + } + wantStrings := []string{ + "listen 127.0.0.1:8082", + "listen [::1]:8082", + "listen 127.0.0.2:8443 ssl", + "listen [::2]:8443 ssl", + } + for _, want := range wantStrings { + if !bytes.Contains(got, []byte(want)) { + t.Errorf("want `%s` in generated template", want) + } + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + +func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPIPV4Only(t *testing.T) { + t.Parallel() + vsCfg := virtualServerCfgWithCustomListenerIP + + vsCfg.Server.HTTPIPv6 = "" + vsCfg.Server.HTTPSIPv6 = "" + vsCfg.Server.HTTPSIPv4 = "" + vsCfg.Server.HTTPSPort = 0 + + executor := newTmplExecutorNGINXPlus(t) + got, err := executor.ExecuteVirtualServerTemplate(&vsCfg) + if err != nil { + t.Error(err) + } + wantStrings := []string{ + "listen 127.0.0.1:8082", + "listen [::]:8082", + } + for _, want := range wantStrings { + if !bytes.Contains(got, []byte(want)) { + t.Errorf("want `%s` in generated template", want) + } + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + +func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPIPV6Only(t *testing.T) { + t.Parallel() + vsCfg := virtualServerCfgWithCustomListenerIP + + vsCfg.Server.HTTPIPv4 = "" + vsCfg.Server.HTTPSIPv6 = "" + vsCfg.Server.HTTPSIPv4 = "" + vsCfg.Server.HTTPSPort = 0 + + executor := newTmplExecutorNGINXPlus(t) + got, err := executor.ExecuteVirtualServerTemplate(&vsCfg) + if err != nil { + t.Error(err) + } + wantStrings := []string{ + "listen 8082", + "listen [::1]:8082", + } + for _, want := range wantStrings { + if !bytes.Contains(got, []byte(want)) { + t.Errorf("want `%s` in generated template", want) + } + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + +func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPSIPV4Only(t *testing.T) { + t.Parallel() + vsCfg := virtualServerCfgWithCustomListenerIP + + vsCfg.Server.HTTPIPv6 = "" + vsCfg.Server.HTTPSIPv6 = "" + vsCfg.Server.HTTPIPv4 = "" + vsCfg.Server.HTTPPort = 0 + + executor := newTmplExecutorNGINXPlus(t) + got, err := executor.ExecuteVirtualServerTemplate(&vsCfg) + if err != nil { + t.Error(err) + } + wantStrings := []string{ + "listen 127.0.0.2:8443 ssl", + "listen [::]:8443 ssl", + } + for _, want := range wantStrings { + if !bytes.Contains(got, []byte(want)) { + t.Errorf("want `%s` in generated template", want) + } + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + +func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPSIPV6Only(t *testing.T) { + t.Parallel() + vsCfg := virtualServerCfgWithCustomListenerIP + + vsCfg.Server.HTTPIPv6 = "" + vsCfg.Server.HTTPIPv4 = "" + vsCfg.Server.HTTPSIPv4 = "" + vsCfg.Server.HTTPPort = 0 + + executor := newTmplExecutorNGINXPlus(t) + got, err := executor.ExecuteVirtualServerTemplate(&vsCfg) + if err != nil { + t.Error(err) + } + wantStrings := []string{ + "listen 8443 ssl", + "listen [::2]:8443 ssl", + } + for _, want := range wantStrings { + if !bytes.Contains(got, []byte(want)) { + t.Errorf("want `%s` in generated template", want) + } + } + snaps.MatchSnapshot(t, string(got)) + t.Log(string(got)) +} + func TestExecuteVirtualServerTemplate_RendersTemplateWithCustomListenerHTTPOnly(t *testing.T) { t.Parallel() executor := newTmplExecutorNGINXPlus(t) @@ -3997,6 +4127,362 @@ var ( }, } + virtualServerCfgWithCustomListenerIP = VirtualServerConfig{ + LimitReqZones: []LimitReqZone{ + { + ZoneName: "pol_rl_test_test_test", Rate: "10r/s", ZoneSize: "10m", Key: "$url", + }, + }, + Upstreams: []Upstream{ + { + Name: "test-upstream", + Servers: []UpstreamServer{ + { + Address: "10.0.0.20:8001", + }, + }, + LBMethod: "random", + Keepalive: 32, + MaxFails: 4, + FailTimeout: "10s", + MaxConns: 31, + SlowStart: "10s", + UpstreamZoneSize: "256k", + Queue: &Queue{Size: 10, Timeout: "60s"}, + SessionCookie: &SessionCookie{Enable: true, Name: "test", Path: "/tea", Expires: "25s"}, + NTLM: true, + }, + { + Name: "coffee-v1", + Servers: []UpstreamServer{ + { + Address: "10.0.0.31:8001", + }, + }, + MaxFails: 8, + FailTimeout: "15s", + MaxConns: 2, + UpstreamZoneSize: "256k", + }, + { + Name: "coffee-v2", + Servers: []UpstreamServer{ + { + Address: "10.0.0.32:8001", + }, + }, + MaxFails: 12, + FailTimeout: "20s", + MaxConns: 4, + UpstreamZoneSize: "256k", + }, + }, + SplitClients: []SplitClient{ + { + Source: "$request_id", + Variable: "$split_0", + Distributions: []Distribution{ + { + Weight: "50%", + Value: "@loc0", + }, + { + Weight: "50%", + Value: "@loc1", + }, + }, + }, + }, + Maps: []Map{ + { + Source: "$match_0_0", + Variable: "$match", + Parameters: []Parameter{ + { + Value: "~^1", + Result: "@match_loc_0", + }, + { + Value: "default", + Result: "@match_loc_default", + }, + }, + }, + { + Source: "$http_x_version", + Variable: "$match_0_0", + Parameters: []Parameter{ + { + Value: "v2", + Result: "1", + }, + { + Value: "default", + Result: "0", + }, + }, + }, + }, + HTTPSnippets: []string{"# HTTP snippet"}, + Server: Server{ + ServerName: "example.com", + StatusZone: "example.com", + ProxyProtocol: true, + SSL: &SSL{ + HTTP2: true, + Certificate: "cafe-secret.pem", + CertificateKey: "cafe-secret.pem", + }, + TLSRedirect: &TLSRedirect{ + BasedOn: "$scheme", + Code: 301, + }, + CustomListeners: true, + HTTPPort: 8082, + HTTPSPort: 8443, + HTTPIPv4: "127.0.0.1", + HTTPIPv6: "::1", + HTTPSIPv4: "127.0.0.2", + HTTPSIPv6: "::2", + ServerTokens: "off", + SetRealIPFrom: []string{"0.0.0.0/0"}, + RealIPHeader: "X-Real-IP", + RealIPRecursive: true, + Allow: []string{"127.0.0.1"}, + Deny: []string{"127.0.0.1"}, + LimitReqs: []LimitReq{ + { + ZoneName: "pol_rl_test_test_test", + Delay: 10, + Burst: 5, + }, + }, + LimitReqOptions: LimitReqOptions{ + LogLevel: "error", + RejectCode: 503, + }, + JWTAuth: &JWTAuth{ + Realm: "My Api", + Secret: "jwk-secret", + }, + IngressMTLS: &IngressMTLS{ + ClientCert: "ingress-mtls-secret", + VerifyClient: "on", + VerifyDepth: 2, + }, + WAF: &WAF{ + ApPolicy: "/etc/nginx/waf/nac-policies/default-dataguard-alarm", + ApSecurityLogEnable: true, + Enable: "on", + ApLogConf: []string{"/etc/nginx/waf/nac-logconfs/default-logconf"}, + }, + Snippets: []string{"# server snippet"}, + InternalRedirectLocations: []InternalRedirectLocation{ + { + Path: "/split", + Destination: "@split_0", + }, + { + Path: "/coffee", + Destination: "@match", + }, + }, + HealthChecks: []HealthCheck{ + { + Name: "coffee", + URI: "/", + Interval: "5s", + Jitter: "0s", + Fails: 1, + Passes: 1, + Port: 50, + ProxyPass: "http://coffee-v2", + Mandatory: true, + Persistent: true, + KeepaliveTime: "60s", + IsGRPC: false, + }, + { + Name: "tea", + Interval: "5s", + Jitter: "0s", + Fails: 1, + Passes: 1, + Port: 50, + ProxyPass: "http://tea-v2", + GRPCPass: "grpc://tea-v3", + GRPCStatus: createPointerFromInt(12), + GRPCService: "tea-servicev2", + IsGRPC: true, + }, + }, + Locations: []Location{ + { + Path: "/", + Snippets: []string{"# location snippet"}, + Allow: []string{"127.0.0.1"}, + Deny: []string{"127.0.0.1"}, + LimitReqs: []LimitReq{ + { + ZoneName: "loc_pol_rl_test_test_test", + }, + }, + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyBuffering: true, + ProxyBuffers: "8 4k", + ProxyBufferSize: "4k", + ProxyMaxTempFileSize: "1024m", + ProxyPass: "http://test-upstream", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + Internal: true, + ProxyPassRequestHeaders: false, + ProxyPassHeaders: []string{"Host"}, + ProxyPassRewrite: "$request_uri", + ProxyHideHeaders: []string{"Header"}, + ProxyIgnoreHeaders: "Cache", + Rewrites: []string{"$request_uri $request_uri", "$request_uri $request_uri"}, + AddHeaders: []AddHeader{ + { + Header: Header{ + Name: "Header-Name", + Value: "Header Value", + }, + Always: true, + }, + }, + EgressMTLS: &EgressMTLS{ + Certificate: "egress-mtls-secret.pem", + CertificateKey: "egress-mtls-secret.pem", + VerifyServer: true, + VerifyDepth: 1, + Ciphers: "DEFAULT", + Protocols: "TLSv1.3", + TrustedCert: "trusted-cert.pem", + SessionReuse: true, + ServerName: true, + }, + }, + { + Path: "@loc0", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + ProxyInterceptErrors: true, + ErrorPages: []ErrorPage{ + { + Name: "@error_page_1", + Codes: "400 500", + ResponseCode: 200, + }, + { + Name: "@error_page_2", + Codes: "500", + ResponseCode: 0, + }, + }, + }, + { + Path: "@loc1", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + }, + { + Path: "@loc2", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v2", + GRPCPass: "grpc://coffee-v3", + }, + { + Path: "@match_loc_0", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + }, + { + Path: "@match_loc_default", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + }, + { + Path: "/return", + ProxyInterceptErrors: true, + ErrorPages: []ErrorPage{ + { + Name: "@return_0", + Codes: "418", + ResponseCode: 200, + }, + }, + InternalProxyPass: "http://unix:/var/lib/nginx/nginx-418-server.sock", + }, + }, + ErrorPageLocations: []ErrorPageLocation{ + { + Name: "@vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0", + DefaultType: "application/json", + Return: &Return{ + Code: 200, + Text: "Hello World", + }, + Headers: nil, + }, + { + Name: "@vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1", + DefaultType: "", + Return: &Return{ + Code: 200, + Text: "Hello World", + }, + Headers: []Header{ + { + Name: "Set-Cookie", + Value: "cookie1=test", + }, + { + Name: "Set-Cookie", + Value: "cookie2=test; Secure", + }, + }, + }, + }, + ReturnLocations: []ReturnLocation{ + { + Name: "@return_0", + DefaultType: "text/html", + Return: Return{ + Code: 200, + Text: "Hello!", + }, + }, + }, + }, + } + virtualServerCfgWithCustomListenerHTTPOnly = VirtualServerConfig{ LimitReqZones: []LimitReqZone{ { diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index f292d960ab..ec5b4911d1 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -3025,6 +3025,165 @@ func TestGenerateVirtualServerConfigWithCustomHttpsListener(t *testing.T) { } } +func TestGenerateVirtualServerConfigWithCustomHttpAndHttpsIPListeners(t *testing.T) { + t.Parallel() + + expected := version2.VirtualServerConfig{ + Upstreams: nil, + HTTPSnippets: []string{}, + LimitReqZones: []version2.LimitReqZone{}, + Server: version2.Server{ + ServerName: virtualServerExWithCustomHTTPAndHTTPSIPListeners.VirtualServer.Spec.Host, + StatusZone: virtualServerExWithCustomHTTPAndHTTPSIPListeners.VirtualServer.Spec.Host, + VSNamespace: virtualServerExWithCustomHTTPAndHTTPSIPListeners.VirtualServer.ObjectMeta.Namespace, + VSName: virtualServerExWithCustomHTTPAndHTTPSIPListeners.VirtualServer.ObjectMeta.Name, + DisableIPV6: false, + HTTPPort: virtualServerExWithCustomHTTPAndHTTPSIPListeners.HTTPPort, + HTTPSPort: virtualServerExWithCustomHTTPAndHTTPSIPListeners.HTTPSPort, + HTTPIPv4: virtualServerExWithCustomHTTPAndHTTPSIPListeners.HTTPIPv4, + HTTPIPv6: virtualServerExWithCustomHTTPAndHTTPSIPListeners.HTTPIPv6, + HTTPSIPv4: virtualServerExWithCustomHTTPAndHTTPSIPListeners.HTTPSIPv4, + HTTPSIPv6: virtualServerExWithCustomHTTPAndHTTPSIPListeners.HTTPSIPv6, + CustomListeners: true, + ProxyProtocol: true, + ServerTokens: "off", + SetRealIPFrom: []string{"0.0.0.0/0"}, + RealIPHeader: "X-Real-IP", + RealIPRecursive: true, + Snippets: []string{"# server snippet"}, + Locations: nil, + }, + } + + vsc := newVirtualServerConfigurator( + &baseCfgParams, + false, + false, + &StaticConfigParams{DisableIPV6: false}, + false, + &fakeBV, + ) + + result, warnings := vsc.GenerateVirtualServerConfig( + &virtualServerExWithCustomHTTPAndHTTPSIPListeners, + 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 TestGenerateVirtualServerConfigWithCustomHttpIPListener(t *testing.T) { + t.Parallel() + + expected := version2.VirtualServerConfig{ + Upstreams: nil, + HTTPSnippets: []string{}, + LimitReqZones: []version2.LimitReqZone{}, + Server: version2.Server{ + ServerName: virtualServerExWithCustomHTTPIPListener.VirtualServer.Spec.Host, + StatusZone: virtualServerExWithCustomHTTPIPListener.VirtualServer.Spec.Host, + VSNamespace: virtualServerExWithCustomHTTPIPListener.VirtualServer.ObjectMeta.Namespace, + VSName: virtualServerExWithCustomHTTPIPListener.VirtualServer.ObjectMeta.Name, + DisableIPV6: false, + HTTPPort: virtualServerExWithCustomHTTPIPListener.HTTPPort, + HTTPSPort: virtualServerExWithCustomHTTPIPListener.HTTPSPort, + HTTPIPv4: virtualServerExWithCustomHTTPIPListener.HTTPIPv4, + HTTPIPv6: virtualServerExWithCustomHTTPIPListener.HTTPIPv6, + HTTPSIPv4: virtualServerExWithCustomHTTPIPListener.HTTPSIPv4, + HTTPSIPv6: virtualServerExWithCustomHTTPIPListener.HTTPSIPv6, + CustomListeners: true, + ProxyProtocol: true, + ServerTokens: "off", + SetRealIPFrom: []string{"0.0.0.0/0"}, + RealIPHeader: "X-Real-IP", + RealIPRecursive: true, + Snippets: []string{"# server snippet"}, + Locations: nil, + }, + } + + vsc := newVirtualServerConfigurator( + &baseCfgParams, + false, + false, + &StaticConfigParams{DisableIPV6: false}, + false, + &fakeBV, + ) + + result, warnings := vsc.GenerateVirtualServerConfig( + &virtualServerExWithCustomHTTPIPListener, + 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 TestGenerateVirtualServerConfigWithCustomHttpsIPListener(t *testing.T) { + t.Parallel() + + expected := version2.VirtualServerConfig{ + Upstreams: nil, + HTTPSnippets: []string{}, + LimitReqZones: []version2.LimitReqZone{}, + Server: version2.Server{ + ServerName: virtualServerExWithCustomHTTPSIPListener.VirtualServer.Spec.Host, + StatusZone: virtualServerExWithCustomHTTPSIPListener.VirtualServer.Spec.Host, + VSNamespace: virtualServerExWithCustomHTTPSIPListener.VirtualServer.ObjectMeta.Namespace, + VSName: virtualServerExWithCustomHTTPSIPListener.VirtualServer.ObjectMeta.Name, + DisableIPV6: false, + HTTPPort: virtualServerExWithCustomHTTPSIPListener.HTTPPort, + HTTPSPort: virtualServerExWithCustomHTTPSIPListener.HTTPSPort, + HTTPIPv4: virtualServerExWithCustomHTTPSIPListener.HTTPIPv4, + HTTPIPv6: virtualServerExWithCustomHTTPSIPListener.HTTPIPv6, + HTTPSIPv4: virtualServerExWithCustomHTTPSIPListener.HTTPSIPv4, + HTTPSIPv6: virtualServerExWithCustomHTTPSIPListener.HTTPSIPv6, + CustomListeners: true, + ProxyProtocol: true, + ServerTokens: "off", + SetRealIPFrom: []string{"0.0.0.0/0"}, + RealIPHeader: "X-Real-IP", + RealIPRecursive: true, + Snippets: []string{"# server snippet"}, + Locations: nil, + }, + } + + vsc := newVirtualServerConfigurator( + &baseCfgParams, + false, + false, + &StaticConfigParams{DisableIPV6: false}, + false, + &fakeBV, + ) + + result, warnings := vsc.GenerateVirtualServerConfig( + &virtualServerExWithCustomHTTPSIPListener, + 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 TestGenerateVirtualServerConfigWithNilListener(t *testing.T) { t.Parallel() @@ -15525,6 +15684,66 @@ var ( }, } + virtualServerExWithCustomHTTPAndHTTPSIPListeners = VirtualServerEx{ + HTTPPort: 8083, + HTTPSPort: 8443, + HTTPIPv4: "192.168.0.2", + HTTPIPv6: "::1", + HTTPSIPv4: "192.168.0.6", + HTTPSIPv6: "::2", + + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "cafe", + Namespace: "default", + }, + Spec: conf_v1.VirtualServerSpec{ + Host: "cafe.example.com", + Listener: &conf_v1.VirtualServerListener{ + HTTP: "http-8083", + HTTPS: "https-8443", + }, + }, + }, + } + + virtualServerExWithCustomHTTPIPListener = VirtualServerEx{ + HTTPPort: 8083, + HTTPIPv4: "192.168.0.2", + HTTPIPv6: "::1", + + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "cafe", + Namespace: "default", + }, + Spec: conf_v1.VirtualServerSpec{ + Host: "cafe.example.com", + Listener: &conf_v1.VirtualServerListener{ + HTTP: "http-8083", + }, + }, + }, + } + + virtualServerExWithCustomHTTPSIPListener = VirtualServerEx{ + HTTPSPort: 8443, + HTTPSIPv4: "192.168.0.6", + HTTPSIPv6: "::2", + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "cafe", + Namespace: "default", + }, + Spec: conf_v1.VirtualServerSpec{ + Host: "cafe.example.com", + Listener: &conf_v1.VirtualServerListener{ + HTTPS: "https-8443", + }, + }, + }, + } + virtualServerExWithNilListener = VirtualServerEx{ VirtualServer: &conf_v1.VirtualServer{ ObjectMeta: meta_v1.ObjectMeta{ diff --git a/pkg/apis/configuration/validation/globalconfiguration_test.go b/pkg/apis/configuration/validation/globalconfiguration_test.go index c1df1ef089..709967db71 100644 --- a/pkg/apis/configuration/validation/globalconfiguration_test.go +++ b/pkg/apis/configuration/validation/globalconfiguration_test.go @@ -77,6 +77,7 @@ func TestValidateListeners(t *testing.T) { { Name: "test-listener-ip", IPv4IP: "127.0.0.1", + IPv6IP: "::1", Port: 8080, Protocol: "HTTP", }, @@ -98,22 +99,40 @@ func TestValidateListeners_FailsOnInvalidIP(t *testing.T) { listeners []conf_v1.Listener }{ { - name: "Invalid IP", + name: "Invalid IPv4 IP", listeners: []conf_v1.Listener{ - {Name: "test-listener-1", IPv4IP: "267.0.0.1", Port: 8082, Protocol: "UDP"}, + {Name: "test-listener-1", IPv4IP: "267.0.0.1", Port: 8082, Protocol: "HTTP"}, }, }, { - name: "Invalid IP with missing octet", + name: "Invalid IPv4 IP with missing octet", listeners: []conf_v1.Listener{ {Name: "test-listener-2", IPv4IP: "127.0.0", Port: 8080, Protocol: "HTTP"}, }, }, + { + name: "Invalid IPv6 IP", + listeners: []conf_v1.Listener{ + {Name: "test-listener-3", IPv6IP: "1200::AB00::1234", Port: 8080, Protocol: "HTTP"}, + }, + }, { name: "Valid and invalid IPs", listeners: []conf_v1.Listener{ - {Name: "test-listener-3", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - {Name: "test-listener-4", IPv4IP: "256.256.256.256", Port: 8081, Protocol: "HTTP"}, + {Name: "test-listener-4", IPv4IP: "192.168.1.1", IPv6IP: "2001:0db1234123123", Port: 8080, Protocol: "HTTP"}, + {Name: "test-listener-5", IPv4IP: "256.256.256.256", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8081, Protocol: "HTTP"}, + }, + }, + { + name: "Valid IPv4 and Invalid IPv6", + listeners: []conf_v1.Listener{ + {Name: "test-listener-6", IPv4IP: "192.168.1.1", IPv6IP: "2001::85a3::8a2e:370:7334", Port: 8080, Protocol: "HTTP"}, + }, + }, + { + name: "Invalid IPv4 and Valid IPv6", + listeners: []conf_v1.Listener{ + {Name: "test-listener-8", IPv4IP: "300.168.1.1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, }, }, } @@ -134,23 +153,60 @@ func TestValidateListeners_FailsOnInvalidIP(t *testing.T) { } } -func TestValidateListeners_FailsOnDuplicateNamesDifferentIP(t *testing.T) { +func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { t.Parallel() - listeners := []conf_v1.Listener{ - {Name: "test-listener", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, - {Name: "test-listener", IPv4IP: "192.168.1.2", Port: 8081, Protocol: "HTTP"}, + testCases := []struct { + name string + listeners []conf_v1.Listener + }{ + { + name: "Same port used with the same protocol", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IPv4IP: "192.168.1.1", IPv6IP: "::1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.1", IPv6IP: "::1", Port: 8080, Protocol: "HTTP"}, + }, + }, + { + name: "Same port used with different protocols", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IPv4IP: "192.168.1.1", IPv6IP: "::1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "TCP"}, + }, + }, + { + name: "Same port used with the same protocol (IPv6)", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IPv4IP: "192.168.1.1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, + }, + }, + { + name: "Same port used with different protocols (IPv6)", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "TCP"}, + }, + }, } gcv := createGlobalConfigurationValidator() - _, allErrs := gcv.getValidListeners(listeners, field.NewPath("listeners")) - if len(allErrs) == 0 { - t.Errorf("validateListeners() returned no errors %v for duplicate names", allErrs) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, allErrs := gcv.getValidListeners(tc.listeners, field.NewPath("listeners")) + if len(allErrs) == 0 { + t.Errorf("Expected errors for port/protocol conflicts, but got none") + } else { + for _, err := range allErrs { + t.Logf("Caught expected error: %v", err) + } + } + }) } } -func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { +func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { t.Parallel() testCases := []struct { @@ -158,10 +214,24 @@ func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { listeners []conf_v1.Listener }{ { - name: "Same port used with the same protocol", + name: "Different Ports and IPs", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IPv4IP: "192.168.1.1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.2", IPv6IP: "::1", Port: 9090, Protocol: "HTTP"}, + }, + }, + { + name: "Same IPs, Same Protocol and Different Port", + listeners: []conf_v1.Listener{ + {Name: "listener-1", IPv4IP: "192.168.1.1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv4IP: "192.168.1.1", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 9090, Protocol: "HTTP"}, + }, + }, + { + name: "Different Types of IPs", listeners: []conf_v1.Listener{ {Name: "listener-1", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - {Name: "listener-2", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, Protocol: "HTTP"}, }, }, } @@ -171,14 +241,14 @@ func TestValidateListeners_FailsOnPortProtocolConflictsSameIP(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { _, allErrs := gcv.getValidListeners(tc.listeners, field.NewPath("listeners")) - if len(allErrs) > 0 { - t.Logf("Caught expected error(s): %v", allErrs) + if len(allErrs) != 0 { + t.Errorf("Unexpected errors for valid listeners: %v", allErrs) } }) } } -func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { +func TestValidateListeners_FailsOnMixedInvalidIPs(t *testing.T) { t.Parallel() testCases := []struct { @@ -186,17 +256,17 @@ func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { listeners []conf_v1.Listener }{ { - name: "Different Ports and IPs", + name: "Valid IPv4 and Invalid IPv6", listeners: []conf_v1.Listener{ {Name: "listener-1", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - {Name: "listener-2", IPv4IP: "192.168.1.2", Port: 9090, Protocol: "HTTP"}, + {Name: "listener-2", IPv6IP: "2001::85a3::8a2e:370:7334", Port: 9090, Protocol: "TCP"}, }, }, { - name: "Same IP, Same Protocol and Different Port", + name: "Invalid IPv4 and Valid IPv6", listeners: []conf_v1.Listener{ - {Name: "listener-1", IPv4IP: "192.168.1.1", Port: 8080, Protocol: "HTTP"}, - {Name: "listener-2", IPv4IP: "192.168.1.1", Port: 9090, Protocol: "HTTP"}, + {Name: "listener-1", IPv4IP: "300.168.1.1", Port: 8080, Protocol: "HTTP"}, + {Name: "listener-2", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 9090, Protocol: "TCP"}, }, }, } @@ -206,8 +276,12 @@ func TestValidateListeners_PassesOnValidIPListeners(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { _, allErrs := gcv.getValidListeners(tc.listeners, field.NewPath("listeners")) - if len(allErrs) != 0 { - t.Errorf("Unexpected errors for valid listeners: %v", allErrs) + if len(allErrs) == 0 { + t.Errorf("Expected errors for mixed invalid IPs, but got none") + } else { + for _, err := range allErrs { + t.Logf("Caught expected error: %v", err) + } } }) } From 41bc4041796fb5318eb49cfccdf09b507b4b2ccb Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 7 Aug 2024 13:18:58 +0100 Subject: [PATCH 14/43] Update examples - incomplete --- .../virtualserver/global-configuration.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml new file mode 100644 index 0000000000..2c9a58c137 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml @@ -0,0 +1,17 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: ip-listener-1-http + port: 8083 + protocol: HTTP + ipv4ip: 127.0.0.1 + - name: ip-listener-2-https + port: 8443 + protocol: HTTP + ipv4ip: 127.0.0.2 + ipv6ip: ::1 + ssl: true From f0b1b9f358b2470b8ccbb3ae0ecf48d90bb4f033 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 7 Aug 2024 13:19:18 +0100 Subject: [PATCH 15/43] Update examples - incomplete --- .../{ => virtualserver}/README.md | 77 ++++++++++++------- .../{ => virtualserver}/cafe-secret.yaml | 0 .../cafe-virtual-server.yaml | 0 .../{ => virtualserver}/cafe.yaml | 0 4 files changed, 51 insertions(+), 26 deletions(-) rename examples/custom-resources/custom-ip-listeners/{ => virtualserver}/README.md (71%) rename examples/custom-resources/custom-ip-listeners/{ => virtualserver}/cafe-secret.yaml (100%) rename examples/custom-resources/custom-ip-listeners/{ => virtualserver}/cafe-virtual-server.yaml (100%) rename examples/custom-resources/custom-ip-listeners/{ => virtualserver}/cafe.yaml (100%) diff --git a/examples/custom-resources/custom-ip-listeners/README.md b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md similarity index 71% rename from examples/custom-resources/custom-ip-listeners/README.md rename to examples/custom-resources/custom-ip-listeners/virtualserver/README.md index 70016b5feb..1216f200d6 100644 --- a/examples/custom-resources/custom-ip-listeners/README.md +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md @@ -1,7 +1,7 @@ -# Custom IP HTTP Listeners +# Custom IPv4 and IPv6 IP Listeners -In this example, we will configure a VirtualServer resource with a custom IP using HTTP listeners. -This will allow HTTP and/or HTTPs based requests to be made on non-default ports using separate IPs. +In this example, we will configure a VirtualServer resource with a custom IPv4 or IPv6 IP using HTTP/HTTPS listeners. +This will allow IPv4 and/or IPv6 IPs using HTTP and/or HTTPS based requests to be made on non-default ports using separate IPs. ## Prerequisites @@ -40,26 +40,27 @@ Example YAML for a LoadBalancer: ## Step 1 - Deploy the GlobalConfiguration resource -Similar to how listeners are configured in our [custom-listeners](../../custom-resource/custom-listeners) examples, +Similar to how listeners are configured in our [custom-listeners](../../custom-listeners) examples, here we deploy a GlobalConfiguration resource with the listeners we want to use in our VirtualServer. ```yaml - apiVersion: k8s.nginx.org/v1alpha1 - kind: GlobalConfiguration - metadata: - name: nginx-configuration - namespace: nginx-ingress - spec: - listeners: - - name: ip-listener-1-http - port: 8083 - protocol: HTTP - ip: 127.0.0.1 - - name: ip-listener-2-https - port: 8443 - protocol: HTTP - ip: 127.0.0.1 - ssl: true +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: ip-listener-1-http + port: 8083 + protocol: HTTP + ipv4ip: 127.0.0.1 + - name: ip-listener-2-https + port: 8443 + protocol: HTTP + ipv4ip: 127.0.0.2 + ipv6ip: ::1 + ssl: true ``` ```console @@ -121,23 +122,23 @@ that was deployed in Step 1. Below is the yaml of this example VirtualServer: ## Step 4 - Test the Configuration -1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServer: +1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServer and the GlobalConfiguration: ```console kubectl describe virtualserver cafe ``` - Below you will see the events as well as the new `Listeners` field + Below you will see the events as well as the new `Listeners` field ```console - . . . - Spec: + . . . + Spec: Host: cafe.example.com Listener: Http: ip-listener-1-http Https: ip-listener-2-https - . . . - Routes: + . . . + Routes: . . . Events: Type Reason Age From Message @@ -145,6 +146,30 @@ that was deployed in Step 1. Below is the yaml of this example VirtualServer: Normal AddedOrUpdated 2s nginx-ingress-controller Configuration for default/cafe was added or updated ``` + ```console + kubectl describe globalconfiguration nginx-configuration -n nginx-ingress + ``` + + ```console + . . . + Spec: + Listeners: + ipv4ip: 127.0.0.1 + Name: ip-listener-1-http + Port: 8083 + Protocol: HTTP + ipv4ip: 127.0.0.2 + ipv6ip: ::1 + Name: ip-listener-2-https + Port: 8443 + Protocol: HTTP + Ssl: true + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Updated 14s nginx-ingress-controller GlobalConfiguration nginx-ingress/nginx-configuration was added or updated + ``` + 2. Since the deployed VirtualServer is using ports `8083` and `8443` in this example. you must explicitly specify these ports when sending requests to the endpoints of this VirtualServer: diff --git a/examples/custom-resources/custom-ip-listeners/cafe-secret.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/cafe-secret.yaml similarity index 100% rename from examples/custom-resources/custom-ip-listeners/cafe-secret.yaml rename to examples/custom-resources/custom-ip-listeners/virtualserver/cafe-secret.yaml diff --git a/examples/custom-resources/custom-ip-listeners/cafe-virtual-server.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/cafe-virtual-server.yaml similarity index 100% rename from examples/custom-resources/custom-ip-listeners/cafe-virtual-server.yaml rename to examples/custom-resources/custom-ip-listeners/virtualserver/cafe-virtual-server.yaml diff --git a/examples/custom-resources/custom-ip-listeners/cafe.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/cafe.yaml similarity index 100% rename from examples/custom-resources/custom-ip-listeners/cafe.yaml rename to examples/custom-resources/custom-ip-listeners/virtualserver/cafe.yaml From e175a6afcbb516456a86e4fcbc8a8f1b4475b12a Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 7 Aug 2024 15:04:48 +0100 Subject: [PATCH 16/43] Update examples readme --- .../virtualserver/README.md | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/README.md b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md index 1216f200d6..139e1f0d3a 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/README.md +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md @@ -170,33 +170,63 @@ that was deployed in Step 1. Below is the yaml of this example VirtualServer: Normal Updated 14s nginx-ingress-controller GlobalConfiguration nginx-ingress/nginx-configuration was added or updated ``` -2. Since the deployed VirtualServer is using ports `8083` and `8443` in this example. you must explicitly specify these ports -when sending requests to the endpoints of this VirtualServer: +2. Since the deployed VirtualServer is using ports `8083` and `8443` in this example. you can see that the specific ips and ports +are set and listening by using the below commands: - For `/coffee` on `8083`: + Access the NGINX Pod: ```console - curl -k http://cafe.example.com:8083/coffee + kubectl get pods -n nginx-ingress ``` ```text - Server address: 10.32.0.40:8080 - Server name: coffee-7dd75bc79b-qmhmv - ... - URI: /coffee - ... + NAME READY STATUS RESTARTS AGE + nginx-ingress-65cd79bb8f-crst4 1/1 Running 0 97s ``` - For `/coffee` on `8443`: + ```console + kubectl debug -it nginx-ingress-65cd79bb8f-crst4 --image=busybox:1.28 --target=nginx-ingress + ``` ```console - curl -k https://cafe.example.com:8443/coffee + / # netstat -tulpn + Active Internet connections (only servers) + Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name + tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN - + tcp 0 0 127.0.0.1:8083 0.0.0.0:* LISTEN - + tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN - + tcp 0 0 127.0.0.2:8443 0.0.0.0:* LISTEN - + tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN - + tcp 0 0 :::8081 :::* LISTEN - + tcp 0 0 :::8080 :::* LISTEN - + tcp 0 0 :::8083 :::* LISTEN - + tcp 0 0 ::1:8443 :::* LISTEN - + tcp 0 0 :::443 :::* LISTEN - + tcp 0 0 :::80 :::* LISTEN - + tcp 0 0 :::9113 :::* LISTEN - ``` - ```text - Server address: 10.32.0.40:8080 - Server name: coffee-7dd75bc79b-qmhmv - ... - URI: /coffee + We can see here that the two IPv4s (`127.0.0.1:8083` and `127.0.0.2:8443`) and the one IPv6 (`::1:8443`) that are set and listening. + +3. Examine the NGINX config using the following command: + + ```console + kubectl exec -it nginx-ingress-65cd79bb8f-crst4 -n nginx-ingress -- cat /etc/nginx/conf.d/vs_default_cafe.conf + ``` + + ```console ... + server { + listen 127.0.0.1:8083; + listen [::]:8083; + + + server_name cafe.example.com; + + set $resource_type "virtualserver"; + set $resource_name "cafe"; + set $resource_namespace "default"; + listen 127.0.0.2:8443 ssl; + listen [::1]:8443 ssl; + ... ``` From 1a50ba600ada0f224fa5c1b7fa16480f0fb7e3db Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 8 Aug 2024 14:29:38 +0100 Subject: [PATCH 17/43] Fix values.yaml globalconfig ipv4ip ipv6ip --- charts/nginx-ingress/values.schema.json | 16 ++++++++++++++++ .../k8s.nginx.org_globalconfigurations.yaml | 4 ++++ deploy/crds.yaml | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/charts/nginx-ingress/values.schema.json b/charts/nginx-ingress/values.schema.json index ba15ff7452..e33977a824 100644 --- a/charts/nginx-ingress/values.schema.json +++ b/charts/nginx-ingress/values.schema.json @@ -994,6 +994,22 @@ "examples": [ "dns-tcp" ] + }, + "ipv4ip": { + "type": "string", + "default": "", + "title": "The ipv4 ip", + "examples": [ + "127.0.0.1" + ] + }, + "ipv6ip": { + "type": "string", + "default": "", + "title": "The ipv6 ip", + "examples": [ + "::1" + ] } } } diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index 0aac371799..18ba9c3bd9 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -94,6 +94,10 @@ spec: items: description: Listener defines a listener. properties: + ipv4ip: + type: string + ipv6ip: + type: string name: type: string port: diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 675d4171d4..66413f9e8e 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -190,6 +190,10 @@ spec: items: description: Listener defines a listener. properties: + ipv4ip: + type: string + ipv6ip: + type: string name: type: string port: From afebad6ac679cc13b5568989a2fe01fc4e31463d Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 13 Aug 2024 09:51:49 +0100 Subject: [PATCH 18/43] pytests - unfinished --- ...ttps-invalid-ipv4ip-http-https-ipv6ip.yaml | 18 +++ ...ttps-ipv4ip-http-https-invalid-ipv6ip.yaml | 24 ++++ ...n-http-https-ipv4ip-http-https-ipv6ip.yaml | 24 ++++ ...uration-http-ipv4ip-address-in-ipv6ip.yaml | 17 +++ ...onfiguration-http-ipv4ip-https-ipv6ip.yaml | 22 ++++ .../global-configuration-http-ipv4ip.yaml | 21 ++++ ...uration-http-ipv6ip-address-in-ipv4ip.yaml | 17 +++ .../global-configuration-https-ipv6ip.yaml | 21 ++++ .../test_virtual_server_custom_listeners.py | 103 +++++++++++++++++- 9 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml create mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml new file mode 100644 index 0000000000..45f18317fd --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml @@ -0,0 +1,18 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv4ip: 567.0.0 + ipv6ip: ::1 diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml new file mode 100644 index 0000000000..a8c107d47d --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml @@ -0,0 +1,24 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv4ip: 567.0.0.1 + ipv6ip: ::24124123 + - name: https-8445 + port: 8445 + protocol: HTTP + ipv4ip: 456.0.0.2 + ipv6ip: ::1123123 + ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml new file mode 100644 index 0000000000..20be3d77a3 --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml @@ -0,0 +1,24 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv4ip: 127.0.0.1 + ipv6ip: ::2 + - name: https-8445 + port: 8445 + protocol: HTTP + ipv4ip: 127.0.0.2 + ipv6ip: ::1 + ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml new file mode 100644 index 0000000000..a2e49b9790 --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml @@ -0,0 +1,17 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv6ip: 127.0.0.1 diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml new file mode 100644 index 0000000000..7369922826 --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv4ip: 127.0.0.1 + - name: https-8445 + port: 8445 + protocol: HTTP + ipv6ip: ::1 + ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml new file mode 100644 index 0000000000..18a9e163f5 --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml @@ -0,0 +1,21 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv4ip: 127.0.0.1 + - name: https-8445 + port: 8445 + protocol: HTTP + ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml new file mode 100644 index 0000000000..5d85550c45 --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml @@ -0,0 +1,17 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + ipv4ip: ::1 diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml new file mode 100644 index 0000000000..6552ff52ed --- /dev/null +++ b/tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml @@ -0,0 +1,21 @@ +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: nginx-configuration + namespace: nginx-ingress +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP + - name: http-8085 + port: 8085 + protocol: HTTP + - name: https-8445 + port: 8445 + protocol: HTTP + ipv6ip: ::1 + ssl: true diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py index a9e8a70a9b..84b80374b7 100644 --- a/tests/suite/test_virtual_server_custom_listeners.py +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -68,6 +68,10 @@ class TestVirtualServerCustomListeners: "http_listener_in_config": bool, "https_listener_in_config": bool, "expected_response_codes": List[int], # responses from requests to port 80, 433, 8085, 8445 + "expected_http_listener_ipv4ip": str, + "expected_https_listener_ipv4ip": str, + "expected_http_listener_ipv6ip": str, + "expected_https_listener_ipv6ip": str, "expected_vs_error_msg": str, "expected_gc_error_msg": str, }, @@ -178,6 +182,72 @@ class TestVirtualServerCustomListeners: "expected_vs_error_msg": "", "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", }, + { + "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "127.0.0.2", + "expected_http_listener_ipv6ip": "::2", + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-ipv4ip-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-ipv4ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_http_listener_ipv6ip": "::", + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_http_listener_ipv6ip": "::24124123", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "must be a valid IPv6 address", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_http_listener_ipv4ip": "567.0.0", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "must be a valid IPv4 address", + "expected_gc_error_msg": "", + }, ], ids=[ "valid_config", @@ -191,6 +261,12 @@ class TestVirtualServerCustomListeners: "update_gc_http_listener_repeated_port", "update_gc_http_listener_forbidden_port", "update_gc_ts_listener_forbidden_port", + "http-https-ipv4ip-http-https-ipv6ip", + "http-ipv4ip-https-ipv6ip", + "http_listener_ipv4", + "https_listener_ipv6", + "http-https-ipv4ip-http-https-invalid-ipv6ip", + "http-https-invalid-ipv4ip-http-https-ipv6ip", ], ) def test_custom_listeners( @@ -231,17 +307,32 @@ def test_custom_listeners( print(vs_config) - if test_setup["http_listener_in_config"]: - assert "listen 8085;" in vs_config - assert "listen [::]:8085;" in vs_config + if test_setup.get("http_listener_in_config"): + if test_setup.get("expected_http_listener_ipv4ip"): + assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config + else: + assert "listen 8085;" in vs_config + + if test_setup.get("expected_http_listener_ipv6ip"): + assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config + else: + assert "listen [::]:8085;" in vs_config else: assert "listen 8085;" not in vs_config assert "listen [::]:8085;" not in vs_config - if test_setup["https_listener_in_config"]: - assert "listen 8445 ssl;" in vs_config - assert "listen [::]:8445 ssl;" in vs_config + if test_setup.get("https_listener_in_config"): + if test_setup.get("expected_https_listener_ipv4ip"): + assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" in vs_config + + if test_setup.get("expected_https_listener_ipv6ip"): + assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config + else: + assert "listen [::]:8445 ssl;" in vs_config + else: assert "listen 8445 ssl;" not in vs_config assert "listen [::]:8445 ssl;" not in vs_config From 762885100f44d8a3e80eb0350a7b9e95bf988c9f Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 13 Aug 2024 11:39:36 +0100 Subject: [PATCH 19/43] fix crds --- config/crd/bases/k8s.nginx.org_globalconfigurations.yaml | 4 ---- deploy/crds.yaml | 4 ---- internal/k8s/configuration.go | 2 +- internal/k8s/configuration_test.go | 6 +++--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index 18ba9c3bd9..0aac371799 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -94,10 +94,6 @@ spec: items: description: Listener defines a listener. properties: - ipv4ip: - type: string - ipv6ip: - type: string name: type: string port: diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 66413f9e8e..675d4171d4 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -190,10 +190,6 @@ spec: items: description: Listener defines a listener. properties: - ipv4ip: - type: string - ipv6ip: - type: string name: type: string port: diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 34201615c7..950460566e 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -1139,7 +1139,7 @@ func (c *Configuration) addWarningsForVirtualServersWithMissConfiguredListeners( } if vsc.VirtualServer.Spec.Listener != nil { if c.globalConfiguration == nil { - warningMsg := "Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration" + warningMsg := "Listeners defined, but no GlobalConfiguration is deployed" c.hosts[vsc.VirtualServer.Spec.Host].AddWarning(warningMsg) continue } diff --git a/internal/k8s/configuration_test.go b/internal/k8s/configuration_test.go index 080f696dd1..e7674a2ccc 100644 --- a/internal/k8s/configuration_test.go +++ b/internal/k8s/configuration_test.go @@ -2625,7 +2625,7 @@ func TestAddVirtualServerWithValidCustomListenersFirstThenAddGlobalConfiguration VirtualServer: virtualServer, HTTPPort: 0, HTTPSPort: 0, - Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration"}, + Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed"}, }, }, } @@ -2663,7 +2663,7 @@ func TestAddVirtualServerWithValidCustomListenersAndNoGlobalConfiguration(t *tes VirtualServer: virtualServer, HTTPPort: 0, HTTPSPort: 0, - Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration"}, + Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed"}, }, }, } @@ -2834,7 +2834,7 @@ func TestDeleteGlobalConfigurationWithVirtualServerDeployedWithValidCustomListen VirtualServer: virtualServer, HTTPPort: 0, HTTPSPort: 0, - Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed or Listener is not defined in GlobalConfiguration"}, + Warnings: []string{"Listeners defined, but no GlobalConfiguration is deployed"}, }, }, } From 180a7d61d955ae73fbe31d247a84b53bdc1c35ae Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 13 Aug 2024 16:57:01 +0100 Subject: [PATCH 20/43] update pytests - 1 working --- ...ttps-ipv4ip-http-https-invalid-ipv6ip.yaml | 8 +- ...n-http-https-ipv4ip-http-https-ipv6ip.yaml | 2 +- .../test_virtual_server_custom_listeners.py | 215 ++++++++++-------- 3 files changed, 117 insertions(+), 108 deletions(-) diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml index a8c107d47d..2c115f713d 100644 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml @@ -14,11 +14,5 @@ spec: - name: http-8085 port: 8085 protocol: HTTP - ipv4ip: 567.0.0.1 + ipv4ip: 127.0.0.1 ipv6ip: ::24124123 - - name: https-8445 - port: 8445 - protocol: HTTP - ipv4ip: 456.0.0.2 - ipv6ip: ::1123123 - ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml index 20be3d77a3..586eb0a79f 100644 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml @@ -15,7 +15,7 @@ spec: port: 8085 protocol: HTTP ipv4ip: 127.0.0.1 - ipv6ip: ::2 + ipv6ip: ::1 - name: https-8445 port: 8445 protocol: HTTP diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py index 84b80374b7..ebdc80a1f4 100644 --- a/tests/suite/test_virtual_server_custom_listeners.py +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -182,72 +182,6 @@ class TestVirtualServerCustomListeners: "expected_vs_error_msg": "", "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", }, - { - "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_https_listener_ipv4ip": "127.0.0.2", - "expected_http_listener_ipv6ip": "::2", - "expected_https_listener_ipv6ip": "::1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-http-ipv4ip-https-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_https_listener_ipv6ip": "::1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-http-ipv4ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_http_listener_ipv6ip": "::", - "expected_https_listener_ipv6ip": "::1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-https-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_https_listener_ipv6ip": "::1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": False, - "expected_http_listener_ipv6ip": "::24124123", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "must be a valid IPv6 address", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": False, - "expected_http_listener_ipv4ip": "567.0.0", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "must be a valid IPv4 address", - "expected_gc_error_msg": "", - }, ], ids=[ "valid_config", @@ -261,12 +195,6 @@ class TestVirtualServerCustomListeners: "update_gc_http_listener_repeated_port", "update_gc_http_listener_forbidden_port", "update_gc_ts_listener_forbidden_port", - "http-https-ipv4ip-http-https-ipv6ip", - "http-ipv4ip-https-ipv6ip", - "http_listener_ipv4", - "https_listener_ipv6", - "http-https-ipv4ip-http-https-invalid-ipv6ip", - "http-https-invalid-ipv4ip-http-https-ipv6ip", ], ) def test_custom_listeners( @@ -307,31 +235,23 @@ def test_custom_listeners( print(vs_config) - if test_setup.get("http_listener_in_config"): - if test_setup.get("expected_http_listener_ipv4ip"): - assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config - else: - assert "listen 8085;" in vs_config + if test_setup["http_listener_in_config"]: + assert "listen 8085;" in vs_config + assert "listen [::]:8085;" in vs_config + assert "listen [::]:8085;" in vs_config - if test_setup.get("expected_http_listener_ipv6ip"): - assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config - else: - assert "listen [::]:8085;" in vs_config + assert "listen [::]:8085;" in vs_config else: assert "listen 8085;" not in vs_config assert "listen [::]:8085;" not in vs_config - if test_setup.get("https_listener_in_config"): - if test_setup.get("expected_https_listener_ipv4ip"): - assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config - else: - assert "listen 8445 ssl;" in vs_config + if test_setup["https_listener_in_config"]: + assert "listen 8445 ssl;" in vs_config + assert "listen [::]:8445 ssl;" in vs_config + assert "listen [::]:8445 ssl;" in vs_config - if test_setup.get("expected_https_listener_ipv6ip"): - assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config - else: - assert "listen [::]:8445 ssl;" in vs_config + assert "listen [::]:8445 ssl;" in vs_config else: assert "listen 8445 ssl;" not in vs_config @@ -382,8 +302,8 @@ def test_custom_listeners( assert ( gc_event_latest.reason == "Updated" and gc_event_latest.type == "Normal" - and "GlobalConfiguration nginx-ingress/nginx-configuration was added " - "or updated" in gc_event_latest.message + and "GlobalConfiguration nginx-ingress/nginx-configuration was added or updated" + in gc_event_latest.message ) print("\nStep 7: Restore test environments") @@ -450,6 +370,76 @@ def test_custom_listeners( "expected_vs_error_msg": "", "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", }, + { + "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "127.0.0.2", + "expected_http_listener_ipv6ip": "::1", + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-ipv4ip-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "", + "expected_http_listener_ipv6ip": "", + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-ipv4ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_http_listener_ipv6ip": "::", + "expected_https_listener_ipv4ip": "", + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_https_listener_ipv6ip": "::1", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_http_listener_ipv4ip": "", + "expected_http_listener_ipv6ip": "::24124123", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "must be a valid IPv6 address", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_http_listener_ipv4ip": "567.0.0", + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "must be a valid IPv4 address", + "expected_gc_error_msg": "", + }, ], ids=[ "delete_gc", @@ -458,6 +448,12 @@ def test_custom_listeners( "update_gc_http_listener_repeated_port", "update_gc_http_listener_forbidden_port", "update_gc_ts_listener_forbidden_port", + "http-https-ipv4ip-http-https-ipv6ip", + "http-ipv4ip-https-ipv6ip", + "http_listener_ipv4", + "https_listener_ipv6", + "http-https-ipv4ip-http-https-invalid-ipv6ip", + "http-https-invalid-ipv4ip-http-https-ipv6ip", ], ) def test_custom_listeners_update( @@ -477,7 +473,7 @@ def test_custom_listeners_update( gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/virtual-server.yaml" - print("\nStep 2: Create VS with custom listener (http-8085, https-8445)") + print("\nStep 2: Create VS with custom listener") patch_virtual_server_from_yaml( kube_apis.custom_objects, virtual_server_setup.vs_name, @@ -501,7 +497,7 @@ def test_custom_listeners_update( with pytest.raises(ConnectionError, match="Connection refused"): make_request(url, virtual_server_setup.vs_host) - print("\nStep 3: Apply gc or vs update") + print("\nStep 3: Apply GC or VS update") if test_setup["gc_yaml"]: global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" patch_gc_from_yaml( @@ -522,20 +518,39 @@ def test_custom_listeners_update( ) print(vs_config) - if test_setup["http_listener_in_config"]: - assert "listen 8085;" in vs_config - assert "listen [::]:8085;" in vs_config + # Validate HTTP listeners + if test_setup.get("http_listener_in_config"): + if test_setup.get("expected_http_listener_ipv4ip"): + assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config + else: + assert "listen 8085;" in vs_config + + if test_setup.get("expected_http_listener_ipv6ip"): + assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config + else: + assert "listen [::]:8085;" in vs_config + else: assert "listen 8085;" not in vs_config assert "listen [::]:8085;" not in vs_config - if test_setup["https_listener_in_config"]: - assert "listen 8445 ssl;" in vs_config - assert "listen [::]:8445 ssl;" in vs_config + # Validate HTTPS listeners + if test_setup.get("https_listener_in_config"): + if test_setup.get("expected_https_listener_ipv4ip"): + assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" in vs_config + + if test_setup.get("expected_https_listener_ipv6ip"): + assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config + else: + assert "listen [::]:8445 ssl;" in vs_config # Updated to match default behavior + else: assert "listen 8445 ssl;" not in vs_config assert "listen [::]:8445 ssl;" not in vs_config + # Ensure default listeners are not present assert "listen 80;" not in vs_config assert "listen [::]:80;" not in vs_config assert "listen 443 ssl;" not in vs_config From 9d5b8b0ad217415ee451f2f1e791099e5a877567 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 14 Aug 2024 11:21:17 +0100 Subject: [PATCH 21/43] update pytests --- tests/suite/test_virtual_server_custom_listeners.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py index ebdc80a1f4..bd13f6a474 100644 --- a/tests/suite/test_virtual_server_custom_listeners.py +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -238,9 +238,6 @@ def test_custom_listeners( if test_setup["http_listener_in_config"]: assert "listen 8085;" in vs_config assert "listen [::]:8085;" in vs_config - assert "listen [::]:8085;" in vs_config - - assert "listen [::]:8085;" in vs_config else: assert "listen 8085;" not in vs_config @@ -249,9 +246,6 @@ def test_custom_listeners( if test_setup["https_listener_in_config"]: assert "listen 8445 ssl;" in vs_config assert "listen [::]:8445 ssl;" in vs_config - assert "listen [::]:8445 ssl;" in vs_config - - assert "listen [::]:8445 ssl;" in vs_config else: assert "listen 8445 ssl;" not in vs_config @@ -550,7 +544,6 @@ def test_custom_listeners_update( assert "listen 8445 ssl;" not in vs_config assert "listen [::]:8445 ssl;" not in vs_config - # Ensure default listeners are not present assert "listen 80;" not in vs_config assert "listen [::]:80;" not in vs_config assert "listen 443 ssl;" not in vs_config From 07735b2065bcdafe229dc185a36b62629237fe5d Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 14 Aug 2024 16:26:17 +0100 Subject: [PATCH 22/43] fix crds --- .../templates/controller-globalconfiguration.yaml | 2 +- config/crd/bases/k8s.nginx.org_globalconfigurations.yaml | 8 ++++---- .../virtualserver/global-configuration.yaml | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/charts/nginx-ingress/templates/controller-globalconfiguration.yaml b/charts/nginx-ingress/templates/controller-globalconfiguration.yaml index 9039ab0440..939923f2e0 100644 --- a/charts/nginx-ingress/templates/controller-globalconfiguration.yaml +++ b/charts/nginx-ingress/templates/controller-globalconfiguration.yaml @@ -1,5 +1,5 @@ {{ if .Values.controller.globalConfiguration.create }} -apiVersion: k8s.nginx.org/v1alpha1 +apiVersion: k8s.nginx.org/v1 kind: GlobalConfiguration metadata: name: {{ include "nginx-ingress.controller.fullname" . }} diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index 0aac371799..2bf8821443 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -46,16 +46,16 @@ spec: items: description: Listener defines a listener. properties: - ipv4ip: - type: string - ipv6ip: - type: string name: type: string port: type: integer protocol: type: string + ipv4ip: + type: string + ipv6ip: + type: string ssl: type: boolean type: object diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml index 2c9a58c137..eaf69f9f30 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml @@ -1,14 +1,15 @@ apiVersion: k8s.nginx.org/v1 kind: GlobalConfiguration metadata: - name: nginx-configuration - namespace: nginx-ingress + name: my-release-nginx-ingress-controller + namespace: default spec: listeners: - name: ip-listener-1-http port: 8083 protocol: HTTP ipv4ip: 127.0.0.1 + ipv6ip: fe80 - name: ip-listener-2-https port: 8443 protocol: HTTP From 60ed214b7d02bd2ec531904a90658b2880ecd7e5 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 15 Aug 2024 08:57:58 +0100 Subject: [PATCH 23/43] fix crds - last --- config/crd/bases/k8s.nginx.org_globalconfigurations.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index 2bf8821443..0aac371799 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -46,16 +46,16 @@ spec: items: description: Listener defines a listener. properties: + ipv4ip: + type: string + ipv6ip: + type: string name: type: string port: type: integer protocol: type: string - ipv4ip: - type: string - ipv6ip: - type: string ssl: type: boolean type: object From c8eabfd4a48e0d124912bcb6d1ee84672ca16cd2 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 15 Aug 2024 09:36:59 +0100 Subject: [PATCH 24/43] update pytests, comment out and remove unused test cases yamls --- pyproject.toml | 1 + ...ttps-invalid-ipv4ip-http-https-ipv6ip.yaml | 18 -- ...ttps-ipv4ip-http-https-invalid-ipv6ip.yaml | 18 -- ...uration-http-ipv4ip-address-in-ipv6ip.yaml | 17 -- ...uration-http-ipv6ip-address-in-ipv4ip.yaml | 17 -- .../test_virtual_server_custom_listeners.py | 175 ++++++++---------- 6 files changed, 76 insertions(+), 170 deletions(-) delete mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml delete mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml delete mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml delete mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml diff --git a/pyproject.toml b/pyproject.toml index 0b3de53574..e67d4ebc50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ markers =[ "vsr_upstream", "watch_namespace", "wildcard_tls", + "alex", ] testpaths = [ "tests", diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml deleted file mode 100644 index 45f18317fd..0000000000 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP - - name: http-8085 - port: 8085 - protocol: HTTP - ipv4ip: 567.0.0 - ipv6ip: ::1 diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml deleted file mode 100644 index 2c115f713d..0000000000 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP - - name: http-8085 - port: 8085 - protocol: HTTP - ipv4ip: 127.0.0.1 - ipv6ip: ::24124123 diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml deleted file mode 100644 index a2e49b9790..0000000000 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-address-in-ipv6ip.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP - - name: http-8085 - port: 8085 - protocol: HTTP - ipv6ip: 127.0.0.1 diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml deleted file mode 100644 index 5d85550c45..0000000000 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv6ip-address-in-ipv4ip.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP - - name: http-8085 - port: 8085 - protocol: HTTP - ipv4ip: ::1 diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py index bd13f6a474..4faf6523a4 100644 --- a/tests/suite/test_virtual_server_custom_listeners.py +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -306,6 +306,7 @@ def test_custom_listeners( if test_setup["gc_yaml"]: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + @pytest.mark.alex @pytest.mark.parametrize( "test_setup", [ @@ -318,52 +319,52 @@ def test_custom_listeners( "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", "expected_gc_error_msg": "", }, - { - "gc_yaml": "global-configuration-https-listener-without-ssl", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": False, - "expected_response_codes": [404, 404, 200, 0], - "expected_vs_error_msg": "Listener https-8445 can't be use in `listener.https` context as SSL is not " - "enabled for that listener.", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-http-listener-with-ssl", - "vs_yaml": "virtual-server", - "http_listener_in_config": False, - "https_listener_in_config": True, - "expected_response_codes": [404, 404, 0, 200], - "expected_vs_error_msg": "Listener http-8085 can't be use in `listener.http` context as SSL is enabled", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-repeated-http-port", - "vs_yaml": "virtual-server", - "http_listener_in_config": False, - "https_listener_in_config": True, - "expected_response_codes": [404, 404, 0, 200], - "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", - "expected_gc_error_msg": "Listener http-8085: Duplicated port/protocol combination 8085/HTTP", - }, - { - "gc_yaml": "global-configuration-forbidden-port-http", - "vs_yaml": "virtual-server", - "http_listener_in_config": False, - "https_listener_in_config": True, - "expected_response_codes": [404, 404, 0, 200], - "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", - "expected_gc_error_msg": "Listener http-8085: port 9113 is forbidden", - }, - { - "gc_yaml": "global-configuration-forbidden-port-preceding-udp", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", - }, + # { + # "gc_yaml": "global-configuration-https-listener-without-ssl", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": True, + # "https_listener_in_config": False, + # "expected_response_codes": [404, 404, 200, 0], + # "expected_vs_error_msg": "Listener https-8445 can't be used in `listener.https` context as SSL is not " + # "enabled for that listener.", + # "expected_gc_error_msg": "", + # }, + # { + # "gc_yaml": "global-configuration-http-listener-with-ssl", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": False, + # "https_listener_in_config": True, + # "expected_response_codes": [404, 404, 0, 200], + # "expected_vs_error_msg": "Listener http-8085 can't be use in `listener.http` context as SSL is enabled", + # "expected_gc_error_msg": "", + # }, + # { + # "gc_yaml": "global-configuration-repeated-http-port", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": False, + # "https_listener_in_config": True, + # "expected_response_codes": [404, 404, 0, 200], + # "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + # "expected_gc_error_msg": "Listener http-8085: Duplicated port/protocol combination 8085/HTTP", + # }, + # { + # "gc_yaml": "global-configuration-forbidden-port-http", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": False, + # "https_listener_in_config": True, + # "expected_response_codes": [404, 404, 0, 200], + # "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + # "expected_gc_error_msg": "Listener http-8085: port 9113 is forbidden", + # }, + # { + # "gc_yaml": "global-configuration-forbidden-port-preceding-udp", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": True, + # "https_listener_in_config": True, + # "expected_response_codes": [404, 404, 200, 200], + # "expected_vs_error_msg": "", + # "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", + # }, { "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", "vs_yaml": "virtual-server", @@ -396,58 +397,32 @@ def test_custom_listeners( "http_listener_in_config": True, "https_listener_in_config": True, "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_http_listener_ipv6ip": "::", - "expected_https_listener_ipv4ip": "", - "expected_https_listener_ipv6ip": "::1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-https-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_https_listener_ipv6ip": "::1", "expected_response_codes": [404, 404, 200, 200], "expected_vs_error_msg": "", "expected_gc_error_msg": "", }, - { - "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-invalid-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": False, - "expected_http_listener_ipv4ip": "", - "expected_http_listener_ipv6ip": "::24124123", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "must be a valid IPv6 address", - "expected_gc_error_msg": "", - }, - { - "gc_yaml": "global-configuration-http-https-invalid-ipv4ip-http-https-ipv6ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": False, - "expected_http_listener_ipv4ip": "567.0.0", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "must be a valid IPv4 address", - "expected_gc_error_msg": "", - }, + # { + # "gc_yaml": "global-configuration-https-ipv6ip", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": True, + # "https_listener_in_config": True, + # "expected_https_listener_ipv6ip": "::1", + # "expected_response_codes": [404, 404, 200, 200], + # "expected_vs_error_msg": "", + # "expected_gc_error_msg": "", + # }, ], ids=[ "delete_gc", - "update_gc_https_listener_ssl_false", - "update_gc_http_listener_ssl_true", - "update_gc_http_listener_repeated_port", - "update_gc_http_listener_forbidden_port", - "update_gc_ts_listener_forbidden_port", + # "update_gc_https_listener_ssl_false", + # "update_gc_http_listener_ssl_true", + # "update_gc_http_listener_repeated_port", + # "update_gc_http_listener_forbidden_port", + # "update_gc_ts_listener_forbidden_port", "http-https-ipv4ip-http-https-ipv6ip", "http-ipv4ip-https-ipv6ip", - "http_listener_ipv4", - "https_listener_ipv6", - "http-https-ipv4ip-http-https-invalid-ipv6ip", - "http-https-invalid-ipv4ip-http-https-ipv6ip", + "http-ipv4ip", + # "https-ipv6ip", ], ) def test_custom_listeners_update( @@ -466,6 +441,7 @@ def test_custom_listeners_update( global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/global-configuration.yaml" gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/virtual-server.yaml" + print(f"initial GC details: {gc_resource}") print("\nStep 2: Create VS with custom listener") patch_virtual_server_from_yaml( @@ -497,6 +473,8 @@ def test_custom_listeners_update( patch_gc_from_yaml( kube_apis.custom_objects, gc_resource["metadata"]["name"], global_config_file, "nginx-ingress" ) + print(f"GC file location {global_config_file}") + print(f"updated GC details: {gc_resource}") else: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") wait_before_test() @@ -512,34 +490,30 @@ def test_custom_listeners_update( ) print(vs_config) - # Validate HTTP listeners - if test_setup.get("http_listener_in_config"): - if test_setup.get("expected_http_listener_ipv4ip"): + if "http_listener_in_config" in test_setup and test_setup["http_listener_in_config"]: + if "expected_http_listener_ipv4ip" in test_setup and test_setup["expected_http_listener_ipv4ip"]: assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config else: assert "listen 8085;" in vs_config - if test_setup.get("expected_http_listener_ipv6ip"): + if "expected_http_listener_ipv6ip" in test_setup and test_setup["expected_http_listener_ipv6ip"]: assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config else: assert "listen [::]:8085;" in vs_config - else: assert "listen 8085;" not in vs_config assert "listen [::]:8085;" not in vs_config - # Validate HTTPS listeners - if test_setup.get("https_listener_in_config"): - if test_setup.get("expected_https_listener_ipv4ip"): + if "https_listener_in_config" in test_setup and test_setup["https_listener_in_config"]: + if "expected_https_listener_ipv4ip" in test_setup and test_setup["expected_https_listener_ipv4ip"]: assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config else: assert "listen 8445 ssl;" in vs_config - if test_setup.get("expected_https_listener_ipv6ip"): + if "expected_https_listener_ipv6ip" in test_setup and test_setup["expected_https_listener_ipv6ip"]: assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config else: - assert "listen [::]:8445 ssl;" in vs_config # Updated to match default behavior - + assert "listen [::]:8445 ssl;" in vs_config else: assert "listen 8445 ssl;" not in vs_config assert "listen [::]:8445 ssl;" not in vs_config @@ -592,3 +566,4 @@ def test_custom_listeners_update( restore_default_vs(kube_apis, virtual_server_setup) if test_setup["gc_yaml"]: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + print(f"deleted GC : {gc_resource}") From 7c8262e85f93d7442a95fe43e0db532c3ed156cb Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 15 Aug 2024 15:46:55 +0100 Subject: [PATCH 25/43] update pytests --- .../test_virtual_server_custom_listeners.py | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py index 4faf6523a4..749b47d483 100644 --- a/tests/suite/test_virtual_server_custom_listeners.py +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -310,15 +310,15 @@ def test_custom_listeners( @pytest.mark.parametrize( "test_setup", [ - { - "gc_yaml": "", # delete gc if empty - "vs_yaml": "virtual-server", - "http_listener_in_config": False, - "https_listener_in_config": False, - "expected_response_codes": [404, 404, 0, 0], - "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", - "expected_gc_error_msg": "", - }, + # { + # "gc_yaml": "", # delete gc if empty + # "vs_yaml": "virtual-server", + # "http_listener_in_config": False, + # "https_listener_in_config": False, + # "expected_response_codes": [404, 404, 0, 0], + # "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", + # "expected_gc_error_msg": "", + # }, # { # "gc_yaml": "global-configuration-https-listener-without-ssl", # "vs_yaml": "virtual-server", @@ -391,16 +391,16 @@ def test_custom_listeners( "expected_vs_error_msg": "", "expected_gc_error_msg": "", }, - { - "gc_yaml": "global-configuration-http-ipv4ip", - "vs_yaml": "virtual-server", - "http_listener_in_config": True, - "https_listener_in_config": True, - "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", - "expected_gc_error_msg": "", - }, + # { + # "gc_yaml": "global-configuration-http-ipv4ip", + # "vs_yaml": "virtual-server", + # "http_listener_in_config": True, + # "https_listener_in_config": True, + # "expected_http_listener_ipv4ip": "127.0.0.1", + # "expected_response_codes": [404, 404, 200, 200], + # "expected_vs_error_msg": "", + # "expected_gc_error_msg": "", + # }, # { # "gc_yaml": "global-configuration-https-ipv6ip", # "vs_yaml": "virtual-server", @@ -413,7 +413,7 @@ def test_custom_listeners( # }, ], ids=[ - "delete_gc", + # "delete_gc", # "update_gc_https_listener_ssl_false", # "update_gc_http_listener_ssl_true", # "update_gc_http_listener_repeated_port", @@ -421,7 +421,7 @@ def test_custom_listeners( # "update_gc_ts_listener_forbidden_port", "http-https-ipv4ip-http-https-ipv6ip", "http-ipv4ip-https-ipv6ip", - "http-ipv4ip", + # "http-ipv4ip", # "https-ipv6ip", ], ) @@ -467,20 +467,29 @@ def test_custom_listeners_update( with pytest.raises(ConnectionError, match="Connection refused"): make_request(url, virtual_server_setup.vs_host) - print("\nStep 3: Apply GC or VS update") + print("\nStep 3: Apply GC update") if test_setup["gc_yaml"]: global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" patch_gc_from_yaml( kube_apis.custom_objects, gc_resource["metadata"]["name"], global_config_file, "nginx-ingress" ) print(f"GC file location {global_config_file}") - print(f"updated GC details: {gc_resource}") else: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") - wait_before_test() + if ( + test_setup["expected_http_listener_ipv4ip"] + or test_setup["expected_http_listener_ipv6ip"] + or test_setup["expected_https_listener_ipv4ip"] + or test_setup["expected_https_listener_ipv6ip"] + ): + print("IP Listeners Detected - Waiting 30 Extra Seconds Required") + wait_before_test(30) + else: + wait_before_test() print("\nStep 4: Test generated VS configs") ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + vs_config = "" vs_config = get_vs_nginx_template_conf( kube_apis.v1, virtual_server_setup.namespace, @@ -563,7 +572,7 @@ def test_custom_listeners_update( print("\nStep 8: Restore test environments") delete_secret(kube_apis.v1, secret_name, virtual_server_setup.namespace) - restore_default_vs(kube_apis, virtual_server_setup) + restore_default_vs(kube_apis, virtual_server_setup) # not working if test_setup["gc_yaml"]: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") print(f"deleted GC : {gc_resource}") From 0b63dce7671e9fbe58e95ea6fca7d76ceb3f632e Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Fri, 16 Aug 2024 14:20:00 +0100 Subject: [PATCH 26/43] fix example --- .../custom-ip-listeners/virtualserver/global-configuration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml index eaf69f9f30..9b0e31a6b9 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml @@ -9,7 +9,7 @@ spec: port: 8083 protocol: HTTP ipv4ip: 127.0.0.1 - ipv6ip: fe80 + ipv6ip: ::1 - name: ip-listener-2-https port: 8443 protocol: HTTP From b359f0dff670531bc7e75718a55b8aff718ee248 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Fri, 16 Aug 2024 14:24:08 +0100 Subject: [PATCH 27/43] fix example --- .../virtualserver/global-configuration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml index 9b0e31a6b9..4ee2a75061 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml @@ -1,8 +1,8 @@ apiVersion: k8s.nginx.org/v1 kind: GlobalConfiguration metadata: - name: my-release-nginx-ingress-controller - namespace: default + name: nginx-configuration + namespace: nginx-ingress spec: listeners: - name: ip-listener-1-http From d7f03ec99f8d0005e25621c464d14e37f9ffc0a3 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Tue, 3 Sep 2024 15:48:14 +0100 Subject: [PATCH 28/43] restore old custom listener pytest --- .../test_virtual_server_custom_listeners.py | 203 ++++++------------ 1 file changed, 60 insertions(+), 143 deletions(-) diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py index 749b47d483..a9e8a70a9b 100644 --- a/tests/suite/test_virtual_server_custom_listeners.py +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -68,10 +68,6 @@ class TestVirtualServerCustomListeners: "http_listener_in_config": bool, "https_listener_in_config": bool, "expected_response_codes": List[int], # responses from requests to port 80, 433, 8085, 8445 - "expected_http_listener_ipv4ip": str, - "expected_https_listener_ipv4ip": str, - "expected_http_listener_ipv6ip": str, - "expected_https_listener_ipv6ip": str, "expected_vs_error_msg": str, "expected_gc_error_msg": str, }, @@ -246,7 +242,6 @@ def test_custom_listeners( if test_setup["https_listener_in_config"]: assert "listen 8445 ssl;" in vs_config assert "listen [::]:8445 ssl;" in vs_config - else: assert "listen 8445 ssl;" not in vs_config assert "listen [::]:8445 ssl;" not in vs_config @@ -296,8 +291,8 @@ def test_custom_listeners( assert ( gc_event_latest.reason == "Updated" and gc_event_latest.type == "Normal" - and "GlobalConfiguration nginx-ingress/nginx-configuration was added or updated" - in gc_event_latest.message + and "GlobalConfiguration nginx-ingress/nginx-configuration was added " + "or updated" in gc_event_latest.message ) print("\nStep 7: Restore test environments") @@ -306,123 +301,72 @@ def test_custom_listeners( if test_setup["gc_yaml"]: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") - @pytest.mark.alex @pytest.mark.parametrize( "test_setup", [ - # { - # "gc_yaml": "", # delete gc if empty - # "vs_yaml": "virtual-server", - # "http_listener_in_config": False, - # "https_listener_in_config": False, - # "expected_response_codes": [404, 404, 0, 0], - # "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", - # "expected_gc_error_msg": "", - # }, - # { - # "gc_yaml": "global-configuration-https-listener-without-ssl", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": True, - # "https_listener_in_config": False, - # "expected_response_codes": [404, 404, 200, 0], - # "expected_vs_error_msg": "Listener https-8445 can't be used in `listener.https` context as SSL is not " - # "enabled for that listener.", - # "expected_gc_error_msg": "", - # }, - # { - # "gc_yaml": "global-configuration-http-listener-with-ssl", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": False, - # "https_listener_in_config": True, - # "expected_response_codes": [404, 404, 0, 200], - # "expected_vs_error_msg": "Listener http-8085 can't be use in `listener.http` context as SSL is enabled", - # "expected_gc_error_msg": "", - # }, - # { - # "gc_yaml": "global-configuration-repeated-http-port", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": False, - # "https_listener_in_config": True, - # "expected_response_codes": [404, 404, 0, 200], - # "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", - # "expected_gc_error_msg": "Listener http-8085: Duplicated port/protocol combination 8085/HTTP", - # }, - # { - # "gc_yaml": "global-configuration-forbidden-port-http", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": False, - # "https_listener_in_config": True, - # "expected_response_codes": [404, 404, 0, 200], - # "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", - # "expected_gc_error_msg": "Listener http-8085: port 9113 is forbidden", - # }, - # { - # "gc_yaml": "global-configuration-forbidden-port-preceding-udp", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": True, - # "https_listener_in_config": True, - # "expected_response_codes": [404, 404, 200, 200], - # "expected_vs_error_msg": "", - # "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", - # }, { - "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", + "gc_yaml": "", # delete gc if empty + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-https-listener-without-ssl", "vs_yaml": "virtual-server", "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 200, 0], + "expected_vs_error_msg": "Listener https-8445 can't be use in `listener.https` context as SSL is not " + "enabled for that listener.", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-listener-with-ssl", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, "https_listener_in_config": True, - "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_https_listener_ipv4ip": "127.0.0.2", - "expected_http_listener_ipv6ip": "::1", - "expected_https_listener_ipv6ip": "::1", - "expected_response_codes": [404, 404, 200, 200], - "expected_vs_error_msg": "", + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 can't be use in `listener.http` context as SSL is enabled", "expected_gc_error_msg": "", }, { - "gc_yaml": "global-configuration-http-ipv4ip-https-ipv6ip", + "gc_yaml": "global-configuration-repeated-http-port", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "Listener http-8085: Duplicated port/protocol combination 8085/HTTP", + }, + { + "gc_yaml": "global-configuration-forbidden-port-http", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "Listener http-8085: port 9113 is forbidden", + }, + { + "gc_yaml": "global-configuration-forbidden-port-preceding-udp", "vs_yaml": "virtual-server", "http_listener_in_config": True, "https_listener_in_config": True, - "expected_http_listener_ipv4ip": "127.0.0.1", - "expected_https_listener_ipv4ip": "", - "expected_http_listener_ipv6ip": "", - "expected_https_listener_ipv6ip": "::1", "expected_response_codes": [404, 404, 200, 200], "expected_vs_error_msg": "", - "expected_gc_error_msg": "", + "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", }, - # { - # "gc_yaml": "global-configuration-http-ipv4ip", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": True, - # "https_listener_in_config": True, - # "expected_http_listener_ipv4ip": "127.0.0.1", - # "expected_response_codes": [404, 404, 200, 200], - # "expected_vs_error_msg": "", - # "expected_gc_error_msg": "", - # }, - # { - # "gc_yaml": "global-configuration-https-ipv6ip", - # "vs_yaml": "virtual-server", - # "http_listener_in_config": True, - # "https_listener_in_config": True, - # "expected_https_listener_ipv6ip": "::1", - # "expected_response_codes": [404, 404, 200, 200], - # "expected_vs_error_msg": "", - # "expected_gc_error_msg": "", - # }, ], ids=[ - # "delete_gc", - # "update_gc_https_listener_ssl_false", - # "update_gc_http_listener_ssl_true", - # "update_gc_http_listener_repeated_port", - # "update_gc_http_listener_forbidden_port", - # "update_gc_ts_listener_forbidden_port", - "http-https-ipv4ip-http-https-ipv6ip", - "http-ipv4ip-https-ipv6ip", - # "http-ipv4ip", - # "https-ipv6ip", + "delete_gc", + "update_gc_https_listener_ssl_false", + "update_gc_http_listener_ssl_true", + "update_gc_http_listener_repeated_port", + "update_gc_http_listener_forbidden_port", + "update_gc_ts_listener_forbidden_port", ], ) def test_custom_listeners_update( @@ -441,9 +385,8 @@ def test_custom_listeners_update( global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/global-configuration.yaml" gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/virtual-server.yaml" - print(f"initial GC details: {gc_resource}") - print("\nStep 2: Create VS with custom listener") + print("\nStep 2: Create VS with custom listener (http-8085, https-8445)") patch_virtual_server_from_yaml( kube_apis.custom_objects, virtual_server_setup.vs_name, @@ -467,29 +410,18 @@ def test_custom_listeners_update( with pytest.raises(ConnectionError, match="Connection refused"): make_request(url, virtual_server_setup.vs_host) - print("\nStep 3: Apply GC update") + print("\nStep 3: Apply gc or vs update") if test_setup["gc_yaml"]: global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" patch_gc_from_yaml( kube_apis.custom_objects, gc_resource["metadata"]["name"], global_config_file, "nginx-ingress" ) - print(f"GC file location {global_config_file}") else: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") - if ( - test_setup["expected_http_listener_ipv4ip"] - or test_setup["expected_http_listener_ipv6ip"] - or test_setup["expected_https_listener_ipv4ip"] - or test_setup["expected_https_listener_ipv6ip"] - ): - print("IP Listeners Detected - Waiting 30 Extra Seconds Required") - wait_before_test(30) - else: - wait_before_test() + wait_before_test() print("\nStep 4: Test generated VS configs") ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) - vs_config = "" vs_config = get_vs_nginx_template_conf( kube_apis.v1, virtual_server_setup.namespace, @@ -499,30 +431,16 @@ def test_custom_listeners_update( ) print(vs_config) - if "http_listener_in_config" in test_setup and test_setup["http_listener_in_config"]: - if "expected_http_listener_ipv4ip" in test_setup and test_setup["expected_http_listener_ipv4ip"]: - assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config - else: - assert "listen 8085;" in vs_config - - if "expected_http_listener_ipv6ip" in test_setup and test_setup["expected_http_listener_ipv6ip"]: - assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config - else: - assert "listen [::]:8085;" in vs_config + if test_setup["http_listener_in_config"]: + assert "listen 8085;" in vs_config + assert "listen [::]:8085;" in vs_config else: assert "listen 8085;" not in vs_config assert "listen [::]:8085;" not in vs_config - if "https_listener_in_config" in test_setup and test_setup["https_listener_in_config"]: - if "expected_https_listener_ipv4ip" in test_setup and test_setup["expected_https_listener_ipv4ip"]: - assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config - else: - assert "listen 8445 ssl;" in vs_config - - if "expected_https_listener_ipv6ip" in test_setup and test_setup["expected_https_listener_ipv6ip"]: - assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config - else: - assert "listen [::]:8445 ssl;" in vs_config + if test_setup["https_listener_in_config"]: + assert "listen 8445 ssl;" in vs_config + assert "listen [::]:8445 ssl;" in vs_config else: assert "listen 8445 ssl;" not in vs_config assert "listen [::]:8445 ssl;" not in vs_config @@ -572,7 +490,6 @@ def test_custom_listeners_update( print("\nStep 8: Restore test environments") delete_secret(kube_apis.v1, secret_name, virtual_server_setup.namespace) - restore_default_vs(kube_apis, virtual_server_setup) # not working + restore_default_vs(kube_apis, virtual_server_setup) if test_setup["gc_yaml"]: delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") - print(f"deleted GC : {gc_resource}") From fc5c95638cc712d7482fbd3590419f0673573f07 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 4 Sep 2024 11:57:00 +0100 Subject: [PATCH 29/43] add new ip listener pytest --- ...test_virtual_server_custom_ip_listeners.py | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 tests/suite/test_virtual_server_custom_ip_listeners.py diff --git a/tests/suite/test_virtual_server_custom_ip_listeners.py b/tests/suite/test_virtual_server_custom_ip_listeners.py new file mode 100644 index 0000000000..93ba1fde29 --- /dev/null +++ b/tests/suite/test_virtual_server_custom_ip_listeners.py @@ -0,0 +1,221 @@ +from typing import List, TypedDict + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.custom_resources_utils import create_gc_from_yaml, delete_gc +from suite.utils.resources_utils import ( + create_secret_from_yaml, + delete_secret, + get_events_for_object, + get_first_pod_name, + wait_before_test, +) +from suite.utils.vs_vsr_resources_utils import get_vs_nginx_template_conf, patch_virtual_server_from_yaml, read_vs + + +def make_request(url, host): + return requests.get( + url, + headers={"host": host}, + allow_redirects=False, + verify=False, + ) + + +def restore_default_vs(kube_apis, virtual_server_setup) -> None: + """ + Function to revert VS deployment to valid state. + """ + patch_src = f"{TEST_DATA}/virtual-server-status/standard/virtual-server.yaml" + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + patch_src, + virtual_server_setup.namespace, + ) + wait_before_test() + + +@pytest.mark.vs +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-global-configuration=nginx-ingress/nginx-configuration", + f"-enable-leader-election=false", + f"-enable-prometheus-metrics=true", + ], + }, + { + "example": "virtual-server-custom-listeners", + "app_type": "simple", + }, + ) + ], + indirect=True, +) +class TestVirtualServerCustomListeners: + TestSetup = TypedDict( + "TestSetup", + { + "gc_yaml": str, + "vs_yaml": str, + "http_listener_in_config": bool, + "https_listener_in_config": bool, + "expected_response_codes": List[int], # responses from requests to port 80, 443, 8085, 8445 + "expected_http_listener_ipv4ip": str, + "expected_https_listener_ipv4ip": str, + "expected_http_listener_ipv6ip": str, + "expected_https_listener_ipv6ip": str, + "expected_vs_error_msg": str, + "expected_gc_error_msg": str, + }, + ) + + @pytest.mark.parametrize( + "test_setup", + [ + { + "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [200, 200], + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "127.0.0.2", + "expected_http_listener_ipv6ip": "::1", + "expected_https_listener_ipv6ip": "::1", + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-ipv4ip-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [200, 200], + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "", + "expected_http_listener_ipv6ip": "", + "expected_https_listener_ipv6ip": "::1", + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + ], + ids=[ + "http-https-ipv4ip-http-https-ipv6ip", + "http-ipv4ip-https-ipv6ip", + ], + ) + def test_custom_listeners_update( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_setup: TestSetup, + ) -> None: + print("\nStep 1: Create GC resource") + secret_name = create_secret_from_yaml( + kube_apis.v1, virtual_server_setup.namespace, f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml" + ) + if test_setup["gc_yaml"]: + global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" + gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") + + print("\nStep 2: Create VS with custom listeners") + vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['vs_yaml']}.yaml" + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_custom_listeners, + virtual_server_setup.namespace, + ) + print("IP Listeners Detected - Waiting 30 Extra Seconds Required") + wait_before_test(30) + + print("\nStep 3: Test generated VS configs") + ic_pod_name = get_first_pod_name(kube_apis.v1, 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, + ) + + print(vs_config) + + if "http_listener_in_config" in test_setup and test_setup["http_listener_in_config"]: + if "expected_http_listener_ipv4ip" in test_setup and test_setup["expected_http_listener_ipv4ip"]: + assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config + else: + assert "listen 8085;" in vs_config + + if "expected_http_listener_ipv6ip" in test_setup and test_setup["expected_http_listener_ipv6ip"]: + assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config + else: + assert "listen [::]:8085;" in vs_config + else: + assert "listen 8085;" not in vs_config + assert "listen [::]:8085;" not in vs_config + + if "https_listener_in_config" in test_setup and test_setup["https_listener_in_config"]: + if "expected_https_listener_ipv4ip" in test_setup and test_setup["expected_https_listener_ipv4ip"]: + assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" in vs_config + + if "expected_https_listener_ipv6ip" in test_setup and test_setup["expected_https_listener_ipv6ip"]: + assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config + else: + assert "listen [::]:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" not in vs_config + assert "listen [::]:8445 ssl;" not in vs_config + + assert "listen 80;" not in vs_config + assert "listen [::]:80;" not in vs_config + assert "listen 443 ssl;" not in vs_config + assert "listen [::]:443 ssl;" not in vs_config + + print("\nStep 4: Test Kubernetes VirtualServer warning events") + if test_setup["expected_vs_error_msg"]: + response = read_vs(kube_apis.custom_objects, virtual_server_setup.namespace, virtual_server_setup.vs_name) + print(response) + assert ( + response["status"]["reason"] == "AddedOrUpdatedWithWarning" + and response["status"]["state"] == "Warning" + and test_setup["expected_vs_error_msg"] in response["status"]["message"] + ) + + print("\nStep 5: Test Kubernetes GlobalConfiguration warning events") + if test_setup["gc_yaml"]: + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + if test_setup["expected_gc_error_msg"]: + assert ( + gc_event_latest.reason == "AddedOrUpdatedWithError" + and gc_event_latest.type == "Warning" + and test_setup["expected_gc_error_msg"] in gc_event_latest.message + ) + else: + assert ( + gc_event_latest.reason == "Updated" + and gc_event_latest.type == "Normal" + and "GlobalConfiguration nginx-ingress/nginx-configuration was added or updated" + in gc_event_latest.message + ) + + print("\nStep 6: Restore test environments") + delete_secret(kube_apis.v1, secret_name, virtual_server_setup.namespace) + restore_default_vs(kube_apis, virtual_server_setup) + if test_setup["gc_yaml"]: + delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + print(f"deleted GC : {gc_resource}") + wait_before_test(10) From 063335dabd6857e85b8bcdcd89093d40cb68f258 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:07:18 +0000 Subject: [PATCH 30/43] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- internal/configs/version2/template_helper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index cb420c4424..88da7e42ed 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -469,7 +469,7 @@ func TestMakeHTTPSListenerWithCustomIPV6(t *testing.T) { got := makeHTTPSListener(tc.server) if got != tc.expected { t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) - + func TestMakeTransportListener(t *testing.T) { t.Parallel() From 037a7e60c7e3780b067dca3ffa121234a6ef2b22 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Wed, 4 Sep 2024 12:13:38 +0100 Subject: [PATCH 31/43] remove transportserver and fix merge mistakes --- .../transportserver/global-configuration.yaml | 13 ------------- internal/configs/version2/template_helper.go | 2 +- internal/configs/version2/template_helper_test.go | 3 +++ 3 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml diff --git a/examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml deleted file mode 100644 index 56353d8586..0000000000 --- a/examples/custom-resources/custom-ip-listeners/transportserver/global-configuration.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 05ff650d63..1f1a97b071 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -125,7 +125,7 @@ func buildListenDirective(ip string, port string, proxyProtocol bool, ipType ipT return directive } -func buildTransportListenDirective(listenType listenerType, port string, ssl *StreamSSL, udp bool) string { +func buildTransportListenDirective(listenType ipType, port string, ssl *StreamSSL, udp bool) string { base := "listen" var directive string diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index 88da7e42ed..de394cd8ce 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -469,6 +469,9 @@ func TestMakeHTTPSListenerWithCustomIPV6(t *testing.T) { got := makeHTTPSListener(tc.server) if got != tc.expected { t.Errorf("Function generated wrong config, got %v but expected %v.", got, tc.expected) + } + } +} func TestMakeTransportListener(t *testing.T) { t.Parallel() From fc91b51af8e94df108a04ecbacae4818c3ce1785 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 10:13:40 +0100 Subject: [PATCH 32/43] Update example and add doc --- .../global-configuration/globalconfiguration-resource.md | 8 ++++++++ .../custom-ip-listeners/virtualserver/README.md | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/docs/content/configuration/global-configuration/globalconfiguration-resource.md b/docs/content/configuration/global-configuration/globalconfiguration-resource.md index 130b3f577f..0e2f02ee71 100644 --- a/docs/content/configuration/global-configuration/globalconfiguration-resource.md +++ b/docs/content/configuration/global-configuration/globalconfiguration-resource.md @@ -74,6 +74,9 @@ The `listeners:` key defines a listener (a combination of a protocol and a port) | *port* | The port of the listener. The port must fall into the range ``1..65535`` with the following exceptions: ``80``, ``443``, the [status port](/nginx-ingress-controller/logging-and-monitoring/status-page), the [Prometheus metrics port](/nginx-ingress-controller/logging-and-monitoring/prometheus). Among all listeners, only a single combination of a port-protocol is allowed. | *int* | Yes | | *protocol* | The protocol of the listener. Supported values: ``TCP``, ``UDP`` and ``HTTP``. | *string* | Yes | | *ssl* | Configures the listener with SSL. This is currently only supported for ``HTTP`` listeners. Default value is ``false`` | *bool* | No | +| *ipv4ip* | Specifies the IPv4 address to listen on. This is currently only supported for ``HTTP`` or ``HTTPS`` listeners. | *string* | No | +| *ipv6ip* | Specifies the IPv6 address to listen on. This is currently only supported for ``HTTP`` or ``HTTPS`` listeners. | *string* | No | + {{}} --- @@ -173,3 +176,8 @@ Events: ``` The events section includes a Warning event with the AddedOrUpdatedWithError reason. + + +## Using IPV4 and IPV6 IP Addresses with GlobalConfiguration + +You can customize the IP listeners with IPv4 and IPv6 in the global configuration and apply them to your VirtualServer resources. See the corresponding example [here](https://github.com/nginxinc/kubernetes-ingress/tree/v{{< nic-version >}}/examples/custom-resources/custom-ip-listeners/virtualserver/) diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/README.md b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md index 139e1f0d3a..889b97daad 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/README.md +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md @@ -38,6 +38,11 @@ Example YAML for a LoadBalancer: app: nginx-ingress ``` +**Note:** + +- **No Updates for GC:** If a GlobalConfiguration resource already exists, delete the previous one before applying the new configuration. +- **Single Replica:** Only one replica is allowed when using this configuration. + ## Step 1 - Deploy the GlobalConfiguration resource Similar to how listeners are configured in our [custom-listeners](../../custom-listeners) examples, From 4e81c84e737e2ac6d555109d34504f562427b3b5 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 11:34:34 +0100 Subject: [PATCH 33/43] Update crds and user facing listener names --- config/crd/bases/k8s.nginx.org_globalconfigurations.yaml | 4 ++-- deploy/crds.yaml | 4 ++-- pkg/apis/configuration/v1/types.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml index e21c2e055f..f9dcaa94cc 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -46,9 +46,9 @@ spec: items: description: Listener defines a listener. properties: - ipv4ip: + ipv4: type: string - ipv6ip: + ipv6: type: string name: type: string diff --git a/deploy/crds.yaml b/deploy/crds.yaml index df70151b56..3f81160c15 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -142,9 +142,9 @@ spec: items: description: Listener defines a listener. properties: - ipv4ip: + ipv4: type: string - ipv6ip: + ipv6: type: string name: type: string diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index fb25c376bb..76c5261980 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -411,8 +411,8 @@ type GlobalConfigurationSpec struct { type Listener struct { Name string `json:"name"` Port int `json:"port"` - IPv4IP string `json:"ipv4ip"` - IPv6IP string `json:"ipv6ip"` + IPv4IP string `json:"ipv4"` + IPv6IP string `json:"ipv6"` Protocol string `json:"protocol"` Ssl bool `json:"ssl"` } From 28e5f89551685fe37098be3aa53821a691f0c48d Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 11:38:33 +0100 Subject: [PATCH 34/43] Update example --- .../virtualserver/global-configuration.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml index 4ee2a75061..361e6a742a 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml @@ -8,11 +8,11 @@ spec: - name: ip-listener-1-http port: 8083 protocol: HTTP - ipv4ip: 127.0.0.1 - ipv6ip: ::1 + ipv4: 127.0.0.1 + ipv6: ::1 - name: ip-listener-2-https port: 8443 protocol: HTTP - ipv4ip: 127.0.0.2 - ipv6ip: ::1 + ipv4: 127.0.0.2 + ipv6: ::1 ssl: true From f7a637274394e6c65cc29a689b9930abb094cb88 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 14:13:40 +0100 Subject: [PATCH 35/43] address comments --- .../globalconfiguration-resource.md | 8 ++++---- .../virtualserver/README.md | 18 +++++++++--------- internal/configs/version2/template_helper.go | 4 ++-- ...on-http-https-ipv4ip-http-https-ipv6ip.yaml | 8 ++++---- ...configuration-http-ipv4ip-https-ipv6ip.yaml | 4 ++-- .../global-configuration-http-ipv4ip.yaml | 2 +- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/content/configuration/global-configuration/globalconfiguration-resource.md b/docs/content/configuration/global-configuration/globalconfiguration-resource.md index 0e2f02ee71..66cf33aa27 100644 --- a/docs/content/configuration/global-configuration/globalconfiguration-resource.md +++ b/docs/content/configuration/global-configuration/globalconfiguration-resource.md @@ -74,8 +74,8 @@ The `listeners:` key defines a listener (a combination of a protocol and a port) | *port* | The port of the listener. The port must fall into the range ``1..65535`` with the following exceptions: ``80``, ``443``, the [status port](/nginx-ingress-controller/logging-and-monitoring/status-page), the [Prometheus metrics port](/nginx-ingress-controller/logging-and-monitoring/prometheus). Among all listeners, only a single combination of a port-protocol is allowed. | *int* | Yes | | *protocol* | The protocol of the listener. Supported values: ``TCP``, ``UDP`` and ``HTTP``. | *string* | Yes | | *ssl* | Configures the listener with SSL. This is currently only supported for ``HTTP`` listeners. Default value is ``false`` | *bool* | No | -| *ipv4ip* | Specifies the IPv4 address to listen on. This is currently only supported for ``HTTP`` or ``HTTPS`` listeners. | *string* | No | -| *ipv6ip* | Specifies the IPv6 address to listen on. This is currently only supported for ``HTTP`` or ``HTTPS`` listeners. | *string* | No | +| *ipv4* | Specifies the IPv4 address to listen on. This is currently only supported for ``HTTP`` or ``HTTPS`` listeners. | *string* | No | +| *ipv6* | Specifies the IPv6 address to listen on. This is currently only supported for ``HTTP`` or ``HTTPS`` listeners. | *string* | No | {{}} @@ -178,6 +178,6 @@ Events: The events section includes a Warning event with the AddedOrUpdatedWithError reason. -## Using IPV4 and IPV6 IP Addresses with GlobalConfiguration +## Using IPV4 and IPV6 Addresses with GlobalConfiguration -You can customize the IP listeners with IPv4 and IPv6 in the global configuration and apply them to your VirtualServer resources. See the corresponding example [here](https://github.com/nginxinc/kubernetes-ingress/tree/v{{< nic-version >}}/examples/custom-resources/custom-ip-listeners/virtualserver/) +You can customize the IPv4 and IPv6 Address listeners in the global configuration and apply them to your VirtualServer resources. See the corresponding example [here](https://github.com/nginxinc/kubernetes-ingress/tree/v{{< nic-version >}}/examples/custom-resources/custom-ip-listeners/virtualserver/) diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/README.md b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md index 889b97daad..07e0c8d1e8 100644 --- a/examples/custom-resources/custom-ip-listeners/virtualserver/README.md +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md @@ -1,7 +1,7 @@ -# Custom IPv4 and IPv6 IP Listeners +# Custom IPv4 and IPv6 Address Listeners -In this example, we will configure a VirtualServer resource with a custom IPv4 or IPv6 IP using HTTP/HTTPS listeners. -This will allow IPv4 and/or IPv6 IPs using HTTP and/or HTTPS based requests to be made on non-default ports using separate IPs. +In this example, we will configure a VirtualServer resource with custom IPv4 and IPv6 Address using HTTP/HTTPS listeners. +This will allow IPv4 and/or IPv6 address using HTTP and/or HTTPS based requests to be made on non-default ports using separate IPs. ## Prerequisites @@ -59,12 +59,12 @@ spec: - name: ip-listener-1-http port: 8083 protocol: HTTP - ipv4ip: 127.0.0.1 + ipv4: 127.0.0.1 - name: ip-listener-2-https port: 8443 protocol: HTTP - ipv4ip: 127.0.0.2 - ipv6ip: ::1 + ipv4: 127.0.0.2 + ipv6: ::1 ssl: true ``` @@ -159,12 +159,12 @@ that was deployed in Step 1. Below is the yaml of this example VirtualServer: . . . Spec: Listeners: - ipv4ip: 127.0.0.1 + ipv4: 127.0.0.1 Name: ip-listener-1-http Port: 8083 Protocol: HTTP - ipv4ip: 127.0.0.2 - ipv6ip: ::1 + ipv4: 127.0.0.2 + ipv6: ::1 Name: ip-listener-2-https Port: 8443 Protocol: HTTP diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 1f1a97b071..b81ad28978 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -125,11 +125,11 @@ func buildListenDirective(ip string, port string, proxyProtocol bool, ipType ipT return directive } -func buildTransportListenDirective(listenType ipType, port string, ssl *StreamSSL, udp bool) string { +func buildTransportListenDirective(ipType ipType, port string, ssl *StreamSSL, udp bool) string { base := "listen" var directive string - if listenType == ipv6 { + if ipType == ipv6 { directive = base + " [::]:" + port } else { directive = base + " " + port diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml index 586eb0a79f..26ea0bf9b6 100644 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-https-ipv4ip-http-https-ipv6ip.yaml @@ -14,11 +14,11 @@ spec: - name: http-8085 port: 8085 protocol: HTTP - ipv4ip: 127.0.0.1 - ipv6ip: ::1 + ipv4: 127.0.0.1 + ipv6: ::1 - name: https-8445 port: 8445 protocol: HTTP - ipv4ip: 127.0.0.2 - ipv6ip: ::1 + ipv4: 127.0.0.2 + ipv6: ::1 ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml index 7369922826..1b98231c50 100644 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip-https-ipv6ip.yaml @@ -14,9 +14,9 @@ spec: - name: http-8085 port: 8085 protocol: HTTP - ipv4ip: 127.0.0.1 + ipv4: 127.0.0.1 - name: https-8445 port: 8445 protocol: HTTP - ipv6ip: ::1 + ipv6: ::1 ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml index 18a9e163f5..afbd5575db 100644 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml +++ b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml @@ -14,7 +14,7 @@ spec: - name: http-8085 port: 8085 protocol: HTTP - ipv4ip: 127.0.0.1 + ipv4: 127.0.0.1 - name: https-8445 port: 8445 protocol: HTTP From cc471bcad6bd8679725d87c7fc9af462d61211b3 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 14:15:07 +0100 Subject: [PATCH 36/43] remove unused file --- .../global-configuration-http-ipv4ip.yaml | 21 ------------------- .../global-configuration-https-ipv6ip.yaml | 21 ------------------- 2 files changed, 42 deletions(-) delete mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml delete mode 100644 tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml deleted file mode 100644 index afbd5575db..0000000000 --- a/tests/data/virtual-server-custom-listeners/global-configuration-http-ipv4ip.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP - - name: http-8085 - port: 8085 - protocol: HTTP - ipv4: 127.0.0.1 - - name: https-8445 - port: 8445 - protocol: HTTP - ssl: true diff --git a/tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml b/tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml deleted file mode 100644 index 6552ff52ed..0000000000 --- a/tests/data/virtual-server-custom-listeners/global-configuration-https-ipv6ip.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: k8s.nginx.org/v1 -kind: GlobalConfiguration -metadata: - name: nginx-configuration - namespace: nginx-ingress -spec: - listeners: - - name: dns-udp - port: 5353 - protocol: UDP - - name: dns-tcp - port: 5353 - protocol: TCP - - name: http-8085 - port: 8085 - protocol: HTTP - - name: https-8445 - port: 8445 - protocol: HTTP - ipv6ip: ::1 - ssl: true From 49d4dfcf719074dc16fb61e85a819d1a9d388cb8 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 15:01:31 +0100 Subject: [PATCH 37/43] Update pyproject.toml Signed-off-by: Alex Fenlon --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e67d4ebc50..0b3de53574 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,6 @@ markers =[ "vsr_upstream", "watch_namespace", "wildcard_tls", - "alex", ] testpaths = [ "tests", From c04d236c816bd1770d52e2d0631ce08122d1cf02 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 15:32:26 +0100 Subject: [PATCH 38/43] rerun pipeline From b3fec89fcc1aaa8ad8d4d91e3ad21203f596948b Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 15:48:24 +0100 Subject: [PATCH 39/43] rerun pipeline --- internal/configs/version2/template_helper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index de394cd8ce..aafb417322 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -443,7 +443,7 @@ func TestMakeHTTPSListenerWithCustomIPV6(t *testing.T) { CustomListeners: true, ProxyProtocol: false, HTTPSPort: 81, - HTTPSIPv6: "::1", + HTTPSIPv6: "::1 ", }, expected: "listen 81 ssl;\n listen [::1]:81 ssl;\n"}, {server: Server{ CustomListeners: true, From a41b39e40a385faf0ab4b92ab8193224485a58c4 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 15:49:15 +0100 Subject: [PATCH 40/43] rerun pipeline - fix --- internal/configs/version2/template_helper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index aafb417322..de394cd8ce 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -443,7 +443,7 @@ func TestMakeHTTPSListenerWithCustomIPV6(t *testing.T) { CustomListeners: true, ProxyProtocol: false, HTTPSPort: 81, - HTTPSIPv6: "::1 ", + HTTPSIPv6: "::1", }, expected: "listen 81 ssl;\n listen [::1]:81 ssl;\n"}, {server: Server{ CustomListeners: true, From d87db54038744a05b511d69cd3bbbcdb84bc1381 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 15:55:45 +0100 Subject: [PATCH 41/43] rerun pipeline --- internal/configs/version2/template_helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index b81ad28978..9d62addf09 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -89,7 +89,7 @@ func getDefaultPort(listenerType protocol) string { if listenerType == http { return "80" } - return "443 ssl" + return "443 ssl " } func getCustomPort(listenerType protocol, s Server) string { From 9c3c57169e23d92b84c9818be35dc2f4b896a3c3 Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 15:56:10 +0100 Subject: [PATCH 42/43] rerun pipeline - fix --- internal/configs/version2/template_helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 9d62addf09..b81ad28978 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -89,7 +89,7 @@ func getDefaultPort(listenerType protocol) string { if listenerType == http { return "80" } - return "443 ssl " + return "443 ssl" } func getCustomPort(listenerType protocol, s Server) string { From ef7902485ce3544caf3f7b5e767fabc35683a42d Mon Sep 17 00:00:00 2001 From: Alex Fenlon Date: Thu, 5 Sep 2024 16:01:02 +0100 Subject: [PATCH 43/43] rerun pipeline --- pkg/apis/configuration/validation/globalconfiguration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/configuration/validation/globalconfiguration.go b/pkg/apis/configuration/validation/globalconfiguration.go index d5da1329e2..689fa625ef 100644 --- a/pkg/apis/configuration/validation/globalconfiguration.go +++ b/pkg/apis/configuration/validation/globalconfiguration.go @@ -53,7 +53,7 @@ func (gcv *GlobalConfigurationValidator) getValidListeners(listeners []conf_v1.L listenerNames := sets.Set[string]{} ipv4PortProtocolCombinations := make(map[string]map[int]string) // map[IP]map[Port]Protocol - ipv6PortProtocolCombinations := make(map[string]map[int]string) // map[IP]map[Port]Protocol + ipv6PortProtocolCombinations := make(map[string]map[int]string) var validListeners []conf_v1.Listener for i, l := range listeners {