Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Listener Isolation for hostnames occupied by other listeners #3067

Merged
merged 4 commits into from
Jan 29, 2025

Conversation

salonichf5
Copy link
Contributor

@salonichf5 salonichf5 commented Jan 27, 2025

Proposed changes

Write a clear and concise description that helps reviewers understand the purpose and impact of your changes. Use the
following format:

Problem: Users want to be able to isolate listeners for routes.

Solution: Adds functionality to filter out listener hostnames from the accepted hostnames of a route which belong to another listener.

Testing:

  • Unit tests added
  • Conformance test added and pass successfully.
CONFORMANCE PROFILE
apiVersion: gateway.networking.k8s.io/v1
date: "2025-01-26T21:09:28Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.2.1
implementation:
  contact:
  - https://github.com/nginx/nginx-gateway-fabric/discussions/new/choose
  organization: nginxinc
  project: nginx-gateway-fabric
  url: https://github.com/nginx/nginx-gateway-fabric
  version: edge
kind: ConformanceReport
mode: default
profiles:
- core:
    result: success
    statistics:
      Failed: 0
      Passed: 11
      Skipped: 0
  name: GATEWAY-TLS
  summary: Core tests succeeded.
- core:
    result: success
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 0
  name: GATEWAY-GRPC
  summary: Core tests succeeded.
- core:
    result: success
    statistics:
      Failed: 0
      Passed: 33
      Skipped: 0
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 11
      Skipped: 0
    supportedFeatures:
    - GatewayHTTPListenerIsolation
    - GatewayPort8080
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRedirect
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteDestinationPortMatching
    - HTTPRouteParentRefPort
    - HTTPRouteRequestMirror
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestTimeout
  name: GATEWAY-HTTP
  summary: Core tests succeeded. Extended tests succeeded.
  • Manual testing for L7 routes
    Listeners that are configured with the gateway
  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:

HTTPRoutes

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: attaches-to-empty-hostname-with-hostname-intersection
spec:
  parentRefs:
  - name: gateway
    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: coffee
      port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: attaches-to-wildcard-example-com-with-hostname-intersection
spec:
  parentRefs:
  - name: gateway
    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: coffee
      port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: attaches-to-wildcard-foo-example-com-with-hostname-intersection
spec:
  parentRefs:
  - name: gateway
    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: coffee
      port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: attaches-to-abc-foo-example-com-with-hostname-intersection

spec:
  parentRefs:
  - name: gateway

    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: coffee
      port: 80

For listener isolation to work, we need

  1. path empty-hostname to not get configured with listeners wildcard-example-com, wildcard-foo-example-com, abc-foo-example-com . When collecting accepted hostnames, the route attaches-to-empty-hostname-with-hostname-intersection attaches to all hostnames since it is attached to catch-all listener (no hostname provided), but these hostnames are part of other listeners.
  • "*.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

So,

  • bar.com/empty-hostname - 200 OK response
  • bar.com/wildcard-example-com - 404 Not Found response
  • bar.com/foo-wildcard-example-com - 404 Not Found response
  • bar.com/abc-foo-example-com - 404 Not Found response
curl --resolve bar.com:$GW_PORT:$GW_IP http://bar.com:$GW_PORT/empty-hostname --include
Handling connection for 8080
HTTP/1.1 200 OK


curl --resolve bar.com:$GW_PORT:$GW_IP http://bar.com:$GW_PORT/wildcard-example-com --include
Handling connection for 8080
HTTP/1.1 404 Not Found

 curl --resolve bar.com:$GW_PORT:$GW_IP http://bar.com:$GW_PORT/foo-wildcard-example-com --include
Handling connection for 8080
HTTP/1.1 404 Not Found

curl --resolve bar.com:$GW_PORT:$GW_IP http://bar.com:$GW_PORT/abc-foo-example-com --include
Handling connection for 8080
HTTP/1.1 404 Not Found
  1. path wildcard-example-com to not get configured with listeners wildcard-foo-example-com, abc-foo-example-com
    When collecting accepted hostnames, the route attaches-to-wildcard-example-com-with-hostname-intersection is associated to a *.example.com so it attaches to these hostnames. But these listeners need to be isolated since the hostname associated with them is part of a listener attached to another route.
  • "*.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

  • Manual testing for L4 Routes

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: gateway
  namespace: default
spec:
  gatewayClassName: nginx
  listeners:
  - name: tls
    port: 443
    protocol: TLS
    allowedRoutes:
      namespaces:
        from: All
      kinds:
        - kind: TLSRoute
    tls:
      mode: Passthrough
  - name: tls-wildcard
    port: 443
    protocol: TLS
    hostname: "*.example.com"
    allowedRoutes:
      namespaces:
        from: All
      kinds:
        - kind: TLSRoute
    tls:
      mode: Passthrough
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
  name: tls-secure-app-route
  namespace: default
spec:
  parentRefs:
  - name: gateway
    namespace: default
    sectionName: tls
  hostnames:
  - "bar.com"
  - "*.example.com"
  rules:
  - backendRefs:
    - name: secure-app
      port: 8443

As per above configuration, this route should only be configured for bar.com since *.example.com is part of another listener and the sectionName belongs to first listener tls

curl --resolve bar.com:8443:$GW_IP https://bar.com:8443 --insecure -v
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.27.3
< Date: Tue, 28 Jan 2025 20:57:16 GMT
< Content-Type: text/plain
< Content-Length: 44
< Connection: keep-alive
<
hello from pod \secure-app-7bfb966596-jq5tm

Curl to a hostname of type *.example.com fails.

curl --resolve cafe.example.com:8443:$GW_IP https://cafe.example.com:8443 --insecure -v
* Added cafe.example.com:8443:127.0.0.1 to DNS cache
* Hostname cafe.example.com was found in DNS cache
*   Trying 127.0.0.1:8443...
* Connected to cafe.example.com (127.0.0.1) port 8443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
Handling connection for 8443
* LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to cafe.example.com:8443
* Closing connection
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to cafe.example.com:8443

Please focus on (optional): If you any specific areas where you would like reviewers to focus their attention or provide
specific feedback, add them here.

Closes #1175

Checklist

Before creating a PR, run through this checklist and mark each as complete.

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have checked that all unit tests pass after adding my changes
  • I have updated necessary documentation
  • I have rebased my branch onto main
  • I will ensure my PR is targeting the main branch and pulling from my branch from my own fork

Release notes

If this PR introduces a change that affects users and needs to be mentioned in the release notes,
please add a brief note that summarizes the change.

Listener isolation supported for all routes. 

@github-actions github-actions bot added enhancement New feature or request tests Pull requests that update tests labels Jan 27, 2025
@salonichf5 salonichf5 marked this pull request as ready for review January 27, 2025 19:53
@salonichf5 salonichf5 requested a review from a team as a code owner January 27, 2025 19:53
Copy link

codecov bot commented Jan 27, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.00%. Comparing base (6dc85d7) to head (1bc1731).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3067      +/-   ##
==========================================
+ Coverage   89.95%   90.00%   +0.04%     
==========================================
  Files         111      111              
  Lines       11453    11501      +48     
  Branches       50       50              
==========================================
+ Hits        10303    10351      +48     
  Misses       1089     1089              
  Partials       61       61              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@kate-osborn
Copy link
Contributor

@salonichf5 what happens when a route attaches to multiple gateways? Or when a route omits the sectionName in the parentRef (effectively matching all listeners in the Gateway)?

Copy link
Contributor

@kate-osborn kate-osborn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@salonichf5 salonichf5 enabled auto-merge (squash) January 29, 2025 20:55
@salonichf5 salonichf5 merged commit 7a08f11 into main Jan 29, 2025
40 checks passed
@salonichf5 salonichf5 deleted the feat/l-isolation branch January 29, 2025 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request tests Pull requests that update tests
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Support Listener Isolation
4 participants