From 56f845c71affaebbf77290f6145480731af4192f Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Thu, 20 May 2021 11:50:12 +0200 Subject: [PATCH] gatewayapi: adding support for TCPRoute and TLSRoute Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> --- docs/content/providers/kubernetes-gateway.md | 8 +- .../kubernetes-gateway-rbac.yml | 4 + .../kubernetes-gateway-resource.yml | 69 +- .../kubernetes-gateway.md | 2 + .../networking.x-k8s.io_tcproutes.yaml | 254 +++ .../networking.x-k8s.io_tlsroutes.yaml | 263 +++ .../routing/providers/kubernetes-gateway.md | 245 ++- integration/fixtures/k8s/01-gateway-api.yml | 516 +++++ integration/fixtures/k8s/03-gateway.yml | 122 ++ integration/fixtures/k8s_gateway.toml | 6 + integration/testdata/rawdata-gateway.json | 129 +- pkg/provider/kubernetes/gateway/client.go | 42 +- .../kubernetes/gateway/client_mock_test.go | 30 +- .../gatewayclass_with_unknown_controller.yml | 3 +- .../{ => httproute}/one_rule_two_targets.yml | 3 +- .../fixtures/{ => httproute}/simple.yml | 3 +- .../{ => httproute}/simple_cross_provider.yml | 5 +- .../simple_to_api_internal.yml | 3 +- .../httproute/simple_with_bad_rule.yml | 47 + .../httproute/simple_with_tls_entrypoint.yml | 6 +- .../fixtures/{ => httproute}/two_rules.yml | 3 +- .../{ => httproute}/with_multiple_host.yml | 3 +- .../{ => httproute}/with_protocol_https.yml | 3 +- ...th_protocol_https_with_tls_passthrough.yml | 64 + .../with_protocol_https_without_tls.yml | 3 +- .../fixtures/httproute/with_protocol_tcp.yml | 47 + .../fixtures/httproute/with_protocol_tls.yml | 64 + .../{ => httproute}/with_several_rules.yml | 3 +- .../with_two_gateways_one_httproute.yml | 6 +- .../with_two_listeners_one_httproute.yml | 6 +- .../with_wrong_service_port.yml | 3 +- .../{ => httproute}/without_gatewayclass.yml | 3 +- .../{ => httproute}/without_httproute.yml | 3 +- .../gateway/fixtures/mixed/simple.yml | 131 ++ .../mixed/with_bad_listener_protocol.yml | 41 + .../mixed/with_bad_listener_route_kind.yml | 41 + ...h_incompatible_protocol_and_route_kind.yml | 41 + ...with_multiple_protocol_using_same_port.yml | 139 ++ .../mixed/with_wrong_routes_selector.yml | 75 + .../kubernetes/gateway/fixtures/services.yml | 37 +- .../gatewayclass_with_unknown_controller.yml | 41 + .../gateway/fixtures/tcproute/simple.yml | 40 + .../tcproute/simple_cross_provider.yml | 47 + .../tcproute/with_multiple_routes.yml | 62 + .../fixtures/tcproute/with_multiple_rules.yml | 44 + .../fixtures/tcproute/with_protocol_http.yml | 42 + .../fixtures/tcproute/with_protocol_https.yml | 59 + .../fixtures/tcproute/with_protocol_tls.yml | 59 + .../tcproute/with_wrong_service_port.yml | 41 + .../tcproute/without_gatewayclass.yml | 33 + .../fixtures/tcproute/without_tcproute.yml | 26 + .../without_tcproute_tls_protocol.yml | 43 + .../gatewayclass_with_unknown_controller.yml | 57 + .../tlsroute/simple_TLS_to_TCPRoute.yml | 57 + ...le_TLS_to_TCPRoute_with_certificateRef.yml | 57 + .../tlsroute/simple_TLS_to_TLSRoute.yml | 54 + .../tlsroute/simple_cross_provider.yml | 63 + .../fixtures/tlsroute/with_SNI_matching.yml | 48 + .../tlsroute/with_invalid_SNI_matching.yml | 49 + .../tlsroute/with_multiple_SNI_matching.yml | 49 + .../tlsroute/with_multiple_routes_kind.yml | 82 + .../fixtures/tlsroute/with_passthrough.yml | 44 + .../tlsroute/with_passthrough_tls.yml | 60 + .../fixtures/tlsroute/with_protocol_http.yml | 42 + .../fixtures/tlsroute/with_protocol_https.yml | 59 + .../fixtures/tlsroute/with_protocol_tcp.yml | 42 + .../tlsroute/with_wrong_service_port.yml | 57 + .../tlsroute/without_gatewayclass.yml | 33 + .../fixtures/tlsroute/without_tlsroute.yml | 28 + pkg/provider/kubernetes/gateway/kubernetes.go | 662 +++++- .../kubernetes/gateway/kubernetes_test.go | 1891 ++++++++++++++++- pkg/provider/kubernetes/k8s/parser.go | 2 +- 72 files changed, 6241 insertions(+), 208 deletions(-) create mode 100644 docs/content/reference/dynamic-configuration/networking.x-k8s.io_tcproutes.yaml create mode 100644 docs/content/reference/dynamic-configuration/networking.x-k8s.io_tlsroutes.yaml rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/gatewayclass_with_unknown_controller.yml (95%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/one_rule_two_targets.yml (95%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/simple.yml (95%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/simple_cross_provider.yml (93%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/simple_to_api_internal.yml (95%) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml rename docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml => pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml (88%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/two_rules.yml (95%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_multiple_host.yml (94%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_protocol_https.yml (96%) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_protocol_https_without_tls.yml (95%) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_several_rules.yml (96%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_two_gateways_one_httproute.yml (94%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_two_listeners_one_httproute.yml (93%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/with_wrong_service_port.yml (95%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/without_gatewayclass.yml (94%) rename pkg/provider/kubernetes/gateway/fixtures/{ => httproute}/without_httproute.yml (91%) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_with_certificateRef.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index 7c5c37d08..0cf679d3d 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -77,13 +77,13 @@ The [getting started guide](https://gateway-api.sigs.k8s.io/guides/getting-start !!! note "" - Keep in mind that the Traefik Gateway provider only supports the `v0.1.0`. + Keep in mind that the Traefik Gateway provider only supports the `v0.2.0`. For now, the Traefik Gateway Provider can be used while following the below guides: * [Simple Gateway](https://gateway-api.sigs.k8s.io/guides/simple-gateway/) * [HTTP routing](https://gateway-api.sigs.k8s.io/guides/http-routing/) -* [TLS](https://gateway-api.sigs.k8s.io/guides/tls/) (Partial support: only on listeners with terminate mode) +* [TLS](https://gateway-api.sigs.k8s.io/guides/tls/) ## Resource Configuration @@ -96,7 +96,9 @@ Traefik implements the following resources: * `GatewayClass` defines a set of Gateways that share a common configuration and behaviour. * `Gateway` describes how traffic can be translated to Services within the cluster. -* `HTTPRoute` define HTTP rules for mapping requests from a Gateway to Kubernetes Services. +* `HTTPRoute` defines HTTP rules for mapping requests from a Gateway to Kubernetes Services. +* `TCPRoute` defines TCP rules for mapping requests from a Gateway to Kubernetes Services. +* `TLSRoute` defines TLS rules for mapping requests from a Gateway to Kubernetes Services. ## Provider Configuration diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml index c2a46c0e0..c864f55af 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml @@ -20,6 +20,8 @@ rules: - gatewayclasses - gateways - httproutes + - tcproutes + - tlsroutes verbs: - get - list @@ -30,6 +32,8 @@ rules: - gatewayclasses/status - gateways/status - httproutes/status + - tcproutes/status + - tlsroutes/status verbs: - update diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml index c25130ac5..5abd2f344 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml @@ -22,7 +22,44 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo + - protocol: HTTPS + port: 443 + tls: + certificateRef: + group: "core" + kind: "Secret" + name: "mysecret" + routes: + kind: HTTPRoute + selector: + matchLabels: + app: foo + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: footcp + - protocol: TLS + port: 9443 + hostname: example.com + tls: + certificateRef: + group: "core" + kind: "Secret" + name: "mysecret" + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: footls --- kind: HTTPRoute @@ -55,3 +92,33 @@ spec: name: myservice@file weight: 1 port: 80 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: footcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: footls +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway.md b/docs/content/reference/dynamic-configuration/kubernetes-gateway.md index 2c9de2e90..e6fddbe24 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway.md +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway.md @@ -9,6 +9,8 @@ Dynamic configuration with Kubernetes Gateway provider. --8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_gatewayclasses.yaml" --8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_gateways.yaml" --8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_httproutes.yaml" +--8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_tcproutes.yaml" +--8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_tlsroutes.yaml" ``` ## Resources diff --git a/docs/content/reference/dynamic-configuration/networking.x-k8s.io_tcproutes.yaml b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_tcproutes.yaml new file mode 100644 index 000000000..9a7631420 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_tcproutes.yaml @@ -0,0 +1,254 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: tcproutes.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TCPRoute is the Schema for the TCPRoute resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + gateways: + default: + allow: SameNamespace + description: Gateways defines which Gateways can use this Route. + properties: + allow: + default: SameNamespace + description: 'Allow indicates which Gateways will be allowed to use this route. Possible values are: * All: Gateways in any namespace can use this route. * FromList: Only Gateways specified in GatewayRefs may use this route. * SameNamespace: Only Gateways in the same namespace may use this route.' + enum: + - All + - FromList + - SameNamespace + type: string + gatewayRefs: + description: GatewayRefs must be specified when Allow is set to "FromList". In that case, only Gateways referenced in this list will be allowed to use this route. This field is ignored for other values of "Allow". + items: + description: GatewayReference identifies a Gateway in a specified namespace. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + type: array + type: object + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + forwardTo: + description: ForwardTo defines the backend(s) where matching requests should be sent. + items: + description: RouteForwardTo defines how a Route should forward a request. + properties: + backendRef: + description: "BackendRef is a reference to a backend to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: "Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. If unspecified, the destination port in the request is used when forwarding to a backendRef or serviceName. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to forward matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n The protocol to use is defined using AppProtocol field (introduced in Kubernetes 1.18) in the Service resource. In the absence of the AppProtocol field a `networking.x-k8s.io/app-protocol` annotation on the BackendPolicy resource may be used to define the protocol. If the AppProtocol field is available, this annotation should not be used. The AppProtocol field, when populated, takes precedence over the annotation in the BackendPolicy resource. For custom backends, it is encouraged to add a semantically-equivalent field in the Custom Resource Definition. \n Support: Core" + maxLength: 253 + type: string + weight: + default: 1 + description: "Weight specifies the proportion of HTTP requests forwarded to the backend referenced by the ServiceName or BackendRef field. This is computed as weight/(sum of all weights in this ForwardTo list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support: Extended" + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + type: object + maxItems: 16 + minItems: 1 + type: array + matches: + description: Matches define conditions used for matching the rule against incoming TCP connections. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. If unspecified, all requests from the associated gateway TCP listener will match. + items: + description: TCPRouteMatch defines the predicate used to match connections to a given action. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"match\" behavior. For example, resource \"mytcproutematcher\" in group \"networking.acme.io\". If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + maxItems: 8 + type: array + required: + - forwardTo + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + gateways: + description: "Gateways is a list of Gateways that are associated with the route, and the status of the route with respect to each Gateway. When a Gateway selects this route, the controller that manages the Gateway must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route is modified. \n A maximum of 100 Gateways will be represented in this list. If this list is full, there may be additional Gateways using this Route that are not included in the list. An empty list means the route has not been admitted by any Gateway." + items: + description: RouteGatewayStatus describes the status of a route with respect to an associated Gateway. + properties: + conditions: + description: Conditions describes the status of the route with respect to the Gateway. The "Admitted" condition must always be specified by controllers to indicate whether the route has been admitted or rejected by the Gateway, and why. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gatewayRef: + description: GatewayRef is a reference to a Gateway object that is associated with the route. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - gatewayRef + type: object + maxItems: 100 + type: array + required: + - gateways + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/networking.x-k8s.io_tlsroutes.yaml b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_tlsroutes.yaml new file mode 100644 index 000000000..17cffbb80 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_tlsroutes.yaml @@ -0,0 +1,263 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: tlsroutes.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + gateways: + default: + allow: SameNamespace + description: Gateways defines which Gateways can use this Route. + properties: + allow: + default: SameNamespace + description: 'Allow indicates which Gateways will be allowed to use this route. Possible values are: * All: Gateways in any namespace can use this route. * FromList: Only Gateways specified in GatewayRefs may use this route. * SameNamespace: Only Gateways in the same namespace may use this route.' + enum: + - All + - FromList + - SameNamespace + type: string + gatewayRefs: + description: GatewayRefs must be specified when Allow is set to "FromList". In that case, only Gateways referenced in this list will be allowed to use this route. This field is ignored for other values of "Allow". + items: + description: GatewayReference identifies a Gateway in a specified namespace. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + type: array + type: object + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + forwardTo: + description: ForwardTo defines the backend(s) where matching requests should be sent. + items: + description: RouteForwardTo defines how a Route should forward a request. + properties: + backendRef: + description: "BackendRef is a reference to a backend to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: "Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. If unspecified, the destination port in the request is used when forwarding to a backendRef or serviceName. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to forward matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n The protocol to use is defined using AppProtocol field (introduced in Kubernetes 1.18) in the Service resource. In the absence of the AppProtocol field a `networking.x-k8s.io/app-protocol` annotation on the BackendPolicy resource may be used to define the protocol. If the AppProtocol field is available, this annotation should not be used. The AppProtocol field, when populated, takes precedence over the annotation in the BackendPolicy resource. For custom backends, it is encouraged to add a semantically-equivalent field in the Custom Resource Definition. \n Support: Core" + maxLength: 253 + type: string + weight: + default: 1 + description: "Weight specifies the proportion of HTTP requests forwarded to the backend referenced by the ServiceName or BackendRef field. This is computed as weight/(sum of all weights in this ForwardTo list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support: Extended" + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + type: object + maxItems: 16 + minItems: 1 + type: array + matches: + description: Matches define conditions used for matching the rule against an incoming TLS handshake. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. If unspecified, all requests from the associated gateway TLS listener will match. + items: + description: TLSRouteMatch defines the predicate used to match connections to a given action. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"match\" behavior. For example, resource \"mytlsroutematcher\" in group \"networking.acme.io\". If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + snis: + description: "SNIs defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. \n SNI can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). The wildcard character `*` must appear by itself as the first DNS label and matches only a single label. You cannot have a wildcard label by itself (e.g. Host == `*`). \n Requests will be matched against the Host field in the following order: \n 1. If SNI is precise, the request matches this rule if the SNI in ClientHello is equal to one of the defined SNIs. 2. If SNI is a wildcard, then the request matches this rule if the SNI is to equal to the suffix (removing the first label) of the wildcard rule. 3. If SNIs is unspecified, all requests associated with the gateway TLS listener will match. This can be used to define a default backend for a TLS listener. \n Support: Core" + items: + description: Hostname is used to specify a hostname that should be matched. + maxLength: 253 + minLength: 1 + type: string + maxItems: 16 + type: array + type: object + maxItems: 8 + type: array + required: + - forwardTo + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + gateways: + description: "Gateways is a list of Gateways that are associated with the route, and the status of the route with respect to each Gateway. When a Gateway selects this route, the controller that manages the Gateway must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route is modified. \n A maximum of 100 Gateways will be represented in this list. If this list is full, there may be additional Gateways using this Route that are not included in the list. An empty list means the route has not been admitted by any Gateway." + items: + description: RouteGatewayStatus describes the status of a route with respect to an associated Gateway. + properties: + conditions: + description: Conditions describes the status of the route with respect to the Gateway. The "Admitted" condition must always be specified by controllers to indicate whether the route has been admitted or rejected by the Gateway, and why. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gatewayRef: + description: GatewayRef is a reference to a Gateway object that is associated with the route. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - gatewayRef + type: object + maxItems: 100 + type: array + required: + - gateways + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index 755374d75..fb8be83e7 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -33,11 +33,13 @@ The Kubernetes Gateway API, The Experimental Way. You can find an excerpt of the supported Kubernetes Gateway API resources in the table below: -| Kind | Purpose | Concept Behind | -|------------------------------------|---------------------------------------------------------------------------|-----------------------------------------------------------------------------| -| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/api-types/gatewayclass) | -| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway) | -| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/api-types/httproute) | +| Kind | Purpose | Concept Behind | +|------------------------------------|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------| +| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/api-types/gatewayclass) | +| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway) | +| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/api-types/httproute) | +| [TCPRoute](#kind-tcproute) | Allows mapping TCP requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/concepts/api-overview/#httptcpfooroute) | +| [TLSRoute](#kind-tlsroute) | Allows mapping TLS requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/concepts/api-overview/#httptcpfooroute) | ### Kind: `GatewayClass` @@ -70,43 +72,114 @@ More details on the Gateway [official documentation](https://gateway-api.sigs.k8 Register the `Gateway` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the Kubernetes cluster before creating `Gateway` objects. +Depending on the Listener Protocol, different modes and Route types are supported. + +| Listener Protocol | TLS Mode | Route Type Supported | +|-------------------|----------------|------------------------------| +| TCP | Not applicable | [TCPRoute](#kind-tcproute) | +| TLS | Passthrough | [TLSRoute](#kind-tlsroute) | +| TLS | Terminate | [TCPRoute](#kind-tcproute) | +| HTTP | Not applicable | [HTTPRoute](#kind-httproute) | +| HTTPS | Terminate | [HTTPRoute](#kind-httproute) | + !!! info "Declaring Gateway" - ``` + ```yaml tab="HTTP Listener" kind: Gateway apiVersion: networking.x-k8s.io/v1alpha1 metadata: - name: my-gateway + name: my-http-gateway + namespace: default + spec: + gatewayClassName: my-gateway-class # [1] + listeners: # [2] + - protocol: HTTP # [3] + port: 80 # [4] + routes: # [8] + kind: HTTPRoute # [9] + selector: # [10] + matchLabels: # [11] + app: foo + ``` + + ```yaml tab="HTTPS Listener" + kind: Gateway + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: my-https-gateway namespace: default spec: gatewayClassName: my-gateway-class # [1] listeners: # [2] - protocol: HTTPS # [3] port: 443 # [4] - tls: # [5] - certificateRef: # [6] + tls: # [6] + certificateRef: # [7] group: "core" kind: "Secret" name: "mysecret" - routes: # [7] - kind: HTTPRoute # [8] - selector: # [9] - matchLabels: # [10] + routes: # [8] + kind: HTTPRoute # [9] + selector: # [10] + matchLabels: # [11] app: foo ``` -| Ref | Attribute | Description | -|------|--------------------|-----------------------------------------------------------------------------------------------------------------------------| -| [1] | `gatewayClassName` | GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. | -| [2] | `listeners` | Logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. | -| [3] | `protocol` | The network protocol this listener expects to receive (only HTTP and HTTPS are implemented). | -| [4] | `port` | The network port. | -| [5] | `tls` | TLS configuration for the Listener. This field is required if the Protocol field is "HTTPS" or "TLS" and ignored otherwise. | -| [6] | `certificateRef` | The reference to Kubernetes object that contains a TLS certificate and private key. | -| [7] | `routes` | A schema for associating routes with the Listener using selectors. | -| [8] | `kind` | The kind of the referent. | -| [9] | `selector` | Routes in namespaces selected by the selector may be used by this Gateway routes to associate with the Gateway. | -| [10] | `matchLabels` | A set of route labels used for selecting routes to associate with the Gateway. | + ```yaml tab="TCP Listener" + kind: Gateway + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: my-tcp-gateway + namespace: default + spec: + gatewayClassName: my-gateway-class # [1] + listeners: # [2] + - protocol: TCP # [3] + port: 8000 # [4] + routes: # [8] + kind: TCPRoute # [9] + selector: # [10] + matchLabels: # [11] + app: footcp + ``` + + ```yaml tab="TLS Listener" + kind: Gateway + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: my-tls-gateway + namespace: default + spec: + gatewayClassName: my-gateway-class # [1] + listeners: # [2] + - protocol: TLS # [3] + port: 443 # [4] + hostname: foo.com # [5] + tls: # [6] + certificateRef: # [7] + group: "core" + kind: "Secret" + name: "mysecret" + routes: # [8] + kind: TLSRoute # [9] + selector: # [10] + matchLabels: # [11] + app: footcp + ``` + +| Ref | Attribute | Description | +|------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `gatewayClassName` | GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. | +| [2] | `listeners` | Logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. | +| [3] | `protocol` | The network protocol this listener expects to receive (only HTTP and HTTPS are implemented). | +| [4] | `port` | The network port. | +| [5] | `hostname` | Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, “”, or *, all hostnames are matched. | +| [6] | `tls` | TLS configuration for the Listener. This field is required if the Protocol field is "HTTPS" or "TLS" and ignored otherwise. | +| [7] | `certificateRef` | The reference to Kubernetes object that contains a TLS certificate and private key. | +| [8] | `routes` | A schema for associating routes with the Listener using selectors. | +| [9] | `kind` | The kind of the referent. | +| [10] | `selector` | Routes in namespaces selected by the selector may be used by this Gateway routes to associate with the Gateway. | +| [11] | `matchLabels` | A set of route labels used for selecting routes to associate with the Gateway. | ### Kind: `HTTPRoute` @@ -149,23 +222,107 @@ Kubernetes cluster before creating `HTTPRoute` objects. weight: 1 ``` -| Ref | Attribute | Description | -|------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [1] | `labels` | Labels to match with the `Gateway` labelselector. | -| [2] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. | -| [3] | `rules` | A list of HTTP matchers, filters and actions. | -| [4] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. | -| [5] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. | -| [6] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). | -| [7] | `value` | The value of the HTTP path to match against. | -| [8] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. | -| [9] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). | -| [10] | `values` | A map of HTTP Headers to be matched. It MUST contain at least one entry. | -| [11] | `forwardTo` | The upstream target(s) where the request should be sent. | -| [12] | `serviceName` | The name of the referent service. | -| [13] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). | -| [14] | `port` | The port of the referent service. | +| Ref | Attribute | Description | +|------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `labels` | Labels to match with the `Gateway` labelselector. | +| [2] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. | +| [3] | `rules` | A list of HTTP matchers, filters and actions. | +| [4] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. | +| [5] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. | +| [6] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). | +| [7] | `value` | The value of the HTTP path to match against. | +| [8] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. | +| [9] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). | +| [10] | `values` | A map of HTTP Headers to be matched. It MUST contain at least one entry. | +| [11] | `forwardTo` | The upstream target(s) where the request should be sent. | +| [12] | `serviceName` | The name of the referent service. | +| [13] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). | +| [14] | `port` | The port of the referent service. | | [15] | `backendRef` | The BackendRef is a reference to a backend (API object within a known namespace) to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. Only `TraefikService` is supported. | -| [16] | `group` | Group is the group of the referent. Only `traefik.containo.us` value is supported. | -| [17] | `kind` | Kind is kind of the referent. Only `TraefikService` value is supported. | -| [18] | `name` | Name is the name of the referent. | +| [16] | `group` | Group is the group of the referent. Only `traefik.containo.us` value is supported. | +| [17] | `kind` | Kind is kind of the referent. Only `TraefikService` value is supported. | +| [18] | `name` | Name is the name of the referent. | + +### Kind: `TCPRoute` + +`TCPRoute` allows mapping TCP requests from a `Gateway` to Kubernetes Services + +Register the `TCPRoute` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the +Kubernetes cluster before creating `TCPRoute` objects. + +!!! info "Declaring TCPRoute" + + ```yaml + kind: TCPRoute + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: tcp-app-1 + namespace: default + labels: # [1] + app: tcp-app-1 + spec: + rules: # [2] + - forwardTo: # [3] + - serviceName: whoamitcp # [4] + weight: 1 # [5] + port: 8080 # [6] + - backendRef: # [7] + group: traefik.containo.us # [8] + kind: TraefikService # [9] + name: api@internal # [10] + ``` + +| Ref | Attribute | Description | +|------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `labels` | Labels to match with the `Gateway` labelselector. | +| [2] | `rules` | Rules are a list of TCP matchers and actions. | +| [3] | `forwardTo` | The upstream target(s) where the request should be sent. | +| [4] | `serviceName` | The name of the referent service. | +| [5] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). | +| [6] | `port` | The port of the referent service. | +| [7] | `backendRef` | The BackendRef is a reference to a backend (API object within a known namespace) to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. Only `TraefikService` is supported. | +| [8] | `group` | Group is the group of the referent. Only `traefik.containo.us` value is supported. | +| [9] | `kind` | Kind is kind of the referent. Only `TraefikService` value is supported. | +| [10] | `name` | Name is the name of the referent. | + +### Kind: `TLSRoute` + +`TLSRoute` allows mapping TLS requests from a `Gateway` to Kubernetes Services + +Register the `TLSRoute` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the +Kubernetes cluster before creating `TLSRoute` objects. + +!!! info "Declaring TCPRoute" + + ```yaml + kind: TLSRoute + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: tls-app-1 + namespace: default + labels: # [1] + app: tls-app-1 + spec: + rules: # [2] + - forwardTo: # [3] + - serviceName: whoamitcp # [4] + weight: 1 # [5] + port: 8080 # [6] + - backendRef: # [7] + group: traefik.containo.us # [8] + kind: TraefikService # [9] + name: api@internal # [10] + ``` + +| Ref | Attribute | Description | +|------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `labels` | Labels to match with the `Gateway` labelselector. | +| [2] | `rules` | Rules are a list of TCP matchers and actions. | +| [3] | `forwardTo` | The upstream target(s) where the request should be sent. | +| [4] | `serviceName` | The name of the referent service. | +| [5] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). | +| [6] | `port` | The port of the referent service. | +| [7] | `backendRef` | The BackendRef is a reference to a backend (API object within a known namespace) to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. Only `TraefikService` is supported. | +| [8] | `group` | Group is the group of the referent. Only `traefik.containo.us` value is supported. | +| [9] | `kind` | Kind is kind of the referent. Only `TraefikService` value is supported. | +| [10] | `name` | Name is the name of the referent. | diff --git a/integration/fixtures/k8s/01-gateway-api.yml b/integration/fixtures/k8s/01-gateway-api.yml index abbf8aa88..fc9e02ccc 100644 --- a/integration/fixtures/k8s/01-gateway-api.yml +++ b/integration/fixtures/k8s/01-gateway-api.yml @@ -1104,3 +1104,519 @@ status: plural: "" conditions: [] storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: tcproutes.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TCPRoute is the Schema for the TCPRoute resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + gateways: + default: + allow: SameNamespace + description: Gateways defines which Gateways can use this Route. + properties: + allow: + default: SameNamespace + description: 'Allow indicates which Gateways will be allowed to use this route. Possible values are: * All: Gateways in any namespace can use this route. * FromList: Only Gateways specified in GatewayRefs may use this route. * SameNamespace: Only Gateways in the same namespace may use this route.' + enum: + - All + - FromList + - SameNamespace + type: string + gatewayRefs: + description: GatewayRefs must be specified when Allow is set to "FromList". In that case, only Gateways referenced in this list will be allowed to use this route. This field is ignored for other values of "Allow". + items: + description: GatewayReference identifies a Gateway in a specified namespace. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + type: array + type: object + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + forwardTo: + description: ForwardTo defines the backend(s) where matching requests should be sent. + items: + description: RouteForwardTo defines how a Route should forward a request. + properties: + backendRef: + description: "BackendRef is a reference to a backend to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: "Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. If unspecified, the destination port in the request is used when forwarding to a backendRef or serviceName. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to forward matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n The protocol to use is defined using AppProtocol field (introduced in Kubernetes 1.18) in the Service resource. In the absence of the AppProtocol field a `networking.x-k8s.io/app-protocol` annotation on the BackendPolicy resource may be used to define the protocol. If the AppProtocol field is available, this annotation should not be used. The AppProtocol field, when populated, takes precedence over the annotation in the BackendPolicy resource. For custom backends, it is encouraged to add a semantically-equivalent field in the Custom Resource Definition. \n Support: Core" + maxLength: 253 + type: string + weight: + default: 1 + description: "Weight specifies the proportion of HTTP requests forwarded to the backend referenced by the ServiceName or BackendRef field. This is computed as weight/(sum of all weights in this ForwardTo list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support: Extended" + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + type: object + maxItems: 16 + minItems: 1 + type: array + matches: + description: Matches define conditions used for matching the rule against incoming TCP connections. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. If unspecified, all requests from the associated gateway TCP listener will match. + items: + description: TCPRouteMatch defines the predicate used to match connections to a given action. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"match\" behavior. For example, resource \"mytcproutematcher\" in group \"networking.acme.io\". If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + maxItems: 8 + type: array + required: + - forwardTo + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + gateways: + description: "Gateways is a list of Gateways that are associated with the route, and the status of the route with respect to each Gateway. When a Gateway selects this route, the controller that manages the Gateway must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route is modified. \n A maximum of 100 Gateways will be represented in this list. If this list is full, there may be additional Gateways using this Route that are not included in the list. An empty list means the route has not been admitted by any Gateway." + items: + description: RouteGatewayStatus describes the status of a route with respect to an associated Gateway. + properties: + conditions: + description: Conditions describes the status of the route with respect to the Gateway. The "Admitted" condition must always be specified by controllers to indicate whether the route has been admitted or rejected by the Gateway, and why. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gatewayRef: + description: GatewayRef is a reference to a Gateway object that is associated with the route. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - gatewayRef + type: object + maxItems: 100 + type: array + required: + - gateways + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: tlsroutes.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + gateways: + default: + allow: SameNamespace + description: Gateways defines which Gateways can use this Route. + properties: + allow: + default: SameNamespace + description: 'Allow indicates which Gateways will be allowed to use this route. Possible values are: * All: Gateways in any namespace can use this route. * FromList: Only Gateways specified in GatewayRefs may use this route. * SameNamespace: Only Gateways in the same namespace may use this route.' + enum: + - All + - FromList + - SameNamespace + type: string + gatewayRefs: + description: GatewayRefs must be specified when Allow is set to "FromList". In that case, only Gateways referenced in this list will be allowed to use this route. This field is ignored for other values of "Allow". + items: + description: GatewayReference identifies a Gateway in a specified namespace. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + type: array + type: object + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + forwardTo: + description: ForwardTo defines the backend(s) where matching requests should be sent. + items: + description: RouteForwardTo defines how a Route should forward a request. + properties: + backendRef: + description: "BackendRef is a reference to a backend to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: "Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. If unspecified, the destination port in the request is used when forwarding to a backendRef or serviceName. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to forward matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n The protocol to use is defined using AppProtocol field (introduced in Kubernetes 1.18) in the Service resource. In the absence of the AppProtocol field a `networking.x-k8s.io/app-protocol` annotation on the BackendPolicy resource may be used to define the protocol. If the AppProtocol field is available, this annotation should not be used. The AppProtocol field, when populated, takes precedence over the annotation in the BackendPolicy resource. For custom backends, it is encouraged to add a semantically-equivalent field in the Custom Resource Definition. \n Support: Core" + maxLength: 253 + type: string + weight: + default: 1 + description: "Weight specifies the proportion of HTTP requests forwarded to the backend referenced by the ServiceName or BackendRef field. This is computed as weight/(sum of all weights in this ForwardTo list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support: Extended" + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + type: object + maxItems: 16 + minItems: 1 + type: array + matches: + description: Matches define conditions used for matching the rule against an incoming TLS handshake. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. If unspecified, all requests from the associated gateway TLS listener will match. + items: + description: TLSRouteMatch defines the predicate used to match connections to a given action. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"match\" behavior. For example, resource \"mytlsroutematcher\" in group \"networking.acme.io\". If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + snis: + description: "SNIs defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. \n SNI can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). The wildcard character `*` must appear by itself as the first DNS label and matches only a single label. You cannot have a wildcard label by itself (e.g. Host == `*`). \n Requests will be matched against the Host field in the following order: \n 1. If SNI is precise, the request matches this rule if the SNI in ClientHello is equal to one of the defined SNIs. 2. If SNI is a wildcard, then the request matches this rule if the SNI is to equal to the suffix (removing the first label) of the wildcard rule. 3. If SNIs is unspecified, all requests associated with the gateway TLS listener will match. This can be used to define a default backend for a TLS listener. \n Support: Core" + items: + description: Hostname is used to specify a hostname that should be matched. + maxLength: 253 + minLength: 1 + type: string + maxItems: 16 + type: array + type: object + maxItems: 8 + type: array + required: + - forwardTo + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + gateways: + description: "Gateways is a list of Gateways that are associated with the route, and the status of the route with respect to each Gateway. When a Gateway selects this route, the controller that manages the Gateway must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route is modified. \n A maximum of 100 Gateways will be represented in this list. If this list is full, there may be additional Gateways using this Route that are not included in the list. An empty list means the route has not been admitted by any Gateway." + items: + description: RouteGatewayStatus describes the status of a route with respect to an associated Gateway. + properties: + conditions: + description: Conditions describes the status of the route with respect to the Gateway. The "Admitted" condition must always be specified by controllers to indicate whether the route has been admitted or rejected by the Gateway, and why. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gatewayRef: + description: GatewayRef is a reference to a Gateway object that is associated with the route. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - gatewayRef + type: object + maxItems: 100 + type: array + required: + - gateways + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/integration/fixtures/k8s/03-gateway.yml b/integration/fixtures/k8s/03-gateway.yml index 45820232c..2741f0baf 100644 --- a/integration/fixtures/k8s/03-gateway.yml +++ b/integration/fixtures/k8s/03-gateway.yml @@ -20,6 +20,95 @@ spec: routes: kind: HTTPRoute +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: TCP + port: 8193 + routes: + kind: TCPRoute + selector: + matchLabels: + app: tcp-app-1 + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNJakNDQVl1Z0F3SUJBZ0lRS1F6S1hWV0duODRNNzk2QmhGUzV0VEFOQmdrcWhraUc5dzBCQVFzRkFEQVMKTVJBd0RnWURWUVFLRXdkQlkyMWxJRU52TUNBWERUY3dNREV3TVRBd01EQXdNRm9ZRHpJd09EUXdNVEk1TVRZdwpNREF3V2pBU01SQXdEZ1lEVlFRS0V3ZEJZMjFsSUVOdk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCCmlRS0JnUURsMHNndm5HSnJOMEt1NXVQanBVZjNwTkY2MTkyL0xSb1FjMmFCeENORkdtZW5XekhsZXpoS3Rnam4KZk0velRxeGU1Y3M5QzBvKzlpdFNrRTBzNEp0S0lob1R6NGEvbklCUmxRZ2hsWkRTQ2ZFVjdXdWxLZGVqbWE3Swp3MittUDVLYy9Qa0ozRkxPSCt0blJRSVZPakZmeDBhMllDS2VxTFJWRmhGOWlMSFBWd0lEQVFBQm8zY3dkVEFPCkJnTlZIUThCQWY4RUJBTUNBcVF3RXdZRFZSMGxCQXd3Q2dZSUt3WUJCUVVIQXdFd0R3WURWUjBUQVFIL0JBVXcKQXdFQi96QWRCZ05WSFE0RUZnUVVyZERBNGFIMHc2WjJHc2dxa3FHMHRqNlFZL2t3SGdZRFZSMFJCQmN3RllJVApkR3h6TG1admJ5NWxlR0Z0Y0d4bExtTnZiVEFOQmdrcWhraUc5dzBCQVFzRkFBT0JnUUIvVFBHcElMUGg0Nlp4CnVXZFM4WDFNWEc0ODVQSlNKYVhxZUNsTW9EVEQxdlVwa0Jzd1hEUUVESFRMQkU0SGROaEJaaUlpOFFLQjZCS1IKZEVqU0xFbmlhK0ExUkwyRjdIa05MbU1ycFVjT3lzdzBiOFg1LzkydkpGYStScXgxdjJwQ0FIUHRGUE9ZM240NQoza3lGZy96ZXUwd2w0NW80MUtNL0ZJT1ljWFA3dVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUNlQUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQW1Jd2dnSmVBZ0VBQW9HQkFPWFN5QytjWW1zM1FxN20KNCtPbFIvZWswWHJYM2I4dEdoQnpab0hFSTBVYVo2ZGJNZVY3T0VxMkNPZDh6L05PckY3bHl6MExTajcySzFLUQpUU3pnbTBvaUdoUFBocitjZ0ZHVkNDR1ZrTklKOFJYdGE2VXAxNk9acnNyRGI2WS9rcHo4K1FuY1VzNGY2MmRGCkFoVTZNVi9IUnJaZ0lwNm90RlVXRVgySXNjOVhBZ01CQUFFQ2dZRUF4MllMRSt2dUpESHM1RTBsZWhTa0RVUHUKRUZRTWE5dFkydDhWR0EreHZqbjdwdU5qdGtRamdnYlFVUEFraUdoQSs1RUt0ZXIrdndQY2NLVU1vSnc3ZmdBTQphMWxZVlJ1M1F6V1hSamdLL2xHbU5SN050bkVZaDBxL0VGcVRDZys5Nkp6UnR6b3FJSGdwN09IVWFVaUJUVURnCmFxUTdvcmc4Z1hiUmMxT0UwNWtDUVFEbXQ3TDRTNDk1Yk1CajFBL3kzamM5aThPeEZ1ZUt6Q1l3NWFZaC8xOGgKZTBYRkhRYmpSKzRxNDM1MmJsMkduMVg5Z0hKUDFUQllPY3V0UUM4Qmt0M2pBa0VBL3dIL2JFejd0RCtaWFVjZgpnb21XZzVEU2xhUmRtb0xrSTdLZllvcDl4VlNPUElTSnU4SEFhdnBhWVhiM1NuN05KL25EcUdZM1BVeFpsdzBZCmJNaEMvUUpBZTI3UUt4S1J3YzZ5NXpXdkNxcGtOMk1zNFBOMkVNWERzT2xNQm1oUGh1UWlvYUF6N1Npd2ZQV1UKMU51YTRja2hBaXpUKzIzOUhWWmVaMlF0UWRSSExRSkJBTE40aUhlRVJyRzVBUXJ3LzNBenZVYWpLbEkrOTlIQwp4U1dLbFRvWkZpTkhPMFBFVTl0Y3BUdWxMdTdoZDNGcWhLRFoyNll0S2p0dC9LK2VlODR6czFFQ1FRQ3oyNWlEClpCY2ZrR0FURlhjdWVzeXgvUlcyL2c4QzhtYkN6RW5oSCtYbWVBak44UFRDREF2VDNLVXltRm5MRVB6amVVYU0KRGRuSlZqc3JaNW8xK1c5WgotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg== + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tls-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: TLS + port: 9001 + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app-1 + - protocol: TLS + port: 9002 + tls: + mode: Terminate + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app-1 + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-https-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: HTTPS + port: 8443 + tls: + mode: Terminate + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + --- kind: HTTPRoute apiVersion: networking.x-k8s.io/v1alpha1 @@ -40,3 +129,36 @@ spec: - serviceName: whoami port: 80 weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app-1 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 8080 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app-1 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 8080 + weight: 1 + matches: + - snis: + - foo.bar diff --git a/integration/fixtures/k8s_gateway.toml b/integration/fixtures/k8s_gateway.toml index 93a0b391d..c0cc76e04 100644 --- a/integration/fixtures/k8s_gateway.toml +++ b/integration/fixtures/k8s_gateway.toml @@ -12,11 +12,17 @@ kubernetesGateway = true [entryPoints] + [entryPoints.footlspassthrough] + address = ":9001" + [entryPoints.footlsterminate] + address = ":9002" [entryPoints.footcp] address = ":8193" [entryPoints.fooudp] address = ":8190/udp" [entryPoints.web] address = ":8180" + [entryPoints.websecure] + address = ":8443" [providers.kubernetesGateway] diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json index 7615f12fb..c5ef16e27 100644 --- a/integration/testdata/rawdata-gateway.json +++ b/integration/testdata/rawdata-gateway.json @@ -38,6 +38,18 @@ "using": [ "web" ] + }, + "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06@kubernetesgateway": { + "entryPoints": [ + "websecure" + ], + "service": "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr", + "rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)", + "tls": {}, + "status": "enabled", + "using": [ + "websecure" + ] } }, "middlewares": { @@ -92,11 +104,25 @@ "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06@kubernetesgateway" ] }, + "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": { + "weighted": { + "services": [ + { + "name": "default-whoami-80", + "weight": 1 + } + ] + }, + "status": "enabled", + "usedBy": [ + "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06@kubernetesgateway" + ] + }, "default-whoami-80@kubernetesgateway": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.3:80" + "url": "http://10.42.0.6:80" }, { "url": "http://10.42.0.7:80" @@ -106,12 +132,111 @@ }, "status": "enabled", "serverStatus": { - "http://10.42.0.3:80": "UP", + "http://10.42.0.6:80": "UP", "http://10.42.0.7:80": "UP" } }, "noop@internal": { "status": "enabled" } + }, + "tcpRouters": { + "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway": { + "entryPoints": [ + "footcp" + ], + "service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr", + "rule": "HostSNI(`*`)", + "status": "enabled", + "using": [ + "footcp" + ] + }, + "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway": { + "entryPoints": [ + "footlsterminate" + ], + "service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr", + "rule": "HostSNI(`*`)", + "tls": { + "passthrough": false + }, + "status": "enabled", + "using": [ + "footlsterminate" + ] + }, + "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26@kubernetesgateway": { + "entryPoints": [ + "footlspassthrough" + ], + "service": "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr", + "rule": "HostSNI(`foo.bar`)", + "tls": { + "passthrough": true + }, + "status": "enabled", + "using": [ + "footlspassthrough" + ] + } + }, + "tcpServices": { + "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr@kubernetesgateway": { + "weighted": { + "services": [ + { + "name": "default-whoamitcp-8080", + "weight": 1 + } + ] + }, + "status": "enabled", + "usedBy": [ + "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway" + ] + }, + "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr@kubernetesgateway": { + "weighted": { + "services": [ + { + "name": "default-whoamitcp-8080", + "weight": 1 + } + ] + }, + "status": "enabled", + "usedBy": [ + "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway" + ] + }, + "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr@kubernetesgateway": { + "weighted": { + "services": [ + { + "name": "default-whoamitcp-8080", + "weight": 1 + } + ] + }, + "status": "enabled", + "usedBy": [ + "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26@kubernetesgateway" + ] + }, + "default-whoamitcp-8080@kubernetesgateway": { + "loadBalancer": { + "terminationDelay": 100, + "servers": [ + { + "address": "10.42.0.2:8080" + }, + { + "address": "10.42.0.4:8080" + } + ] + }, + "status": "enabled" + } } } \ No newline at end of file diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 6756ec920..f514a6298 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -56,6 +56,8 @@ type Client interface { UpdateGatewayClassStatus(gatewayClass *v1alpha1.GatewayClass, condition metav1.Condition) error GetGateways() []*v1alpha1.Gateway GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) + GetTCPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) + GetTLSRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) @@ -176,6 +178,8 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< factoryGateway := externalversions.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, externalversions.WithNamespace(ns)) factoryGateway.Networking().V1alpha1().Gateways().Informer().AddEventHandler(eventHandler) factoryGateway.Networking().V1alpha1().HTTPRoutes().Informer().AddEventHandler(eventHandler) + factoryGateway.Networking().V1alpha1().TCPRoutes().Informer().AddEventHandler(eventHandler) + factoryGateway.Networking().V1alpha1().TLSRoutes().Informer().AddEventHandler(eventHandler) factoryKube := informers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, informers.WithNamespace(ns)) factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) @@ -228,7 +232,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< func (c *clientWrapper) GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { if !c.isWatchedNamespace(namespace) { - return nil, fmt.Errorf("failed to get HTTPRoute %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) + return nil, fmt.Errorf("failed to get HTTPRoutes %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) } httpRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(selector) if err != nil { @@ -236,12 +240,46 @@ func (c *clientWrapper) GetHTTPRoutes(namespace string, selector labels.Selector } if len(httpRoutes) == 0 { - log.WithoutContext().Debugf("No HTTPRoute found in %q namespace with labels selector %s", namespace, selector) + log.WithoutContext().Debugf("No HTTPRoutes found in %q namespace with labels selector %s", namespace, selector) } return httpRoutes, nil } +func (c *clientWrapper) GetTCPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get TCPRoutes %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) + } + + tcpRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().TCPRoutes().Lister().TCPRoutes(namespace).List(selector) + if err != nil { + return nil, err + } + + if len(tcpRoutes) == 0 { + log.WithoutContext().Debugf("No TCPRoutes found in %q namespace with labels selector %s", namespace, selector) + } + + return tcpRoutes, nil +} + +func (c *clientWrapper) GetTLSRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get TLSRoutes %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) + } + + tlsRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().TLSRoutes().Lister().TLSRoutes(namespace).List(selector) + if err != nil { + return nil, err + } + + if len(tlsRoutes) == 0 { + log.WithoutContext().Debugf("No TLSRoutes found in %q namespace with labels selector %s", namespace, selector) + } + + return tlsRoutes, nil +} + func (c *clientWrapper) GetGateways() []*v1alpha1.Gateway { var result []*v1alpha1.Gateway diff --git a/pkg/provider/kubernetes/gateway/client_mock_test.go b/pkg/provider/kubernetes/gateway/client_mock_test.go index 45087dccd..f92063e44 100644 --- a/pkg/provider/kubernetes/gateway/client_mock_test.go +++ b/pkg/provider/kubernetes/gateway/client_mock_test.go @@ -35,6 +35,8 @@ type clientMock struct { gatewayClasses []*v1alpha1.GatewayClass gateways []*v1alpha1.Gateway httpRoutes []*v1alpha1.HTTPRoute + tcpRoutes []*v1alpha1.TCPRoute + tlsRoutes []*v1alpha1.TLSRoute watchChan chan interface{} } @@ -63,6 +65,10 @@ func newClientMock(paths ...string) clientMock { c.gateways = append(c.gateways, o) case *v1alpha1.HTTPRoute: c.httpRoutes = append(c.httpRoutes, o) + case *v1alpha1.TCPRoute: + c.tcpRoutes = append(c.tcpRoutes, o) + case *v1alpha1.TLSRoute: + c.tlsRoutes = append(c.tlsRoutes, o) default: panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o)) } @@ -126,7 +132,7 @@ func (c clientMock) GetGateways() []*v1alpha1.Gateway { } func (c clientMock) GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { - httpRoutes := make([]*v1alpha1.HTTPRoute, len(c.httpRoutes)) + var httpRoutes []*v1alpha1.HTTPRoute for _, httpRoute := range c.httpRoutes { if httpRoute.Namespace == namespace && selector.Matches(labels.Set(httpRoute.Labels)) { @@ -136,6 +142,28 @@ func (c clientMock) GetHTTPRoutes(namespace string, selector labels.Selector) ([ return httpRoutes, nil } +func (c clientMock) GetTCPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) { + var tcpRoutes []*v1alpha1.TCPRoute + + for _, tcpRoute := range c.tcpRoutes { + if tcpRoute.Namespace == namespace && selector.Matches(labels.Set(tcpRoute.Labels)) { + tcpRoutes = append(tcpRoutes, tcpRoute) + } + } + return tcpRoutes, nil +} + +func (c clientMock) GetTLSRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) { + var tlsRoutes []*v1alpha1.TLSRoute + + for _, tlsRoute := range c.tlsRoutes { + if tlsRoute.Namespace == namespace && selector.Matches(labels.Set(tlsRoute.Labels)) { + tlsRoutes = append(tlsRoutes, tlsRoute) + } + } + return tlsRoutes, nil +} + func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) { if c.apiServiceError != nil { return nil, false, c.apiServiceError diff --git a/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/gatewayclass_with_unknown_controller.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml index 5dbeae273..d380ec774 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_with_unknown_controller.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/one_rule_two_targets.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/one_rule_two_targets.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml index d88b90889..6d847a175 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/one_rule_two_targets.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/simple.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml index 821a8fccc..c3116d2f5 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/simple.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml similarity index 93% rename from pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml index 744599728..9494c0ba5 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute @@ -49,4 +50,4 @@ spec: port: 80 - serviceName: whoami port: 80 - weight: 1 \ No newline at end of file + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml index b86f2773f..50bfc0bc6 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml new file mode 100644 index 000000000..2c7cf375e --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml @@ -0,0 +1,47 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: ImplementationSpecific + value: /bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml similarity index 88% rename from docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml index 5721c96de..5d9cad308 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml @@ -14,9 +14,9 @@ metadata: namespace: default spec: gatewayClassName: my-gateway-class - listeners: + listeners: # Use GatewayClass defaults for listener definition. - protocol: HTTP - port: 80 + port: 443 routes: kind: HTTPRoute namespaces: @@ -35,7 +35,7 @@ metadata: app: foo spec: hostnames: - - "whoami" + - "foo.com" rules: - matches: - path: diff --git a/pkg/provider/kubernetes/gateway/fixtures/two_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/two_rules.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml index 6ac21dc0d..f2b2b10f4 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/two_rules.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_multiple_host.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml similarity index 94% rename from pkg/provider/kubernetes/gateway/fixtures/with_multiple_host.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml index 9333258fa..91cc07280 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_multiple_host.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml similarity index 96% rename from pkg/provider/kubernetes/gateway/fixtures/with_protocol_https.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml index 1e76a834a..8262a65d3 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml @@ -38,7 +38,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml new file mode 100644 index 000000000..93c611d59 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml @@ -0,0 +1,64 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTPS + port: 443 + tls: + mode: Passthrough + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https_without_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/with_protocol_https_without_tls.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml index 525557dab..9968576ae 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https_without_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml new file mode 100644 index 000000000..17d31657b --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml @@ -0,0 +1,47 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 443 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml new file mode 100644 index 000000000..97213a151 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml @@ -0,0 +1,64 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 443 + tls: + mode: Terminate + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_several_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml similarity index 96% rename from pkg/provider/kubernetes/gateway/fixtures/with_several_rules.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml index 05c0e8675..7821d2b74 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_several_rules.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_two_gateways_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml similarity index 94% rename from pkg/provider/kubernetes/gateway/fixtures/with_two_gateways_one_httproute.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml index 09c65d346..1a1ee1253 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_two_gateways_one_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml @@ -38,7 +38,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: Gateway @@ -56,7 +57,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_two_listeners_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml similarity index 93% rename from pkg/provider/kubernetes/gateway/fixtures/with_two_listeners_one_httproute.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml index ba86b19cb..928d77d27 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_two_listeners_one_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml @@ -38,7 +38,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo - protocol: HTTP port: 80 routes: @@ -46,7 +47,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml similarity index 95% rename from pkg/provider/kubernetes/gateway/fixtures/with_wrong_service_port.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml index 2ffa7cb44..43fbaa501 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_wrong_service_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml @@ -22,7 +22,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml similarity index 94% rename from pkg/provider/kubernetes/gateway/fixtures/without_gatewayclass.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml index 13c851195..d585d7f2c 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/without_gatewayclass.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml @@ -14,7 +14,8 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo --- kind: HTTPRoute diff --git a/pkg/provider/kubernetes/gateway/fixtures/without_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml similarity index 91% rename from pkg/provider/kubernetes/gateway/fixtures/without_httproute.yml rename to pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml index 4694217dc..2bbc734b1 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/without_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml @@ -22,4 +22,5 @@ spec: namespaces: from: Same selector: - app: foo + matchLabels: + app: foo diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml new file mode 100644 index 000000000..77c66c1c9 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml @@ -0,0 +1,131 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + - protocol: HTTPS + port: 9443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 10000 + hostname: tls.foo.example.com + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 11000 + hostname: pass.tls.foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml new file mode 100644 index 000000000..b42d857c5 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml @@ -0,0 +1,41 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: UNKNOWN + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml new file mode 100644 index 000000000..be61af4e1 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml @@ -0,0 +1,41 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: HTTP + port: 9080 + routes: + kind: UnknownRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml new file mode 100644 index 000000000..16467833b --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml @@ -0,0 +1,41 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: TLS + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml new file mode 100644 index 000000000..cb4dd126d --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml @@ -0,0 +1,139 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app-1 + - protocol: HTTP + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app-2 + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 9000 + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + - protocol: TLS + port: 9000 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-2 + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml new file mode 100644 index 000000000..93f0661c1 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml @@ -0,0 +1,75 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-mixed-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + selector: + matchLabels: + app: label-tls-app-1 + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + selector: + matchLabels: + app: label-tls-app-1 + - protocol: TLS + port: 9443 + tls: + mode: Passthrough + routes: + kind: TLSRoute + selector: + matchLabels: + app: label-http-app-1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: label-tls-app-1 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: label-http-app-1 +spec: + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoamitcp + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/services.yml b/pkg/provider/kubernetes/gateway/fixtures/services.yml index 9c75a59c3..b2c81d94b 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/services.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/services.yml @@ -107,7 +107,6 @@ spec: - name: websecure2 port: 8443 targetPort: websecure2 - scheme: https selector: app: containous task: whoami3 @@ -164,3 +163,39 @@ spec: - name: https protocol: TCP port: 443 + + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoamitcp + namespace: default + +subsets: + - addresses: + - ip: 10.10.0.9 + - ip: 10.10.0.10 + ports: + - name: tcp-1 + protocol: TCP + port: 9000 + - name: tcp-2 + protocol: TCP + port: 10000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoamitcp + namespace: default + +spec: + ports: + - protocol: TCP + port: 9000 + name: tcp-1 + - protocol: TCP + port: 10000 + name: tcp-2 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml new file mode 100644 index 000000000..df4658917 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml @@ -0,0 +1,41 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: unkown.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 8080 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: TCP-app-1 + namespace: default + labels: + app: foo +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml new file mode 100644 index 000000000..bde9383db --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml @@ -0,0 +1,40 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + selector: + matchLabels: + app: whoamitcp + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml new file mode 100644 index 000000000..264599220 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml @@ -0,0 +1,47 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - weight: 1 + backendRef: + group: traefik.containo.us + kind: TraefikService + name: service@file + port: 9000 + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml new file mode 100644 index 000000000..863b37c10 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml @@ -0,0 +1,62 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + selector: + matchLabels: + app: label-tcp-app-1 + - protocol: TCP + port: 10000 + routes: + kind: TCPRoute + selector: + matchLabels: + app: label-tcp-app-2 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: label-tcp-app-1 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-2 + namespace: default + labels: + app: label-tcp-app-2 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 10000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml new file mode 100644 index 000000000..ea8a95ed9 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml @@ -0,0 +1,44 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + selector: + matchLabels: + app: label-tcp-app + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app + namespace: default + labels: + app: label-tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + - forwardTo: + - serviceName: whoamitcp + port: 10000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml new file mode 100644 index 000000000..8f70a67a7 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml @@ -0,0 +1,42 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9000 + hostname: foo.example.com + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml new file mode 100644 index 000000000..f19ded9c4 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml @@ -0,0 +1,59 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTPS + port: 9000 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml new file mode 100644 index 000000000..b43f91575 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml @@ -0,0 +1,59 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9000 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml new file mode 100644 index 000000000..88fd5793a --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml @@ -0,0 +1,41 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 8080 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: TCP-app-1 + namespace: default + labels: + app: foo +spec: + rules: + - forwardTo: + - serviceName: whoami + weight: 1 + port: 9000 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml new file mode 100644 index 000000000..5c8eef5f2 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml @@ -0,0 +1,33 @@ +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 8080 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: TCP-app-1 + namespace: default + labels: + app: foo +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml new file mode 100644 index 000000000..53d84d6af --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml @@ -0,0 +1,26 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 8080 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml new file mode 100644 index 000000000..03ee5a759 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml @@ -0,0 +1,43 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 8080 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml new file mode 100644 index 000000000..f92d5b608 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: unkown.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + port: 8080 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: foo +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml new file mode 100644 index 000000000..2aff54e94 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tls-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + hostname: foo.example.com + port: 9000 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + selector: + matchLabels: + app: whoamitcp + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_with_certificateRef.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_with_certificateRef.yml new file mode 100644 index 000000000..b9f4e6c28 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_with_certificateRef.yml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tls-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + hostname: foo.example.com + port: 9000 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TLSRoute + selector: + matchLabels: + app: whoamitls + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: whoamitls +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml new file mode 100644 index 000000000..37fd4de2b --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml @@ -0,0 +1,54 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tls-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + hostname: foo.example.com + port: 9000 + tls: + mode: Passthrough + routes: + kind: TLSRoute + selector: + matchLabels: + app: whoamitcp + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml new file mode 100644 index 000000000..9a230285e --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml @@ -0,0 +1,63 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9000 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - weight: 1 + backendRef: + group: traefik.containo.us + kind: TraefikService + name: service@file + port: 9000 + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml new file mode 100644 index 000000000..ffc3c459b --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml @@ -0,0 +1,48 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + matches: + - snis: + - foo.bar + diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml new file mode 100644 index 000000000..8a8a55a50 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml @@ -0,0 +1,49 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + matches: + - snis: + - foo.bar + - "*" + diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml new file mode 100644 index 000000000..ced8e2288 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml @@ -0,0 +1,49 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + matches: + - snis: + - foo.bar + - fiz.baz + diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml new file mode 100644 index 000000000..a156b6d3a --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml @@ -0,0 +1,82 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tls-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + hostname: foo.example.com + port: 9000 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + selector: + matchLabels: + app: label-tcp-app-1 + - protocol: TLS + port: 10000 + tls: + mode: Passthrough + routes: + kind: TLSRoute + selector: + matchLabels: + app: label-tls-app-1 + + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: label-tcp-app-1 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: label-tls-app-1 +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 10000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml new file mode 100644 index 000000000..6d34c2abb --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml @@ -0,0 +1,44 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml new file mode 100644 index 000000000..8b718b2c1 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml @@ -0,0 +1,60 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + # certificateRef is ignored with mode "Passthrough" + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml new file mode 100644 index 000000000..21c9fe572 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml @@ -0,0 +1,42 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9001 + hostname: foo.example.com + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml new file mode 100644 index 000000000..f4ba9656b --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml @@ -0,0 +1,59 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTPS + port: 9001 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml new file mode 100644 index 000000000..c1f5071dc --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml @@ -0,0 +1,42 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9001 + hostname: foo.example.com + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-1 + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml new file mode 100644 index 000000000..e6ad0c281 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 8080 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: foo +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + weight: 1 + port: 9001 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml new file mode 100644 index 000000000..d4944ad90 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml @@ -0,0 +1,33 @@ +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 8080 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-1 + namespace: default + labels: + app: foo +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 8080 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml new file mode 100644 index 000000000..9a3e281c3 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml @@ -0,0 +1,28 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 8080 + tls: + mode: Passthrough + routes: + kind: TLSRoutes + namespaces: + from: Same + selector: + matchLabels: + app: foo diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 491c0235c..b3ff2a1c7 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -32,6 +32,9 @@ const ( providerName = "kubernetesgateway" traefikServiceKind = "TraefikService" traefikServiceGroupName = "traefik.containo.us" + routeHTTPKind = "HTTPRoute" + routeTCPKind = "TCPRoute" + routeTLSKind = "TLSRoute" ) // Provider holds configurations of the provider. @@ -222,7 +225,7 @@ func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Clie continue } - cfg, err := p.createGatewayConf(client, gateway) + cfg, err := p.createGatewayConf(ctxLog, client, gateway) if err != nil { logger.Error(err) continue @@ -262,7 +265,7 @@ func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Clie return conf } -func (p *Provider) createGatewayConf(client Client, gateway *v1alpha1.Gateway) (*dynamic.Configuration, error) { +func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway *v1alpha1.Gateway) (*dynamic.Configuration, error) { conf := &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -285,7 +288,7 @@ func (p *Provider) createGatewayConf(client Client, gateway *v1alpha1.Gateway) ( // GatewayReasonListenersNotValid is used when one or more // Listeners have an invalid or unsupported configuration // and cannot be configured on the Gateway. - listenerStatuses := p.fillGatewayConf(client, gateway, conf, tlsConfigs) + listenerStatuses := p.fillGatewayConf(ctx, client, gateway, conf, tlsConfigs) gatewayStatus, errG := p.makeGatewayStatus(listenerStatuses) @@ -305,8 +308,10 @@ func (p *Provider) createGatewayConf(client Client, gateway *v1alpha1.Gateway) ( return conf, nil } -func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []v1alpha1.ListenerStatus { +func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *v1alpha1.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []v1alpha1.ListenerStatus { listenerStatuses := make([]v1alpha1.ListenerStatus, len(gateway.Spec.Listeners)) + logger := log.FromContext(ctx) + allocatedPort := map[v1alpha1.PortNumber]v1alpha1.ProtocolType{} for i, listener := range gateway.Spec.Listeners { listenerStatuses[i] = v1alpha1.ListenerStatus{ @@ -315,7 +320,8 @@ func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, con } // Supported Protocol - if listener.Protocol != v1alpha1.HTTPProtocolType && listener.Protocol != v1alpha1.HTTPSProtocolType { + if listener.Protocol != v1alpha1.HTTPProtocolType && listener.Protocol != v1alpha1.HTTPSProtocolType && + listener.Protocol != v1alpha1.TCPProtocolType && listener.Protocol != v1alpha1.TLSProtocolType { // update "Detached" status true with "UnsupportedProtocol" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ Type: string(v1alpha1.ListenerConditionDetached), @@ -328,6 +334,50 @@ func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, con continue } + // Supported Route types + if listener.Routes.Kind != routeHTTPKind && listener.Routes.Kind != routeTCPKind && listener.Routes.Kind != routeTLSKind { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Unsupported Route Kind %q", listener.Routes.Kind), + }) + + continue + } + + // Protocol compliant with route type + if listener.Protocol == v1alpha1.HTTPProtocolType && listener.Routes.Kind != routeHTTPKind || + listener.Protocol == v1alpha1.HTTPSProtocolType && listener.Routes.Kind != routeHTTPKind || + listener.Protocol == v1alpha1.TCPProtocolType && listener.Routes.Kind != routeTCPKind || + listener.Protocol == v1alpha1.TLSProtocolType && listener.Routes.Kind != routeTLSKind && listener.Routes.Kind != routeTCPKind { + // update "Detached" status true with "UnsupportedProtocol" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionDetached), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonUnsupportedProtocol), + Message: fmt.Sprintf("listener protocol %q not supported with route kind %q", listener.Protocol, listener.Routes.Kind), + }) + + continue + } + + if _, ok := allocatedPort[listener.Port]; ok { + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionDetached), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonPortUnavailable), + Message: fmt.Sprintf("port %d unavailable", listener.Port), + }) + + continue + } + + allocatedPort[listener.Port] = listener.Protocol ep, err := p.entryPointName(listener.Port, listener.Protocol) if err != nil { // update "Detached" status with "PortUnavailable" reason @@ -342,8 +392,9 @@ func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, con continue } - if listener.Protocol == v1alpha1.HTTPSProtocolType { - if listener.TLS == nil { + // TLS + if listener.Protocol == v1alpha1.HTTPSProtocolType || listener.Protocol == v1alpha1.TLSProtocolType { + if listener.TLS == nil || (listener.TLS.CertificateRef == nil && listener.TLS.Mode != v1alpha1.TLSModePassthrough) { // update "Detached" status with "UnsupportedProtocol" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ Type: string(v1alpha1.ListenerConditionDetached), @@ -356,167 +407,397 @@ func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, con continue } - if listener.TLS.CertificateRef.Kind != "Secret" || listener.TLS.CertificateRef.Group != "core" { - // update "ResolvedRefs" status true with "InvalidCertificateRef" reason + if listener.TLS.Mode == v1alpha1.TLSModePassthrough && listener.TLS.CertificateRef != nil { + // https://gateway-api.sigs.k8s.io/guides/tls/ + logger.Warnf("In case of Passthrough TLS mode, no TLS settings take effect as the TLS session from the client is NOT terminated at the Gateway") + } + + isTLSPassthrough := listener.TLS.Mode == v1alpha1.TLSModePassthrough + + // Allowed configurations: + // Protocol TLS -> Passthrough -> TLSRoute + // Protocol TLS -> Terminate -> TCPRoute + // Protocol HTTPS -> Terminate -> HTTPRoute + if !(listener.Protocol == v1alpha1.TLSProtocolType && isTLSPassthrough && listener.Routes.Kind == routeTLSKind || + listener.Protocol == v1alpha1.TLSProtocolType && !isTLSPassthrough && listener.Routes.Kind == routeTCPKind || + listener.Protocol == v1alpha1.HTTPSProtocolType && !isTLSPassthrough && listener.Routes.Kind == routeHTTPKind) { + // update "ConditionDetached" status true with "ReasonUnsupportedProtocol" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(v1alpha1.ListenerConditionResolvedRefs), - Status: metav1.ConditionFalse, + Type: string(v1alpha1.ListenerConditionDetached), + Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now(), - Reason: string(v1alpha1.ListenerReasonInvalidCertificateRef), - Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind : %v/%v", listener.TLS.CertificateRef.Group, listener.TLS.CertificateRef.Kind), + Reason: string(v1alpha1.ListenerReasonUnsupportedProtocol), + Message: fmt.Sprintf("Unsupported route kind %q with %q", + listener.Routes.Kind, listener.TLS.Mode), }) continue } - configKey := gateway.Namespace + "/" + listener.TLS.CertificateRef.Name - if _, tlsExists := tlsConfigs[configKey]; !tlsExists { - tlsConf, err := getTLS(client, listener.TLS.CertificateRef.Name, gateway.Namespace) - if err != nil { + if !isTLSPassthrough { + if listener.TLS.CertificateRef.Kind != "Secret" || listener.TLS.CertificateRef.Group != "core" { // update "ResolvedRefs" status true with "InvalidCertificateRef" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ Type: string(v1alpha1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonInvalidCertificateRef), - Message: fmt.Sprintf("Error while retrieving certificate: %v", err), + Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind : %v/%v", listener.TLS.CertificateRef.Group, listener.TLS.CertificateRef.Kind), }) continue } - tlsConfigs[configKey] = tlsConf + configKey := gateway.Namespace + "/" + listener.TLS.CertificateRef.Name + if _, tlsExists := tlsConfigs[configKey]; !tlsExists { + tlsConf, err := getTLS(client, listener.TLS.CertificateRef.Name, gateway.Namespace) + if err != nil { + // update "ResolvedRefs" status true with "InvalidCertificateRef" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidCertificateRef), + Message: fmt.Sprintf("Error while retrieving certificate: %v", err), + }) + + continue + } + + tlsConfigs[configKey] = tlsConf + } } } - // Supported Route types - if listener.Routes.Kind != "HTTPRoute" { - // update "ResolvedRefs" status true with "InvalidRoutesRef" reason - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(v1alpha1.ListenerConditionResolvedRefs), - Status: metav1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), - Message: fmt.Sprintf("Unsupported Route Kind %q", listener.Routes.Kind), - }) - - continue + switch listener.Routes.Kind { + case routeHTTPKind: + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, gatewayHTTPRouteToHTTPConf(ctx, ep, listener, gateway, client, conf)...) + case routeTCPKind: + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, gatewayTCPRouteToTCPConf(ctx, ep, listener, gateway, client, conf)...) + case routeTLSKind: + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, gatewayTLSRouteToTCPConf(ctx, ep, listener, gateway, client, conf)...) } + } - // TODO: support RouteNamespaces - httpRoutes, err := client.GetHTTPRoutes(gateway.Namespace, labels.SelectorFromSet(listener.Routes.Selector.MatchLabels)) + return listenerStatuses +} + +func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener v1alpha1.Listener, gateway *v1alpha1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { + // TODO: support RouteNamespaces + selector := labels.SelectorFromSet(listener.Routes.Selector.MatchLabels) + httpRoutes, err := client.GetHTTPRoutes(gateway.Namespace, selector) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch %ss for namespace %q and matchLabels %v", listener.Routes.Kind, gateway.Namespace, listener.Routes.Selector.MatchLabels), + }} + } + + if len(httpRoutes) == 0 { + log.FromContext(ctx).Debugf("No HTTPRoutes found for selector %q", selector) + return nil + } + + var conditions []metav1.Condition + for _, httpRoute := range httpRoutes { + hostRule, err := hostRule(httpRoute.Spec) if err != nil { - // update "ResolvedRefs" status true with "InvalidRoutesRef" reason - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + conditions = append(conditions, metav1.Condition{ Type: string(v1alpha1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), - Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), - Message: fmt.Sprintf("Cannot fetch HTTPRoutes for namespace %q and matchLabels %v", gateway.Namespace, listener.Routes.Selector.MatchLabels), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Skipping HTTPRoute %s: invalid hostname: %v", httpRoute.Name, err), }) continue } - for _, httpRoute := range httpRoutes { - // Should never happen - if httpRoute == nil { - continue - } - - hostRule, err := hostRule(httpRoute.Spec) + for _, routeRule := range httpRoute.Spec.Rules { + rule, err := extractRule(routeRule, hostRule) if err != nil { - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ Type: string(v1alpha1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Skipping HTTPRoute %s: invalid hostname: %v", httpRoute.Name, err), + Message: fmt.Sprintf("Skipping %s %s: cannot generate rule: %v", listener.Routes.Kind, httpRoute.Name, err), }) + } + + router := dynamic.Router{ + Rule: rule, + EntryPoints: []string{ep}, + } + + if listener.TLS != nil { + // TODO support let's encrypt + router.TLS = &dynamic.RouterTLSConfig{} + } + + // Adding the gateway name and the entryPoint name prevents overlapping of routers build from the same routes. + routerName := httpRoute.Name + "-" + gateway.Name + "-" + ep + routerKey, err := makeRouterKey(router.Rule, makeID(httpRoute.Namespace, routerName)) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Skipping %s %s: cannot make router's key with rule %s: %v", listener.Routes.Kind, httpRoute.Name, router.Rule, err), + }) + + // TODO update the RouteStatus condition / deduplicate conditions on listener continue } - for _, routeRule := range httpRoute.Spec.Rules { - rule, err := extractRule(routeRule, hostRule) + if routeRule.ForwardTo == nil { + continue + } + + // Traefik internal service can be used only if there is only one ForwardTo service reference. + if len(routeRule.ForwardTo) == 1 && isInternalService(routeRule.ForwardTo[0]) { + router.Service = routeRule.ForwardTo[0].BackendRef.Name + } else { + wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo) if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + conditions = append(conditions, metav1.Condition{ Type: string(v1alpha1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Skipping HTTPRoute %s: cannot generate rule: %v", httpRoute.Name, err), - }) - continue - } - - router := dynamic.Router{ - Rule: rule, - EntryPoints: []string{ep}, - } - - if listener.TLS != nil { - // TODO support let's encrypt - router.TLS = &dynamic.RouterTLSConfig{} - } - - // Adding the gateway name and the entryPoint name prevents overlapping of routers build from the same routes. - routerName := httpRoute.Name + "-" + gateway.Name + "-" + ep - routerKey, err := makeRouterKey(router.Rule, makeID(httpRoute.Namespace, routerName)) - if err != nil { - // update "ResolvedRefs" status true with "DroppedRoutes" reason - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(v1alpha1.ListenerConditionResolvedRefs), - Status: metav1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Skipping HTTPRoute %s: cannot make router's key with rule %s: %v", httpRoute.Name, router.Rule, err), + Message: fmt.Sprintf("Cannot load service from %s %s/%s : %v", listener.Routes.Kind, gateway.Namespace, httpRoute.Name, err), }) // TODO update the RouteStatus condition / deduplicate conditions on listener continue } - if routeRule.ForwardTo != nil { - // Traefik internal service can be used only if there is only one ForwardTo service reference. - if len(routeRule.ForwardTo) == 1 && isInternalService(routeRule.ForwardTo[0]) { - router.Service = routeRule.ForwardTo[0].BackendRef.Name - } else { - wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo) - if err != nil { - // update "ResolvedRefs" status true with "DroppedRoutes" reason - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(v1alpha1.ListenerConditionResolvedRefs), - Status: metav1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Cannot load service from HTTPRoute %s/%s : %v", gateway.Namespace, httpRoute.Name, err), - }) - - // TODO update the RouteStatus condition / deduplicate conditions on listener - continue - } - - for svcName, svc := range subServices { - conf.HTTP.Services[svcName] = svc - } - - serviceName := provider.Normalize(routerKey + "-wrr") - conf.HTTP.Services[serviceName] = wrrService - - router.Service = serviceName - } + for svcName, svc := range subServices { + conf.HTTP.Services[svcName] = svc } - if router.Service != "" { - routerKey = provider.Normalize(routerKey) + serviceName := provider.Normalize(routerKey + "-wrr") + conf.HTTP.Services[serviceName] = wrrService - conf.HTTP.Routers[routerKey] = &router - } + router.Service = serviceName } + + routerKey = provider.Normalize(routerKey) + conf.HTTP.Routers[routerKey] = &router } } - return listenerStatuses + return conditions +} + +func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1.Listener, gateway *v1alpha1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { + // TODO: support RouteNamespaces + selector := labels.SelectorFromSet(listener.Routes.Selector.MatchLabels) + tcpRoutes, err := client.GetTCPRoutes(gateway.Namespace, selector) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch %ss for namespace %q and matchLabels %v", listener.Routes.Kind, gateway.Namespace, listener.Routes.Selector.MatchLabels), + }} + } + + if len(tcpRoutes) == 0 { + log.FromContext(ctx).Debugf("No TCPRoutes found for selector %q", selector) + return nil + } + + var conditions []metav1.Condition + for _, tcpRoute := range tcpRoutes { + if len(tcpRoute.Spec.Rules) > 1 { + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Skipping %s %s: multiple rules are not supported", listener.Routes.Kind, tcpRoute.Name), + }) + continue + } + + for _, routeRule := range tcpRoute.Spec.Rules { + router := dynamic.TCPRouter{ + Rule: "HostSNI(`*`)", // Gateway listener hostname not available in TCP + EntryPoints: []string{ep}, + } + + if listener.TLS != nil { + // TODO support let's encrypt + router.TLS = &dynamic.RouterTCPTLSConfig{} + } + + // Adding the gateway name and the entryPoint name prevents overlapping of routers build from the same routes. + routerName := tcpRoute.Name + "-" + gateway.Name + "-" + ep + routerKey, err := makeRouterKey("", makeID(tcpRoute.Namespace, routerName)) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Skipping %s %s: cannot make router's key with rule %s: %v", listener.Routes.Kind, tcpRoute.Name, router.Rule, err), + }) + + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + // Should not happen due to validation + // https://github.com/kubernetes-sigs/gateway-api/blob/af68a622f072811767d246ef5897135d93af0704/apis/v1alpha1/tcproute_types.go#L76 + if routeRule.ForwardTo == nil { + continue + } + + wrrService, subServices, err := loadTCPServices(client, gateway.Namespace, routeRule.ForwardTo) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Cannot load service from %s %s/%s : %v", listener.Routes.Kind, gateway.Namespace, tcpRoute.Name, err), + }) + + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + for svcName, svc := range subServices { + conf.TCP.Services[svcName] = svc + } + + serviceName := provider.Normalize(routerKey + "-wrr") + conf.TCP.Services[serviceName] = wrrService + + router.Service = serviceName + + routerKey = provider.Normalize(routerKey) + conf.TCP.Routers[routerKey] = &router + } + } + + return conditions +} + +func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1.Listener, gateway *v1alpha1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { + // TODO: support RouteNamespaces + selector := labels.SelectorFromSet(listener.Routes.Selector.MatchLabels) + tlsRoutes, err := client.GetTLSRoutes(gateway.Namespace, selector) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch %ss for namespace %q and matchLabels %v", listener.Routes.Kind, gateway.Namespace, listener.Routes.Selector.MatchLabels), + }} + } + + if len(tlsRoutes) == 0 { + log.FromContext(ctx).Debugf("No TLSRoutes found for selector %q", selector) + return nil + } + + var conditions []metav1.Condition + for _, tlsRoute := range tlsRoutes { + for _, routeRule := range tlsRoute.Spec.Rules { + rule, err := hostSNIRule(routeRule) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Skipping %s %s: cannot make route's SNI match: %v", listener.Routes.Kind, tlsRoute.Name, err), + }) + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + router := dynamic.TCPRouter{ + Rule: rule, + EntryPoints: []string{ep}, + } + + if listener.TLS != nil { + // TODO support let's encrypt + router.TLS = &dynamic.RouterTCPTLSConfig{ + Passthrough: listener.TLS.Mode == v1alpha1.TLSModePassthrough, + } + } + + // Adding the gateway name and the entryPoint name prevents overlapping of routers build from the same routes. + routerName := tlsRoute.Name + "-" + gateway.Name + "-" + ep + routerKey, err := makeRouterKey(rule, makeID(tlsRoute.Namespace, routerName)) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Skipping %s %s: cannot make router's key with rule %s: %v", listener.Routes.Kind, tlsRoute.Name, router.Rule, err), + }) + + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + // Should not happen due to validation + // https://github.com/kubernetes-sigs/gateway-api/blob/af68a622f072811767d246ef5897135d93af0704/apis/v1alpha1/tlsroute_types.go#L79 + if routeRule.ForwardTo == nil { + continue + } + + wrrService, subServices, err := loadTCPServices(client, gateway.Namespace, routeRule.ForwardTo) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + conditions = append(conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Cannot load service from %s %s/%s : %v", listener.Routes.Kind, gateway.Namespace, tlsRoute.Name, err), + }) + + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + for svcName, svc := range subServices { + conf.TCP.Services[svcName] = svc + } + + serviceName := provider.Normalize(routerKey + "-wrr") + conf.TCP.Services[serviceName] = wrrService + + router.Service = serviceName + + routerKey = provider.Normalize(routerKey) + conf.TCP.Routers[routerKey] = &router + } + } + + return conditions } func (p *Provider) makeGatewayStatus(listenerStatuses []v1alpha1.ListenerStatus) (v1alpha1.GatewayStatus, error) { @@ -601,7 +882,7 @@ func hostRule(httpRouteSpec v1alpha1.HTTPRouteSpec) (string, error) { continue } - // https://gateway-api.sigs.k8s.io/spec/#networking.x-k8s.io/v1alpha1.Hostname + // https://gateway-api.sigs.k8s.io/references/spec/#networking.x-k8s.io/v1alpha1.Hostname if !strings.HasPrefix(host, "*.") || wildcard > 1 { return "", fmt.Errorf("invalid rule: %q", host) } @@ -627,6 +908,38 @@ func hostRule(httpRouteSpec v1alpha1.HTTPRouteSpec) (string, error) { return hostRegexp, nil } +func hostSNIRule(rule v1alpha1.TLSRouteRule) (string, error) { + uniqHostnames := map[string]struct{}{} + var hostnames []string + for _, match := range rule.Matches { + for _, hostname := range match.SNIs { + if len(hostname) == 0 { + continue + } + + h := string(hostname) + + // first naive validation, should be improved + wildcardNb := strings.Count(h, "*") + if wildcardNb != 0 && !strings.HasPrefix(h, "*.") || wildcardNb > 1 { + return "", fmt.Errorf("invalid hostname: %q", h) + } + + hostname := "`" + h + "`" + if _, ok := uniqHostnames[hostname]; !ok { + hostnames = append(hostnames, hostname) + uniqHostnames[hostname] = struct{}{} + } + } + } + + if len(hostnames) == 0 { + return "HostSNI(`*`)", nil + } + + return "HostSNI(" + strings.Join(hostnames, ",") + ")", nil +} + func extractRule(routeRule v1alpha1.HTTPRouteRule, hostRule string) (string, error) { var rule string var matchesRules []string @@ -700,6 +1013,7 @@ func (p *Provider) entryPointName(port v1alpha1.PortNumber, protocol v1alpha1.Pr for name, entryPoint := range p.EntryPoints { if strings.HasSuffix(entryPoint.Address, ":"+portStr) { // if the protocol is HTTP the entryPoint must have no TLS conf + // Not relevant for v1alpha1.TLSProtocolType && v1alpha1.TCPProtocolType if protocol == v1alpha1.HTTPProtocolType && entryPoint.HasHTTPTLSConf { continue } @@ -860,13 +1174,11 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF continue } - var portName string var portSpec corev1.ServicePort var match bool for _, p := range service.Spec.Ports { if forwardTo.Port == nil || p.Port == int32(*forwardTo.Port) { - portName = p.Name portSpec = p match = true break @@ -894,7 +1206,7 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF var portStr string for _, subset := range endpoints.Subsets { for _, p := range subset.Ports { - if portName == p.Name { + if portSpec.Name == p.Name { port = p.Port break } @@ -904,7 +1216,7 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF return nil, nil, errors.New("cannot define a port") } - protocol := getProtocol(portSpec, portName) + protocol := getProtocol(portSpec) portStr = strconv.FormatInt(int64(port), 10) for _, addr := range subset.Addresses { @@ -927,9 +1239,127 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF return wrrSvc, services, nil } -func getProtocol(portSpec corev1.ServicePort, portName string) string { +// loadTCPServices is generating a WRR service, even when there is only one target. +func loadTCPServices(client Client, namespace string, targets []v1alpha1.RouteForwardTo) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) { + services := map[string]*dynamic.TCPService{} + + wrrSvc := &dynamic.TCPService{ + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{}, + }, + } + + for _, forwardTo := range targets { + weight := int(forwardTo.Weight) + + if forwardTo.ServiceName == nil && forwardTo.BackendRef != nil { + if !(forwardTo.BackendRef.Group == traefikServiceGroupName && forwardTo.BackendRef.Kind == traefikServiceKind) { + continue + } + + if strings.HasSuffix(forwardTo.BackendRef.Name, "@internal") { + return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a TCP service", forwardTo.BackendRef.Name) + } + + wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: forwardTo.BackendRef.Name, Weight: &weight}) + continue + } + + if forwardTo.ServiceName == nil { + continue + } + + svc := dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{}, + } + + service, exists, err := client.GetService(namespace, *forwardTo.ServiceName) + if err != nil { + return nil, nil, err + } + + if !exists { + return nil, nil, errors.New("service not found") + } + + if len(service.Spec.Ports) > 1 && forwardTo.Port == nil { + // If the port is unspecified and the backend is a Service + // object consisting of multiple port definitions, the route + // must be dropped from the Gateway. The controller should + // raise the "ResolvedRefs" condition on the Gateway with the + // "DroppedRoutes" reason. The gateway status for this route + // should be updated with a condition that describes the error + // more specifically. + log.WithoutContext().Errorf("A multiple ports Kubernetes Service cannot be used if unspecified forwardTo.Port") + continue + } + + var portSpec corev1.ServicePort + var match bool + + for _, p := range service.Spec.Ports { + if forwardTo.Port == nil || p.Port == int32(*forwardTo.Port) { + portSpec = p + match = true + break + } + } + + if !match { + return nil, nil, errors.New("service port not found") + } + + endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, *forwardTo.ServiceName) + if endpointsErr != nil { + return nil, nil, endpointsErr + } + + if !endpointsExists { + return nil, nil, errors.New("endpoints not found") + } + + if len(endpoints.Subsets) == 0 { + return nil, nil, errors.New("subset not found") + } + + var port int32 + var portStr string + for _, subset := range endpoints.Subsets { + for _, p := range subset.Ports { + if portSpec.Name == p.Name { + port = p.Port + break + } + } + + if port == 0 { + return nil, nil, errors.New("cannot define a port") + } + + portStr = strconv.FormatInt(int64(port), 10) + for _, addr := range subset.Addresses { + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{ + Address: net.JoinHostPort(addr.IP, portStr), + }) + } + } + + serviceName := provider.Normalize(makeID(service.Namespace, service.Name) + "-" + portStr) + services[serviceName] = &svc + + wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight}) + } + + if len(wrrSvc.Weighted.Services) == 0 { + return nil, nil, errors.New("no service has been created") + } + + return wrrSvc, services, nil +} + +func getProtocol(portSpec corev1.ServicePort) string { protocol := "http" - if portSpec.Port == 443 || strings.HasPrefix(portName, "https") { + if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") { protocol = "https" } diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 09f4fe100..d34d3fd87 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -45,7 +45,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Empty because missing entry point", - paths: []string{"services.yml", "simple.yml"}, + paths: []string{"services.yml", "httproute/simple.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":443", }}, @@ -68,7 +68,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Empty because no http route defined", - paths: []string{"services.yml", "without_httproute.yml"}, + paths: []string{"services.yml", "httproute/without_httproute.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -94,7 +94,7 @@ func TestLoadHTTPRoutes(t *testing.T) { entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, - paths: []string{"services.yml", "without_gatewayclass.yml"}, + paths: []string{"services.yml", "httproute/without_gatewayclass.yml"}, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -117,7 +117,7 @@ func TestLoadHTTPRoutes(t *testing.T) { entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, - paths: []string{"services.yml", "gatewayclass_with_unknown_controller.yml"}, + paths: []string{"services.yml", "httproute/gatewayclass_with_unknown_controller.yml"}, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -140,7 +140,7 @@ func TestLoadHTTPRoutes(t *testing.T) { entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, - paths: []string{"services.yml", "with_wrong_service_port.yml"}, + paths: []string{"services.yml", "httproute/with_wrong_service_port.yml"}, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -163,7 +163,7 @@ func TestLoadHTTPRoutes(t *testing.T) { entryPoints: map[string]Entrypoint{"websecure": { Address: ":443", }}, - paths: []string{"services.yml", "with_protocol_https_without_tls.yml"}, + paths: []string{"services.yml", "httproute/with_protocol_https_without_tls.yml"}, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -181,9 +181,272 @@ func TestLoadHTTPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Empty caused by HTTPS with TLS passthrough", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":443", + }}, + paths: []string{"services.yml", "httproute/with_protocol_https_with_tls_passthrough.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by HTTPRoute with protocol TLS", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":443", + }}, + paths: []string{"services.yml", "httproute/with_protocol_tls.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by HTTPRoute with protocol TCP", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":443", + }}, + paths: []string{"services.yml", "httproute/with_protocol_tcp.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TCPRoute with protocol HTTP", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":9000", + }}, + paths: []string{"services.yml", "tcproute/with_protocol_http.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TCPRoute with protocol HTTPS", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":9000", + }}, + paths: []string{"services.yml", "tcproute/with_protocol_https.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TLSRoute with protocol TCP", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":9001", + }}, + paths: []string{"services.yml", "tlsroute/with_protocol_tcp.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TLSRoute with protocol HTTP", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":9001", + }}, + paths: []string{"services.yml", "tlsroute/with_protocol_http.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TLSRoute with protocol HTTPS", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":9001", + }}, + paths: []string{"services.yml", "tlsroute/with_protocol_https.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused use http entrypoint with tls activated with HTTPRoute", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":443", + HasHTTPTLSConf: true, + }}, + paths: []string{"services.yml", "httproute/simple_with_tls_entrypoint.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused unsupported HTTPRoute rule", + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + paths: []string{"services.yml", "httproute/simple_with_bad_rule.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because no tcp route defined tls protocol", + paths: []string{"services.yml", "tcproute/without_tcproute_tls_protocol.yml"}, + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, { desc: "Simple HTTPRoute, with foo entrypoint", - paths: []string{"services.yml", "simple.yml"}, + paths: []string{"services.yml", "httproute/simple.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -236,7 +499,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Simple HTTPRoute, with api@internal service", - paths: []string{"services.yml", "simple_to_api_internal.yml"}, + paths: []string{"services.yml", "httproute/simple_to_api_internal.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -265,7 +528,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Simple HTTPRoute, with myservice@file service", - paths: []string{"services.yml", "simple_cross_provider.yml"}, + paths: []string{"services.yml", "httproute/simple_cross_provider.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -322,7 +585,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Simple HTTPRoute with protocol HTTPS", - paths: []string{"services.yml", "with_protocol_https.yml"}, + paths: []string{"services.yml", "httproute/with_protocol_https.yml"}, entryPoints: map[string]Entrypoint{"websecure": { Address: ":443", }}, @@ -385,7 +648,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Simple HTTPRoute, with multiple hosts", - paths: []string{"services.yml", "with_multiple_host.yml"}, + paths: []string{"services.yml", "httproute/with_multiple_host.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -544,7 +807,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "One HTTPRoute with two different rules", - paths: []string{"services.yml", "two_rules.yml"}, + paths: []string{"services.yml", "httproute/two_rules.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -625,7 +888,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "One HTTPRoute with one rule two targets", - paths: []string{"services.yml", "one_rule_two_targets.yml"}, + paths: []string{"services.yml", "httproute/one_rule_two_targets.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -695,7 +958,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Two Gateways and one HTTPRoute", - paths: []string{"services.yml", "with_two_gateways_one_httproute.yml"}, + paths: []string{"services.yml", "httproute/with_two_gateways_one_httproute.yml"}, entryPoints: map[string]Entrypoint{ "web": { Address: ":80", @@ -778,7 +1041,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Gateway with two listeners and one HTTPRoute", - paths: []string{"services.yml", "with_two_listeners_one_httproute.yml"}, + paths: []string{"services.yml", "httproute/with_two_listeners_one_httproute.yml"}, entryPoints: map[string]Entrypoint{ "web": { Address: ":80", @@ -861,7 +1124,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, { desc: "Simple HTTPRoute, with several rules", - paths: []string{"services.yml", "with_several_rules.yml"}, + paths: []string{"services.yml", "httproute/with_several_rules.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, @@ -946,6 +1209,1460 @@ func TestLoadHTTPRoutes(t *testing.T) { } } +func TestLoadTCPRoutes(t *testing.T) { + testCases := []struct { + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + }{ + { + desc: "Empty", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because missing entry point", + paths: []string{"services.yml", "tcproute/simple.yml"}, + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8000", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because no tcp route defined", + paths: []string{"services.yml", "tcproute/without_tcproute.yml"}, + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by missing GatewayClass", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + paths: []string{"services.yml", "tcproute/without_gatewayclass.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by unknown GatewayClass controller name", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + paths: []string{"services.yml", "tcproute/gatewayclass_with_unknown_controller.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by multiport service with wrong TargetPort", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + paths: []string{"services.yml", "tcproute/with_wrong_service_port.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TCPRoute, with foo entrypoint", + paths: []string{"services.yml", "tcproute/simple.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-1-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Multiple TCPRoute, with foo entrypoint", + paths: []string{"services.yml", "tcproute/with_multiple_routes.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp-1": {Address: ":9000"}, + "tcp-2": {Address: ":10000"}, + "not-tcp": {Address: ":11000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp-1"}, + Service: "default-tcp-app-1-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp-2"}, + Service: "default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-10000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + "default-whoamitcp-10000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:10000", + }, + { + Address: "10.10.0.10:10000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TCPRoute with multiple rules", + paths: []string{"services.yml", "tcproute/with_multiple_rules.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp-1": {Address: ":9000"}, + "tcp-2": {Address: ":10000"}, + "not-tcp": {Address: ":11000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TCPRoute, with backendRef", + paths: []string{"services.yml", "tcproute/simple_cross_provider.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "service@file", + Weight: func(i int) *int { return &i }(1), + }, + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TCPRoute, with TLS", + paths: []string{"services.yml", "tcproute/with_protocol_tls.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls"}, + Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{{ + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }}, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + if test.expected == nil { + return + } + + p := Provider{EntryPoints: test.entryPoints} + conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + assert.Equal(t, test.expected, conf) + }) + } +} + +func TestLoadTLSRoutes(t *testing.T) { + testCases := []struct { + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + }{ + { + desc: "Empty", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because no matching entry point", + paths: []string{"services.yml", "tlsroute/simple_TLS_to_TCPRoute.yml"}, + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8000", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because no tls route defined", + paths: []string{"services.yml", "tlsroute/without_tlsroute.yml"}, + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by missing GatewayClass", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + paths: []string{"services.yml", "tlsroute/without_gatewayclass.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by unknown GatewayClass controller name", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + paths: []string{"services.yml", "tlsroute/gatewayclass_with_unknown_controller.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by multiport service with wrong TargetPort", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":8080", + }}, + paths: []string{"services.yml", "tlsroute/with_wrong_service_port.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by mixed routes wrong selector", + entryPoints: map[string]Entrypoint{ + "tcp": { + Address: ":9000", + }, + "tcp-tls": { + Address: ":9443", + }, + "http": { + Address: ":80", + }, + }, + paths: []string{"services.yml", "mixed/with_wrong_routes_selector.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by TLSRoute using a certificateRef", + entryPoints: map[string]Entrypoint{"TCP": { + Address: ":9000", + }}, + paths: []string{"services.yml", "tlsroute/simple_TLS_to_TCPRoute_with_certificateRef.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by simple TLSRoute with invalid SNI matching", + paths: []string{"services.yml", "tlsroute/with_invalid_SNI_matching.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TLS listener to TCPRoute, with foo entrypoint", + paths: []string{"services.yml", "tlsroute/simple_TLS_to_TCPRoute.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Simple TLS listener to TLSRoute", + paths: []string{"services.yml", "tlsroute/simple_TLS_to_TLSRoute.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a": { + EntryPoints: []string{"tcp"}, + Service: "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Multiple TLSRoute, with foo entrypoint", + paths: []string{"services.yml", "tlsroute/with_multiple_routes_kind.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + "tcp": {Address: ":10000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-tls-gateway-tls-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls"}, + Service: "default-tcp-app-1-my-tls-gateway-tls-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a": { + EntryPoints: []string{"tcp"}, + Service: "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-tls-gateway-tls-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-10000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + "default-whoamitcp-10000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:10000", + }, + { + Address: "10.10.0.10:10000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Simple TLSRoute, with backendRef", + paths: []string{"services.yml", "tlsroute/simple_cross_provider.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls"}, + Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "service@file", + Weight: func(i int) *int { return &i }(1), + }, + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Simple TLSRoute, with Passthrough and TLS configuration should raise a warn", + paths: []string{"services.yml", "tlsroute/with_passthrough_tls.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-1-my-gateway-tls-673acf455cb2dab0b43a": { + EntryPoints: []string{"tls"}, + Service: "default-tls-app-1-my-gateway-tls-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-1-my-gateway-tls-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TLSRoute, with Passthrough", + paths: []string{"services.yml", "tlsroute/with_passthrough.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-1-my-gateway-tls-673acf455cb2dab0b43a": { + EntryPoints: []string{"tls"}, + Service: "default-tls-app-1-my-gateway-tls-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-1-my-gateway-tls-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TLSRoute, with single SNI matching", + paths: []string{"services.yml", "tlsroute/with_SNI_matching.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-1-my-gateway-tls-2279fe75c5156dc5eb26": { + EntryPoints: []string{"tls"}, + Service: "default-tls-app-1-my-gateway-tls-2279fe75c5156dc5eb26-wrr", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-1-my-gateway-tls-2279fe75c5156dc5eb26-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple TLSRoute, with multiple SNI matching", + paths: []string{"services.yml", "tlsroute/with_multiple_SNI_matching.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-1-my-gateway-tls-177bd313b8e78ce821eb": { + EntryPoints: []string{"tls"}, + Service: "default-tls-app-1-my-gateway-tls-177bd313b8e78ce821eb-wrr", + Rule: "HostSNI(`foo.bar`,`fiz.baz`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-1-my-gateway-tls-177bd313b8e78ce821eb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + if test.expected == nil { + return + } + + p := Provider{EntryPoints: test.entryPoints} + conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + assert.Equal(t, test.expected, conf) + }) + } +} + +func TestLoadMixedRoutes(t *testing.T) { + testCases := []struct { + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + }{ + { + desc: "Empty", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by unsupported listener.Protocol", + paths: []string{"services.yml", "mixed/with_bad_listener_protocol.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by unsupported listener.Route.Kind", + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + }, + paths: []string{"services.yml", "mixed/with_bad_listener_route_kind.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by listener.Protocol does not support listener.Route.Kind", + paths: []string{"services.yml", "mixed/with_incompatible_protocol_and_route_kind.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Mixed routes", + paths: []string{"services.yml", "mixed/simple.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + "websecure": {Address: ":9443"}, + "tcp": {Address: ":9000"}, + "tls-1": {Address: ":10000"}, + "tls-2": {Address: ":11000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls-1"}, + Service: "default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + "default-tls-app-1-my-gateway-tls-2-673acf455cb2dab0b43a": { + EntryPoints: []string{"tls-2"}, + Service: "default-tls-app-1-my-gateway-tls-2-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tls-app-1-my-gateway-tls-2-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-1-my-gateway-web-a431b128267aabc954fd": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + }, + "default-http-app-1-my-gateway-websecure-a431b128267aabc954fd": { + EntryPoints: []string{"websecure"}, + Service: "default-http-app-1-my-gateway-websecure-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-1-my-gateway-websecure-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Empty caused by mixed routes multiple protocol using same port", + paths: []string{"services.yml", "mixed/with_multiple_protocol_using_same_port.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + "tcp": {Address: ":9000"}, + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + if test.expected == nil { + return + } + + p := Provider{EntryPoints: test.entryPoints} + conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + assert.Equal(t, test.expected, conf) + }) + } +} + func TestHostRule(t *testing.T) { testCases := []struct { desc string @@ -1244,3 +2961,145 @@ func TestExtractRule(t *testing.T) { }) } } + +func TestHostSNI(t *testing.T) { + testCases := []struct { + desc string + routeRule v1alpha1.TLSRouteRule + expectedRule string + expectError bool + }{ + { + desc: "Empty rule", + expectedRule: "HostSNI(`*`)", + }, + { + desc: "Empty rule and matches", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{}, + }, + expectedRule: "HostSNI(`*`)", + }, + { + desc: "One match, SNI with empty hostname", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{""}, + }, + }, + }, + expectedRule: "HostSNI(`*`)", + }, + { + desc: "One match, SNI with one unsupported wildcard", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"*"}, + }, + }, + }, + expectError: true, + }, + { + desc: "One match, SNI with multiple malformed wildcard", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"*.foo.*"}, + }, + }, + }, + expectError: true, + }, + { + desc: "One match, SNI with some empty hostnames", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"foo", "", "bar"}, + }, + }, + }, + expectedRule: "HostSNI(`foo`,`bar`)", + }, + { + desc: "One match, one SNI hostname", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"foo"}, + }, + }, + }, + expectedRule: "HostSNI(`foo`)", + }, + { + desc: "One match, multiple SNI hostnames", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"foo", "bar"}, + }, + }, + }, + expectedRule: "HostSNI(`foo`,`bar`)", + }, + { + desc: "One SNI multiple hostnames", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"foo", "bar"}, + }, + }, + }, + expectedRule: "HostSNI(`foo`,`bar`)", + }, + { + desc: "Multiple SNI multiple hostnames", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"foo", "bar"}, + }, + { + SNIs: []v1alpha1.Hostname{"foz", "baz"}, + }, + }, + }, + expectedRule: "HostSNI(`foo`,`bar`,`foz`,`baz`)", + }, + { + desc: "Multiple SNI multiple hostnames overlapping", + routeRule: v1alpha1.TLSRouteRule{ + Matches: []v1alpha1.TLSRouteMatch{ + { + SNIs: []v1alpha1.Hostname{"foo", "bar"}, + }, + { + SNIs: []v1alpha1.Hostname{"foo", "baz"}, + }, + }, + }, + expectedRule: "HostSNI(`foo`,`bar`,`baz`)", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rule, err := hostSNIRule(test.routeRule) + if test.expectError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, test.expectedRule, rule) + }) + } +} diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index c424ca303..dac0380ad 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`) files := strings.Split(string(content), "---") retVal := make([]runtime.Object, 0, len(files))