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/charts/nginx-ingress/values.schema.json b/charts/nginx-ingress/values.schema.json index 30264314e4..4679145193 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 b70d87debe..f9dcaa94cc 100644 --- a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -46,6 +46,10 @@ spec: items: description: Listener defines a listener. properties: + ipv4: + type: string + ipv6: + type: string name: type: string port: diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 411e32c025..3f81160c15 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -142,6 +142,10 @@ spec: items: description: Listener defines a listener. properties: + ipv4: + type: string + ipv6: + type: string name: type: string port: diff --git a/docs/content/configuration/global-configuration/globalconfiguration-resource.md b/docs/content/configuration/global-configuration/globalconfiguration-resource.md index 130b3f577f..66cf33aa27 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 | +| *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 | + {{}} --- @@ -173,3 +176,8 @@ Events: ``` The events section includes a Warning event with the AddedOrUpdatedWithError reason. + + +## Using IPV4 and IPV6 Addresses with GlobalConfiguration + +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 new file mode 100644 index 0000000000..07e0c8d1e8 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/README.md @@ -0,0 +1,237 @@ +# Custom IPv4 and IPv6 Address Listeners + +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 + +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 + ``` + +**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, +here we deploy a GlobalConfiguration resource with the listeners we want to use in our VirtualServer. + + ```yaml +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 + ipv4: 127.0.0.1 + - name: ip-listener-2-https + port: 8443 + protocol: HTTP + ipv4: 127.0.0.2 + ipv6: ::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 and the GlobalConfiguration: + + ```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 + ``` + + ```console + kubectl describe globalconfiguration nginx-configuration -n nginx-ingress + ``` + + ```console + . . . + Spec: + Listeners: + ipv4: 127.0.0.1 + Name: ip-listener-1-http + Port: 8083 + Protocol: HTTP + ipv4: 127.0.0.2 + ipv6: ::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 can see that the specific ips and ports +are set and listening by using the below commands: + + Access the NGINX Pod: + + ```console + kubectl get pods -n nginx-ingress + ``` + + ```text + NAME READY STATUS RESTARTS AGE + nginx-ingress-65cd79bb8f-crst4 1/1 Running 0 97s + ``` + + ```console + kubectl debug -it nginx-ingress-65cd79bb8f-crst4 --image=busybox:1.28 --target=nginx-ingress + ``` + + ```console + / # 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 - + ``` + + 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; + ... + ``` diff --git a/examples/custom-resources/custom-ip-listeners/virtualserver/cafe-secret.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/cafe-secret.yaml new file mode 100644 index 0000000000..8f9fd84855 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/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/virtualserver/cafe-virtual-server.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/cafe-virtual-server.yaml new file mode 100644 index 0000000000..08f940d406 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/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/virtualserver/cafe.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/cafe.yaml new file mode 100644 index 0000000000..eebdd58535 --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/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/virtualserver/global-configuration.yaml b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml new file mode 100644 index 0000000000..361e6a742a --- /dev/null +++ b/examples/custom-resources/custom-ip-listeners/virtualserver/global-configuration.yaml @@ -0,0 +1,18 @@ +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 + ipv4: 127.0.0.1 + ipv6: ::1 + - name: ip-listener-2-https + port: 8443 + protocol: HTTP + ipv4: 127.0.0.2 + ipv6: ::1 + ssl: true diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index 23e822183e..2f66b55e17 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/http.go b/internal/configs/version2/http.go index 00ba221451..254554319e 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -57,6 +57,10 @@ type Server struct { ServerName string StatusZone string CustomListeners bool + 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 9669fb802e..b81ad28978 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -16,10 +16,10 @@ const ( https ) -type listenerType int +type ipType int const ( - ipv4 listenerType = iota + ipv4 ipType = iota ipv6 ) @@ -53,29 +53,32 @@ func makeListener(listenerType protocol, s Server) string { } func buildDefaultListenerDirectives(listenerType protocol, s Server) string { - var directives string port := getDefaultPort(listenerType) - - directives += buildListenDirective(port, s.ProxyProtocol, ipv4) - - if !s.DisableIPV6 { - directives += spacing - directives += buildListenDirective(port, s.ProxyProtocol, ipv6) - } - - return directives + return buildListenerDirectives(listenerType, s, port) } 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) - directives += buildListenDirective(port, s.ProxyProtocol, ipv4) + 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 { directives += spacing - directives += buildListenDirective(port, s.ProxyProtocol, ipv6) + 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,14 +99,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, ipType ipType) string { base := "listen" var directive string - if listenType == ipv6 { - directive = base + " [::]:" + port + if ipType == ipv6 { + if ip != "" { + directive = fmt.Sprintf("%s [%s]:%s", base, ip, port) + } else { + directive = fmt.Sprintf("%s [::]:%s", base, port) + } } else { - directive = base + " " + port + if ip != "" { + directive = fmt.Sprintf("%s %s:%s", base, ip, port) + } else { + directive = fmt.Sprintf("%s %s", base, port) + } } if proxyProtocol { @@ -114,11 +125,11 @@ func buildListenDirective(port string, proxyProtocol bool, listenType listenerTy return directive } -func buildTransportListenDirective(listenType listenerType, 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/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index a79fd47bc4..de394cd8ce 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -268,6 +268,211 @@ func TestMakeHTTPSListener(t *testing.T) { } } +func TestMakeHTTPListenerAndHTTPSListenerWithCustomIPs(t *testing.T) { + t.Parallel() + + testCases := []struct { + server Server + expected string + }{ + {server: Server{ + CustomListeners: true, + DisableIPV6: true, + ProxyProtocol: false, + HTTPPort: 80, + HTTPIPv4: "192.168.0.2", + }, expected: "listen 192.168.0.2:80;\n"}, + {server: Server{ + CustomListeners: true, + DisableIPV6: false, + ProxyProtocol: false, + HTTPPort: 80, + HTTPIPv4: "192.168.1.2", + }, expected: "listen 192.168.1.2:80;\n listen [::]:80;\n"}, + {server: Server{ + CustomListeners: true, + HTTPPort: 81, + HTTPIPv4: "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, + HTTPIPv4: "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 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 TestMakeTransportListener(t *testing.T) { t.Parallel() 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.go b/internal/configs/virtualserver.go index 23facedcbf..86bc6cdd99 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -87,6 +87,10 @@ type VirtualServerEx struct { VirtualServer *conf_v1.VirtualServer HTTPPort int HTTPSPort int + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string Endpoints map[string][]string VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool @@ -835,6 +839,10 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( StatusZone: vsEx.VirtualServer.Spec.Host, HTTPPort: vsEx.HTTPPort, HTTPSPort: vsEx.HTTPSPort, + HTTPIPv4: vsEx.HTTPIPv4, + HTTPIPv6: vsEx.HTTPIPv6, + HTTPSIPv4: vsEx.HTTPSIPv4, + HTTPSIPv6: vsEx.HTTPSIPv6, CustomListeners: useCustomListeners, ProxyProtocol: vsc.cfgParams.ProxyProtocol, SSL: sslConfig, diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index db53b48bdb..bb1741cbda 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() @@ -15543,6 +15702,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/internal/k8s/configuration.go b/internal/k8s/configuration.go index f4d9bb05a3..950460566e 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -200,6 +200,10 @@ type VirtualServerConfiguration struct { Warnings []string HTTPPort int HTTPSPort int + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string } // NewVirtualServerConfiguration creates a VirtualServerConfiguration. @@ -813,19 +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 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 - } + 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 } } + + 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. @@ -1783,6 +1788,15 @@ 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.HTTPIPv4 != oldVsc.HTTPIPv4 { + updatedHosts = append(updatedHosts, h) + } + + if newVsc.HTTPIPv6 != oldVsc.HTTPIPv6 { + updatedHosts = append(updatedHosts, h) + } + } return removedHosts, updatedHosts, addedHosts diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index a66429a757..09bd5eb6f5 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -2499,6 +2499,10 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. if vsc, ok := resource.(*VirtualServerConfiguration); ok { virtualServerEx.HTTPPort = vsc.HTTPPort virtualServerEx.HTTPSPort = vsc.HTTPSPort + 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 6048882121..76c5261980 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -411,6 +411,8 @@ type GlobalConfigurationSpec struct { type Listener struct { Name string `json:"name"` Port int `json:"port"` + IPv4IP string `json:"ipv4"` + IPv6IP string `json:"ipv6"` 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 daf7579320..689fa625ef 100644 --- a/pkg/apis/configuration/validation/globalconfiguration.go +++ b/pkg/apis/configuration/validation/globalconfiguration.go @@ -11,6 +11,13 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) +type ipType int + +const ( + ipv4 ipType = iota + ipv6 +) + var allowedProtocols = map[string]bool{ "TCP": true, "UDP": true, @@ -45,54 +52,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) + ipv4PortProtocolCombinations := 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 { 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.checkForDuplicateName(listenerNames, l, idxPath); err != nil { + allErrs = append(allErrs, err) + continue + } + + 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 { + allErrs = append(allErrs, err) + continue } + + gcv.updatePortProtocolCombinations(ipv4PortProtocolCombinations, ipv4, l) + gcv.updatePortProtocolCombinations(ipv6PortProtocolCombinations, ipv6, l) + + validListeners = append(validListeners, l) } return validListeners, allErrs } +// 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 +} + +// 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 + } + + 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.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 for both IPv4 and IPv6. +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) + } + 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) } @@ -101,6 +148,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 } @@ -136,6 +185,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 de3a3af458..709967db71 100644 --- a/pkg/apis/configuration/validation/globalconfiguration_test.go +++ b/pkg/apis/configuration/validation/globalconfiguration_test.go @@ -74,6 +74,13 @@ func TestValidateListeners(t *testing.T) { Port: 53, Protocol: "UDP", }, + { + Name: "test-listener-ip", + IPv4IP: "127.0.0.1", + IPv6IP: "::1", + Port: 8080, + Protocol: "HTTP", + }, } gcv := createGlobalConfigurationValidator() @@ -84,6 +91,202 @@ func TestValidateListeners(t *testing.T) { } } +func TestValidateListeners_FailsOnInvalidIP(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + listeners []conf_v1.Listener + }{ + { + name: "Invalid IPv4 IP", + listeners: []conf_v1.Listener{ + {Name: "test-listener-1", IPv4IP: "267.0.0.1", Port: 8082, Protocol: "HTTP"}, + }, + }, + { + 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-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"}, + }, + }, + } + + 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_FailsOnPortProtocolConflictsSameIP(t *testing.T) { + t.Parallel() + + 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() + + 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_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", 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", IPv6IP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", Port: 8080, 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("Unexpected errors for valid listeners: %v", allErrs) + } + }) + } +} + +func TestValidateListeners_FailsOnMixedInvalidIPs(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + listeners []conf_v1.Listener + }{ + { + 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", IPv6IP: "2001::85a3::8a2e:370:7334", Port: 9090, Protocol: "TCP"}, + }, + }, + { + name: "Invalid IPv4 and Valid IPv6", + listeners: []conf_v1.Listener{ + {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"}, + }, + }, + } + + 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 mixed invalid IPs, but got none") + } else { + for _, err := range allErrs { + t.Logf("Caught expected error: %v", err) + } + } + }) + } +} + func TestValidateListenersFails(t *testing.T) { t.Parallel() tests := []struct { 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..26ea0bf9b6 --- /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 + ipv4: 127.0.0.1 + ipv6: ::1 + - name: https-8445 + port: 8445 + protocol: HTTP + 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 new file mode 100644 index 0000000000..1b98231c50 --- /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 + ipv4: 127.0.0.1 + - name: https-8445 + port: 8445 + protocol: HTTP + ipv6: ::1 + ssl: true 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)