diff --git a/deployments/service/loadbalancer-aws-elb.yaml b/deployments/service/loadbalancer-aws-elb.yaml index d8b8aec359..ee66f46244 100644 --- a/deployments/service/loadbalancer-aws-elb.yaml +++ b/deployments/service/loadbalancer-aws-elb.yaml @@ -4,8 +4,8 @@ metadata: name: nginx-ingress namespace: nginx-ingress annotations: - service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp" - service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" + service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" spec: type: LoadBalancer ports: diff --git a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md index d703caaeb6..c6f3f7e4ba 100644 --- a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md +++ b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md @@ -51,7 +51,7 @@ spec: {{% table %}} |Field | Description | Type | Required | | ---| ---| ---| --- | -|``host`` | The host (domain name) of the server. Must be a valid subdomain as defined in RFC 1123, such as ``my-app`` or ``hello.example.com``. Wildcard domains like ``*.example.com`` are not allowed. The ``host`` value needs to be unique among all Ingress and VirtualServer resources. See also [Handling Host and Listener Collisions](/nginx-ingress-controller/configuration/handling-host-and-listener-collisions). | ``string`` | Yes | +|``host`` | The host (domain name) of the server. Must be a valid subdomain as defined in RFC 1123, such as ``my-app`` or ``hello.example.com``. When using a wildcard domain like ``*.example.com`` the domain must be contained in double quotes. The ``host`` value needs to be unique among all Ingress and VirtualServer resources. See also [Handling Host and Listener Collisions](/nginx-ingress-controller/configuration/handling-host-and-listener-collisions). | ``string`` | Yes | |``tls`` | The TLS termination configuration. | [tls](#virtualservertls) | No | |``externalDNS`` | The externalDNS configuration for a VirtualServer. | [externalDNS](#virtualserverexternaldns) | No | |``dos`` | A reference to a DosProtectedResource, setting this enables DOS protection of the VirtualServer. | ``string`` | No | @@ -249,7 +249,7 @@ Note that each subroute must have a `path` that starts with the same prefix (her {{% table %}} |Field | Description | Type | Required | | ---| ---| ---| --- | -|``host`` | The host (domain name) of the server. Must be a valid subdomain as defined in RFC 1123, such as ``my-app`` or ``hello.example.com``. Wildcard domains like ``*.example.com`` are not allowed. Must be the same as the ``host`` of the VirtualServer that references this resource. | ``string`` | Yes | +|``host`` | The host (domain name) of the server. Must be a valid subdomain as defined in RFC 1123, such as ``my-app`` or ``hello.example.com``. When using a wildcard domain like ``*.example.com`` the domain must be contained in double quotes. Must be the same as the ``host`` of the VirtualServer that references this resource. | ``string`` | Yes | |``upstreams`` | A list of upstreams. | [[]upstream](#upstream) | No | |``subroutes`` | A list of subroutes. | [[]subroute](#virtualserverroutesubroute) | No | |``ingressClassName`` | Specifies which Ingress Controller must handle the VirtualServerRoute resource. Must be the same as the ``ingressClassName`` of the VirtualServer that references this resource. | ``string``_ | No | diff --git a/internal/externaldns/sync.go b/internal/externaldns/sync.go index 16295d0109..debaf6a6c1 100644 --- a/internal/externaldns/sync.go +++ b/internal/externaldns/sync.go @@ -142,7 +142,7 @@ func buildDNSEndpoint(extdnsLister []extdnslisters.DNSEndpointLister, vs *vsapi. var existingDNSEndpoint *extdnsapi.DNSEndpoint var err error for _, el := range extdnsLister { - existingDNSEndpoint, err = el.DNSEndpoints(vs.Namespace).Get(vs.Spec.Host) + existingDNSEndpoint, err = el.DNSEndpoints(vs.Namespace).Get(vs.ObjectMeta.Name) if err == nil { break } @@ -154,7 +154,7 @@ func buildDNSEndpoint(extdnsLister []extdnslisters.DNSEndpointLister, vs *vsapi. dnsEndpoint := &extdnsapi.DNSEndpoint{ ObjectMeta: metav1.ObjectMeta{ - Name: vs.Spec.Host, + Name: vs.ObjectMeta.Name, Namespace: vs.Namespace, Labels: vs.Labels, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(vs, controllerGVK)}, diff --git a/pkg/apis/configuration/validation/virtualserver.go b/pkg/apis/configuration/validation/virtualserver.go index 29a937f7cb..8a8805f690 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -92,6 +92,8 @@ func (vsv *VirtualServerValidator) validateVirtualServerSpec(spec *v1.VirtualSer return allErrs } +const wildcardPrefix = "*." + func validateHost(host string, fieldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -99,8 +101,14 @@ func validateHost(host string, fieldPath *field.Path) field.ErrorList { return append(allErrs, field.Required(fieldPath, "")) } - for _, msg := range validation.IsDNS1123Subdomain(host) { - allErrs = append(allErrs, field.Invalid(fieldPath, host, msg)) + if strings.HasPrefix(host, wildcardPrefix) { + for _, msg := range validation.IsWildcardDNS1123Subdomain(host) { + allErrs = append(allErrs, field.Invalid(fieldPath, host, msg)) + } + } else { + for _, msg := range validation.IsDNS1123Subdomain(host) { + allErrs = append(allErrs, field.Invalid(fieldPath, host, msg)) + } } return allErrs diff --git a/pkg/apis/configuration/validation/virtualserver_test.go b/pkg/apis/configuration/validation/virtualserver_test.go index d982b905b9..de16fef91a 100644 --- a/pkg/apis/configuration/validation/virtualserver_test.go +++ b/pkg/apis/configuration/validation/virtualserver_test.go @@ -74,6 +74,7 @@ func TestValidateHost(t *testing.T) { "hello", "example.com", "hello-world-1", + "*.example.com", } for _, h := range validHosts { @@ -89,6 +90,7 @@ func TestValidateHost(t *testing.T) { "..", ".example.com", "-hello-world-1", + "*.-example.com", } for _, h := range invalidHosts { diff --git a/tests/data/virtual-server-wildcard/virtual-server-wildcard.yaml b/tests/data/virtual-server-wildcard/virtual-server-wildcard.yaml new file mode 100644 index 0000000000..c3dcffdf36 --- /dev/null +++ b/tests/data/virtual-server-wildcard/virtual-server-wildcard.yaml @@ -0,0 +1,20 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: virtual-server-wildcard +spec: + host: "*.example.com" + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + - name: backend1 + service: backend1-svc + port: 80 + routes: + - path: "/backend1" + action: + pass: backend1 + - path: "/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/suite/test_virtual_server_externaldns.py b/tests/suite/test_virtual_server_externaldns.py index a674afd23b..ef089eeece 100644 --- a/tests/suite/test_virtual_server_externaldns.py +++ b/tests/suite/test_virtual_server_externaldns.py @@ -4,7 +4,7 @@ from suite.custom_resources_utils import is_dnsendpoint_present from suite.resources_utils import get_events, wait_before_test from suite.vs_vsr_resources_utils import patch_virtual_server_from_yaml -from suite.yaml_utils import get_first_host_from_yaml, get_namespace_from_yaml +from suite.yaml_utils import get_name_from_yaml, get_namespace_from_yaml VS_YAML = f"{TEST_DATA}/virtual-server-external-dns/standard/virtual-server.yaml" @@ -27,11 +27,11 @@ def test_responses_after_setup( self, kube_apis, crd_ingress_controller_with_ed, create_externaldns, virtual_server_setup ): print("\nStep 1: Verify DNSEndpoint exists") - dns_name = get_first_host_from_yaml(VS_YAML) + dns_ep_name = get_name_from_yaml(VS_YAML) retry = 0 - dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_name, virtual_server_setup.namespace) + dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_ep_name, virtual_server_setup.namespace) while dep == False and retry <= 60: - dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_name, virtual_server_setup.namespace) + dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_ep_name, virtual_server_setup.namespace) retry += 1 wait_before_test(1) print(f"DNSEndpoint not created, retrying... #{retry}") diff --git a/tests/suite/test_virtual_server_wildcard.py b/tests/suite/test_virtual_server_wildcard.py new file mode 100644 index 0000000000..79a92d87cc --- /dev/null +++ b/tests/suite/test_virtual_server_wildcard.py @@ -0,0 +1,55 @@ +import time + +import pytest +from settings import TEST_DATA +from suite.custom_assertions import wait_and_assert_status_code +from suite.custom_resources_utils import read_custom_resource +from suite.resources_utils import wait_before_test +from suite.vs_vsr_resources_utils import create_virtual_server_from_yaml, delete_virtual_server + + +@pytest.mark.vs +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + {"type": "complete", "extra_args": [f"-enable-custom-resources"]}, + {"example": "virtual-server", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestVirtualServerWildcard: + def test_vs_status(self, kube_apis, crd_ingress_controller, virtual_server_setup): + + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) + wait_and_assert_status_code(404, virtual_server_setup.backend_1_url, "test.example.com") + wait_and_assert_status_code(404, virtual_server_setup.backend_2_url, "test.example.com") + + # create virtual server with wildcard hostname + retry = 0 + manifest_vs_wc = f"{TEST_DATA}/virtual-server-wildcard/virtual-server-wildcard.yaml" + vs_wc_name = create_virtual_server_from_yaml( + kube_apis.custom_objects, manifest_vs_wc, virtual_server_setup.namespace + ) + wait_before_test() + response = {} + while ("status" not in response) and (retry <= 30): + print("Waiting for VS status update...") + time.sleep(1) + retry += 1 + response = read_custom_resource( + kube_apis.custom_objects, + virtual_server_setup.namespace, + "virtualservers", + vs_wc_name, + ) + + assert response["status"]["reason"] == "AddedOrUpdated" and response["status"]["state"] == "Valid" + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, "test.example.com") + wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, "test.example.com") + wait_and_assert_status_code(404, virtual_server_setup.backend_1_url, "test.xexample.com") + wait_and_assert_status_code(404, virtual_server_setup.backend_2_url, "test.xexample.com") + + delete_virtual_server(kube_apis.custom_objects, vs_wc_name, virtual_server_setup.namespace)