From f438606da74ece645a05c6265ecd646145be5333 Mon Sep 17 00:00:00 2001 From: Michael Pleshakov Date: Mon, 11 Dec 2023 16:31:12 -0500 Subject: [PATCH 1/4] Add conformance test for isolation of HTTP listeners - Introduce GatewayHTTPListenerIsolation extended feature - Add GatewayHTTPListenerIsolation conformance test --- ...-isolation-with-hostname-intersection.yaml | 131 +++++++++++++ .../tests/gateway-http-listener-isolation.go | 176 ++++++++++++++++++ .../gateway-http-listener-isolation.yaml | 111 +++++++++++ conformance/utils/suite/features.go | 5 + 4 files changed, 423 insertions(+) create mode 100644 conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml create mode 100644 conformance/tests/gateway-http-listener-isolation.go create mode 100644 conformance/tests/gateway-http-listener-isolation.yaml diff --git a/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml b/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml new file mode 100644 index 0000000000..4f00531be2 --- /dev/null +++ b/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml @@ -0,0 +1,131 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: http-listener-isolation-with-hostname-intersection + namespace: gateway-conformance-infra +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: empty-hostname + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: All + - name: wildcard-example-com + port: 80 + protocol: HTTP + hostname: "*.example.com" + allowedRoutes: + namespaces: + from: All + - name: wildcard-foo-example-com + port: 80 + protocol: HTTP + hostname: "*.foo.example.com" + allowedRoutes: + namespaces: + from: All + - name: abc-foo-example-com + port: 80 + protocol: HTTP + hostname: "abc.foo.example.com" + allowedRoutes: + namespaces: + from: All +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-empty-hostname-with-hostname-intersection + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation-with-hostname-intersection + namespace: gateway-conformance-infra + sectionName: empty-hostname + hostnames: + - "bar.com" + - "*.example.com" # request matching is prevented by the isolation wildcard-example-com listener + - "*.foo.example.com" # request matching is prevented by the isolation wildcard-foo-example-com listener + - "abc.foo.example.com" # request matching is prevented by the isolation of abc-foo-example-com listener + rules: + - matches: + - path: + type: PathPrefix + value: /empty-hostname + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-wildcard-example-com-with-hostname-intersection + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation-with-hostname-intersection + namespace: gateway-conformance-infra + sectionName: wildcard-example-com + hostnames: + - "bar.com" # doesn't match wildcard-example-com listener + - "*.example.com" + - "*.foo.example.com" # request matching is prevented by the isolation of wildcard-foo-example-com listener + - "abc.foo.example.com" # request matching is prevented by the isolation of abc-foo-example-com listener + rules: + - matches: + - path: + type: PathPrefix + value: /wildcard-example-com + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-wildcard-foo-example-com-with-hostname-intersection + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation-with-hostname-intersection + namespace: gateway-conformance-infra + sectionName: wildcard-foo-example-com + hostnames: + - "bar.com" # doesn't match wildcard-foo-example-com listener + - "*.example.com" # this becomes *.foo.example.com, as the hostname cannot be less specific than *.foo.example.com of the listener + - "*.foo.example.com" + - "abc.foo.example.com" # request matching is prevented by the isolation abc-foo-example-com listener + rules: + - matches: + - path: + type: PathPrefix + value: /wildcard-foo-example-com + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-abc-foo-example-com-with-hostname-intersection + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation-with-hostname-intersection + namespace: gateway-conformance-infra + sectionName: abc-foo-example-com + hostnames: + - "bar.com" # doesn't match abc-foo-example-com listener + - "*.example.com" # becomes abc.foo.example.com as it cannot be less specific than abc.foo.example.com of the listener + - "*.foo.example.com" # becomes abc.foo.example.com as it cannot be less specific than abc.foo.example.com of the listener + - "abc.foo.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /abc-foo-example-com + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/conformance/tests/gateway-http-listener-isolation.go b/conformance/tests/gateway-http-listener-isolation.go new file mode 100644 index 0000000000..baec6ee379 --- /dev/null +++ b/conformance/tests/gateway-http-listener-isolation.go @@ -0,0 +1,176 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, GatewayHTTPListenerIsolation) +} + +var GatewayHTTPListenerIsolation = suite.ConformanceTest{ + ShortName: "GatewayHTTPListenerIsolation", + Description: "Listener isolation for HTTP listeners with multiple listeners and HTTPRoutes", + Features: []suite.SupportedFeature{ + suite.SupportGateway, + suite.SupportGatewayHTTPListenerIsolation, + suite.SupportHTTPRoute, + }, + Manifests: []string{ + "tests/gateway-http-listener-isolation.yaml", + "tests/gateway-http-listener-isolation-with-hostname-intersection.yaml", + }, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + + kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) + + testCases := []http.ExpectedResponse{ + // Requests to the empty-hostname listener + { + Request: http.Request{Host: "bar.com", Path: "/empty-hostname"}, + Backend: "infra-backend-v1", + Namespace: ns, + }, + { + Request: http.Request{Host: "bar.com", Path: "/wildcard-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "bar.com", Path: "/wildcard-foo-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "bar.com", Path: "/abc-foo-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + // Requests to the wildcard-example-com listener + { + Request: http.Request{Host: "bar.example.com", Path: "/empty-hostname"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "bar.example.com", Path: "/wildcard-example-com"}, + Backend: "infra-backend-v1", + Namespace: ns, + }, + { + Request: http.Request{Host: "bar.example.com", Path: "/wildcard-foo-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "bar.example.com", Path: "/abc-foo-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + // Requests to the foo-wildcard-example-com listener + { + Request: http.Request{Host: "bar.foo.example.com", Path: "/empty-hostname"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "bar.foo.example.com", Path: "/wildcard-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "bar.foo.example.com", Path: "/wildcard-foo-example-com"}, + Backend: "infra-backend-v1", + Namespace: ns, + }, + { + Request: http.Request{Host: "bar.foo.example.com", Path: "/abc-foo-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + // Requests to the abc-foo-example-com listener + { + Request: http.Request{Host: "abc.foo.example.com", Path: "/empty-hostname"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "abc.foo.example.com", Path: "/wildcard-example-com"}, + Response: http.Response{StatusCode: 404}, + }, + { + Request: http.Request{Host: "abc.foo.example.com", Path: "/wildcard-foo-example-com"}, + Response: http.Response{StatusCode: 404}, + Namespace: ns, + }, + { + Request: http.Request{Host: "abc.foo.example.com", Path: "/abc-foo-example-com"}, + Backend: "infra-backend-v1", + Namespace: ns, + }, + } + + t.Run("hostnames are configured only in listeners", func(t *testing.T) { + gwNN := types.NamespacedName{Name: "http-listener-isolation", Namespace: ns} + routes := []types.NamespacedName{ + {Namespace: ns, Name: "attaches-to-empty-hostname"}, + {Namespace: ns, Name: "attaches-to-wildcard-example-com"}, + {Namespace: ns, Name: "attaches-to-wildcard-foo-example-com"}, + {Namespace: ns, Name: "attaches-to-abc-foo-example-com"}, + } + + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routes...) + for _, routeNN := range routes { + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + } + + for i := range testCases { + // Declare tc here to avoid loop variable + // reuse issues across parallel tests. + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + }) + } + }) + + t.Run("intersecting hostnames are configured in listeners and HTTPRoutes", func(t *testing.T) { + gwNN := types.NamespacedName{Name: "http-listener-isolation-with-hostname-intersection", Namespace: ns} + routes := []types.NamespacedName{ + {Namespace: ns, Name: "attaches-to-empty-hostname-with-hostname-intersection"}, + {Namespace: ns, Name: "attaches-to-wildcard-example-com-with-hostname-intersection"}, + {Namespace: ns, Name: "attaches-to-wildcard-foo-example-com-with-hostname-intersection"}, + {Namespace: ns, Name: "attaches-to-abc-foo-example-com-with-hostname-intersection"}, + } + + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routes...) + for _, routeNN := range routes { + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + } + + for i := range testCases { + // Declare tc here to avoid loop variable + // reuse issues across parallel tests. + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + }) + } + }) + }, +} diff --git a/conformance/tests/gateway-http-listener-isolation.yaml b/conformance/tests/gateway-http-listener-isolation.yaml new file mode 100644 index 0000000000..ddb49e4c3c --- /dev/null +++ b/conformance/tests/gateway-http-listener-isolation.yaml @@ -0,0 +1,111 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: http-listener-isolation + namespace: gateway-conformance-infra +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: empty-hostname + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: All + - name: wildcard-example-com + port: 80 + protocol: HTTP + hostname: "*.example.com" + allowedRoutes: + namespaces: + from: All + - name: wildcard-foo-example-com + port: 80 + protocol: HTTP + hostname: "*.foo.example.com" + allowedRoutes: + namespaces: + from: All + - name: abc-foo-example-com + port: 80 + protocol: HTTP + hostname: "abc.foo.example.com" + allowedRoutes: + namespaces: + from: All +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-empty-hostname + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation + namespace: gateway-conformance-infra + sectionName: empty-hostname + rules: + - matches: + - path: + type: PathPrefix + value: /empty-hostname + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-wildcard-example-com + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation + namespace: gateway-conformance-infra + sectionName: wildcard-example-com + rules: + - matches: + - path: + type: PathPrefix + value: /wildcard-example-com + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-wildcard-foo-example-com + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation + namespace: gateway-conformance-infra + sectionName: wildcard-foo-example-com + rules: + - matches: + - path: + type: PathPrefix + value: /wildcard-foo-example-com + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: attaches-to-abc-foo-example-com + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: http-listener-isolation + namespace: gateway-conformance-infra + sectionName: abc-foo-example-com + rules: + - matches: + - path: + type: PathPrefix + value: /abc-foo-example-com + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/conformance/utils/suite/features.go b/conformance/utils/suite/features.go index 79a0cc74b3..7d50df91ff 100644 --- a/conformance/utils/suite/features.go +++ b/conformance/utils/suite/features.go @@ -54,6 +54,10 @@ const ( // of allocating pre-determined addresses, rather than dynamically having // addresses allocated for it. SupportGatewayStaticAddresses SupportedFeature = "GatewayStaticAddresses" + + // SupportGatewayHTTPListenerIsolation option indicates support for the isolation + // of HTTP listeners. + SupportGatewayHTTPListenerIsolation SupportedFeature = "GatewayHTTPListenerIsolation" ) // GatewayExtendedFeatures are extra generic features that implementations may @@ -61,6 +65,7 @@ const ( var GatewayExtendedFeatures = sets.New( SupportGatewayPort8080, SupportGatewayStaticAddresses, + SupportGatewayHTTPListenerIsolation, ) // ----------------------------------------------------------------------------- From 2405c98a1ac42eb7ff264ee00294538d5e2d5811 Mon Sep 17 00:00:00 2001 From: Michael Pleshakov Date: Mon, 19 Feb 2024 23:15:29 -0500 Subject: [PATCH 2/4] Update conformance/tests/gateway-http-listener-isolation.go Co-authored-by: Rob Scott --- conformance/tests/gateway-http-listener-isolation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/tests/gateway-http-listener-isolation.go b/conformance/tests/gateway-http-listener-isolation.go index baec6ee379..f78bf5078a 100644 --- a/conformance/tests/gateway-http-listener-isolation.go +++ b/conformance/tests/gateway-http-listener-isolation.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The Kubernetes Authors. +Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 749d51e3c7d5357f581c3e7494cdcf6759ceba31 Mon Sep 17 00:00:00 2001 From: Michael Pleshakov Date: Mon, 19 Feb 2024 23:15:40 -0500 Subject: [PATCH 3/4] Update conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml Co-authored-by: Rob Scott --- ...eway-http-listener-isolation-with-hostname-intersection.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml b/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml index 4f00531be2..04207d6e4b 100644 --- a/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml +++ b/conformance/tests/gateway-http-listener-isolation-with-hostname-intersection.yaml @@ -1,4 +1,4 @@ -apiVersion: gateway.networking.k8s.io/v1beta1 +apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: http-listener-isolation-with-hostname-intersection From d6abccdaca053589b1d550512693445b2f6436d6 Mon Sep 17 00:00:00 2001 From: Michael Pleshakov Date: Mon, 19 Feb 2024 23:19:23 -0500 Subject: [PATCH 4/4] Update gateway resource api version --- conformance/tests/gateway-http-listener-isolation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/tests/gateway-http-listener-isolation.yaml b/conformance/tests/gateway-http-listener-isolation.yaml index ddb49e4c3c..c9903b33e8 100644 --- a/conformance/tests/gateway-http-listener-isolation.yaml +++ b/conformance/tests/gateway-http-listener-isolation.yaml @@ -1,4 +1,4 @@ -apiVersion: gateway.networking.k8s.io/v1beta1 +apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: http-listener-isolation