From 943d5e29468f49e65894e1ae71ff6107a5db63f9 Mon Sep 17 00:00:00 2001 From: Edwin Xie Date: Thu, 1 Feb 2024 09:41:10 -0800 Subject: [PATCH] Support gateway api BackendTLSPolicy (#6119) The BackendTLSPolicy will allow a user to connect an httproute to a backend service with TLS. - Only implements BackendTLSPolicy for HTTPRoute - Only allows Services for spec.targetRef - Allows ConfigMap or Secret for spec.TLS.CACertRefs - Adds backendtlspolicies to the ClusterRole for Contour - Adds configmaps to the ClusterRole for Contour - Controller reconciles on BackendTLSPolicy and ConfigMaps now - BackendTLSPolicy spec.targetRef can specify SectionName to be port name of a service to target a particular section of the service. Signed-off-by: Edwin Xie Signed-off-by: Christian Ang Co-authored-by: Christian Ang --- .../unreleased/6119-flawedmatrix-major.md | 9 + cmd/contour/serve.go | 44 +- examples/contour/02-role-contour.yaml | 3 + examples/gateway-provisioner/01-roles.yaml | 29 +- examples/gateway/00-crds.yaml | 527 +++ examples/render/contour-deployment.yaml | 3 + .../render/contour-gateway-provisioner.yaml | 556 +++- examples/render/contour-gateway.yaml | 530 +++ examples/render/contour.yaml | 3 + hack/generate-gateway-yaml.sh | 2 +- internal/controller/backendtlspolicy.go | 76 + internal/controller/controller_test.go | 3 + internal/dag/builder_test.go | 2852 ++++++++--------- internal/dag/cache.go | 143 +- internal/dag/cache_test.go | 506 ++- internal/dag/dag.go | 13 +- internal/dag/dag_test.go | 44 +- internal/dag/gatewayapi_processor.go | 76 + internal/dag/httpproxy_processor.go | 4 +- internal/envoy/cluster.go | 8 +- internal/envoy/v3/auth_test.go | 14 +- internal/envoy/v3/cluster_test.go | 62 +- internal/envoy/v3/listener_test.go | 108 +- .../featuretests/v3/backendclientauth_test.go | 4 +- .../v3/downstreamvalidation_test.go | 36 +- internal/featuretests/v3/envoy.go | 8 +- internal/featuretests/v3/upstreamtls_test.go | 119 + internal/k8s/helpers.go | 7 +- internal/k8s/helpers_test.go | 22 +- internal/k8s/rbac.go | 6 +- .../testdata/configmap-content-change.yaml | 27 + .../testdata/configmap-metadata-change.yaml | 30 + .../provisioner/objects/rbac/util/util.go | 6 +- test/e2e/fixtures.go | 9 +- test/e2e/framework.go | 6 + test/e2e/gateway/backend_tls_policy_test.go | 188 ++ test/e2e/gateway/gateway_test.go | 2 + test/e2e/gatewayapi_predicates.go | 8 + .../backend_tls_protocol_version_test.go | 2 +- test/e2e/httpproxy/backend_tls_test.go | 2 +- test/e2e/httpproxy/external_name_test.go | 2 +- test/e2e/ingress/backend_tls_test.go | 2 +- 42 files changed, 4355 insertions(+), 1746 deletions(-) create mode 100644 changelogs/unreleased/6119-flawedmatrix-major.md create mode 100644 internal/controller/backendtlspolicy.go create mode 100644 internal/k8s/testdata/configmap-content-change.yaml create mode 100644 internal/k8s/testdata/configmap-metadata-change.yaml create mode 100644 test/e2e/gateway/backend_tls_policy_test.go diff --git a/changelogs/unreleased/6119-flawedmatrix-major.md b/changelogs/unreleased/6119-flawedmatrix-major.md new file mode 100644 index 00000000000..600f9b8f2a2 --- /dev/null +++ b/changelogs/unreleased/6119-flawedmatrix-major.md @@ -0,0 +1,9 @@ +## Support for Gateway API BackendTLSPolicy + +The BackendTLSPolicy CRD can now be used with HTTPRoute to configure a Contour gateway to connect to a backend Service with TLS. This will give users the ability to use Gateway API to configure their routes to securely connect to backends that use TLS with Contour. + +The BackendTLSPolicy spec requires you to specify a `targetRef`, which can currently only be a Kubernetes Service within the same namespace as the BackendTLSPolicy. The targetRef is what Service should be watched to apply the BackendTLSPolicy to. A `SectionName` can also be configured to the port name of a Service to reference a specific section of the Service. + +The spec also requires you to specify `caCertRefs`, which can either be a ConfigMap or Secret with a `ca.crt` key in the data map containing a PEM-encoded TLS certificate. The CA certificates referenced will be configured to be used by the gateway to perform TLS to the backend Service. You will also need to specify a `Hostname`, which will be used to configure the SNI the gateway will use for the connection. + +See Gateway API's [GEP-1897](https://gateway-api.sigs.k8s.io/geps/gep-1897) for the proposal for BackendTLSPolicy. diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index fc176298ba4..7831265b2b6 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -133,7 +133,7 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext) serve.Flag("debug", "Enable debug logging.").Short('d').BoolVar(&ctx.Config.Debug) serve.Flag("debug-http-address", "Address the debug http endpoint will bind to.").PlaceHolder("").StringVar(&ctx.debugAddr) serve.Flag("debug-http-port", "Port the debug http endpoint will bind to.").PlaceHolder("").IntVar(&ctx.debugPort) - serve.Flag("disable-feature", "Do not start an informer for the specified resources.").PlaceHolder("").EnumsVar(&ctx.disabledFeatures, "extensionservices", "tlsroutes", "grpcroutes", "tcproutes") + serve.Flag("disable-feature", "Do not start an informer for the specified resources.").PlaceHolder("").EnumsVar(&ctx.disabledFeatures, "extensionservices", "tlsroutes", "grpcroutes", "tcproutes", "backendtlspolicies") serve.Flag("disable-leader-election", "Disable leader election mechanism.").BoolVar(&ctx.LeaderElection.Disable) serve.Flag("envoy-http-access-log", "Envoy HTTP access log.").PlaceHolder("/path/to/file").StringVar(&ctx.httpAccessLog) @@ -260,6 +260,28 @@ func NewServer(log logrus.FieldLogger, ctx *serveContext) (*Server, error) { return secret, nil }, }, + &corev1.ConfigMap{}: { + Transform: func(obj any) (any, error) { + configMap, ok := obj.(*corev1.ConfigMap) + // TransformFunc should handle the tombstone of type cache.DeletedFinalStateUnknown + if !ok { + return obj, nil + } + + // Keep ConfigMaps that have the ca.crt key because they may be necessary + if _, ok := configMap.Data[dag.CACertificateKey]; ok { + return obj, nil + } + + // Other types of ConfigMaps will never be referred to, so we can remove all data. + // Last-applied-configuration annotation might contain a copy of the complete data. + configMap.Data = map[string]string{} + configMap.SetManagedFields(nil) + configMap.SetAnnotations(nil) + + return configMap, nil + }, + }, }, // DefaultTransform is called for objects that do not have a TransformByObject function. DefaultTransform: func(obj any) (any, error) { @@ -1060,9 +1082,10 @@ func (s *Server) setupGatewayAPI(contourConfiguration contour_api_v1alpha1.Conto // Some features may be disabled. features := map[string]struct{}{ - "tlsroutes": {}, - "grpcroutes": {}, - "tcproutes": {}, + "tlsroutes": {}, + "grpcroutes": {}, + "tcproutes": {}, + "backendtlspolicies": {}, } for _, f := range s.ctx.disabledFeatures { delete(features, f) @@ -1094,6 +1117,18 @@ func (s *Server) setupGatewayAPI(contourConfiguration contour_api_v1alpha1.Conto } } + // Create and register the BackendTLSPolicy controller with the manager. + if _, enabled := features["backendtlspolicies"]; enabled { + // Inform on ConfigMap if BackendTLSPolicy is enabled + if err := s.informOnResource(&corev1.ConfigMap{}, eventHandler); err != nil { + s.log.WithError(err).WithField("resource", "configmaps").Fatal("failed to create informer") + } + + if err := controller.RegisterBackendTLSPolicyController(s.log.WithField("context", "backendtlspolicy-controller"), mgr, eventHandler); err != nil { + s.log.WithError(err).Fatal("failed to create backendtlspolicy-controller") + } + } + // Inform on ReferenceGrants. if err := s.informOnResource(&gatewayapi_v1beta1.ReferenceGrant{}, eventHandler); err != nil { s.log.WithError(err).WithField("resource", "referencegrants").Fatal("failed to create informer") @@ -1239,6 +1274,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { PerConnectionBufferLimitBytes: dbc.perConnectionBufferLimitBytes, SetSourceMetadataOnRoutes: true, GlobalCircuitBreakerDefaults: dbc.globalCircuitBreakerDefaults, + UpstreamTLS: dbc.upstreamTLS, }) } diff --git a/examples/contour/02-role-contour.yaml b/examples/contour/02-role-contour.yaml index 35e9386e719..21bf5738142 100644 --- a/examples/contour/02-role-contour.yaml +++ b/examples/contour/02-role-contour.yaml @@ -10,6 +10,7 @@ rules: - apiGroups: - "" resources: + - configmaps - endpoints - namespaces - secrets @@ -29,6 +30,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies - gatewayclasses - gateways - grpcroutes @@ -43,6 +45,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies/status - gatewayclasses/status - gateways/status - grpcroutes/status diff --git a/examples/gateway-provisioner/01-roles.yaml b/examples/gateway-provisioner/01-roles.yaml index 32bafdae071..e48893105d6 100644 --- a/examples/gateway-provisioner/01-roles.yaml +++ b/examples/gateway-provisioner/01-roles.yaml @@ -10,6 +10,7 @@ rules: - apiGroups: - "" resources: + - configmaps - endpoints - namespaces - secrets @@ -78,15 +79,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: - - gatewayclasses - - gateways - verbs: - - get - - list - - watch -- apiGroups: - - gateway.networking.k8s.io - resources: + - backendtlspolicies - gatewayclasses - gateways - grpcroutes @@ -101,19 +94,29 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies/status - gatewayclasses/status - gateways/status + - grpcroutes/status + - httproutes/status + - tcproutes/status + - tlsroutes/status verbs: - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + - gateways + verbs: + - get + - list + - watch - apiGroups: - gateway.networking.k8s.io resources: - gatewayclasses/status - gateways/status - - grpcroutes/status - - httproutes/status - - tcproutes/status - - tlsroutes/status verbs: - update - apiGroups: diff --git a/examples/gateway/00-crds.yaml b/examples/gateway/00-crds.yaml index f483bcb0ac6..bbb71f11f65 100644 --- a/examples/gateway/00-crds.yaml +++ b/examples/gateway/00-crds.yaml @@ -1,3 +1,509 @@ +# Copyright 2023 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# +--- +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: BackendTLSPolicy provides a way to configure how a Gateway connects + to a Backend via TLS. + 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 BackendTLSPolicy. + properties: + targetRef: + description: "TargetRef identifies an API object to apply the policy + to. Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. Note that + this config applies to the entire referenced resource by default, + but this default may change in the future to provide a more granular + application of the policy. \n Support: Extended for Kubernetes Service + \n Support: Implementation-specific for any other resource" + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When + unspecified, the local namespace is inferred. Even when policy + targets a resource in a different namespace, it MUST only apply + to traffic originating from the same namespace as the policy. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + sectionName: + description: "SectionName is the name of a section within the + target resource. When unspecified, this targetRef targets the + entire resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name * + Service: Port Name \n If a SectionName is specified, but does + not exist on the targeted object, the Policy must fail to attach, + and the policy implementation should record a `ResolvedRefs` + or similar Condition in the Policy's status." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + tls: + description: TLS contains backend TLS policy configuration. + properties: + caCertRefs: + description: "CACertRefs contains one or more references to Kubernetes + objects that contain a PEM-encoded TLS CA certificate bundle, + which is used to validate a TLS handshake between the Gateway + and backend Pod. \n If CACertRefs is empty or unspecified, then + WellKnownCACerts must be specified. Only one of CACertRefs or + WellKnownCACerts may be specified, not both. If CACertRefs is + empty or unspecified, the configuration for WellKnownCACerts + MUST be honored instead. \n References to a resource in a different + namespace are invalid for the moment, although we will revisit + this in the future. \n A single CACertRef to a Kubernetes ConfigMap + kind has \"Core\" support. Implementations MAY choose to support + attaching multiple certificates to a backend, but this behavior + is implementation-specific. \n Support: Core - An optional single + reference to a Kubernetes ConfigMap, with the CA certificate + in a key named `ca.crt`. \n Support: Implementation-specific + (More than one reference, or other kinds of resources)." + items: + description: "LocalObjectReference identifies an API object + within the namespace of the referrer. The API object must + be valid in the cluster; the Group and Kind must be registered + in the cluster for this reference to be valid. \n References + to objects with invalid Group and Kind are not valid, and + must be rejected by the implementation, with appropriate Conditions + set on the containing object." + properties: + group: + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: "Hostname is used for two purposes in the connection + between Gateways and backends: \n 1. Hostname MUST be used as + the SNI to connect to the backend (RFC 6066). 2. Hostname MUST + be used for authentication and MUST match the certificate served + by the matching backend. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + wellKnownCACerts: + description: "WellKnownCACerts specifies whether system CA certificates + may be used in the TLS handshake between the gateway and backend + pod. \n If WellKnownCACerts is unspecified or empty (\"\"), + then CACertRefs must be specified with at least one entry for + a valid configuration. Only one of CACertRefs or WellKnownCACerts + may be specified, not both. \n Support: Core for \"System\"" + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertRefs and WellKnownCACerts + rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) + && self.wellKnownCACerts != "")' + - message: must specify either CACertRefs or WellKnownCACerts + rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) + && self.wellKnownCACerts != "") + required: + - targetRef + - tls + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: "Ancestors is a list of ancestor resources (usually Gateways) + that are associated with the policy, and the status of the policy + with respect to each ancestor. When this policy attaches to a parent, + the controller that manages the parent and the ancestors MUST add + an entry to this list when the controller first sees the policy + and SHOULD update the entry as appropriate when the relevant ancestor + is modified. \n Note that choosing the relevant ancestor is left + to the Policy designers; an important part of Policy design is designing + the right object level at which to namespace this status. \n Note + also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations + MUST use the ControllerName field to uniquely identify the entries + in this list that they are responsible for. \n Note that to achieve + this, the list of PolicyAncestorStatus structs MUST be treated as + a map with a composite key, made up of the AncestorRef and ControllerName + fields combined. \n A maximum of 16 ancestors will be represented + in this list. An empty list means the Policy is not relevant for + any ancestors. \n If this slice is full, implementations MUST NOT + add further entries. Instead they MUST consider the policy unimplementable + and signal that on any related resources such as the ancestor that + would be referenced here. For example, if this list was full on + BackendTLSPolicy, no additional Gateways would be able to reference + the Service targeted by the BackendTLSPolicy." + items: + description: "PolicyAncestorStatus describes the status of a route + with respect to an associated Ancestor. \n Ancestors refer to + objects that are either the Target of a policy or above it in + terms of object hierarchy. For example, if a policy targets a + Service, the Policy's Ancestors are, in order, the Service, the + HTTPRoute, the Gateway, and the GatewayClass. Almost always, in + this hierarchy, the Gateway will be the most useful object to + place Policy status on, so we recommend that implementations SHOULD + use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. \n In the context of policy + attachment, the Ancestor is used to distinguish which resource + results in a distinct application of this policy. For example, + if a policy targets a Service, it may have a distinct result per + attached Gateway. \n Policies targeting the same resource may + have different effects depending on the ancestors of those resources. + For example, different Gateways targeting the same Service may + have different capabilities, especially if they have different + underlying implementations. \n For example, in BackendTLSPolicy, + the Policy attaches to a Service that is used as a backend in + a HTTPRoute that is itself attached to a Gateway. In this case, + the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. \n Note that a parent + is also an ancestor, so for objects where the parent is the relevant + object for status, this struct SHOULD still be used. \n This struct + is intended to be used in a slice that's effectively a map, with + a composite key made up of the AncestorRef and the ControllerName." + properties: + ancestorRef: + description: AncestorRef corresponds with a ParentRef in the + spec that this PolicyAncestorStatus struct describes the status + of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + 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, \n 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 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -495,6 +1001,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -2300,6 +2809,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -4027,6 +4539,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -8986,6 +9501,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9272,6 +9790,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9910,6 +10431,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -10597,6 +11121,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 0559bd7f1c9..5085f15db8c 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -8865,6 +8865,7 @@ rules: - apiGroups: - "" resources: + - configmaps - endpoints - namespaces - secrets @@ -8884,6 +8885,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies - gatewayclasses - gateways - grpcroutes @@ -8898,6 +8900,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies/status - gatewayclasses/status - gateways/status - grpcroutes/status diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 77dc2dc3a21..9d6a8e51877 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -8544,6 +8544,512 @@ spec: status: {} --- +# Copyright 2023 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# +--- +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: BackendTLSPolicy provides a way to configure how a Gateway connects + to a Backend via TLS. + 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 BackendTLSPolicy. + properties: + targetRef: + description: "TargetRef identifies an API object to apply the policy + to. Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. Note that + this config applies to the entire referenced resource by default, + but this default may change in the future to provide a more granular + application of the policy. \n Support: Extended for Kubernetes Service + \n Support: Implementation-specific for any other resource" + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When + unspecified, the local namespace is inferred. Even when policy + targets a resource in a different namespace, it MUST only apply + to traffic originating from the same namespace as the policy. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + sectionName: + description: "SectionName is the name of a section within the + target resource. When unspecified, this targetRef targets the + entire resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name * + Service: Port Name \n If a SectionName is specified, but does + not exist on the targeted object, the Policy must fail to attach, + and the policy implementation should record a `ResolvedRefs` + or similar Condition in the Policy's status." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + tls: + description: TLS contains backend TLS policy configuration. + properties: + caCertRefs: + description: "CACertRefs contains one or more references to Kubernetes + objects that contain a PEM-encoded TLS CA certificate bundle, + which is used to validate a TLS handshake between the Gateway + and backend Pod. \n If CACertRefs is empty or unspecified, then + WellKnownCACerts must be specified. Only one of CACertRefs or + WellKnownCACerts may be specified, not both. If CACertRefs is + empty or unspecified, the configuration for WellKnownCACerts + MUST be honored instead. \n References to a resource in a different + namespace are invalid for the moment, although we will revisit + this in the future. \n A single CACertRef to a Kubernetes ConfigMap + kind has \"Core\" support. Implementations MAY choose to support + attaching multiple certificates to a backend, but this behavior + is implementation-specific. \n Support: Core - An optional single + reference to a Kubernetes ConfigMap, with the CA certificate + in a key named `ca.crt`. \n Support: Implementation-specific + (More than one reference, or other kinds of resources)." + items: + description: "LocalObjectReference identifies an API object + within the namespace of the referrer. The API object must + be valid in the cluster; the Group and Kind must be registered + in the cluster for this reference to be valid. \n References + to objects with invalid Group and Kind are not valid, and + must be rejected by the implementation, with appropriate Conditions + set on the containing object." + properties: + group: + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: "Hostname is used for two purposes in the connection + between Gateways and backends: \n 1. Hostname MUST be used as + the SNI to connect to the backend (RFC 6066). 2. Hostname MUST + be used for authentication and MUST match the certificate served + by the matching backend. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + wellKnownCACerts: + description: "WellKnownCACerts specifies whether system CA certificates + may be used in the TLS handshake between the gateway and backend + pod. \n If WellKnownCACerts is unspecified or empty (\"\"), + then CACertRefs must be specified with at least one entry for + a valid configuration. Only one of CACertRefs or WellKnownCACerts + may be specified, not both. \n Support: Core for \"System\"" + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertRefs and WellKnownCACerts + rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) + && self.wellKnownCACerts != "")' + - message: must specify either CACertRefs or WellKnownCACerts + rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) + && self.wellKnownCACerts != "") + required: + - targetRef + - tls + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: "Ancestors is a list of ancestor resources (usually Gateways) + that are associated with the policy, and the status of the policy + with respect to each ancestor. When this policy attaches to a parent, + the controller that manages the parent and the ancestors MUST add + an entry to this list when the controller first sees the policy + and SHOULD update the entry as appropriate when the relevant ancestor + is modified. \n Note that choosing the relevant ancestor is left + to the Policy designers; an important part of Policy design is designing + the right object level at which to namespace this status. \n Note + also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations + MUST use the ControllerName field to uniquely identify the entries + in this list that they are responsible for. \n Note that to achieve + this, the list of PolicyAncestorStatus structs MUST be treated as + a map with a composite key, made up of the AncestorRef and ControllerName + fields combined. \n A maximum of 16 ancestors will be represented + in this list. An empty list means the Policy is not relevant for + any ancestors. \n If this slice is full, implementations MUST NOT + add further entries. Instead they MUST consider the policy unimplementable + and signal that on any related resources such as the ancestor that + would be referenced here. For example, if this list was full on + BackendTLSPolicy, no additional Gateways would be able to reference + the Service targeted by the BackendTLSPolicy." + items: + description: "PolicyAncestorStatus describes the status of a route + with respect to an associated Ancestor. \n Ancestors refer to + objects that are either the Target of a policy or above it in + terms of object hierarchy. For example, if a policy targets a + Service, the Policy's Ancestors are, in order, the Service, the + HTTPRoute, the Gateway, and the GatewayClass. Almost always, in + this hierarchy, the Gateway will be the most useful object to + place Policy status on, so we recommend that implementations SHOULD + use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. \n In the context of policy + attachment, the Ancestor is used to distinguish which resource + results in a distinct application of this policy. For example, + if a policy targets a Service, it may have a distinct result per + attached Gateway. \n Policies targeting the same resource may + have different effects depending on the ancestors of those resources. + For example, different Gateways targeting the same Service may + have different capabilities, especially if they have different + underlying implementations. \n For example, in BackendTLSPolicy, + the Policy attaches to a Service that is used as a backend in + a HTTPRoute that is itself attached to a Gateway. In this case, + the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. \n Note that a parent + is also an ancestor, so for objects where the parent is the relevant + object for status, this struct SHOULD still be used. \n This struct + is intended to be used in a slice that's effectively a map, with + a composite key made up of the AncestorRef and the ControllerName." + properties: + ancestorRef: + description: AncestorRef corresponds with a ParentRef in the + spec that this PolicyAncestorStatus struct describes the status + of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + 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, \n 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 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9041,6 +9547,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -10846,6 +11355,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -12573,6 +13085,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -17532,6 +18047,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -17818,6 +18336,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -18456,6 +18977,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -19143,6 +19667,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -19805,6 +20332,7 @@ rules: - apiGroups: - "" resources: + - configmaps - endpoints - namespaces - secrets @@ -19873,15 +20401,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: - - gatewayclasses - - gateways - verbs: - - get - - list - - watch -- apiGroups: - - gateway.networking.k8s.io - resources: + - backendtlspolicies - gatewayclasses - gateways - grpcroutes @@ -19896,19 +20416,29 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies/status - gatewayclasses/status - gateways/status + - grpcroutes/status + - httproutes/status + - tcproutes/status + - tlsroutes/status verbs: - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + - gateways + verbs: + - get + - list + - watch - apiGroups: - gateway.networking.k8s.io resources: - gatewayclasses/status - gateways/status - - grpcroutes/status - - httproutes/status - - tcproutes/status - - tlsroutes/status verbs: - update - apiGroups: diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 569a39d35f3..9bc2b73bdcc 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -8868,6 +8868,7 @@ rules: - apiGroups: - "" resources: + - configmaps - endpoints - namespaces - secrets @@ -8887,6 +8888,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies - gatewayclasses - gateways - grpcroutes @@ -8901,6 +8903,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies/status - gatewayclasses/status - gateways/status - grpcroutes/status @@ -9260,6 +9263,512 @@ spec: runAsGroup: 65534 --- +# Copyright 2023 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# +--- +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: BackendTLSPolicy provides a way to configure how a Gateway connects + to a Backend via TLS. + 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 BackendTLSPolicy. + properties: + targetRef: + description: "TargetRef identifies an API object to apply the policy + to. Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. Note that + this config applies to the entire referenced resource by default, + but this default may change in the future to provide a more granular + application of the policy. \n Support: Extended for Kubernetes Service + \n Support: Implementation-specific for any other resource" + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When + unspecified, the local namespace is inferred. Even when policy + targets a resource in a different namespace, it MUST only apply + to traffic originating from the same namespace as the policy. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + sectionName: + description: "SectionName is the name of a section within the + target resource. When unspecified, this targetRef targets the + entire resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name * + Service: Port Name \n If a SectionName is specified, but does + not exist on the targeted object, the Policy must fail to attach, + and the policy implementation should record a `ResolvedRefs` + or similar Condition in the Policy's status." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + tls: + description: TLS contains backend TLS policy configuration. + properties: + caCertRefs: + description: "CACertRefs contains one or more references to Kubernetes + objects that contain a PEM-encoded TLS CA certificate bundle, + which is used to validate a TLS handshake between the Gateway + and backend Pod. \n If CACertRefs is empty or unspecified, then + WellKnownCACerts must be specified. Only one of CACertRefs or + WellKnownCACerts may be specified, not both. If CACertRefs is + empty or unspecified, the configuration for WellKnownCACerts + MUST be honored instead. \n References to a resource in a different + namespace are invalid for the moment, although we will revisit + this in the future. \n A single CACertRef to a Kubernetes ConfigMap + kind has \"Core\" support. Implementations MAY choose to support + attaching multiple certificates to a backend, but this behavior + is implementation-specific. \n Support: Core - An optional single + reference to a Kubernetes ConfigMap, with the CA certificate + in a key named `ca.crt`. \n Support: Implementation-specific + (More than one reference, or other kinds of resources)." + items: + description: "LocalObjectReference identifies an API object + within the namespace of the referrer. The API object must + be valid in the cluster; the Group and Kind must be registered + in the cluster for this reference to be valid. \n References + to objects with invalid Group and Kind are not valid, and + must be rejected by the implementation, with appropriate Conditions + set on the containing object." + properties: + group: + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: "Hostname is used for two purposes in the connection + between Gateways and backends: \n 1. Hostname MUST be used as + the SNI to connect to the backend (RFC 6066). 2. Hostname MUST + be used for authentication and MUST match the certificate served + by the matching backend. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + wellKnownCACerts: + description: "WellKnownCACerts specifies whether system CA certificates + may be used in the TLS handshake between the gateway and backend + pod. \n If WellKnownCACerts is unspecified or empty (\"\"), + then CACertRefs must be specified with at least one entry for + a valid configuration. Only one of CACertRefs or WellKnownCACerts + may be specified, not both. \n Support: Core for \"System\"" + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertRefs and WellKnownCACerts + rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) + && self.wellKnownCACerts != "")' + - message: must specify either CACertRefs or WellKnownCACerts + rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) + && self.wellKnownCACerts != "") + required: + - targetRef + - tls + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: "Ancestors is a list of ancestor resources (usually Gateways) + that are associated with the policy, and the status of the policy + with respect to each ancestor. When this policy attaches to a parent, + the controller that manages the parent and the ancestors MUST add + an entry to this list when the controller first sees the policy + and SHOULD update the entry as appropriate when the relevant ancestor + is modified. \n Note that choosing the relevant ancestor is left + to the Policy designers; an important part of Policy design is designing + the right object level at which to namespace this status. \n Note + also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations + MUST use the ControllerName field to uniquely identify the entries + in this list that they are responsible for. \n Note that to achieve + this, the list of PolicyAncestorStatus structs MUST be treated as + a map with a composite key, made up of the AncestorRef and ControllerName + fields combined. \n A maximum of 16 ancestors will be represented + in this list. An empty list means the Policy is not relevant for + any ancestors. \n If this slice is full, implementations MUST NOT + add further entries. Instead they MUST consider the policy unimplementable + and signal that on any related resources such as the ancestor that + would be referenced here. For example, if this list was full on + BackendTLSPolicy, no additional Gateways would be able to reference + the Service targeted by the BackendTLSPolicy." + items: + description: "PolicyAncestorStatus describes the status of a route + with respect to an associated Ancestor. \n Ancestors refer to + objects that are either the Target of a policy or above it in + terms of object hierarchy. For example, if a policy targets a + Service, the Policy's Ancestors are, in order, the Service, the + HTTPRoute, the Gateway, and the GatewayClass. Almost always, in + this hierarchy, the Gateway will be the most useful object to + place Policy status on, so we recommend that implementations SHOULD + use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. \n In the context of policy + attachment, the Ancestor is used to distinguish which resource + results in a distinct application of this policy. For example, + if a policy targets a Service, it may have a distinct result per + attached Gateway. \n Policies targeting the same resource may + have different effects depending on the ancestors of those resources. + For example, different Gateways targeting the same Service may + have different capabilities, especially if they have different + underlying implementations. \n For example, in BackendTLSPolicy, + the Policy attaches to a Service that is used as a backend in + a HTTPRoute that is itself attached to a Gateway. In this case, + the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. \n Note that a parent + is also an ancestor, so for objects where the parent is the relevant + object for status, this struct SHOULD still be used. \n This struct + is intended to be used in a slice that's effectively a map, with + a composite key made up of the AncestorRef and the ControllerName." + properties: + ancestorRef: + description: AncestorRef corresponds with a ParentRef in the + spec that this PolicyAncestorStatus struct describes the status + of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + 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, \n 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 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9757,6 +10266,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -11562,6 +12074,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -13289,6 +13804,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -18248,6 +18766,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -18534,6 +19055,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -19172,6 +19696,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -19859,6 +20386,9 @@ status: conditions: null storedVersions: null --- +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 5a0340e9be7..4b8212d47e4 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -8865,6 +8865,7 @@ rules: - apiGroups: - "" resources: + - configmaps - endpoints - namespaces - secrets @@ -8884,6 +8885,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies - gatewayclasses - gateways - grpcroutes @@ -8898,6 +8900,7 @@ rules: - apiGroups: - gateway.networking.k8s.io resources: + - backendtlspolicies/status - gatewayclasses/status - gateways/status - grpcroutes/status diff --git a/hack/generate-gateway-yaml.sh b/hack/generate-gateway-yaml.sh index d8f0c65b0d9..c417c46dc16 100755 --- a/hack/generate-gateway-yaml.sh +++ b/hack/generate-gateway-yaml.sh @@ -14,4 +14,4 @@ run::sed() { } -kubectl kustomize -o examples/gateway/00-crds.yaml "github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=${GATEWAY_API_VERSION}" +wget -O examples/gateway/00-crds.yaml "https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/experimental-install.yaml" diff --git a/internal/controller/backendtlspolicy.go b/internal/controller/backendtlspolicy.go new file mode 100644 index 00000000000..b8e7dca8865 --- /dev/null +++ b/internal/controller/backendtlspolicy.go @@ -0,0 +1,76 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + gatewayapi_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +type backendTLSPolicyReconciler struct { + client client.Client + eventHandler cache.ResourceEventHandler + logrus.FieldLogger +} + +// RegisterBackendTLSPolicyController creates the backendtlspolicy controller from mgr. The controller will be pre-configured +// to watch for BackendTLSPolicy objects across all namespaces. +func RegisterBackendTLSPolicyController(log logrus.FieldLogger, mgr manager.Manager, eventHandler cache.ResourceEventHandler) error { + r := &backendTLSPolicyReconciler{ + client: mgr.GetClient(), + eventHandler: eventHandler, + FieldLogger: log, + } + c, err := controller.NewUnmanaged("backendtlspolicy-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + if err := mgr.Add(&noLeaderElectionController{c}); err != nil { + return err + } + + return c.Watch(source.Kind(mgr.GetCache(), &gatewayapi_v1alpha2.BackendTLSPolicy{}), &handler.EnqueueRequestForObject{}) +} + +func (r *backendTLSPolicyReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + // Fetch the BackendTLSPolicy from the cache. + backendTLSPolicy := &gatewayapi_v1alpha2.BackendTLSPolicy{} + err := r.client.Get(ctx, request.NamespacedName, backendTLSPolicy) + if errors.IsNotFound(err) { + r.eventHandler.OnDelete(&gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: request.Name, + Namespace: request.Namespace, + }, + }) + return reconcile.Result{}, nil + } + + // Pass the new changed object off to the eventHandler. + r.eventHandler.OnAdd(backendTLSPolicy, false) + + return reconcile.Result{}, nil +} diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index 68e1bcb030a..2169047989c 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -45,6 +45,9 @@ func TestRegisterControllers(t *testing.T) { "grpcroute controller": func(mockManager *mocks.Manager) error { return controller.RegisterGRPCRouteController(fixture.NewTestLogger(t), mockManager, nil) }, + "backendtlspolicy controller": func(mockManager *mocks.Manager) error { + return controller.RegisterBackendTLSPolicyController(fixture.NewTestLogger(t), mockManager, nil) + }, } for name, test := range tests { diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index 5709a3d9450..78941f7bd7e 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -31,6 +31,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" gatewayapi_v1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapi_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gatewayapi_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -87,6 +88,29 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, } + tlsService := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlssvc", + Namespace: "projectcontour", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{makeServicePort("https", "TCP", 443, 8443)}, + }, + } + + tlsAndNonTLSService := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlsandnontlssvc", + Namespace: "projectcontour", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + makeServicePort("http", "TCP", 80, 8080), + makeServicePort("https", "TCP", 443, 8443), + }, + }, + } + validClass := &gatewayapi_v1beta1.GatewayClass{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -312,6 +336,37 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, } + cert1 := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "projectcontour", + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + CACertificateKey: []byte(fixture.CERTIFICATE), + }, + } + cert2 := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca2", + Namespace: "projectcontour", + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + CACertificateKey: []byte(fixture.EC_CERTIFICATE), + }, + } + + configMapCert1 := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "projectcontour", + }, + Data: map[string]string{ + CACertificateKey: fixture.CERTIFICATE, + }, + } + sec1 := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "secret", @@ -471,10 +526,26 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, } + crossNSBackendHTTPRoute := makeHTTPRoute("basic", "default", "", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ + BackendRef: gatewayapi_v1beta1.BackendRef{ + BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ + Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), + Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), + Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), + Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), + }, + Weight: ref.To(int32(1)), + }, + }}, + }) + tests := map[string]struct { objs []any gatewayclass *gatewayapi_v1beta1.GatewayClass gateway *gatewayapi_v1beta1.Gateway + upstreamTLS *UpstreamTLS want []*Listener }{ "insert basic single route, single hostname": { @@ -531,24 +602,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPSameNamespace, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "different-ns-than-gateway", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "different-ns-than-gateway", "test.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners(), }, @@ -566,24 +620,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, }, kuardServiceCustomNs, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "custom", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "custom", "test.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -608,24 +645,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, }, kuardServiceCustomNs, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "custom", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "custom", "test.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners(), }, @@ -996,24 +1016,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { objs: []any{ kuardService, basicHTTPRoute, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic-two", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "another.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic-two", "projectcontour", "another.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -1093,35 +1096,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { objs: []any{ kuardService, blogService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }, { - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/blog"), - BackendRefs: gatewayapi.HTTPBackendRef("blogsvc", 80, 1), - }, { - Matches: append( - gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/another"), - gatewayapi_v1beta1.HTTPRouteMatch{ - Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchExact, "X-Foo-Header", "some_value"), - }, - ), - BackendRefs: gatewayapi.HTTPBackendRef("blogsvc", 80, 1), - }}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", + makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1), + makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/blog", "blogsvc", 80, 1), + gatewayapi_v1beta1.HTTPRouteRule{ + Matches: append( + gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/another"), + gatewayapi_v1beta1.HTTPRouteMatch{ + Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchExact, "X-Foo-Header", "some_value"), + }, + ), + BackendRefs: gatewayapi.HTTPBackendRef("blogsvc", 80, 1), }, - }, + ), }, want: listeners( &Listener{ @@ -1197,21 +1184,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -1227,24 +1200,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "*.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "*.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -1263,24 +1219,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "192.168.122.1", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "192.168.122.1", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners(), }, @@ -1289,24 +1228,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io:80", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "default", "test.projectcontour.io:80", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners(), }, @@ -1315,24 +1237,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "*", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "default", "*", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners(), }, @@ -1342,21 +1247,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []any{ - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -1372,30 +1263,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []any{ - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ - { - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Name: "kuard", - }, - }, + makeHTTPRoute("basic", "default", "", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + { + BackendRef: gatewayapi_v1beta1.BackendRef{ + BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ + Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), + Name: "kuard", }, }, - }}, + }, }, - }, + }), }, want: listeners( &Listener{ @@ -1411,31 +1291,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, }, want: listeners(&Listener{ Name: "http-80", @@ -1449,31 +1305,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, &gatewayapi_v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -1501,31 +1333,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, &gatewayapi_v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -1554,31 +1362,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, &gatewayapi_v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -1608,31 +1392,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, &gatewayapi_v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -1662,31 +1422,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, &gatewayapi_v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -1716,31 +1452,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "default", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{{ - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi_v1beta1.BackendObjectReference{ - Kind: ref.To(gatewayapi_v1beta1.Kind("Service")), - Namespace: ref.To(gatewayapi_v1beta1.Namespace(kuardService.Namespace)), - Name: gatewayapi_v1beta1.ObjectName(kuardService.Name), - Port: ref.To(gatewayapi_v1beta1.PortNumber(8080)), - }, - Weight: ref.To(int32(1)), - }, - }}, - }}, - }, - }, + crossNSBackendHTTPRoute, &gatewayapi_v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -1771,24 +1483,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchExact, "/blog"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchExact, "/blog", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -1805,24 +1500,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchRegularExpression, "/bl+og"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchRegularExpression, "/bl+og", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -1840,39 +1518,26 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - }, { - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/blog"), - }, - }, { - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/tech"), - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", + gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), + }, + }, { + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/blog"), + }, + }, { + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/tech"), + }, }}, - }, - }, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2324,24 +1989,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []any{ - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "*.*.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("blogsvc", 80, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "*.*.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "blobsvc", 80, 1)), }, want: listeners(), }, @@ -2485,30 +2133,16 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchExact, "foo", "bar"), - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchExact, "foo", "bar"), + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2530,39 +2164,23 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{ + { + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/blog"), + }, + }, { + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/tech"), + }, + Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchExact, "foo", "bar"), + }, }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{ - { - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{ - { - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/blog"), - }, - }, { - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/tech"), - }, - Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchExact, "foo", "bar"), - }, - }, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }, - }, - }, - }, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2588,26 +2206,12 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchRegularExpression, "foo", "^abc$"), - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Headers: gatewayapi.HTTPHeaderMatch(gatewayapi_v1.HeaderMatchRegularExpression, "foo", "^abc$"), + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2629,31 +2233,17 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Headers: []gatewayapi_v1beta1.HTTPHeaderMatch{ + {Name: "header-1", Value: "value-1"}, + {Name: "header-2", Value: "value-2"}, + {Name: "header-1", Value: "value-3"}, + {Name: "HEADER-1", Value: "value-4"}, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Headers: []gatewayapi_v1beta1.HTTPHeaderMatch{ - {Name: "header-1", Value: "value-1"}, - {Name: "header-2", Value: "value-2"}, - {Name: "header-1", Value: "value-3"}, - {Name: "HEADER-1", Value: "value-4"}, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2676,30 +2266,16 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - Method: ref.To(gatewayapi_v1.HTTPMethodGet), - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + Method: ref.To(gatewayapi_v1.HTTPMethodGet), + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2721,35 +2297,21 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ + { + Name: "param-1", + Value: "value-1", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ - { - Name: "param-1", - Value: "value-1", - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2771,36 +2333,22 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-1", + Value: "value-1", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-1", - Value: "value-1", - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2822,51 +2370,37 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-1", + Value: "value-1", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-2", + Value: "value-2", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-1", + Value: "value-3", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "Param-1", + Value: "value-4", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-1", - Value: "value-1", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-2", - Value: "value-2", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-1", - Value: "value-3", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "Param-1", - Value: "value-4", - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2890,51 +2424,37 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-1", + Value: "value-1", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-2", + Value: "value-2", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-1", + Value: "value-3", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchRegularExpression), + Name: "Param-1", + Value: "value-4", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-1", - Value: "value-1", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-2", - Value: "value-2", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-1", - Value: "value-3", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchRegularExpression), - Name: "Param-1", - Value: "value-4", - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -2960,41 +2480,27 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ + { + Type: ref.To(gatewayapi_v1.QueryParamMatchExact), + Name: "param-1", + Value: "value-1", + }, + { + Type: ref.To(gatewayapi_v1.QueryParamMatchRegularExpression), + Name: "param-1", + Value: "value-2", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ - { - Type: ref.To(gatewayapi_v1.QueryParamMatchExact), - Name: "param-1", - Value: "value-1", - }, - { - Type: ref.To(gatewayapi_v1.QueryParamMatchRegularExpression), - Name: "param-1", - Value: "value-2", - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -3018,36 +2524,22 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ + Path: &gatewayapi_v1beta1.HTTPPathMatch{ + Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), + Value: ref.To("/"), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ + { + Type: ref.To(gatewayapi_v1.QueryParamMatchRegularExpression), + Name: "query-param-regex", + Value: "value-%d-[a-zA-Z0-9]", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: []gatewayapi_v1beta1.HTTPRouteMatch{{ - Path: &gatewayapi_v1beta1.HTTPPathMatch{ - Type: ref.To(gatewayapi_v1.PathMatchPathPrefix), - Value: ref.To("/"), - }, - QueryParams: []gatewayapi_v1beta1.HTTPQueryParamMatch{ - { - Type: ref.To(gatewayapi_v1.QueryParamMatchRegularExpression), - Name: "query-param-regex", - Value: "value-%d-[a-zA-Z0-9]", - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -3070,53 +2562,39 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + Filters: []gatewayapi_v1.HTTPRouteFilter{ + { + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "foo-bar"}, + }, + Remove: []string{"x-remove"}, + }, }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + { + // Second instance of filter should be ignored. + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "ignored"}, + }, + Remove: []string{"x-remove-ignored"}, + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - Filters: []gatewayapi_v1.HTTPRouteFilter{ - { - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "foo-bar"}, - }, - Remove: []string{"x-remove"}, - }, - }, - { - // Second instance of filter should be ignored. - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "ignored"}, - }, - Remove: []string{"x-remove-ignored"}, - }, - }, - }, - }}, }, - }, + }), }, want: listeners( &Listener{ @@ -3145,53 +2623,39 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + Filters: []gatewayapi_v1.HTTPRouteFilter{ + { + Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "foo-bar"}, + }, + Remove: []string{"x-remove"}, + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - Filters: []gatewayapi_v1.HTTPRouteFilter{ - { - Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "foo-bar"}, - }, - Remove: []string{"x-remove"}, - }, + { + // Second instance of filter should be ignored. + Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, }, - { - // Second instance of filter should be ignored. - Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "ignored"}, - }, - Remove: []string{"x-remove-ignored"}, - }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "ignored"}, }, + Remove: []string{"x-remove-ignored"}, }, - }}, + }, }, - }, + }), }, want: listeners( &Listener{ @@ -3220,60 +2684,46 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + { + BackendRef: gatewayapi_v1beta1.BackendRef{ + BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), + Weight: ref.To(int32(1)), + }, + Filters: []gatewayapi_v1.HTTPRouteFilter{ { - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), - Weight: ref.To(int32(1)), + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "foo-bar"}, + }, + Remove: []string{"x-remove"}, }, - Filters: []gatewayapi_v1.HTTPRouteFilter{ - { - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "foo-bar"}, - }, - Remove: []string{"x-remove"}, - }, + }, + { + // Second instance of filter should be ignored. + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, }, - { - // Second instance of filter should be ignored. - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "ignored"}, - }, - Remove: []string{"x-remove-ignored"}, - }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "ignored"}, }, + Remove: []string{"x-remove-ignored"}, }, }, }, - }}, + }, }, - }, + }), }, want: listeners( &Listener{ @@ -3292,60 +2742,46 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + { + BackendRef: gatewayapi_v1beta1.BackendRef{ + BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), + Weight: ref.To(int32(1)), + }, + Filters: []gatewayapi_v1.HTTPRouteFilter{ { - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), - Weight: ref.To(int32(1)), + Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "foo-bar"}, + }, + Remove: []string{"x-remove"}, }, - Filters: []gatewayapi_v1.HTTPRouteFilter{ - { - Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "foo-bar"}, - }, - Remove: []string{"x-remove"}, - }, + }, + { + // Second instance of filter should be ignored. + Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, }, - { - // Second instance of filter should be ignored. - Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "ignored"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar-ignored.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "custom-header-add", Value: "ignored"}, - }, - Remove: []string{"x-remove-ignored"}, - }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "custom-header-add", Value: "ignored"}, }, + Remove: []string{"x-remove-ignored"}, }, }, }, - }}, + }, }, - }, + }), }, want: listeners( &Listener{ @@ -3364,38 +2800,22 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{ - { - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "!invalid-header-add", Value: "foo-bar"}, - }, - }, - }}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "!invalid-header-add", Value: "foo-bar"}, }, }, - }, - }, + }}, + }), }, want: listeners( &Listener{ @@ -3419,45 +2839,29 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{ - { - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ - { - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), - Weight: ref.To(int32(1)), - }, - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "!invalid-header-add", Value: "foo-bar"}, - }, - }, - }}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + { + BackendRef: gatewayapi_v1beta1.BackendRef{ + BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), + Weight: ref.To(int32(1)), + }, + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "!invalid-header-add", Value: "foo-bar"}, }, }, - }, + }}, }, }, - }, + }), }, want: listeners( &Listener{ @@ -3476,45 +2880,29 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{ - { - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ - { - BackendRef: gatewayapi_v1beta1.BackendRef{ - BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), - Weight: ref.To(int32(1)), - }, - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, - {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, - }, - Add: []gatewayapi_v1beta1.HTTPHeader{ - {Name: "!invalid-header-add", Value: "foo-bar"}, - }, - }, - }}, - }, - }, - }, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: []gatewayapi_v1beta1.HTTPBackendRef{ + { + BackendRef: gatewayapi_v1beta1.BackendRef{ + BackendObjectReference: gatewayapi.ServiceBackendObjectRef("kuard", 8080), + Weight: ref.To(int32(1)), + }, + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + {Name: gatewayapi_v1.HTTPHeaderName("custom-header-set"), Value: "foo-bar"}, + {Name: gatewayapi_v1.HTTPHeaderName("Host"), Value: "bar.com"}, + }, + Add: []gatewayapi_v1beta1.HTTPHeader{ + {Name: "!invalid-header-add", Value: "foo-bar"}, + }, + }, + }}, }, }, - }, + }), }, want: listeners( &Listener{ @@ -3533,32 +2921,18 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ + Scheme: ref.To("https"), + Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("envoyproxy.io")), + Port: ref.To(gatewayapi_v1beta1.PortNumber(443)), + StatusCode: ref.To(301), }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ - Scheme: ref.To("https"), - Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("envoyproxy.io")), - Port: ref.To(gatewayapi_v1beta1.PortNumber(443)), - StatusCode: ref.To(301), - }, - }}, - }}, - }, - }, + }}, + }), }, want: listeners( &Listener{ @@ -3582,35 +2956,21 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: append( + gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/another-match")..., + ), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ + Scheme: ref.To("https"), + Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("envoyproxy.io")), + Port: ref.To(gatewayapi_v1beta1.PortNumber(443)), + StatusCode: ref.To(301), }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: append( - gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/another-match")..., - ), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ - Scheme: ref.To("https"), - Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("envoyproxy.io")), - Port: ref.To(gatewayapi_v1beta1.PortNumber(443)), - StatusCode: ref.To(301), - }, - }}, - }}, - }, - }, + }}, + }), }, want: listeners( &Listener{ @@ -3643,33 +3003,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ + Path: &gatewayapi_v1beta1.HTTPPathModifier{ + Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ref.To("/replacement"), + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ - Path: &gatewayapi_v1beta1.HTTPPathModifier{ - Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ref.To("/replacement"), - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -3692,33 +3038,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ + Path: &gatewayapi_v1beta1.HTTPPathModifier{ + Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ref.To("/"), + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ - Path: &gatewayapi_v1beta1.HTTPPathModifier{ - Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ref.To("/"), - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -3741,33 +3073,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ + Path: &gatewayapi_v1beta1.HTTPPathModifier{ + Type: gatewayapi_v1.FullPathHTTPPathModifier, + ReplaceFullPath: ref.To("/replacement"), + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayapi_v1beta1.HTTPRequestRedirectFilter{ - Path: &gatewayapi_v1beta1.HTTPPathModifier{ - Type: gatewayapi_v1.FullPathHTTPPathModifier, - ReplaceFullPath: ref.To("/replacement"), - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -3791,30 +3109,16 @@ func TestDAGInsertGatewayAPI(t *testing.T) { objs: []any{ kuardService, kuardService2, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, + RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ + BackendRef: gatewayapi.ServiceBackendObjectRef("kuard2", 8080), }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, - RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ - BackendRef: gatewayapi.ServiceBackendObjectRef("kuard2", 8080), - }, - }}, - }}, - }, - }, + }}, + }), }, want: listeners( &Listener{ @@ -3831,38 +3135,24 @@ func TestDAGInsertGatewayAPI(t *testing.T) { kuardService, kuardService2, kuardService3, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + Filters: []gatewayapi_v1.HTTPRouteFilter{ + { + Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, + RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ + BackendRef: gatewayapi.ServiceBackendObjectRef("kuard2", 8080), + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - Filters: []gatewayapi_v1.HTTPRouteFilter{ - { - Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, - RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ - BackendRef: gatewayapi.ServiceBackendObjectRef("kuard2", 8080), - }, - }, - { - Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, - RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ - BackendRef: gatewayapi.ServiceBackendObjectRef("kuard3", 8080), - }, - }, + { + Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, + RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ + BackendRef: gatewayapi.ServiceBackendObjectRef("kuard3", 8080), }, - }}, + }, }, - }, + }), }, want: listeners( &Listener{ @@ -3878,33 +3168,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { objs: []any{ kuardService, kuardService2, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: append( + gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/another-match")..., + ), + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, + RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ + BackendRef: gatewayapi.ServiceBackendObjectRef("kuard2", 8080), }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: append( - gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/another-match")..., - ), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterRequestMirror, - RequestMirror: &gatewayapi_v1beta1.HTTPRequestMirrorFilter{ - BackendRef: gatewayapi.ServiceBackendObjectRef("kuard2", 8080), - }, - }}, - }}, - }, - }, + }}, + }), }, want: listeners( &Listener{ @@ -3921,33 +3197,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, + URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ + Path: &gatewayapi_v1beta1.HTTPPathModifier{ + Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ref.To("/replacement"), + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, - URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ - Path: &gatewayapi_v1beta1.HTTPPathModifier{ - Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ref.To("/replacement"), - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -3969,33 +3231,19 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, + URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ + Path: &gatewayapi_v1beta1.HTTPPathModifier{ + Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ref.To("/"), + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, - URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ - Path: &gatewayapi_v1beta1.HTTPPathModifier{ - Type: gatewayapi_v1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ref.To("/"), - }, - }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), }, want: listeners( &Listener{ @@ -4017,205 +3265,644 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPAllNamespaces, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, + URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ + Path: &gatewayapi_v1beta1.HTTPPathModifier{ + Type: gatewayapi_v1.FullPathHTTPPathModifier, + ReplaceFullPath: ref.To("/replacement"), + }, }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + &Route{ + PathMatchCondition: prefixSegment("/prefix"), + PathRewritePolicy: &PathRewritePolicy{ + FullPathRewrite: "/replacement", + }, + Clusters: clustersWeight(service(kuardService)), + }, + )), + }, + ), + }, + "HTTPRoute rule with URLRewrite filter with Hostname rewrite": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + objs: []any{ + kuardService, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{{ + Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, + URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ + Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("rewritten.com")), + }, + }}, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + &Route{ + PathMatchCondition: prefixSegment("/prefix"), + RequestHeadersPolicy: &HeadersPolicy{HostRewrite: "rewritten.com"}, + Clusters: clustersWeight(service(kuardService)), + }, + )), + }, + ), + }, + "HTTPRoute rule with RequestHeadersModifier and URLRewrite filter with Hostname rewrite": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + objs: []any{ + kuardService, + makeHTTPRoute("basic", "projectcontour", "test.projectcontour.io", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), + Filters: []gatewayapi_v1.HTTPRouteFilter{ + { + Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ + Set: []gatewayapi_v1beta1.HTTPHeader{ + { + Name: "Host", + Value: "requestheader.rewritten.com", + }, + }, + }, + }, + { + Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, + URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ + Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("url.rewritten.com")), + }, + }, + }, + BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), + }), + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + &Route{ + PathMatchCondition: prefixSegment("/prefix"), + RequestHeadersPolicy: &HeadersPolicy{ + Add: map[string]string{}, + HostRewrite: "url.rewritten.com", + }, + Clusters: clustersWeight(service(kuardService)), + }, + )), + }, + ), + }, + + "HTTPRoute rule with request timeout": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + objs: []any{ + kuardService, + makeHTTPRouteWithTimeouts("5s", ""), + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts( + virtualhost("test.projectcontour.io", + &Route{ + PathMatchCondition: prefixString("/"), + Clusters: clustersWeight(service(kuardService)), + TimeoutPolicy: RouteTimeoutPolicy{ + ResponseTimeout: timeout.DurationSetting(5 * time.Second), + }, + }, + ), + ), + }, + ), + }, + "HTTPRoute rule with request and backendRequest timeout": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + objs: []any{ + kuardService, + makeHTTPRouteWithTimeouts("5s", "5s"), + }, + want: listeners(), + }, + + "HTTPRoute rule with backendRequest timeout only": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + objs: []any{ + kuardService, + makeHTTPRouteWithTimeouts("", "5s"), + }, + want: listeners(), + }, + "HTTPRoute rule with invalid request timeout": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + objs: []any{ + kuardService, + makeHTTPRouteWithTimeouts("invalid", ""), + }, + want: listeners(), + }, + + "HTTPRoute with BackendTLSPolicy": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + objs: []any{ + tlsService, + configMapCert1, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "tlssvc", 443, 1)), + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlssvc", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "ConfigMap", + Name: gatewayapi_v1.ObjectName(configMapCert1.Name), + }}, + Hostname: "example.com", + }, + }, + }, + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts( + virtualhost("*", routeCluster("/", + &Cluster{ + Weight: 1, + Upstream: &Service{ + Protocol: "tls", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsService.Name, + ServiceNamespace: tlsService.Namespace, + ServicePort: tlsService.Spec.Ports[0], + HealthPort: tlsService.Spec.Ports[0], + }, + }, + Protocol: "tls", + UpstreamValidation: &PeerValidationContext{ + CACertificates: []*Secret{ + caSecret(cert1), + }, + SubjectNames: []string{"example.com"}, + }, + UpstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + }, + )), + ), + }, + ), + }, + "HTTPRoute with BackendTLSPolicy and CA cert in secret": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + objs: []any{ + tlsService, + cert1, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "tlssvc", 443, 1)), + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlssvc", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "Secret", + Name: gatewayapi_v1.ObjectName(cert1.Name), + }}, + Hostname: "example.com", + }, + }, + }, + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts( + virtualhost("*", routeCluster("/", + &Cluster{ + Weight: 1, + Upstream: &Service{ + Protocol: "tls", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsService.Name, + ServiceNamespace: tlsService.Namespace, + ServicePort: tlsService.Spec.Ports[0], + HealthPort: tlsService.Spec.Ports[0], + }, + }, + Protocol: "tls", + UpstreamValidation: &PeerValidationContext{ + CACertificates: []*Secret{ + caSecret(cert1), + }, + SubjectNames: []string{"example.com"}, + }, + UpstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + }, + )), + ), + }, + ), + }, + "HTTPRoute with BackendTLSPolicy using multiple certs": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + objs: []any{ + tlsService, + cert1, + cert2, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "tlssvc", 443, 1)), + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlssvc", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{ + { + Kind: "Secret", + Name: gatewayapi_v1.ObjectName(cert1.Name), + }, + { + Kind: "Secret", + Name: gatewayapi_v1.ObjectName(cert2.Name), + }, + }, + Hostname: "example.com", + }, + }, + }, + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts( + virtualhost("*", routeCluster("/", + &Cluster{ + Weight: 1, + Upstream: &Service{ + Protocol: "tls", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsService.Name, + ServiceNamespace: tlsService.Namespace, + ServicePort: tlsService.Spec.Ports[0], + HealthPort: tlsService.Spec.Ports[0], + }, + }, + Protocol: "tls", + UpstreamValidation: &PeerValidationContext{ + CACertificates: []*Secret{ + caSecret(cert1), + caSecret(cert2), + }, + SubjectNames: []string{"example.com"}, + }, + UpstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + }, + )), + ), + }, + ), + }, + "HTTPRoute with BackendTLSPolicy and sectionName set": { + gatewayclass: validClass, + gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + objs: []any{ + tlsAndNonTLSService, + cert1, + makeHTTPRoute("tls-basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/tls", "tlsandnontlssvc", 443, 1)), + &gatewayapi_v1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "non-tls-basic", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1beta1.HTTPRouteSpec{ + CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ + ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, }, Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, - URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ - Path: &gatewayapi_v1beta1.HTTPPathModifier{ - Type: gatewayapi_v1.FullPathHTTPPathModifier, - ReplaceFullPath: ref.To("/replacement"), + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/non-tls"), + BackendRefs: gatewayapi.HTTPBackendRefs( + gatewayapi.HTTPBackendRef("tlsandnontlssvc", 80, 1), + ), + }}, + }, + }, + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlsandnontlssvc", + }, + SectionName: ptr.To(gatewayapi_v1.SectionName("https")), + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "Secret", + Name: gatewayapi_v1.ObjectName(cert1.Name), + }}, + Hostname: "example.com", + }, + }, + }, + }, + want: listeners( + &Listener{ + Name: "http-80", + VirtualHosts: virtualhosts( + virtualhost("*", + &Route{ + PathMatchCondition: prefixSegment("/tls"), + Clusters: []*Cluster{ + { + Weight: 1, + Upstream: &Service{ + Protocol: "tls", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsAndNonTLSService.Name, + ServiceNamespace: tlsAndNonTLSService.Namespace, + ServicePort: tlsAndNonTLSService.Spec.Ports[1], + HealthPort: tlsAndNonTLSService.Spec.Ports[1], + }, + }, + Protocol: "tls", + UpstreamValidation: &PeerValidationContext{ + CACertificates: []*Secret{ + caSecret(cert1), + }, + SubjectNames: []string{"example.com"}, + }, + UpstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, + }, + }, + }, + &Route{ + PathMatchCondition: prefixSegment("/non-tls"), + Clusters: []*Cluster{ + { + Weight: 1, + Upstream: &Service{ + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsAndNonTLSService.Name, + ServiceNamespace: tlsAndNonTLSService.Namespace, + ServicePort: tlsAndNonTLSService.Spec.Ports[0], + HealthPort: tlsAndNonTLSService.Spec.Ports[0], + }, + }, }, }, - }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, - }, - want: listeners( - &Listener{ - Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - &Route{ - PathMatchCondition: prefixSegment("/prefix"), - PathRewritePolicy: &PathRewritePolicy{ - FullPathRewrite: "/replacement", }, - Clusters: clustersWeight(service(kuardService)), - }, - )), + ), + ), }, ), }, - "HTTPRoute rule with URLRewrite filter with Hostname rewrite": { + "HTTPRoute with invalid BackendTLSPolicy certRef secret": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, objs: []any{ - kuardService, - &gatewayapi_v1beta1.HTTPRoute{ + tlsService, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "tlssvc", 443, 1)), + &gatewayapi_v1alpha2.BackendTLSPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "basic", Namespace: "projectcontour", }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlssvc", + }, }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{{ - Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, - URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ - Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("rewritten.com")), - }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "Secret", + Name: gatewayapi_v1.ObjectName(cert1.Name), }}, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, + Hostname: "example.com", + }, }, }, }, want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - &Route{ - PathMatchCondition: prefixSegment("/prefix"), - RequestHeadersPolicy: &HeadersPolicy{HostRewrite: "rewritten.com"}, - Clusters: clustersWeight(service(kuardService)), - }, - )), + VirtualHosts: virtualhosts( + virtualhost("*", routeCluster("/", + &Cluster{ + Weight: 1, + Upstream: &Service{ + Protocol: "", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsService.Name, + ServiceNamespace: tlsService.Namespace, + ServicePort: tlsService.Spec.Ports[0], + HealthPort: tlsService.Spec.Ports[0], + }, + }, + }, + )), + ), }, ), }, - "HTTPRoute rule with RequestHeadersModifier and URLRewrite filter with Hostname rewrite": { + "HTTPRoute with invalid BackendTLSPolicy certRef configmap": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, objs: []any{ - kuardService, - &gatewayapi_v1beta1.HTTPRoute{ + tlsService, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "tlssvc", 443, 1)), + &gatewayapi_v1alpha2.BackendTLSPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "basic", Namespace: "projectcontour", }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlssvc", + }, }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "test.projectcontour.io", + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "ConfigMap", + Name: gatewayapi_v1.ObjectName(cert1.Name), + }}, + Hostname: "example.com", }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/prefix"), - Filters: []gatewayapi_v1.HTTPRouteFilter{ - { - Type: gatewayapi_v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi_v1beta1.HTTPHeaderFilter{ - Set: []gatewayapi_v1beta1.HTTPHeader{ - { - Name: "Host", - Value: "requestheader.rewritten.com", - }, - }, - }, - }, - { - Type: gatewayapi_v1.HTTPRouteFilterURLRewrite, - URLRewrite: &gatewayapi_v1beta1.HTTPURLRewriteFilter{ - Hostname: ref.To(gatewayapi_v1beta1.PreciseHostname("url.rewritten.com")), - }, - }, - }, - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, }, }, }, want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - &Route{ - PathMatchCondition: prefixSegment("/prefix"), - RequestHeadersPolicy: &HeadersPolicy{ - Add: map[string]string{}, - HostRewrite: "url.rewritten.com", + VirtualHosts: virtualhosts( + virtualhost("*", routeCluster("/", + &Cluster{ + Weight: 1, + Upstream: &Service{ + Protocol: "", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsService.Name, + ServiceNamespace: tlsService.Namespace, + ServicePort: tlsService.Spec.Ports[0], + HealthPort: tlsService.Spec.Ports[0], + }, + }, }, - Clusters: clustersWeight(service(kuardService)), - }, - )), + )), + ), }, ), }, - - "HTTPRoute rule with request timeout": { + "HTTPRoute with invalid BackendTLSPolicy certRef kind": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, + upstreamTLS: &UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }, objs: []any{ - kuardService, - makeHTTPRoute("5s", ""), + tlsService, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "tlssvc", 443, 1)), + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "tlssvc", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "Invalid", + Name: gatewayapi_v1.ObjectName(cert1.Name), + }}, + Hostname: "example.com", + }, + }, + }, }, want: listeners( &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", - &Route{ - PathMatchCondition: prefixString("/"), - Clusters: clustersWeight(service(kuardService)), - TimeoutPolicy: RouteTimeoutPolicy{ - ResponseTimeout: timeout.DurationSetting(5 * time.Second), + virtualhost("*", routeCluster("/", + &Cluster{ + Weight: 1, + Upstream: &Service{ + Protocol: "", + Weighted: WeightedService{ + Weight: 1, + ServiceName: tlsService.Name, + ServiceNamespace: tlsService.Namespace, + ServicePort: tlsService.Spec.Ports[0], + HealthPort: tlsService.Spec.Ports[0], + }, }, }, - ), + )), ), }, ), }, - "HTTPRoute rule with request and backendRequest timeout": { - gatewayclass: validClass, - gateway: gatewayHTTPAllNamespaces, - objs: []any{ - kuardService, - makeHTTPRoute("5s", "5s"), - }, - want: listeners(), - }, - - "HTTPRoute rule with backendRequest timeout only": { - gatewayclass: validClass, - gateway: gatewayHTTPAllNamespaces, - objs: []any{ - kuardService, - makeHTTPRoute("", "5s"), - }, - want: listeners(), - }, - "HTTPRoute rule with invalid request timeout": { - gatewayclass: validClass, - gateway: gatewayHTTPAllNamespaces, - objs: []any{ - kuardService, - makeHTTPRoute("invalid", ""), - }, - want: listeners(), - }, - "different weights for multiple forwardTos": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, @@ -4223,25 +3910,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { kuardService, kuardService2, kuardService3, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRefs( - gatewayapi.HTTPBackendRef("kuard", 8080, 5), - gatewayapi.HTTPBackendRef("kuard2", 8080, 10), - gatewayapi.HTTPBackendRef("kuard3", 8080, 15), - ), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRefs( + gatewayapi.HTTPBackendRef("kuard", 8080, 5), + gatewayapi.HTTPBackendRef("kuard2", 8080, 10), + gatewayapi.HTTPBackendRef("kuard3", 8080, 15), + ), + }), }, want: listeners( &Listener{ @@ -4287,25 +3963,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { kuardService, kuardService2, kuardService3, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRefs( - gatewayapi.HTTPBackendRef("kuard", 8080, 5), - gatewayapi.HTTPBackendRef("kuard2", 8080, 0), - gatewayapi.HTTPBackendRef("kuard3", 8080, 15), - ), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "", gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRefs( + gatewayapi.HTTPBackendRef("kuard", 8080, 5), + gatewayapi.HTTPBackendRef("kuard2", 8080, 0), + gatewayapi.HTTPBackendRef("kuard3", 8080, 15), + ), + }), }, want: listeners( &Listener{ @@ -4351,21 +4016,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { kuardService, kuardService2, kuardService3, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 0), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchPathPrefix, "/", "kuard", 8080, 0)), }, want: listeners( &Listener{ @@ -5105,21 +4756,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPWithHostname, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchExact, "/blog"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "", makeHTTPRouteRule(gatewayapi_v1.PathMatchExact, "/blog", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -5136,24 +4773,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { gateway: gatewayHTTPWithWildcardHostname, objs: []any{ kuardService, - &gatewayapi_v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "basic", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1beta1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ - ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, - }, - Hostnames: []gatewayapi_v1beta1.Hostname{ - "http.projectcontour.io", - }, - Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ - Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchExact, "/blog"), - BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1), - }}, - }, - }, + makeHTTPRoute("basic", "projectcontour", "http.projectcontour.io", makeHTTPRouteRule(gatewayapi_v1.PathMatchExact, "/blog", "kuard", 8080, 1)), }, want: listeners( &Listener{ @@ -5906,6 +5526,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, &GatewayAPIProcessor{ FieldLogger: fixture.NewTestLogger(t), + UpstreamTLS: tc.upstreamTLS, }, }, } @@ -11359,8 +10980,10 @@ func TestDAGInsert(t *testing.T) { }, Protocol: "tls", UpstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), - SubjectNames: []string{"example.com"}, + CACertificates: []*Secret{ + caSecret(cert1), + }, + SubjectNames: []string{"example.com"}, }, }, ), @@ -11392,8 +11015,10 @@ func TestDAGInsert(t *testing.T) { }, Protocol: "h2", UpstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), - SubjectNames: []string{"example.com"}, + CACertificates: []*Secret{ + caSecret(cert1), + }, + SubjectNames: []string{"example.com"}, }, }, ), @@ -11467,8 +11092,10 @@ func TestDAGInsert(t *testing.T) { }, Protocol: "tls", UpstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert2), - SubjectNames: []string{"example.com"}, + CACertificates: []*Secret{ + caSecret(cert2), + }, + SubjectNames: []string{"example.com"}, }, }, ), @@ -11503,7 +11130,9 @@ func TestDAGInsert(t *testing.T) { MaxTLSVersion: "1.3", Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), + CACertificates: []*Secret{ + caSecret(cert1), + }, }, }, ), @@ -11533,7 +11162,9 @@ func TestDAGInsert(t *testing.T) { MaxTLSVersion: "1.3", Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), + CACertificates: []*Secret{ + caSecret(cert1), + }, }, }, ), @@ -11600,7 +11231,9 @@ func TestDAGInsert(t *testing.T) { Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ SkipClientCertValidation: true, - CACertificate: caSecret(cert1), + CACertificates: []*Secret{ + caSecret(cert1), + }, }, }, ), @@ -11633,8 +11266,10 @@ func TestDAGInsert(t *testing.T) { MaxTLSVersion: "1.3", Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), - CRL: crlSecret(crl), + CACertificates: []*Secret{ + caSecret(cert1), + }, + CRL: crlSecret(crl), }, }, ), @@ -11667,7 +11302,9 @@ func TestDAGInsert(t *testing.T) { MaxTLSVersion: "1.3", Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), + CACertificates: []*Secret{ + caSecret(cert1), + }, CRL: crlSecret(crl), OnlyVerifyLeafCertCrl: true, }, @@ -11702,7 +11339,9 @@ func TestDAGInsert(t *testing.T) { MaxTLSVersion: "1.3", Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), + CACertificates: []*Secret{ + caSecret(cert1), + }, ForwardClientCertificate: &ClientCertificateDetails{ Subject: true, Cert: true, @@ -11742,7 +11381,9 @@ func TestDAGInsert(t *testing.T) { MaxTLSVersion: "1.3", Secret: secret(sec1), DownstreamValidation: &PeerValidationContext{ - CACertificate: caSecret(cert1), + CACertificates: []*Secret{ + caSecret(cert1), + }, OptionalClientCertificate: true, }, }, @@ -16361,7 +16002,7 @@ func makeHTTPRouteTimeouts(request, backendRequest string) *gatewayapi_v1.HTTPRo return httpRouteTimeouts } -func makeHTTPRoute(request, backendRequest string) *gatewayapi_v1beta1.HTTPRoute { +func makeHTTPRouteWithTimeouts(request, backendRequest string) *gatewayapi_v1beta1.HTTPRoute { return &gatewayapi_v1beta1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ Name: "basic", @@ -16382,3 +16023,36 @@ func makeHTTPRoute(request, backendRequest string) *gatewayapi_v1beta1.HTTPRoute }, } } + +func makeHTTPRoute(name, namespace, hostname string, firstRule gatewayapi_v1beta1.HTTPRouteRule, additionalRules ...gatewayapi_v1beta1.HTTPRouteRule) *gatewayapi_v1beta1.HTTPRoute { + rules := []gatewayapi_v1beta1.HTTPRouteRule{firstRule} + if len(additionalRules) > 0 { + rules = append(rules, additionalRules...) + } + var hostnames []gatewayapi_v1beta1.Hostname + if hostname != "" { + hostnames = []gatewayapi_v1beta1.Hostname{ + gatewayapi_v1beta1.Hostname(hostname), + } + } + return &gatewayapi_v1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gatewayapi_v1beta1.HTTPRouteSpec{ + CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ + ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")}, + }, + Hostnames: hostnames, + Rules: rules, + }, + } +} + +func makeHTTPRouteRule(pathType gatewayapi_v1beta1.PathMatchType, pathValue, serviceName string, port int, weight int32) gatewayapi_v1beta1.HTTPRouteRule { + return gatewayapi_v1beta1.HTTPRouteRule{ + Matches: gatewayapi.HTTPRouteMatch(pathType, pathValue), + BackendRefs: gatewayapi.HTTPBackendRef(serviceName, port, weight), + } +} diff --git a/internal/dag/cache.go b/internal/dag/cache.go index 21f2bdaa7a4..a194e9364ca 100644 --- a/internal/dag/cache.go +++ b/internal/dag/cache.go @@ -63,6 +63,7 @@ type KubernetesCache struct { ingresses map[types.NamespacedName]*networking_v1.Ingress httpproxies map[types.NamespacedName]*contour_api_v1.HTTPProxy secrets map[types.NamespacedName]*Secret + configmapsecrets map[types.NamespacedName]*Secret tlscertificatedelegations map[types.NamespacedName]*contour_api_v1.TLSCertificateDelegation services map[types.NamespacedName]*v1.Service namespaces map[string]*v1.Namespace @@ -73,6 +74,7 @@ type KubernetesCache struct { grpcroutes map[types.NamespacedName]*gatewayapi_v1alpha2.GRPCRoute tcproutes map[types.NamespacedName]*gatewayapi_v1alpha2.TCPRoute referencegrants map[types.NamespacedName]*gatewayapi_v1beta1.ReferenceGrant + backendtlspolicies map[types.NamespacedName]*gatewayapi_v1alpha2.BackendTLSPolicy extensions map[types.NamespacedName]*contour_api_v1alpha1.ExtensionService // Metrics contains Prometheus metrics. @@ -99,6 +101,7 @@ func (kc *KubernetesCache) init() { kc.ingresses = make(map[types.NamespacedName]*networking_v1.Ingress) kc.httpproxies = make(map[types.NamespacedName]*contour_api_v1.HTTPProxy) kc.secrets = make(map[types.NamespacedName]*Secret) + kc.configmapsecrets = make(map[types.NamespacedName]*Secret) kc.tlscertificatedelegations = make(map[types.NamespacedName]*contour_api_v1.TLSCertificateDelegation) kc.services = make(map[types.NamespacedName]*v1.Service) kc.namespaces = make(map[string]*v1.Namespace) @@ -107,6 +110,7 @@ func (kc *KubernetesCache) init() { kc.tlsroutes = make(map[types.NamespacedName]*gatewayapi_v1alpha2.TLSRoute) kc.grpcroutes = make(map[types.NamespacedName]*gatewayapi_v1alpha2.GRPCRoute) kc.tcproutes = make(map[types.NamespacedName]*gatewayapi_v1alpha2.TCPRoute) + kc.backendtlspolicies = make(map[types.NamespacedName]*gatewayapi_v1alpha2.BackendTLSPolicy) kc.extensions = make(map[types.NamespacedName]*contour_api_v1alpha1.ExtensionService) } @@ -125,6 +129,15 @@ func (kc *KubernetesCache) Insert(obj any) bool { kc.secrets[k8s.NamespacedNameOf(obj)] = &Secret{Object: obj} return kc.secretTriggersRebuild(obj), len(kc.secrets) + case *v1.ConfigMap: + // Only insert configmaps that are CA certs, i.e has 'ca.crt' key, + // into cache. + if secret, isCA := kc.convertCACertConfigMapToSecret(obj); isCA { + kc.configmapsecrets[k8s.NamespacedNameOf(obj)] = &Secret{Object: secret} + return kc.configMapTriggersRebuild(obj), len(kc.configmapsecrets) + } + return false, len(kc.configmapsecrets) + case *v1.Service: kc.services[k8s.NamespacedNameOf(obj)] = obj return kc.serviceTriggersRebuild(obj), len(kc.services) @@ -236,6 +249,10 @@ func (kc *KubernetesCache) Insert(obj any) bool { kc.referencegrants[k8s.NamespacedNameOf(obj)] = obj return true, len(kc.referencegrants) + case *gatewayapi_v1alpha2.BackendTLSPolicy: + kc.backendtlspolicies[k8s.NamespacedNameOf(obj)] = obj + return true, len(kc.backendtlspolicies) + case *contour_api_v1alpha1.ExtensionService: kc.extensions[k8s.NamespacedNameOf(obj)] = obj return true, len(kc.extensions) @@ -303,6 +320,11 @@ func (kc *KubernetesCache) remove(obj any) (bool, int) { delete(kc.secrets, m) return kc.secretTriggersRebuild(obj), len(kc.secrets) + case *v1.ConfigMap: + m := k8s.NamespacedNameOf(obj) + delete(kc.configmapsecrets, m) + return kc.configMapTriggersRebuild(obj), len(kc.configmapsecrets) + case *v1.Service: m := k8s.NamespacedNameOf(obj) delete(kc.services, m) @@ -389,6 +411,12 @@ func (kc *KubernetesCache) remove(obj any) (bool, int) { delete(kc.referencegrants, m) return ok, len(kc.referencegrants) + case *gatewayapi_v1alpha2.BackendTLSPolicy: + m := k8s.NamespacedNameOf(obj) + _, ok := kc.backendtlspolicies[m] + delete(kc.backendtlspolicies, m) + return ok, len(kc.backendtlspolicies) + case *contour_api_v1alpha1.ExtensionService: m := k8s.NamespacedNameOf(obj) _, ok := kc.extensions[m] @@ -579,6 +607,32 @@ func isRefToSecret(ref gatewayapi_v1beta1.SecretObjectReference, secret *v1.Secr string(ref.Name) == secret.Name } +// configMapTriggersRebuild returns true if this configmap is referenced by a +// BackendTLSPolicy object. +func (kc *KubernetesCache) configMapTriggersRebuild(configMapObj *v1.ConfigMap) bool { + configMap := types.NamespacedName{ + Namespace: configMapObj.Namespace, + Name: configMapObj.Name, + } + + for _, backendtlspolicy := range kc.backendtlspolicies { + for _, caCertRef := range backendtlspolicy.Spec.TLS.CACertRefs { + if caCertRef.Group != "" || caCertRef.Kind != "ConfigMap" { + continue + } + + caCertRefNamespacedName := types.NamespacedName{ + Namespace: backendtlspolicy.Namespace, + Name: string(caCertRef.Name), + } + if configMap == caCertRefNamespacedName { + return true + } + } + } + return false +} + // routeTriggersRebuild returns true if this route references gateway in this cache. func (kc *KubernetesCache) routeTriggersRebuild(parentRefs []gatewayapi_v1beta1.ParentReference) bool { if kc.gateway == nil { @@ -631,6 +685,27 @@ func (kc *KubernetesCache) LookupCASecret(name types.NamespacedName, targetNames return sec, nil } +// LookupCAConfigMap returns ConfigMap converted into dag.Secret with CA certificate from cache. +func (kc *KubernetesCache) LookupCAConfigMap(name types.NamespacedName) (*Secret, error) { + sec, ok := kc.configmapsecrets[name] + if !ok { + return nil, fmt.Errorf("ConfigMap not found") + } + + // Compute and store the validation result if not + // already stored. + if sec.ValidCASecret == nil { + sec.ValidCASecret = &SecretValidationStatus{ + Error: validCASecret(sec.Object), + } + } + + if err := sec.ValidCASecret.Error; err != nil { + return nil, err + } + return sec, nil +} + // LookupCRLSecret returns Secret with CRL from the cache. // If name (referred Secret) is in different namespace than targetNamespace (the referring object), // then delegation check is performed. @@ -676,7 +751,9 @@ func (kc *KubernetesCache) LookupUpstreamValidation(uv *contour_api_v1.UpstreamV } return nil, fmt.Errorf("invalid CA Secret %q: %s", caCertificate, err) } - pvc.CACertificate = cacert + pvc.CACertificates = []*Secret{ + cacert, + } // CEL validation should enforce that SubjectName must be set if SubjectNames is used. So, SubjectName will always be present. if uv.SubjectName == "" { @@ -777,3 +854,67 @@ func (kc *KubernetesCache) LookupService(meta types.NamespacedName, port intstr. return nil, v1.ServicePort{}, fmt.Errorf("port %q on service %q not matched", port.String(), meta) } + +// LookupBackendTLSPolicyByTargetRef returns the Kubernetes BackendTLSPolicies that matches the provided targetRef with +// a SectionName, if possible. A BackendTLSPolicy may be returned if there is a BackendTLSPolicy matching the targetRef +// but has no SectionName. +// +// For example, there could be two BackendTLSPolicies matching Service "foo". One of them matches SectionName "https", +// but the other has no SectionName and functions as a catch-all policy for service "foo". +// +// The namespace on the provided targetRef will be used to match the namespace on the backendTLSPolicy. If not set it is +// assumed to be the default namespace. +// +// If a policy is found, true is returned. +func (kc *KubernetesCache) LookupBackendTLSPolicyByTargetRef(targetRef gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName) (*gatewayapi_v1alpha2.BackendTLSPolicy, bool) { + var fallbackBackendTLSPolicy *gatewayapi_v1alpha2.BackendTLSPolicy + for _, v := range kc.backendtlspolicies { + // Match the namespace in the targetRef to the BackendTLSPolicy namespace instead of it's + // spec.targetRef.namespace. Cross namespace references aren't allowed and validating the targetRef's + // namespace is either the same or empty is checked further down. + namespaceMatches := targetRef.PolicyTargetReference.Namespace == nil && (v.Namespace == "" || v.Namespace == "default") || + targetRef.PolicyTargetReference.Namespace != nil && v.Namespace == string(*targetRef.PolicyTargetReference.Namespace) + + // Match the targetRef namespace to the backendtlspolicy namespace or ensure it is empty + targetRefNamespaceMatchesPolicyNamspace := v.Spec.TargetRef.PolicyTargetReference.Namespace == nil || + v.Namespace == string(*v.Spec.TargetRef.PolicyTargetReference.Namespace) + + sectionNameMatches := v.Spec.TargetRef.SectionName == nil && targetRef.SectionName == nil || + v.Spec.TargetRef.SectionName != nil && targetRef.SectionName != nil && + *v.Spec.TargetRef.SectionName == *targetRef.SectionName + + if v.Spec.TargetRef.PolicyTargetReference.Group == targetRef.Group && + v.Spec.TargetRef.PolicyTargetReference.Kind == targetRef.Kind && + v.Spec.TargetRef.PolicyTargetReference.Name == targetRef.Name && + namespaceMatches && + targetRefNamespaceMatchesPolicyNamspace { + if sectionNameMatches { + return v, true + } + + if v.Spec.TargetRef.SectionName == nil { + fallbackBackendTLSPolicy = v + } + } + } + + if fallbackBackendTLSPolicy != nil { + return fallbackBackendTLSPolicy, true + } + + return nil, false +} + +func (kc *KubernetesCache) convertCACertConfigMapToSecret(configMap *v1.ConfigMap) (*v1.Secret, bool) { + if _, ok := configMap.Data[CACertificateKey]; !ok { + return nil, false + } + + return &v1.Secret{ + ObjectMeta: configMap.ObjectMeta, + Data: map[string][]byte{ + CACertificateKey: []byte(configMap.Data[CACertificateKey]), + }, + Type: v1.SecretTypeOpaque, + }, true +} diff --git a/internal/dag/cache_test.go b/internal/dag/cache_test.go index 61f6cf74741..e50b5474a3b 100644 --- a/internal/dag/cache_test.go +++ b/internal/dag/cache_test.go @@ -31,8 +31,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" gatewayapi_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/apis/v1beta1" gatewayapi_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -361,6 +363,91 @@ func TestKubernetesCacheInsert(t *testing.T) { }, want: true, }, + "insert certificate secret referenced by BackendTLSPolicy": { + pre: []any{ + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-btp", + Namespace: "default", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{ + { + Kind: "Secret", + Name: "ca", + }, + }, + }, + }, + }, + }, + obj: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "default", + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + CACertificateKey: []byte(fixture.CERTIFICATE), + }, + }, + want: true, + }, + "insert certificate configmap not referenced": { + obj: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "default", + }, + Data: map[string]string{ + CACertificateKey: fixture.CERTIFICATE, + }, + }, + want: false, + }, + "insert generic configmap not referenced": { + obj: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "default", + }, + Data: map[string]string{ + "not-ca.crt": fixture.CERTIFICATE, + }, + }, + want: false, + }, + "insert certificate configmap referenced by BackendTLSPolicy": { + pre: []any{ + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-btp", + Namespace: "default", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{ + { + Kind: "ConfigMap", + Name: "ca", + }, + }, + }, + }, + }, + }, + obj: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "default", + }, + Data: map[string]string{ + CACertificateKey: fixture.CERTIFICATE, + }, + }, + want: true, + }, "insert ingressv1 empty ingress class": { obj: &networking_v1.Ingress{ ObjectMeta: metav1.ObjectMeta{ @@ -1035,6 +1122,48 @@ func TestKubernetesCacheInsert(t *testing.T) { }, want: true, }, + "insert backendtlspolicy targeting backend Service": { + pre: []any{ + &gatewayapi_v1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "httproute", + Namespace: "default", + }, + Spec: gatewayapi_v1beta1.HTTPRouteSpec{ + CommonRouteSpec: gatewayapi_v1alpha2.CommonRouteSpec{ + ParentRefs: []gatewayapi_v1alpha2.ParentReference{ + gatewayapi.GatewayParentRef("projectcontour", "contour"), + }, + }, + Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ + BackendRefs: gatewayapi.HTTPBackendRef("service", 80, 1), + }}, + }, + }, + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service", + Namespace: "default", + }, + }, + }, + obj: &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backendtlspolicy", + Namespace: "default", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "service", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{}, + }, + }, + want: true, + }, // SPECIFIC GATEWAY TESTS "specific gateway configured, insert gatewayclass, no gateway cached": { @@ -1195,6 +1324,24 @@ func TestKubernetesCacheRemove(t *testing.T) { }, want: false, }, + "remove configmap": { + cache: cache(&v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap", + Namespace: "default", + }, + Data: map[string]string{ + CACertificateKey: fixture.CERTIFICATE, + }, + }), + obj: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap", + Namespace: "default", + }, + }, + want: false, + }, "remove service": { cache: cache(&v1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -1655,6 +1802,98 @@ func TestKubernetesCacheRemove(t *testing.T) { }, want: true, }, + "remove gateway-api BackendTLSPolicy": { + cache: cache(&gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backendtlspolicy", + Namespace: "default", + }, + }), + obj: &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backendtlspolicy", + Namespace: "default", + }, + }, + want: true, + }, + "remove secret that is referenced by gateway-api BackendTLSPolicy": { + cache: cache( + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backendtlspolicy", + Namespace: "default", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []v1beta1.LocalObjectReference{ + { + Kind: "Secret", + Name: "ca", + }, + }, + }, + }, + }, + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "default", + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + CACertificateKey: []byte(fixture.CERTIFICATE), + }, + }, + ), + obj: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca", + Namespace: "default", + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + CACertificateKey: []byte(fixture.CERTIFICATE), + }, + }, + want: true, + }, + "remove configmap that is referenced by gateway-api BackendTLSPolicy": { + cache: cache( + &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backendtlspolicy", + Namespace: "default", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []v1beta1.LocalObjectReference{ + { + Kind: "ConfigMap", + Name: "configmap", + }, + }, + }, + }, + }, + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap", + Namespace: "default", + }, + Data: map[string]string{ + CACertificateKey: fixture.CERTIFICATE, + }, + }, + ), + obj: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap", + Namespace: "default", + }, + }, + want: true, + }, "remove extension service": { cache: cache(&contour_api_v1alpha1.ExtensionService{ ObjectMeta: fixture.ObjectMeta("default/extension"), @@ -2693,9 +2932,11 @@ func TestLookupUpstreamValidation(t *testing.T) { pvc := func(subjectNames []string) *PeerValidationContext { return &PeerValidationContext{ - CACertificate: &Secret{ - Object: secret(), - ValidCASecret: &SecretValidationStatus{}, + CACertificates: []*Secret{ + { + Object: secret(), + ValidCASecret: &SecretValidationStatus{}, + }, }, SubjectNames: subjectNames, } @@ -2751,3 +2992,262 @@ func TestLookupUpstreamValidation(t *testing.T) { }) } } + +func TestLookupBackendTLSPolicyByTargetRef(t *testing.T) { + targetRef := func(group, kind, name string, namespace, sectionName *string) gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName { + var ns *gatewayapi_v1alpha2.Namespace + if namespace != nil { + ns = ptr.To(gatewayapi_v1alpha2.Namespace(*namespace)) + } + var sn *gatewayapi_v1alpha2.SectionName + if sectionName != nil { + sn = ptr.To(gatewayapi_v1alpha2.SectionName(*sectionName)) + } + return gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Group: gatewayapi_v1alpha2.Group(group), + Kind: gatewayapi_v1alpha2.Kind(kind), + Name: gatewayapi_v1alpha2.ObjectName(name), + Namespace: ns, + }, + SectionName: sn, + } + } + + backendTLSPolicy := func(name, namespace, serviceName string, targetNamespace, sectionName *string) *gatewayapi_v1alpha2.BackendTLSPolicy { + return &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: targetRef("", "Service", serviceName, targetNamespace, sectionName), + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1beta1.LocalObjectReference{ + { + Group: "", + Kind: "Secret", + Name: "ca", + }, + }, + Hostname: "example.com", + }, + }, + } + } + + tests := map[string]struct { + targetRef gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName + backendTLSPolicies []*gatewayapi_v1alpha2.BackendTLSPolicy + want *gatewayapi_v1alpha2.BackendTLSPolicy + wantFound bool + }{ + "finds the BackendTLSPolicy with the matching targetRef": { + targetRef: targetRef("", "Service", "backend-service", nil, nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "", "backend-service-with-section-name", nil, ptr.To("https")), + backendTLSPolicy("btp2", "", "backend-service-with-section-name", nil, ptr.To("https2")), + }, + want: backendTLSPolicy("btp", "", "backend-service", nil, nil), + wantFound: true, + }, + "finds the BackendTLSPolicy matching targetRef with section name": { + targetRef: targetRef("", "Service", "backend-service-with-section-name", nil, ptr.To("https2")), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "", "backend-service-with-section-name", nil, ptr.To("https")), + backendTLSPolicy("btp2", "", "backend-service-with-section-name", nil, ptr.To("https2")), + }, + want: backendTLSPolicy("btp2", "", "backend-service-with-section-name", nil, ptr.To("https2")), + wantFound: true, + }, + "finds the fallback BackendTLSPolicy matching targetRef but not section name": { + targetRef: targetRef("", "Service", "backend-service-with-fallback", nil, ptr.To("https2")), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "", "backend-service-with-fallback", nil, nil), + backendTLSPolicy("btp2", "", "backend-service-with-fallback", nil, ptr.To("https")), + }, + want: backendTLSPolicy("btp1", "", "backend-service-with-fallback", nil, nil), + wantFound: true, + }, + "finds the fallback BackendTLSPolicy matching targetRef with section name": { + targetRef: targetRef("", "Service", "backend-service-with-fallback", nil, ptr.To("https")), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "", "backend-service-with-fallback", nil, nil), + backendTLSPolicy("btp2", "", "backend-service-with-fallback", nil, ptr.To("https")), + }, + want: backendTLSPolicy("btp2", "", "backend-service-with-fallback", nil, ptr.To("https")), + wantFound: true, + }, + "finds the BackendTLSPolicy matching namespace": { + targetRef: targetRef("", "Service", "backend-service-with-ns", ptr.To("some-ns"), nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "other-ns", "backend-service-with-other-ns", ptr.To("other-ns"), nil), + backendTLSPolicy("btp2", "some-ns", "backend-service-with-ns", ptr.To("some-ns"), nil), + }, + want: backendTLSPolicy("btp2", "some-ns", "backend-service-with-ns", ptr.To("some-ns"), nil), + wantFound: true, + }, + "finds the BackendTLSPolicy matching namespace even if backendtlspolicy targetRef namespace is empty": { + targetRef: targetRef("", "Service", "backend-service-with-ns", ptr.To("some-ns"), nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "other-ns", "backend-service-with-other-ns", nil, nil), + backendTLSPolicy("btp2", "some-ns", "backend-service-with-ns", nil, nil), + }, + want: backendTLSPolicy("btp2", "some-ns", "backend-service-with-ns", nil, nil), + wantFound: true, + }, + "finds the BackendTLSPolicy in default namespace": { + targetRef: targetRef("", "Service", "backend-service", nil, nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "default", "backend-service", nil, nil), + }, + want: backendTLSPolicy("btp", "default", "backend-service", nil, nil), + wantFound: true, + }, + "does not find the BackendTLSPolicy if the target namespace doesn't match the policy namespace": { + targetRef: targetRef("", "Service", "backend-service-with-ns", ptr.To("some-ns"), nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + backendTLSPolicy("btp1", "some-ns", "backend-service-with-ns", ptr.To("wrong-ns"), nil), + backendTLSPolicy("btp2", "wrong-ns", "backend-service-with-ns", ptr.To("some-ns"), nil), + }, + wantFound: false, + }, + "does not find the BackendTLSPolicy if the namespace does not match": { + targetRef: targetRef("", "Service", "backend-service", ptr.To("not-default"), nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + }, + wantFound: false, + }, + "does not find the BackendTLSPolicy if the service name does not match": { + targetRef: targetRef("", "Service", "other-service", nil, nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + }, + wantFound: false, + }, + "does not find the BackendTLSPolicy if the GroupKind does not match": { + targetRef: targetRef("example.api", "ExampleService", "backend-service", nil, nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + }, + wantFound: false, + }, + "does not find the BackendTLSPolicy if the group does not match": { + targetRef: targetRef("core", "Service", "backend-service", ptr.To("not-default"), nil), + backendTLSPolicies: []*gatewayapi_v1alpha2.BackendTLSPolicy{ + backendTLSPolicy("btp", "", "backend-service", nil, nil), + }, + wantFound: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + cache := KubernetesCache{ + FieldLogger: fixture.NewTestLogger(t), + } + + for _, backendTLSPolicy := range tc.backendTLSPolicies { + cache.Insert(backendTLSPolicy) + } + + gotBTP, gotFound := cache.LookupBackendTLSPolicyByTargetRef(tc.targetRef) + + if tc.wantFound { + assert.True(t, gotFound) + assert.Equal(t, tc.want, gotBTP) + } else { + assert.False(t, gotFound) + assert.Nil(t, gotBTP) + } + }) + } +} + +func TestLookupCAConfigMap(t *testing.T) { + cache := func(objs ...any) *KubernetesCache { + cache := KubernetesCache{ + FieldLogger: fixture.NewTestLogger(t), + } + for _, o := range objs { + cache.Insert(o) + } + return &cache + } + + configmap := func(name, namespace, data string) *v1.ConfigMap { + return &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: map[string]string{ + CACertificateKey: data, + }, + } + } + + secret := func(name, namespace, data string) *Secret { + return &Secret{ + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + CACertificateKey: []byte(data), + }, + }, + ValidCASecret: &SecretValidationStatus{ + Error: nil, + }, + } + } + + tests := map[string]struct { + cache *KubernetesCache + meta types.NamespacedName + wantSecret *Secret + wantErr error + }{ + "finds configmap by namespacedname and returns it as dag secret": { + cache: cache( + configmap("ca", "default", fixture.CA_CERT), + configmap("another-ca", "default", fixture.EC_CERTIFICATE), + ), + meta: types.NamespacedName{Namespace: "default", Name: "ca"}, + wantSecret: secret("ca", "default", fixture.CA_CERT), + }, + "returns an error if configmap secret is not a valid cert": { + cache: cache( + configmap("ca", "default", "invalid-ca-data"), + ), + meta: types.NamespacedName{Namespace: "default", Name: "ca"}, + wantErr: errors.New("invalid CA certificate bundle: failed to locate certificate"), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + gotSecret, gotErr := tc.cache.LookupCAConfigMap(tc.meta) + + switch { + case tc.wantErr != nil: + require.Error(t, gotErr) + require.EqualError(t, tc.wantErr, gotErr.Error()) + default: + require.NoError(t, gotErr) + assert.Equal(t, tc.wantSecret, gotSecret) + } + }) + } +} diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 95aaf383e42..e85b1e7808f 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -668,7 +668,7 @@ type ClientCertificateDetails struct { type PeerValidationContext struct { // CACertificate holds a reference to the Secret containing the CA to be used to // verify the upstream connection. - CACertificate *Secret + CACertificates []*Secret // SubjectNames holds optional subject names which Envoy will check against the // certificate presented by the upstream. The first entry must match the value of SubjectName SubjectNames []string @@ -691,11 +691,18 @@ type PeerValidationContext struct { // GetCACertificate returns the CA certificate from PeerValidationContext. func (pvc *PeerValidationContext) GetCACertificate() []byte { - if pvc == nil || pvc.CACertificate == nil { + if pvc == nil || len(pvc.CACertificates) == 0 { // No validation required. return nil } - return pvc.CACertificate.Object.Data[CACertificateKey] + var certs []byte + for _, cert := range pvc.CACertificates { + if cert == nil { + continue + } + certs = append(certs, cert.Object.Data[CACertificateKey]...) + } + return certs } // GetSubjectName returns the SubjectNames from PeerValidationContext. diff --git a/internal/dag/dag_test.go b/internal/dag/dag_test.go index f57a931d846..7c8edb67eaf 100644 --- a/internal/dag/dag_test.go +++ b/internal/dag/dag_test.go @@ -76,10 +76,12 @@ func TestSecureVirtualHostValid(t *testing.T) { func TestPeerValidationContext(t *testing.T) { pvc1 := PeerValidationContext{ - CACertificate: &Secret{ - Object: &v1.Secret{ - Data: map[string][]byte{ - CACertificateKey: []byte("cacert"), + CACertificates: []*Secret{ + { + Object: &v1.Secret{ + Data: map[string][]byte{ + CACertificateKey: []byte("cacert"), + }, }, }, }, @@ -87,13 +89,45 @@ func TestPeerValidationContext(t *testing.T) { } pvc2 := PeerValidationContext{} var pvc3 *PeerValidationContext + pvc4 := PeerValidationContext{ + CACertificates: []*Secret{ + { + Object: &v1.Secret{ + Data: map[string][]byte{ + CACertificateKey: []byte("-cacert-"), + }, + }, + }, + { + Object: &v1.Secret{ + Data: map[string][]byte{ + CACertificateKey: []byte("-cacert2-"), + }, + }, + }, + { + Object: &v1.Secret{ + Data: map[string][]byte{}, + }, + }, + nil, + }, + SubjectNames: []string{"subject"}, + } + pvc5 := PeerValidationContext{ + CACertificates: []*Secret{}, + } - assert.Equal(t, "subject", pvc1.GetSubjectNames()[0]) + assert.ElementsMatch(t, []string{"subject"}, pvc1.GetSubjectNames()) assert.Equal(t, []byte("cacert"), pvc1.GetCACertificate()) assert.Equal(t, []string(nil), pvc2.GetSubjectNames()) assert.Equal(t, []byte(nil), pvc2.GetCACertificate()) assert.Equal(t, []string(nil), pvc3.GetSubjectNames()) assert.Equal(t, []byte(nil), pvc3.GetCACertificate()) + assert.ElementsMatch(t, []string{"subject"}, pvc4.GetSubjectNames()) + assert.Equal(t, []byte("-cacert--cacert2-"), pvc4.GetCACertificate()) + assert.Equal(t, []string(nil), pvc5.GetSubjectNames()) + assert.Equal(t, []byte(nil), pvc5.GetCACertificate()) } func TestObserverFunc(t *testing.T) { diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index eeeb4bfb579..5c78b3993a3 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -34,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" gatewayapi_v1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapi_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -78,6 +79,10 @@ type GatewayAPIProcessor struct { // GlobalCircuitBreakerDefaults defines global circuit breaker defaults. GlobalCircuitBreakerDefaults *contour_api_v1alpha1.GlobalCircuitBreakerDefaults + + // UpstreamTLS defines the TLS settings like min/max version + // and cipher suites for upstream connections. + UpstreamTLS *UpstreamTLS } // matchConditions holds match rules. @@ -1976,6 +1981,75 @@ func (p *GatewayAPIProcessor) httpClusters(routeNamespace string, backendRefs [] continue } + var upstreamValidation *PeerValidationContext + var backendRefGroup gatewayapi_v1alpha2.Group + if backendRef.Group != nil { + backendRefGroup = *backendRef.Group + } + + var backendRefKind gatewayapi_v1alpha2.Kind + if backendRef.Kind != nil { + backendRefKind = *backendRef.Kind + } + + var backendNamespace *gatewayapi_v1.Namespace + if backendRef.Namespace != nil && *backendRef.Namespace != "" { + backendNamespace = backendRef.Namespace + } else { + backendNamespace = ptr.To(gatewayapi_v1.Namespace(routeNamespace)) + } + + policyTargetRef := gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Group: backendRefGroup, + Kind: backendRefKind, + Name: backendRef.Name, + Namespace: backendNamespace, + }, + SectionName: ptr.To(gatewayapi_v1alpha2.SectionName(service.Weighted.ServicePort.Name)), + } + + var upstreamTLS *UpstreamTLS + // Check to see if there is any BackendTLSPolicy matching this service and service port + backendTLSPolicy, found := p.source.LookupBackendTLSPolicyByTargetRef(policyTargetRef) + if found { + var caSecrets []*Secret + for _, certRef := range backendTLSPolicy.Spec.TLS.CACertRefs { + switch certRef.Kind { + case "Secret": + caSecret, err := p.source.LookupCASecret(types.NamespacedName{ + Name: string(certRef.Name), + Namespace: backendTLSPolicy.Namespace, + }, backendTLSPolicy.Namespace) + if err != nil { + continue + } + caSecrets = append(caSecrets, caSecret) + case "ConfigMap": + caSecret, err := p.source.LookupCAConfigMap(types.NamespacedName{ + Name: string(certRef.Name), + Namespace: backendTLSPolicy.Namespace, + }) + if err != nil { + continue + } + caSecrets = append(caSecrets, caSecret) + default: + continue + } + } + + if len(caSecrets) != 0 { + upstreamValidation = &PeerValidationContext{ + CACertificates: caSecrets, + SubjectNames: []string{string(backendTLSPolicy.Spec.TLS.Hostname)}, + } + + service.Protocol = "tls" + upstreamTLS = p.UpstreamTLS + } + } + var clusterRequestHeaderPolicy *HeadersPolicy var clusterResponseHeaderPolicy *HeadersPolicy @@ -2037,6 +2111,8 @@ func (p *GatewayAPIProcessor) httpClusters(routeNamespace string, backendRefs [] TimeoutPolicy: ClusterTimeoutPolicy{ConnectTimeout: p.ConnectTimeout}, MaxRequestsPerConnection: p.MaxRequestsPerConnection, PerConnectionBufferLimitBytes: p.PerConnectionBufferLimitBytes, + UpstreamValidation: upstreamValidation, + UpstreamTLS: upstreamTLS, }) } return clusters, totalWeight, true diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 72590f74d21..8fd38718c7a 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -340,7 +340,9 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) { } return } - dv.CACertificate = cacert + dv.CACertificates = []*Secret{ + cacert, + } } else if !tls.ClientValidation.SkipClientCertValidation { validCond.AddErrorf(contour_api_v1.ConditionTypeTLSError, "ClientValidationInvalid", "Spec.VirtualHost.TLS client validation is invalid: CA Secret must be specified") diff --git a/internal/envoy/cluster.go b/internal/envoy/cluster.go index 8048b371a93..60afe0d8e29 100644 --- a/internal/envoy/cluster.go +++ b/internal/envoy/cluster.go @@ -47,8 +47,12 @@ func Clustername(cluster *dag.Cluster) string { buf += hc.Path } if uv := cluster.UpstreamValidation; uv != nil { - buf += uv.CACertificate.Object.ObjectMeta.Name - buf += uv.SubjectNames[0] + if len(uv.CACertificates) > 0 { + buf += uv.CACertificates[0].Object.ObjectMeta.Name + } + if len(uv.SubjectNames) > 0 { + buf += uv.SubjectNames[0] + } } buf += cluster.Protocol + cluster.SNI if !cluster.TimeoutPolicy.IdleConnectionTimeout.UseDefault() { diff --git a/internal/envoy/v3/auth_test.go b/internal/envoy/v3/auth_test.go index 6ddd906a61e..1e178b0b827 100644 --- a/internal/envoy/v3/auth_test.go +++ b/internal/envoy/v3/auth_test.go @@ -59,7 +59,9 @@ func TestUpstreamTLSContext(t *testing.T) { }, "no alpn, missing altname": { validation: &dag.PeerValidationContext{ - CACertificate: secret, + CACertificates: []*dag.Secret{ + secret, + }, }, want: &envoy_v3_tls.UpstreamTlsContext{ CommonTlsContext: &envoy_v3_tls.CommonTlsContext{}, @@ -75,8 +77,10 @@ func TestUpstreamTLSContext(t *testing.T) { }, "no alpn, ca and altname": { validation: &dag.PeerValidationContext{ - CACertificate: secret, - SubjectNames: []string{"www.example.com"}, + CACertificates: []*dag.Secret{ + secret, + }, + SubjectNames: []string{"www.example.com"}, }, want: &envoy_v3_tls.UpstreamTlsContext{ CommonTlsContext: &envoy_v3_tls.CommonTlsContext{ @@ -125,7 +129,9 @@ func TestUpstreamTLSContext(t *testing.T) { }, "multiple subjectnames": { validation: &dag.PeerValidationContext{ - CACertificate: secret, + CACertificates: []*dag.Secret{ + secret, + }, SubjectNames: []string{ "foo.com", "bar.com", diff --git a/internal/envoy/v3/cluster_test.go b/internal/envoy/v3/cluster_test.go index bfedbc7924e..76c3192e4df 100644 --- a/internal/envoy/v3/cluster_test.go +++ b/internal/envoy/v3/cluster_test.go @@ -299,8 +299,10 @@ func TestCluster(t *testing.T) { Upstream: service(s1, "tls"), Protocol: "tls", UpstreamValidation: &dag.PeerValidationContext{ - CACertificate: secret, - SubjectNames: []string{"foo.bar.io"}, + CACertificates: []*dag.Secret{ + secret, + }, + SubjectNames: []string{"foo.bar.io"}, }, }, want: &envoy_cluster_v3.Cluster{ @@ -314,8 +316,10 @@ func TestCluster(t *testing.T) { TransportSocket: UpstreamTLSTransportSocket( UpstreamTLSContext( &dag.PeerValidationContext{ - CACertificate: secret, - SubjectNames: []string{"foo.bar.io"}, + CACertificates: []*dag.Secret{ + secret, + }, + SubjectNames: []string{"foo.bar.io"}, }, "", nil, @@ -328,8 +332,10 @@ func TestCluster(t *testing.T) { Upstream: service(s1, "tls"), Protocol: "tls", UpstreamValidation: &dag.PeerValidationContext{ - CACertificate: secret, - SubjectNames: []string{"foo.bar.io"}, + CACertificates: []*dag.Secret{ + secret, + }, + SubjectNames: []string{"foo.bar.io"}, }, UpstreamTLS: &dag.UpstreamTLS{ MinimumProtocolVersion: "1.3", @@ -347,8 +353,10 @@ func TestCluster(t *testing.T) { TransportSocket: UpstreamTLSTransportSocket( UpstreamTLSContext( &dag.PeerValidationContext{ - CACertificate: secret, - SubjectNames: []string{"foo.bar.io"}, + CACertificates: []*dag.Secret{ + secret, + }, + SubjectNames: []string{"foo.bar.io"}, }, "", nil, @@ -935,10 +943,12 @@ func TestDNSNameCluster(t *testing.T) { Port: 443, DNSLookupFamily: "auto", UpstreamValidation: &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - Data: map[string][]byte{ - "ca.crt": []byte("ca-cert"), + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + Data: map[string][]byte{ + "ca.crt": []byte("ca-cert"), + }, }, }, }, @@ -966,10 +976,12 @@ func TestDNSNameCluster(t *testing.T) { }, }, TransportSocket: UpstreamTLSTransportSocket(UpstreamTLSContext(&dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - Data: map[string][]byte{ - "ca.crt": []byte("ca-cert"), + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + Data: map[string][]byte{ + "ca.crt": []byte("ca-cert"), + }, }, }, }, @@ -1093,14 +1105,16 @@ func TestClustername(t *testing.T) { }, LoadBalancerPolicy: "Random", UpstreamValidation: &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: []byte("somethingsecret"), + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: []byte("somethingsecret"), + }, }, }, }, diff --git a/internal/envoy/v3/listener_test.go b/internal/envoy/v3/listener_test.go index ee119c41714..82c8cdf72c1 100644 --- a/internal/envoy/v3/listener_test.go +++ b/internal/envoy/v3/listener_test.go @@ -302,14 +302,16 @@ func TestDownstreamTLSContext(t *testing.T) { } peerValidationContext := &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: ca, + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: ca, + }, }, }, }, @@ -317,14 +319,16 @@ func TestDownstreamTLSContext(t *testing.T) { // Negative test case: downstream validation should not contain subjectname. peerValidationContextWithSubjectName := &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: ca, + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: ca, + }, }, }, }, @@ -340,14 +344,16 @@ func TestDownstreamTLSContext(t *testing.T) { }, } peerValidationContextSkipClientCertValidationWithCA := &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: ca, + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: ca, + }, }, }, }, @@ -364,28 +370,32 @@ func TestDownstreamTLSContext(t *testing.T) { }, } peerValidationContextOptionalClientCertValidationWithCA := &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: ca, + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: ca, + }, }, }, }, OptionalClientCertificate: true, } peerValidationContextWithCRLCheck := &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: ca, + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: ca, + }, }, }, }, @@ -417,14 +427,16 @@ func TestDownstreamTLSContext(t *testing.T) { } peerValidationContextWithCRLCheckOnlyLeaf := &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret", - Namespace: "default", - }, - Data: map[string][]byte{ - dag.CACertificateKey: ca, + CACertificates: []*dag.Secret{ + { + Object: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "default", + }, + Data: map[string][]byte{ + dag.CACertificateKey: ca, + }, }, }, }, diff --git a/internal/featuretests/v3/backendclientauth_test.go b/internal/featuretests/v3/backendclientauth_test.go index 293f0cf7400..39f45fb979c 100644 --- a/internal/featuretests/v3/backendclientauth_test.go +++ b/internal/featuretests/v3/backendclientauth_test.go @@ -207,8 +207,8 @@ func TestBackendClientAuthenticationWithExtensionService(t *testing.T) { tlsSocket := envoy_v3.UpstreamTLSTransportSocket( envoy_v3.UpstreamTLSContext( &dag.PeerValidationContext{ - CACertificate: &dag.Secret{Object: featuretests.CASecret(t, "secret", &featuretests.CACertificate)}, - SubjectNames: []string{"subjname"}, + CACertificates: []*dag.Secret{{Object: featuretests.CASecret(t, "secret", &featuretests.CACertificate)}}, + SubjectNames: []string{"subjname"}, }, "subjname", &dag.Secret{Object: clientSecret}, diff --git a/internal/featuretests/v3/downstreamvalidation_test.go b/internal/featuretests/v3/downstreamvalidation_test.go index 1322aee9a28..3642c488b31 100644 --- a/internal/featuretests/v3/downstreamvalidation_test.go +++ b/internal/featuretests/v3/downstreamvalidation_test.go @@ -72,8 +72,10 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) { filterchaintls("example.com", serverTLSSecret, httpsFilterFor("example.com"), &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: clientCASecret, + CACertificates: []*dag.Secret{ + { + Object: clientCASecret, + }, }, }, "h2", "http/1.1", @@ -171,8 +173,10 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) { httpsFilterFor("example.com"), &dag.PeerValidationContext{ SkipClientCertValidation: true, - CACertificate: &dag.Secret{ - Object: clientCASecret, + CACertificates: []*dag.Secret{ + { + Object: clientCASecret, + }, }, }, "h2", "http/1.1", @@ -224,8 +228,10 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) { filterchaintls("example.com", serverTLSSecret, httpsFilterFor("example.com"), &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: clientCASecret, + CACertificates: []*dag.Secret{ + { + Object: clientCASecret, + }, }, CRL: &dag.Secret{ Object: crlSecret, @@ -276,8 +282,10 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) { filterchaintls("example.com", serverTLSSecret, httpsFilterFor("example.com"), &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: clientCASecret, + CACertificates: []*dag.Secret{ + { + Object: clientCASecret, + }, }, CRL: &dag.Secret{ Object: crlSecret, @@ -328,8 +336,10 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) { filterchaintls("example.com", serverTLSSecret, httpsFilterFor("example.com"), &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: clientCASecret, + CACertificates: []*dag.Secret{ + { + Object: clientCASecret, + }, }, OptionalClientCertificate: true, }, @@ -389,8 +399,10 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) { URI: true, }), &dag.PeerValidationContext{ - CACertificate: &dag.Secret{ - Object: clientCASecret, + CACertificates: []*dag.Secret{ + { + Object: clientCASecret, + }, }, }, "h2", "http/1.1", diff --git a/internal/featuretests/v3/envoy.go b/internal/featuretests/v3/envoy.go index 31aea35f55c..5e2cc4d3da3 100644 --- a/internal/featuretests/v3/envoy.go +++ b/internal/featuretests/v3/envoy.go @@ -190,16 +190,16 @@ func tlsCluster(c *envoy_cluster_v3.Cluster, ca *v1.Secret, subjectName, sni str } // Secret for validation is optional. - var s *dag.Secret + var s []*dag.Secret if ca != nil { - s = &dag.Secret{Object: ca} + s = []*dag.Secret{{Object: ca}} } c.TransportSocket = envoy_v3.UpstreamTLSTransportSocket( envoy_v3.UpstreamTLSContext( &dag.PeerValidationContext{ - CACertificate: s, - SubjectNames: []string{subjectName}, + CACertificates: s, + SubjectNames: []string{subjectName}, }, sni, secret, diff --git a/internal/featuretests/v3/upstreamtls_test.go b/internal/featuretests/v3/upstreamtls_test.go index 52367a24c5f..09a89a3343f 100644 --- a/internal/featuretests/v3/upstreamtls_test.go +++ b/internal/featuretests/v3/upstreamtls_test.go @@ -28,12 +28,16 @@ import ( envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" "github.com/projectcontour/contour/internal/featuretests" "github.com/projectcontour/contour/internal/fixture" + "github.com/projectcontour/contour/internal/gatewayapi" "github.com/projectcontour/contour/internal/ref" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" networking_v1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + gatewayapi_v1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapi_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayapi_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) func TestUpstreamTLSWithHTTPProxy(t *testing.T) { @@ -223,3 +227,118 @@ func TestUpstreamTLSWithExtensionService(t *testing.T) { ), }) } + +func TestUpstreamTLSWithHTTPRoute(t *testing.T) { + rh, c, done := setup(t, func(b *dag.Builder) { + for _, processor := range b.Processors { + if gatewayAPIProcessor, ok := processor.(*dag.GatewayAPIProcessor); ok { + gatewayAPIProcessor.UpstreamTLS = &dag.UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + } + } + } + }) + defer done() + + sec1 := featuretests.TLSSecret(t, "sec1", &featuretests.ClientCertificate) + sec2 := featuretests.CASecret(t, "sec2", &featuretests.CACertificate) + rh.OnAdd(sec1) + rh.OnAdd(sec2) + + rh.OnAdd(&gatewayapi_v1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: fixture.ObjectMeta("test-gc"), + Spec: gatewayapi_v1beta1.GatewayClassSpec{ + ControllerName: "projectcontour.io/contour", + }, + Status: gatewayapi_v1beta1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: string(gatewayapi_v1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionTrue, + }, + }, + }, + }) + + gateway := &gatewayapi_v1beta1.Gateway{ + ObjectMeta: fixture.ObjectMeta("projectcontour/contour"), + Spec: gatewayapi_v1beta1.GatewaySpec{ + Listeners: []gatewayapi_v1beta1.Listener{{ + Name: "http", + Port: 80, + Protocol: gatewayapi_v1.HTTPProtocolType, + AllowedRoutes: &gatewayapi_v1beta1.AllowedRoutes{ + Namespaces: &gatewayapi_v1beta1.RouteNamespaces{ + From: ref.To(gatewayapi_v1.NamespacesFromAll), + }, + }, + }}, + }, + } + rh.OnAdd(gateway) + + svc := fixture.NewService("backend"). + WithPorts(v1.ServicePort{Name: "http", Port: 443}) + rh.OnAdd(svc) + + rh.OnAdd(&gatewayapi_v1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "authenticated", + Namespace: "default", + }, + Spec: gatewayapi_v1beta1.HTTPRouteSpec{ + CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ + ParentRefs: []gatewayapi_v1beta1.ParentReference{ + gatewayapi.GatewayParentRef("projectcontour", "contour"), + }, + }, + Hostnames: []gatewayapi_v1beta1.Hostname{ + "test.projectcontour.io", + }, + Rules: []gatewayapi_v1beta1.HTTPRouteRule{{ + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("backend", 443, 1), + }}, + }, + }) + + rh.OnAdd(&gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "authenticated", + Namespace: "default", + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Kind: "Service", + Name: "backend", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1alpha2.LocalObjectReference{{ + Kind: "Secret", + Name: gatewayapi_v1.ObjectName(sec2.Name), + }}, + Hostname: "subjname", + }, + }, + }) + + c.Request(clusterType).Equals(&envoy_discovery_v3.DiscoveryResponse{ + Resources: resources(t, + tlsCluster( + cluster("default/backend/443/867941ed65", "default/backend/http", "default_backend_443"), + sec2, + "subjname", + "", + nil, + &dag.UpstreamTLS{ + MinimumProtocolVersion: "1.2", + MaximumProtocolVersion: "1.2", + }), + ), + TypeUrl: clusterType, + }) +} diff --git a/internal/k8s/helpers.go b/internal/k8s/helpers.go index 5356c8a2149..959b46d35d7 100644 --- a/internal/k8s/helpers.go +++ b/internal/k8s/helpers.go @@ -116,7 +116,8 @@ func IsObjectEqual(oldObj, newObj client.Object) (bool, error) { *gatewayapi_v1beta1.HTTPRoute, *gatewayapi_v1alpha2.TLSRoute, *gatewayapi_v1alpha2.GRPCRoute, - *gatewayapi_v1alpha2.TCPRoute: + *gatewayapi_v1alpha2.TCPRoute, + *gatewayapi_v1alpha2.BackendTLSPolicy: return isGenerationEqual(oldObj, newObj), nil // Slow path: compare the content of the objects. @@ -128,6 +129,10 @@ func IsObjectEqual(oldObj, newObj client.Object) (bool, error) { if newObj, ok := newObj.(*v1.Secret); ok { return reflect.DeepEqual(oldObj.Data, newObj.Data), nil } + case *v1.ConfigMap: + if newObj, ok := newObj.(*v1.ConfigMap); ok { + return reflect.DeepEqual(oldObj.Data, newObj.Data), nil + } case *v1.Service: if newObj, ok := newObj.(*v1.Service); ok { return apiequality.Semantic.DeepEqual(oldObj.Spec, newObj.Spec) && diff --git a/internal/k8s/helpers_test.go b/internal/k8s/helpers_test.go index 420aaf43a40..2f1d042c612 100644 --- a/internal/k8s/helpers_test.go +++ b/internal/k8s/helpers_test.go @@ -48,6 +48,16 @@ func TestIsObjectEqual(t *testing.T) { filename: "testdata/secret-metadata-change.yaml", equals: true, }, + { + name: "ConfigMap with content change", + filename: "testdata/configmap-content-change.yaml", + equals: false, + }, + { + name: "ConfigMap with metadata change", + filename: "testdata/configmap-metadata-change.yaml", + equals: true, + }, { name: "Service with status change", filename: "testdata/service-status-change.yaml", @@ -135,16 +145,20 @@ func TestIsEqualForResourceVersion(t *testing.T) { assert.True(t, got) } -// TestIsEqualFallback compares with ConfigMap objects, which are not supported. +// TestIsEqualFallback compares with ServiceAccount objects, which are not supported. func TestIsEqualFallback(t *testing.T) { - oldObj := &v1.ConfigMap{ + oldObj := &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "default", ResourceVersion: "123", }, - Data: map[string]string{ - "foo": "bar", + Secrets: []v1.ObjectReference{ + { + Kind: "Secret", + Name: "test", + Namespace: "default", + }, }, } diff --git a/internal/k8s/rbac.go b/internal/k8s/rbac.go index 2eccadecc89..3bd43c64d3d 100644 --- a/internal/k8s/rbac.go +++ b/internal/k8s/rbac.go @@ -19,10 +19,10 @@ package k8s // +kubebuilder:rbac:groups="projectcontour.io",resources=httpproxies;tlscertificatedelegations;extensionservices;contourconfigurations,verbs=get;list;watch // +kubebuilder:rbac:groups="projectcontour.io",resources=httpproxies/status;extensionservices/status;contourconfigurations/status,verbs=create;get;update -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;tlsroutes;grpcroutes;tcproutes;referencegrants,verbs=get;list;watch -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status;tlsroutes/status;grpcroutes/status;tcproutes/status,verbs=update +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;tlsroutes;grpcroutes;tcproutes;referencegrants;backendtlspolicies,verbs=get;list;watch +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status;tlsroutes/status;grpcroutes/status;tcproutes/status;backendtlspolicies/status,verbs=update -// +kubebuilder:rbac:groups="",resources=secrets;endpoints;services;namespaces,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=secrets;endpoints;services;namespaces;configmaps,verbs=get;list;watch // Add RBAC policy to support leader election. // +kubebuilder:rbac:groups="",resources=events,verbs=create;get;update,namespace=projectcontour diff --git a/internal/k8s/testdata/configmap-content-change.yaml b/internal/k8s/testdata/configmap-content-change.yaml new file mode 100644 index 00000000000..1f08fe3a533 --- /dev/null +++ b/internal/k8s/testdata/configmap-content-change.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +data: + file: aGVsbG8= +kind: ConfigMap +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","data":{"file":"aGVsbG8="},"kind":"ConfigMap","metadata":{"annotations":{},"creationTimestamp":null,"name":"my-configmap","namespace":"default"}} + creationTimestamp: "2023-02-09T10:43:43Z" + name: my-configmap + namespace: default + resourceVersion: "62125" + uid: b6e2da92-1b70-4c76-aac7-a2169e9b49b3 +--- +apiVersion: v1 +data: + file: d29ybGQ= +kind: ConfigMap +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","data":{"file":"d29ybGQ="},"kind":"ConfigMap","metadata":{"annotations":{},"creationTimestamp":null,"name":"my-configmap","namespace":"default"}} + creationTimestamp: "2023-02-09T10:43:43Z" + name: my-configmap + namespace: default + resourceVersion: "62159" + uid: b6e2da92-1b70-4c76-aac7-a2169e9b49b3 diff --git a/internal/k8s/testdata/configmap-metadata-change.yaml b/internal/k8s/testdata/configmap-metadata-change.yaml new file mode 100644 index 00000000000..86eee18c18a --- /dev/null +++ b/internal/k8s/testdata/configmap-metadata-change.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +data: + file: d29ybGQ= +kind: ConfigMap +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","data":{"file":"d29ybGQ="},"kind":"ConfigMap","metadata":{"annotations":{},"creationTimestamp":null,"name":"my-configmap","namespace":"default"}} + creationTimestamp: "2023-02-09T10:43:43Z" + name: my-configmap + namespace: default + resourceVersion: "62159" + uid: b6e2da92-1b70-4c76-aac7-a2169e9b49b3 +--- +apiVersion: v1 +data: + file: d29ybGQ= +kind: ConfigMap +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","data":{"file":"d29ybGQ="},"kind":"ConfigMap","metadata":{"annotations":{},"creationTimestamp":null,"name":"my-configmap","namespace":"default"}} + my-annotation: foo + creationTimestamp: "2023-02-09T10:43:43Z" + labels: + my-label: bar + name: my-configmap + namespace: default + resourceVersion: "72965" + uid: b6e2da92-1b70-4c76-aac7-a2169e9b49b3 diff --git a/internal/provisioner/objects/rbac/util/util.go b/internal/provisioner/objects/rbac/util/util.go index f93daab3727..698cb828144 100644 --- a/internal/provisioner/objects/rbac/util/util.go +++ b/internal/provisioner/objects/rbac/util/util.go @@ -43,15 +43,15 @@ func PolicyRuleFor(apiGroup string, verbs []string, resources ...string) rbacv1. func NamespacedResourcePolicyRules() []rbacv1.PolicyRule { return []rbacv1.PolicyRule{ // Core Contour-watched resources. - PolicyRuleFor(corev1.GroupName, getListWatch, "secrets", "endpoints", "services"), + PolicyRuleFor(corev1.GroupName, getListWatch, "secrets", "endpoints", "services", "configmaps"), // Discovery Contour-watched resources. PolicyRuleFor(discoveryv1.GroupName, getListWatch, "endpointslices"), // Gateway API resources. // Note, ReferenceGrant does not currently have a .status field so it's omitted from the status rule. - PolicyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gateways", "httproutes", "tlsroutes", "grpcroutes", "tcproutes", "referencegrants"), - PolicyRuleFor(gatewayv1alpha2.GroupName, update, "gateways/status", "httproutes/status", "tlsroutes/status", "grpcroutes/status", "tcproutes/status"), + PolicyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gateways", "httproutes", "tlsroutes", "grpcroutes", "tcproutes", "referencegrants", "backendtlspolicies"), + PolicyRuleFor(gatewayv1alpha2.GroupName, update, "gateways/status", "httproutes/status", "tlsroutes/status", "grpcroutes/status", "tcproutes/status", "backendtlspolicies/status"), // Ingress resources. PolicyRuleFor(networkingv1.GroupName, getListWatch, "ingresses"), diff --git a/test/e2e/fixtures.go b/test/e2e/fixtures.go index 675d0f76a07..bbab25500fd 100644 --- a/test/e2e/fixtures.go +++ b/test/e2e/fixtures.go @@ -287,7 +287,7 @@ type EchoSecure struct { // fails the test if it encounters an error. Namespace is defaulted to "default" // and name is defaulted to "ingress-conformance-echo-tls" if not provided. Returns // a cleanup function. -func (e *EchoSecure) Deploy(ns, name string) func() { +func (e *EchoSecure) Deploy(ns, name string, preApplyHook func(deployment *appsv1.Deployment, service *corev1.Service)) func() { ns = valOrDefault(ns, "default") name = valOrDefault(name, "ingress-conformance-echo-tls") @@ -388,7 +388,6 @@ func (e *EchoSecure) Deploy(ns, name string) func() { }, }, } - require.NoError(e.t, e.client.Create(context.TODO(), deployment)) service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -414,6 +413,12 @@ func (e *EchoSecure) Deploy(ns, name string) func() { Selector: map[string]string{"app.kubernetes.io/name": name}, }, } + + if preApplyHook != nil { + preApplyHook(deployment, service) + } + + require.NoError(e.t, e.client.Create(context.TODO(), deployment)) require.NoError(e.t, e.client.Create(context.TODO(), service)) return func() { diff --git a/test/e2e/framework.go b/test/e2e/framework.go index 7ab06cb85b3..0d7e41866d5 100644 --- a/test/e2e/framework.go +++ b/test/e2e/framework.go @@ -389,6 +389,12 @@ func (f *Framework) CreateTCPRouteAndWaitFor(route *gatewayapi_v1alpha2.TCPRoute return createAndWaitFor(f.t, f.Client, route, condition, f.RetryInterval, f.RetryTimeout) } +// CreateBackendTLSPolicy creates the provided BackendTLSPolicy in the Kubernetes API +// and then waits for the specified condition to be true. +func (f *Framework) CreateBackendTLSPolicyAndWaitFor(route *gatewayapi_v1alpha2.BackendTLSPolicy, condition func(*gatewayapi_v1alpha2.BackendTLSPolicy) bool) (*gatewayapi_v1alpha2.BackendTLSPolicy, bool) { + return createAndWaitFor(f.t, f.Client, route, condition, f.RetryInterval, f.RetryTimeout) +} + // CreateNamespace creates a namespace with the given name in the // Kubernetes API or fails the test if it encounters an error. func (f *Framework) CreateNamespace(name string) { diff --git a/test/e2e/gateway/backend_tls_policy_test.go b/test/e2e/gateway/backend_tls_policy_test.go new file mode 100644 index 00000000000..207c8bc5892 --- /dev/null +++ b/test/e2e/gateway/backend_tls_policy_test.go @@ -0,0 +1,188 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build e2e + +package gateway + +import ( + "context" + "encoding/json" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + . "github.com/onsi/ginkgo/v2" + "github.com/projectcontour/contour/internal/gatewayapi" + "github.com/projectcontour/contour/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + gatewayapi_v1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapi_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayapi_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func testBackendTLSPolicy(namespace string, gateway types.NamespacedName) { + Specify("Creates a BackendTLSPolicy configures an HTTPRoute to use TLS to a backend service", func() { + protocolVersion := "TLSv1.3" + t := f.T() + + // Top level issuer. + selfSignedIssuer := &certmanagerv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "selfsigned", + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + SelfSigned: &certmanagerv1.SelfSignedIssuer{}, + }, + }, + } + require.NoError(f.T(), f.Client.Create(context.TODO(), selfSignedIssuer)) + + // CA to sign backend certs with. + caCertificate := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "ca-cert", + }, + Spec: certmanagerv1.CertificateSpec{ + IsCA: true, + Usages: []certmanagerv1.KeyUsage{ + certmanagerv1.UsageSigning, + certmanagerv1.UsageCertSign, + }, + CommonName: "ca-cert", + SecretName: "ca-cert", + IssuerRef: certmanagermetav1.ObjectReference{ + Name: "selfsigned", + }, + }, + } + require.NoError(f.T(), f.Client.Create(context.TODO(), caCertificate)) + + // Issuer based on CA to generate new certs with. + basedOnCAIssuer := &certmanagerv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "ca-issuer", + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + CA: &certmanagerv1.CAIssuer{ + SecretName: "ca-cert", + }, + }, + }, + } + require.NoError(f.T(), f.Client.Create(context.TODO(), basedOnCAIssuer)) + + // Backend server cert signed by CA. + backendServerCert := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "backend-server-cert", + }, + Spec: certmanagerv1.CertificateSpec{ + Usages: []certmanagerv1.KeyUsage{ + certmanagerv1.UsageServerAuth, + }, + CommonName: "echo-secure", + DNSNames: []string{"echo-secure"}, + SecretName: "backend-server-cert", + IssuerRef: certmanagermetav1.ObjectReference{ + Name: "ca-issuer", + }, + }, + } + + require.NoError(f.T(), f.Client.Create(context.TODO(), backendServerCert)) + f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure", func(deployment *appsv1.Deployment, service *corev1.Service) { + delete(service.Annotations, "projectcontour.io/upstream-protocol.tls") + }) + + route := &gatewayapi_v1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "http-route-1", + }, + Spec: gatewayapi_v1beta1.HTTPRouteSpec{ + Hostnames: []gatewayapi_v1beta1.Hostname{"backend-tls-policy.projectcontour.io"}, + CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{ + ParentRefs: []gatewayapi_v1beta1.ParentReference{ + gatewayapi.GatewayParentRef(gateway.Namespace, gateway.Name), + }, + }, + Rules: []gatewayapi_v1beta1.HTTPRouteRule{ + { + Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"), + BackendRefs: gatewayapi.HTTPBackendRef("echo-secure", 443, 1), + }, + }, + }, + } + f.CreateHTTPRouteAndWaitFor(route, e2e.HTTPRouteAccepted) + + backendTLSPolicy := &gatewayapi_v1alpha2.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "echo-secure-backend-tls-policy", + Namespace: namespace, + }, + Spec: gatewayapi_v1alpha2.BackendTLSPolicySpec{ + TargetRef: gatewayapi_v1alpha2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gatewayapi_v1alpha2.PolicyTargetReference{ + Group: "", + Kind: "Service", + Name: "echo-secure", + }, + }, + TLS: gatewayapi_v1alpha2.BackendTLSPolicyConfig{ + CACertRefs: []gatewayapi_v1.LocalObjectReference{ + { + Group: "", + Kind: "Secret", + Name: "backend-server-cert", + }, + }, + Hostname: "echo-secure", + }, + }, + } + + f.CreateBackendTLSPolicyAndWaitFor(backendTLSPolicy, e2e.BackendTLSPolicyAccepted) + + type responseTLSDetails struct { + TLS struct { + Version string + } + } + + // Ensure http (insecure) request routes to echo-secure. + res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{ + Host: "backend-tls-policy.projectcontour.io", + Condition: e2e.HasStatusCode(200), + }) + require.NotNil(t, res) + assert.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode) + assert.Equal(t, "echo-secure", f.GetEchoResponseBody(res.Body).Service) + + // Get cert presented to backend app. + tlsInfo := new(responseTLSDetails) + require.NoError(f.T(), json.Unmarshal(res.Body, tlsInfo)) + assert.Equal(f.T(), tlsInfo.TLS.Version, protocolVersion) + }) +} diff --git a/test/e2e/gateway/gateway_test.go b/test/e2e/gateway/gateway_test.go index 7d55f4e78f0..d43dae2c6a5 100644 --- a/test/e2e/gateway/gateway_test.go +++ b/test/e2e/gateway/gateway_test.go @@ -209,6 +209,8 @@ var _ = Describe("Gateway API", func() { f.NamespacedTest("gateway-host-rewrite", testWithHTTPGateway(testHostRewrite)) f.NamespacedTest("gateway-request-redirect-rule", testWithHTTPGateway(testRequestRedirectRule)) + + f.NamespacedTest("gateway-backend-tls-policy", testWithHTTPGateway(testBackendTLSPolicy)) }) Describe("Gateway with one HTTP listener and one HTTPS listener", func() { diff --git a/test/e2e/gatewayapi_predicates.go b/test/e2e/gatewayapi_predicates.go index 51e9d660250..aa1946ad860 100644 --- a/test/e2e/gatewayapi_predicates.go +++ b/test/e2e/gatewayapi_predicates.go @@ -145,6 +145,14 @@ func TCPRouteAccepted(route *gatewayapi_v1alpha2.TCPRoute) bool { return false } +// BackendTLSPolicyAccepted returns true if the backend TLS policy has a .status.conditions +// entry of "Accepted: true". +func BackendTLSPolicyAccepted(btp *gatewayapi_v1alpha2.BackendTLSPolicy) bool { + // TODO (christianang): Right now this always returns true if a backendtlspolicy is + // provided since status conditions are not implemented yet for BackendTLSPolicy + return btp != nil +} + func conditionExists(conditions []metav1.Condition, conditionType string, conditionStatus metav1.ConditionStatus) bool { for _, cond := range conditions { if cond.Type == conditionType && cond.Status == conditionStatus { diff --git a/test/e2e/httpproxy/backend_tls_protocol_version_test.go b/test/e2e/httpproxy/backend_tls_protocol_version_test.go index d3ae8a39221..9d47fb227d4 100644 --- a/test/e2e/httpproxy/backend_tls_protocol_version_test.go +++ b/test/e2e/httpproxy/backend_tls_protocol_version_test.go @@ -50,7 +50,7 @@ func testBackendTLSProtocolVersion(namespace, protocolVersion string) { }, } require.NoError(f.T(), f.Client.Create(context.TODO(), backendServerCert)) - f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure") + f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure", nil) p := &contourv1.HTTPProxy{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/e2e/httpproxy/backend_tls_test.go b/test/e2e/httpproxy/backend_tls_test.go index d1c2be67860..d69dd450624 100644 --- a/test/e2e/httpproxy/backend_tls_test.go +++ b/test/e2e/httpproxy/backend_tls_test.go @@ -53,7 +53,7 @@ func testBackendTLS(namespace string) { }, } require.NoError(f.T(), f.Client.Create(context.TODO(), backendServerCert)) - f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure") + f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure", nil) p := &contourv1.HTTPProxy{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/e2e/httpproxy/external_name_test.go b/test/e2e/httpproxy/external_name_test.go index 15ebcd3ff39..022a2a84853 100644 --- a/test/e2e/httpproxy/external_name_test.go +++ b/test/e2e/httpproxy/external_name_test.go @@ -101,7 +101,7 @@ func testExternalNameServiceTLS(namespace string) { f.Certs.CreateSelfSignedCert(namespace, "backend-server-cert", "backend-server-cert", "echo") - f.Fixtures.EchoSecure.Deploy(namespace, "echo-tls") + f.Fixtures.EchoSecure.Deploy(namespace, "echo-tls", nil) externalNameService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/e2e/ingress/backend_tls_test.go b/test/e2e/ingress/backend_tls_test.go index 4d100f8483e..0a862a58588 100644 --- a/test/e2e/ingress/backend_tls_test.go +++ b/test/e2e/ingress/backend_tls_test.go @@ -53,7 +53,7 @@ func testBackendTLS(namespace string) { }, } require.NoError(f.T(), f.Client.Create(context.TODO(), backendServerCert)) - f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure") + f.Fixtures.EchoSecure.Deploy(namespace, "echo-secure", nil) i := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{