From 4f708f891780fe0df4cc329aca76d0aa1b2b9163 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 1 Oct 2022 11:14:09 +0530 Subject: [PATCH 01/30] TLS Passthrough support This commit adds a tlsroute controller which is further used to configure tls passthrough in envoy. Signed-off-by: Shubham Chauhan --- internal/cmd/server.go | 1 + internal/envoygateway/scheme.go | 6 +- internal/gatewayapi/contexts.go | 244 +++++++- internal/gatewayapi/helpers.go | 11 +- internal/gatewayapi/helpers_v1alpha2.go | 126 +++++ internal/gatewayapi/runner/runner.go | 3 + ...ener-with-valid-tls-configuration.out.yaml | 31 +- ...to-gateway-with-wildcard-hostname.out.yaml | 2 +- internal/gatewayapi/translator.go | 523 +++++++++++++----- internal/ir/xds.go | 18 +- internal/ir/xds_test.go | 4 +- internal/message/types.go | 13 + internal/provider/kubernetes/httproute.go | 1 + internal/provider/kubernetes/kubernetes.go | 7 + internal/provider/kubernetes/tlsroute.go | 231 ++++++++ internal/xds/translator/cluster.go | 6 +- internal/xds/translator/listener.go | 50 +- internal/xds/translator/route.go | 8 +- internal/xds/translator/translator.go | 15 +- 19 files changed, 1110 insertions(+), 190 deletions(-) create mode 100644 internal/gatewayapi/helpers_v1alpha2.go create mode 100644 internal/provider/kubernetes/tlsroute.go diff --git a/internal/cmd/server.go b/internal/cmd/server.go index 3a7bf62511..eff0454846 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -153,6 +153,7 @@ func setupRunners(cfg *config.Server) error { pResources.Namespaces.Close() pResources.GatewayStatuses.Close() pResources.HTTPRouteStatuses.Close() + pResources.TLSRoutes.Close() xdsIR.Close() infraIR.Close() xds.Close() diff --git a/internal/envoygateway/scheme.go b/internal/envoygateway/scheme.go index e4971c7c7e..31fc950c4c 100644 --- a/internal/envoygateway/scheme.go +++ b/internal/envoygateway/scheme.go @@ -3,7 +3,8 @@ package envoygateway import ( "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/envoyproxy/gateway/api/config/v1alpha1" ) @@ -28,6 +29,9 @@ func init() { if err := gwapiv1b1.AddToScheme(scheme); err != nil { panic(err) } + if err := gwapiv1a2.AddToScheme(scheme); err != nil { + panic(err) + } } // GetScheme returns a scheme with types supported by the Kubernetes provider. diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index 23523401c7..048cc01bcb 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -7,6 +7,8 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/gateway-api/apis/v1alpha2" "sigs.k8s.io/gateway-api/apis/v1beta1" egv1alpha1 "github.com/envoyproxy/gateway/api/config/v1alpha1" @@ -20,6 +22,41 @@ type GatewayContext struct { listeners map[v1beta1.SectionName]*ListenerContext } +func (g *GatewayContext) SetCondition(conditionType v1beta1.GatewayConditionType, status metav1.ConditionStatus, reason v1beta1.GatewayConditionReason, message string) { + cond := metav1.Condition{ + Type: string(conditionType), + Status: status, + Reason: string(reason), + Message: message, + ObservedGeneration: g.Generation, + LastTransitionTime: metav1.NewTime(time.Now()), + } + + idx := -1 + for i, existing := range g.Status.Conditions { + if existing.Type == cond.Type { + // return early if the condition is unchanged + if existing.Status == cond.Status && + existing.Reason == cond.Reason && + existing.Message == cond.Message { + return + } + idx = i + break + } + } + + if idx > -1 { + g.Status.Conditions[idx] = cond + } else { + g.Status.Conditions = append(g.Status.Conditions, cond) + } +} + +// GetListenerContext returns the ListenerContext with its name matching +// listenerName from GatewayContext. If the listener exists in the Gateway Spec +// but NOT yet in the GatewayContext, this creates a new ListenerContext for the +// listener and attaches it to the GatewayContext. func (g *GatewayContext) GetListenerContext(listenerName v1beta1.SectionName) *ListenerContext { if g.listeners == nil { g.listeners = make(map[v1beta1.SectionName]*ListenerContext) @@ -70,7 +107,12 @@ type ListenerContext struct { gateway *v1beta1.Gateway listenerStatusIdx int namespaceSelector labels.Selector - tlsSecret *v1.Secret + tls listenerContextTLSConfig +} + +type listenerContextTLSConfig struct { + mode v1beta1.TLSModeType + secret *v1.Secret } func (l *ListenerContext) SetCondition(conditionType v1beta1.ListenerConditionType, status metav1.ConditionStatus, reason v1beta1.ListenerConditionReason, message string) { @@ -163,8 +205,32 @@ func (l *ListenerContext) GetConditions() []metav1.Condition { return l.gateway.Status.Listeners[l.listenerStatusIdx].Conditions } -func (l *ListenerContext) SetTLSSecret(tlsSecret *v1.Secret) { - l.tlsSecret = tlsSecret +func (l *ListenerContext) SetTLSConfig(mode v1beta1.TLSModeType, secret *v1.Secret) { + l.tls = listenerContextTLSConfig{mode, secret} +} + +// RouteContext represents a generic Route object (HTTPRoute, TLSRoute, etc.) +// that can reference Gateway objects. +type RouteContext interface { + client.Object + + // GetRouteType returns the Kind of the Route object, HTTPRoute, + // TLSRoute, TCPRoute, UDPRoute etc. + GetRouteType() string + + // TODO: [v1alpha2-v1beta1] This should not be required once all Route + // objects being implemented are of type v1beta1. + // GetHostnames returns the hosts targeted by the Route object. + GetHostnames() []string + + // TODO: [v1alpha2-v1beta1] This should not be required once all Route + // objects being implemented are of type v1beta1. + // GetParentReferences returns the ParentReference of the Route object. + GetParentReferences() []v1beta1.ParentReference + + // GetRouteParentContext returns RouteParentContext by using the Route + // objects' ParentReference. + GetRouteParentContext(forParentRef v1beta1.ParentReference) *RouteParentContext } // HTTPRouteContext wraps an HTTPRoute and provides helper methods for @@ -175,6 +241,22 @@ type HTTPRouteContext struct { parentRefs map[v1beta1.ParentReference]*RouteParentContext } +func (h *HTTPRouteContext) GetRouteType() string { + return KindHTTPRoute +} + +func (h *HTTPRouteContext) GetHostnames() []string { + hostnames := make([]string, len(h.Spec.Hostnames)) + for idx, s := range h.Spec.Hostnames { + hostnames[idx] = string(s) + } + return hostnames +} + +func (h *HTTPRouteContext) GetParentReferences() []v1beta1.ParentReference { + return h.Spec.ParentRefs +} + func (h *HTTPRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentReference) *RouteParentContext { if h.parentRefs == nil { h.parentRefs = make(map[v1beta1.ParentReference]*RouteParentContext) @@ -215,20 +297,96 @@ func (h *HTTPRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefe ctx := &RouteParentContext{ ParentReference: parentRef, - route: h.HTTPRoute, + httpRoute: h.HTTPRoute, routeParentStatusIdx: routeParentStatusIdx, } h.parentRefs[forParentRef] = ctx return ctx } +// TLSRouteContext wraps a TLSRoute and provides helper methods for +// accessing the route's parents. +type TLSRouteContext struct { + *v1alpha2.TLSRoute + + parentRefs map[v1beta1.ParentReference]*RouteParentContext +} + +func (t *TLSRouteContext) GetRouteType() string { + return KindTLSRoute +} + +func (t *TLSRouteContext) GetHostnames() []string { + hostnames := make([]string, len(t.Spec.Hostnames)) + for idx, s := range t.Spec.Hostnames { + hostnames[idx] = string(s) + } + return hostnames +} + +func (t *TLSRouteContext) GetParentReferences() []v1beta1.ParentReference { + parentReferences := make([]v1beta1.ParentReference, len(t.Spec.ParentRefs)) + for idx, p := range t.Spec.ParentRefs { + parentReferences[idx] = UpgradeParentReference(p) + } + return parentReferences +} + +func (t *TLSRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentReference) *RouteParentContext { + if t.parentRefs == nil { + t.parentRefs = make(map[v1beta1.ParentReference]*RouteParentContext) + } + + if ctx := t.parentRefs[forParentRef]; ctx != nil { + return ctx + } + + var parentRef *v1beta1.ParentReference + for i, p := range t.Spec.ParentRefs { + p := UpgradeParentReference(p) + if *p.Namespace == *forParentRef.Namespace && p.Name == forParentRef.Name { + upgraded := UpgradeParentReference(t.Spec.ParentRefs[i]) + parentRef = &upgraded + break + } + } + if parentRef == nil { + panic("parentRef not found") + } + + routeParentStatusIdx := -1 + for i := range t.Status.Parents { + if UpgradeParentReference(t.Status.Parents[i].ParentRef) == forParentRef { + routeParentStatusIdx = i + break + } + } + if routeParentStatusIdx == -1 { + t.Status.Parents = append(t.Status.Parents, v1alpha2.RouteParentStatus{ParentRef: DowngradeParentReference(forParentRef)}) + routeParentStatusIdx = len(t.Status.Parents) - 1 + } + + ctx := &RouteParentContext{ + ParentReference: parentRef, + + tlsRoute: t.TLSRoute, + routeParentStatusIdx: routeParentStatusIdx, + } + t.parentRefs[forParentRef] = ctx + return ctx +} + // RouteParentContext wraps a ParentReference and provides helper methods for // setting conditions and other status information on the associated -// HTTPRoute, etc. +// HTTPRoute, TLSRoute etc. type RouteParentContext struct { *v1beta1.ParentReference - route *v1beta1.HTTPRoute + // TODO: [v1alpha2-v1beta1] This can probably be replaced with + // a single field pointing to *v1beta1.RouteStatus. + httpRoute *v1beta1.HTTPRoute + tlsRoute *v1alpha2.TLSRoute + routeParentStatusIdx int listeners []*ListenerContext } @@ -237,43 +395,77 @@ func (r *RouteParentContext) SetListeners(listeners ...*ListenerContext) { r.listeners = append(r.listeners, listeners...) } -func (r *RouteParentContext) SetCondition(conditionType v1beta1.RouteConditionType, status metav1.ConditionStatus, reason v1beta1.RouteConditionReason, message string) { +func (r *RouteParentContext) SetCondition(route RouteContext, conditionType v1beta1.RouteConditionType, status metav1.ConditionStatus, reason v1beta1.RouteConditionReason, message string) { cond := metav1.Condition{ Type: string(conditionType), Status: status, Reason: string(reason), Message: message, - ObservedGeneration: r.route.Generation, + ObservedGeneration: route.GetGeneration(), LastTransitionTime: metav1.NewTime(time.Now()), } idx := -1 - for i, existing := range r.route.Status.Parents[r.routeParentStatusIdx].Conditions { - if existing.Type == cond.Type { - // return early if the condition is unchanged - if existing.Status == cond.Status && - existing.Reason == cond.Reason && - existing.Message == cond.Message { - return + switch route.GetRouteType() { + case KindHTTPRoute: + for i, existing := range r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions { + if existing.Type == cond.Type { + // return early if the condition is unchanged + if existing.Status == cond.Status && + existing.Reason == cond.Reason && + existing.Message == cond.Message { + return + } + idx = i + break } - idx = i - break } - } - if idx > -1 { - r.route.Status.Parents[r.routeParentStatusIdx].Conditions[idx] = cond - } else { - r.route.Status.Parents[r.routeParentStatusIdx].Conditions = append(r.route.Status.Parents[r.routeParentStatusIdx].Conditions, cond) + if idx > -1 { + r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions[idx] = cond + } else { + r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions = append(r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions, cond) + } + case KindTLSRoute: + for i, existing := range r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions { + if existing.Type == cond.Type { + // return early if the condition is unchanged + if existing.Status == cond.Status && + existing.Reason == cond.Reason && + existing.Message == cond.Message { + return + } + idx = i + break + } + } + + if idx > -1 { + r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions[idx] = cond + } else { + r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions = append(r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions, cond) + } } } -func (r *RouteParentContext) ResetConditions() { - r.route.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) +func (r *RouteParentContext) ResetConditions(route RouteContext) { + switch route.GetRouteType() { + case KindHTTPRoute: + r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) + case KindTLSRoute: + r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) + } } -func (r *RouteParentContext) IsAccepted() bool { - for _, cond := range r.route.Status.Parents[r.routeParentStatusIdx].Conditions { +func (r *RouteParentContext) IsAccepted(route RouteContext) bool { + var conditions []metav1.Condition + switch route.GetRouteType() { + case KindHTTPRoute: + conditions = r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions + case KindTLSRoute: + conditions = r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions + } + for _, cond := range conditions { if cond.Type == string(v1beta1.RouteConditionAccepted) && cond.Status == metav1.ConditionTrue { return true } diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index f4313c93b2..36220fbbb8 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -27,6 +27,11 @@ func FromNamespacesPtr(fromNamespaces v1beta1.FromNamespaces) *v1beta1.FromNames return &fromNamespaces } +func SectionNamePtr(name string) *v1beta1.SectionName { + sectionName := v1beta1.SectionName(name) + return §ionName +} + func StringPtr(val string) *string { return &val } @@ -138,9 +143,9 @@ func HasReadyListener(listeners []*ListenerContext) bool { return false } -// ComputeHosts returns a list of the intersecting hostnames between the route +// computeHosts returns a list of the intersecting hostnames between the route // and the listener. -func ComputeHosts(routeHostnames []v1beta1.Hostname, listenerHostname *v1beta1.Hostname) []string { +func computeHosts(routeHostnames []string, listenerHostname *v1beta1.Hostname) []string { var listenerHostnameVal string if listenerHostname != nil { listenerHostnameVal = string(*listenerHostname) @@ -159,7 +164,7 @@ func ComputeHosts(routeHostnames []v1beta1.Hostname, listenerHostname *v1beta1.H var hostnames []string for i := range routeHostnames { - routeHostname := string(routeHostnames[i]) + routeHostname := routeHostnames[i] // TODO ensure routeHostname is a valid hostname diff --git a/internal/gatewayapi/helpers_v1alpha2.go b/internal/gatewayapi/helpers_v1alpha2.go new file mode 100644 index 0000000000..1f21260e8f --- /dev/null +++ b/internal/gatewayapi/helpers_v1alpha2.go @@ -0,0 +1,126 @@ +package gatewayapi + +import ( + "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// TODO: [v1alpha2-v1beta1] +// This file can be removed once TLSRoute graduates to v1beta1. + +func GroupPtrV1Alpha2(group string) *v1alpha2.Group { + gwGroup := v1alpha2.Group(group) + return &gwGroup +} + +func KindPtrV1Alpha2(kind string) *v1alpha2.Kind { + gwKind := v1alpha2.Kind(kind) + return &gwKind +} + +func NamespacePtrV1Alpha2(namespace string) *v1alpha2.Namespace { + gwNamespace := v1alpha2.Namespace(namespace) + return &gwNamespace +} + +func SectionNamePtrV1Alpha2(sectionName string) *v1alpha2.SectionName { + gwSectionName := v1alpha2.SectionName(sectionName) + return &gwSectionName +} + +func PortNumPtrV1Alpha2(port int) *v1alpha2.PortNumber { + pn := v1alpha2.PortNumber(port) + return &pn +} + +// UpgradeParentReference converts v1alpha2.ParentReference to v1beta1.ParentReference +func UpgradeParentReference(old v1alpha2.ParentReference) v1beta1.ParentReference { + upgraded := v1beta1.ParentReference{} + + if old.Group != nil { + upgraded.Group = GroupPtr(string(*old.Group)) + } + + if old.Kind != nil { + upgraded.Kind = KindPtr(string(*old.Kind)) + } + + if old.Namespace != nil { + upgraded.Namespace = NamespacePtr(string(*old.Namespace)) + } + + upgraded.Name = v1beta1.ObjectName(old.Name) + + if old.SectionName != nil { + upgraded.SectionName = SectionNamePtr(string(*old.SectionName)) + } + + if old.Port != nil { + upgraded.Port = PortNumPtr(int32(*old.Port)) + } + + return upgraded +} + +func DowngradeParentReference(old v1beta1.ParentReference) v1alpha2.ParentReference { + downgraded := v1alpha2.ParentReference{} + + if old.Group != nil { + downgraded.Group = GroupPtrV1Alpha2(string(*old.Group)) + } + + if old.Kind != nil { + downgraded.Kind = KindPtrV1Alpha2(string(*old.Kind)) + } + + if old.Namespace != nil { + downgraded.Namespace = NamespacePtrV1Alpha2(string(*old.Namespace)) + } + + downgraded.Name = v1alpha2.ObjectName(old.Name) + + if old.SectionName != nil { + downgraded.SectionName = SectionNamePtrV1Alpha2(string(*old.SectionName)) + } + + if old.Port != nil { + downgraded.Port = PortNumPtrV1Alpha2(int(*old.Port)) + } + + return downgraded +} + +func UpgradeRouteParentStatuses(routeParentStatuses []v1alpha2.RouteParentStatus) []v1beta1.RouteParentStatus { + var res []v1beta1.RouteParentStatus + + for _, rps := range routeParentStatuses { + res = append(res, v1beta1.RouteParentStatus{ + ParentRef: UpgradeParentReference(rps.ParentRef), + ControllerName: v1beta1.GatewayController(rps.ControllerName), + Conditions: rps.Conditions, + }) + } + + return res +} + +func DowngradeRouteParentStatuses(routeParentStatuses []v1beta1.RouteParentStatus) []v1alpha2.RouteParentStatus { + var res []v1alpha2.RouteParentStatus + + for _, rps := range routeParentStatuses { + res = append(res, v1alpha2.RouteParentStatus{ + ParentRef: DowngradeParentReference(rps.ParentRef), + ControllerName: v1alpha2.GatewayController(rps.ControllerName), + Conditions: rps.Conditions, + }) + } + + return res +} + +func NamespaceDerefOrAlpha(namespace *v1alpha2.Namespace, defaultNamespace string) string { + if namespace != nil && *namespace != "" { + return string(*namespace) + } + return defaultNamespace +} diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 05c5929a7a..95a9dcaf50 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -44,6 +44,7 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { gatewayClassesCh := r.ProviderResources.GatewayClasses.Subscribe(ctx) gatewaysCh := r.ProviderResources.Gateways.Subscribe(ctx) httpRoutesCh := r.ProviderResources.HTTPRoutes.Subscribe(ctx) + tlsRoutesCh := r.ProviderResources.TLSRoutes.Subscribe(ctx) servicesCh := r.ProviderResources.Services.Subscribe(ctx) namespacesCh := r.ProviderResources.Namespaces.Subscribe(ctx) @@ -54,6 +55,7 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { case <-gatewayClassesCh: case <-gatewaysCh: case <-httpRoutesCh: + case <-tlsRoutesCh: case <-servicesCh: case <-namespacesCh: } @@ -61,6 +63,7 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { // Load all resources required for translation in.Gateways = r.ProviderResources.GetGateways() in.HTTPRoutes = r.ProviderResources.GetHTTPRoutes() + in.TLSRoutes = r.ProviderResources.GetTLSRoutes() in.Services = r.ProviderResources.GetServices() in.Namespaces = r.ProviderResources.GetNamespaces() gatewayClasses := r.ProviderResources.GetGatewayClasses() diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml index c13cd35b07..ba0e15761d 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml @@ -61,21 +61,22 @@ xdsIR: envoy-gateway-gateway-1: http: - name: envoy-gateway-gateway-1-tls - address: 0.0.0.0 - port: 10443 - hostnames: - - "*" - tls: - serverCertificate: Zm9vCg== - privateKey: YmFyCg== - routes: - - name: default-httproute-1-rule-0-match-0-* - pathMatch: - prefix: "/" - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + address: 0.0.0.0 + port: 10443 + hostnames: + - "*" + tls: + tlsMode: Terminate + serverCertificate: Zm9vCg== + privateKey: YmFyCg== + routes: + - name: default-httproute-1-rule-0-match-0-* + pathMatch: + prefix: "/" + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: envoy-gateway-gateway-1: proxy: diff --git a/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml b/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml index 3bb183f147..d27e79946e 100644 --- a/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml @@ -55,7 +55,7 @@ httpRoutes: - type: Accepted status: "False" reason: NoMatchingListenerHostname - message: There were no hostname intersections between the HTTPRoute and this parent ref's Listener(s). + message: There were no hostname intersections between the Route and this parent ref's Listener(s). xdsIR: envoy-gateway-gateway-1: http: diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index edf0bd3dc5..ea3bc79990 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -19,6 +19,7 @@ import ( const ( KindGateway = "Gateway" KindHTTPRoute = "HTTPRoute" + KindTLSRoute = "TLSRoute" KindService = "Service" KindSecret = "Secret" @@ -45,6 +46,7 @@ type InfraIRMap map[string]*ir.Infra type Resources struct { Gateways []*v1beta1.Gateway HTTPRoutes []*v1beta1.HTTPRoute + TLSRoutes []*v1alpha2.TLSRoute ReferenceGrants []*v1alpha2.ReferenceGrant Namespaces []*v1.Namespace Services []*v1.Service @@ -90,11 +92,14 @@ type Translator struct { type TranslateResult struct { Gateways []*v1beta1.Gateway HTTPRoutes []*v1beta1.HTTPRoute + TLSRoutes []*v1alpha2.TLSRoute XdsIR XdsIRMap InfraIR InfraIRMap } -func newTranslateResult(gateways []*GatewayContext, httpRoutes []*HTTPRouteContext, xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { +func newTranslateResult(gateways []*GatewayContext, + httpRoutes []*HTTPRouteContext, tlsRoutes []*TLSRouteContext, + xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { translateResult := &TranslateResult{ XdsIR: xdsIR, InfraIR: infraIR, @@ -106,6 +111,9 @@ func newTranslateResult(gateways []*GatewayContext, httpRoutes []*HTTPRouteConte for _, httpRoute := range httpRoutes { translateResult.HTTPRoutes = append(translateResult.HTTPRoutes, httpRoute.HTTPRoute) } + for _, tlsRoute := range tlsRoutes { + translateResult.TLSRoutes = append(translateResult.TLSRoutes, tlsRoute.TLSRoute) + } return translateResult } @@ -123,10 +131,13 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { // Process all relevant HTTPRoutes. httpRoutes := t.ProcessHTTPRoutes(resources.HTTPRoutes, gateways, resources, xdsIR) + // Process all relevant TLSRoutes. + tlsRoutes := t.ProcessTLSRoutes(resources.TLSRoutes, gateways, resources, xdsIR) + // Sort xdsIR based on the Gateway API spec sortXdsIRMap(xdsIR) - return newTranslateResult(gateways, httpRoutes, xdsIR, infraIR) + return newTranslateResult(gateways, httpRoutes, tlsRoutes, xdsIR, infraIR) } func (t *Translator) GetRelevantGateways(gateways []*v1beta1.Gateway) []*GatewayContext { @@ -246,6 +257,33 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap for _, listener := range gateway.listeners { // Process protocol & supported kinds switch listener.Protocol { + case v1beta1.TLSProtocolType: + if listener.AllowedRoutes == nil || len(listener.AllowedRoutes.Kinds) == 0 { + listener.SetSupportedKinds(v1beta1.RouteGroupKind{Group: GroupPtr(v1beta1.GroupName), Kind: KindTLSRoute}) + } else { + for _, kind := range listener.AllowedRoutes.Kinds { + if kind.Group != nil && string(*kind.Group) != v1beta1.GroupName { + listener.SetCondition( + v1beta1.ListenerConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.ListenerReasonInvalidRouteKinds, + fmt.Sprintf("Group is not supported, group must be %s", v1beta1.GroupName), + ) + continue + } + + if kind.Kind != KindTLSRoute { + listener.SetCondition( + v1beta1.ListenerConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.ListenerReasonInvalidRouteKinds, + fmt.Sprintf("Kind is not supported, kind must be %s", KindTLSRoute), + ) + continue + } + listener.SetSupportedKinds(kind) + } + } case v1beta1.HTTPProtocolType, v1beta1.HTTPSProtocolType: if listener.AllowedRoutes == nil || len(listener.AllowedRoutes.Kinds) == 0 { listener.SetSupportedKinds(v1beta1.RouteGroupKind{Group: GroupPtr(v1beta1.GroupName), Kind: KindHTTPRoute}) @@ -258,6 +296,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap v1beta1.ListenerReasonInvalidRouteKinds, fmt.Sprintf("Group is not supported, group must be %s", v1beta1.GroupName), ) + continue } if kind.Kind != KindHTTPRoute { @@ -267,7 +306,9 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap v1beta1.ListenerReasonInvalidRouteKinds, fmt.Sprintf("Kind is not supported, kind must be %s", KindHTTPRoute), ) + continue } + listener.SetSupportedKinds(kind) } } default: @@ -431,7 +472,39 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap break } - listener.SetTLSSecret(secret) + listener.SetTLSConfig(v1beta1.TLSModeTerminate, secret) + case v1beta1.TLSProtocolType: + if listener.TLS == nil { + listener.SetCondition( + v1beta1.ListenerConditionReady, + metav1.ConditionFalse, + v1beta1.ListenerReasonInvalid, + fmt.Sprintf("Listener must have TLS set when protocol is %s.", listener.Protocol), + ) + break + } + + if listener.TLS.Mode != nil && *listener.TLS.Mode != v1beta1.TLSModePassthrough { + listener.SetCondition( + v1beta1.ListenerConditionReady, + metav1.ConditionFalse, + "UnsupportedTLSMode", + fmt.Sprintf("TLS %s mode is not supported, TLS mode must be Passthrough.", *listener.TLS.Mode), + ) + break + } + + if len(listener.TLS.CertificateRefs) > 0 { + listener.SetCondition( + v1beta1.ListenerConditionReady, + metav1.ConditionFalse, + v1beta1.ListenerReasonInvalid, + "Listener must not have TLS certificate refs set for TLS mode Passthrough", + ) + break + } + + listener.SetTLSConfig(v1beta1.TLSModePassthrough, nil) } lConditions := listener.GetConditions() @@ -466,7 +539,9 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap Name: irListenerName(listener), Address: "0.0.0.0", Port: uint32(containerPort), - TLS: irTLSConfig(listener.tlsSecret), + } + if listener.TLS != nil { + irListener.TLS = irTLSConfig(listener.tls.mode, listener.tls.secret) } if listener.Hostname != nil { irListener.Hostnames = append(irListener.Hostnames, string(*listener.Hostname)) @@ -525,7 +600,7 @@ func buildRuleRouteDest(backendRef v1beta1.HTTPBackendRef, } if backendRef.Group != nil && *backendRef.Group != "" { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonInvalidKind, @@ -535,7 +610,7 @@ func buildRuleRouteDest(backendRef v1beta1.HTTPBackendRef, } if backendRef.Kind != nil && *backendRef.Kind != KindService { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonInvalidKind, @@ -559,7 +634,7 @@ func buildRuleRouteDest(backendRef v1beta1.HTTPBackendRef, }, resources.ReferenceGrants, ) { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonRefNotPermitted, @@ -570,7 +645,7 @@ func buildRuleRouteDest(backendRef v1beta1.HTTPBackendRef, } if backendRef.Port == nil { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, "PortNotSpecified", @@ -581,7 +656,7 @@ func buildRuleRouteDest(backendRef v1beta1.HTTPBackendRef, service := resources.GetService(NamespaceDerefOr(backendRef.Namespace, httpRoute.Namespace), string(backendRef.Name)) if service == nil { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonBackendNotFound, @@ -599,7 +674,7 @@ func buildRuleRouteDest(backendRef v1beta1.HTTPBackendRef, } if !portFound { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, "PortNotFound", @@ -628,42 +703,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways // Find out if this route attaches to one of our Gateway's listeners, // and if so, get the list of listeners that allow it to attach for each // parentRef. - var relevantRoute bool - for _, parentRef := range httpRoute.Spec.ParentRefs { - isRelevantParentRef, selectedListeners := GetReferencedListeners(parentRef, gateways) - - // Parent ref is not to a Gateway that we control: skip it - if !isRelevantParentRef { - continue - } - relevantRoute = true - - parentRefCtx := httpRoute.GetRouteParentContext(parentRef) - // Reset conditions since they will be recomputed during translation - parentRefCtx.ResetConditions() - - if !HasReadyListener(selectedListeners) { - parentRefCtx.SetCondition(v1beta1.RouteConditionAccepted, metav1.ConditionFalse, "NoReadyListeners", "There are no ready listeners for this parent ref") - continue - } - - var allowedListeners []*ListenerContext - for _, listener := range selectedListeners { - if listener.AllowsKind(v1beta1.RouteGroupKind{Group: GroupPtr(v1beta1.GroupName), Kind: KindHTTPRoute}) && listener.AllowsNamespace(resources.GetNamespace(httpRoute.Namespace)) { - allowedListeners = append(allowedListeners, listener) - } - } - - if len(allowedListeners) == 0 { - parentRefCtx.SetCondition(v1beta1.RouteConditionAccepted, metav1.ConditionFalse, v1beta1.RouteReasonNotAllowedByListeners, "No listeners included by this parent ref allowed this attachment.") - continue - } - - parentRefCtx.SetListeners(allowedListeners...) - - parentRefCtx.SetCondition(v1beta1.RouteConditionAccepted, metav1.ConditionTrue, v1beta1.RouteReasonAccepted, "Route is accepted") - } - + relevantRoute := processAllowedListenersForParentRefs(httpRoute, gateways, resources) if !relevantRoute { continue } @@ -672,7 +712,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways for _, parentRef := range httpRoute.parentRefs { // Skip parent refs that did not accept the route - if !parentRef.IsAccepted() { + if !parentRef.IsAccepted(httpRoute) { continue } @@ -700,7 +740,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways case v1beta1.HTTPRouteFilterRequestRedirect: // Can't have two redirects for the same route if redirectResponse != nil { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -722,7 +762,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways redir.Scheme = redirect.Scheme } else { errMsg := fmt.Sprintf("Scheme: %s is unsupported, only 'https' and 'http' are supported", *redirect.Scheme) - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -734,7 +774,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways if redirect.Hostname != nil { if err := isValidHostname(string(*redirect.Hostname)); err != nil { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -763,7 +803,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } default: errMsg := fmt.Sprintf("Redirect path type: %s is invalid, only \"ReplaceFullPath\" and \"ReplacePrefixMatch\" are supported", redirect.Path.Type) - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -780,7 +820,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways redir.StatusCode = &redirectCode } else { errMsg := fmt.Sprintf("Status code %d is invalid, only 302 and 301 are supported", redirectCode) - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -812,7 +852,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways for _, addHeader := range headersToAdd { emptyFilterConfig = false if addHeader.Name == "" { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -823,7 +863,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.Contains(string(addHeader.Name), "/") || strings.Contains(string(addHeader.Name), ":") { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -842,7 +882,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } if !canAddHeader { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -869,7 +909,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways for _, setHeader := range headersToSet { if setHeader.Name == "" { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -879,7 +919,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.Contains(string(setHeader.Name), "/") || strings.Contains(string(setHeader.Name), ":") { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -898,7 +938,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } } if !canAddHeader { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -925,7 +965,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } for _, removedHeader := range headersToRemove { if removedHeader == "" { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -942,7 +982,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } } if !canRemHeader { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -958,7 +998,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways // Update the status if the filter failed to configure any valid headers to add/remove if len(addRequestHeaders) == 0 && len(removeRequestHeaders) == 0 && !emptyFilterConfig { - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -969,7 +1009,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways // "If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. // Instead, requests that would have been processed by that filter MUST receive a HTTP error response." errMsg := fmt.Sprintf("Unknown custom filter type: %s", filter.Type) - parentRef.SetCondition( + parentRef.SetCondition(httpRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonUnsupportedValue, @@ -1028,7 +1068,6 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } for _, backendRef := range rule.BackendRefs { - destination, backendWeight := buildRuleRouteDest(backendRef, parentRef, httpRoute, resources) for _, route := range ruleRoutes { // If the route already has a direct response or redirect configured, then it was from a filter so skip @@ -1043,7 +1082,6 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } } } - } // If the route has no valid backends then just use a direct response and don't fuss with weighted responses @@ -1062,79 +1100,302 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways routeRoutes = append(routeRoutes, ruleRoutes...) } - var hasHostnameIntersection bool - for _, listener := range parentRef.listeners { - hosts := ComputeHosts(httpRoute.Spec.Hostnames, listener.Hostname) - if len(hosts) == 0 { - continue + addRoutesToListener(httpRoute, parentRef, routeRoutes, xdsIR) + } + } + + return relevantHTTPRoutes +} + +func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways []*GatewayContext, resources *Resources, xdsIR XdsIRMap) []*TLSRouteContext { + var relevantTLSRoutes []*TLSRouteContext + + for _, t := range tlsRoutes { + if t == nil { + panic("received nil tlsroute") + } + tlsRoute := &TLSRouteContext{TLSRoute: t} + + // Find out if this route attaches to one of our Gateway's listeners, + // and if so, get the list of listeners that allow it to attach for each + // parentRef. + relevantRoute := processAllowedListenersForParentRefs(tlsRoute, gateways, resources) + if !relevantRoute { + continue + } + + relevantTLSRoutes = append(relevantTLSRoutes, tlsRoute) + + for _, parentRef := range tlsRoute.parentRefs { + // Skip parent refs that did not accept the route + if !parentRef.IsAccepted(tlsRoute) { + continue + } + + // Need to compute Route rules within the parentRef loop because + // any conditions that come out of it have to go on each RouteParentStatus, + // not on the Route as a whole. + var routeRoutes []*ir.HTTPRoute + + // compute backends + for ruleIdx, rule := range tlsRoute.Spec.Rules { + ruleRoute := &ir.HTTPRoute{ + Name: routeName(tlsRoute, ruleIdx, 0), } - hasHostnameIntersection = true - - var perHostRoutes []*ir.HTTPRoute - for _, host := range hosts { - var headerMatches []*ir.StringMatch - - // If the intersecting host is more specific than the Listener's hostname, - // add an additional header match to all of the routes for it - if host != "*" && (listener.Hostname == nil || string(*listener.Hostname) != host) { - headerMatches = append(headerMatches, &ir.StringMatch{ - Name: ":authority", - Exact: StringPtr(host), - }) + + for _, backendRef := range rule.BackendRefs { + if backendRef.Group != nil && *backendRef.Group != "" { + parentRef.SetCondition( + tlsRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.RouteReasonInvalidKind, + "Group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string) is supported", + ) + continue + } + + if backendRef.Kind != nil && *backendRef.Kind != KindService { + parentRef.SetCondition( + tlsRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.RouteReasonInvalidKind, + "Kind is invalid, only Service is supported", + ) + continue } - for _, routeRoute := range routeRoutes { - hostRoute := &ir.HTTPRoute{ - Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), - PathMatch: routeRoute.PathMatch, - HeaderMatches: append(headerMatches, routeRoute.HeaderMatches...), - QueryParamMatches: routeRoute.QueryParamMatches, - AddRequestHeaders: routeRoute.AddRequestHeaders, - RemoveRequestHeaders: routeRoute.RemoveRequestHeaders, - Destinations: routeRoute.Destinations, - Redirect: routeRoute.Redirect, - DirectResponse: routeRoute.DirectResponse, + if backendRef.Namespace != nil && string(*backendRef.Namespace) != "" && string(*backendRef.Namespace) != tlsRoute.Namespace { + if !isValidCrossNamespaceRef( + crossNamespaceFrom{ + group: v1beta1.GroupName, + kind: KindTLSRoute, + namespace: tlsRoute.Namespace, + }, + crossNamespaceTo{ + group: "", + kind: KindService, + namespace: string(*backendRef.Namespace), + name: string(backendRef.Name), + }, + resources.ReferenceGrants, + ) { + parentRef.SetCondition(tlsRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.RouteReasonRefNotPermitted, + fmt.Sprintf("Backend ref to service %s/%s not permitted by any ReferenceGrant", *backendRef.Namespace, backendRef.Name), + ) + continue } - // Don't bother copying over the weights unless the route has invalid backends. - if routeRoute.BackendWeights.Invalid > 0 { - hostRoute.BackendWeights = routeRoute.BackendWeights + } + + if backendRef.Port == nil { + parentRef.SetCondition(tlsRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + "PortNotSpecified", + "A valid port number corresponding to a port on the Service must be specified", + ) + continue + } + + // TODO: [v1alpha2-v1beta1] Replace with NamespaceDerefOr when TLSRoute graduates to v1beta1. + serviceNamespace := NamespaceDerefOrAlpha(backendRef.Namespace, tlsRoute.Namespace) + service := resources.GetService(serviceNamespace, string(backendRef.Name)) + if service == nil { + parentRef.SetCondition( + tlsRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.RouteReasonBackendNotFound, + fmt.Sprintf("Service %s/%s not found", serviceNamespace, string(backendRef.Name)), + ) + continue + } + + var portFound bool + for _, port := range service.Spec.Ports { + if port.Port == int32(*backendRef.Port) { + portFound = true + break } - perHostRoutes = append(perHostRoutes, hostRoute) } - } - irKey := irStringKey(listener.gateway) - irListener := xdsIR[irKey].GetListener(irListenerName(listener)) - if irListener != nil { - irListener.Routes = append(irListener.Routes, perHostRoutes...) + if !portFound { + parentRef.SetCondition( + tlsRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + "PortNotFound", + fmt.Sprintf("Port %d not found on service %s/%s", *backendRef.Port, serviceNamespace, string(backendRef.Name)), + ) + continue + } + + weight := uint32(1) + if backendRef.Weight != nil { + weight = uint32(*backendRef.Weight) + } + + ruleRoute.Destinations = append(ruleRoute.Destinations, &ir.RouteDestination{ + Host: service.Spec.ClusterIP, + Port: uint32(*backendRef.Port), + Weight: weight, + }) } - // Theoretically there should only be one parent ref per - // Route that attaches to a given Listener, so fine to just increment here, but we - // might want to check to ensure we're not double-counting. - if len(routeRoutes) > 0 { - listener.IncrementAttachedRoutes() + + // TODO handle: + // - no valid backend refs + // - sum of weights for valid backend refs is 0 + // - returning 500's for invalid backend refs + // - etc. + + routeRoutes = append(routeRoutes, ruleRoute) + } + + addRoutesToListener(tlsRoute, parentRef, routeRoutes, xdsIR) + } + } + + return relevantTLSRoutes +} + +func addRoutesToListener(routeContext RouteContext, parentRef *RouteParentContext, routeRoutes []*ir.HTTPRoute, xdsIR XdsIRMap) { + var hasHostnameIntersection bool + for _, listener := range parentRef.listeners { + hosts := computeHosts(routeContext.GetHostnames(), listener.Hostname) + if len(hosts) == 0 { + continue + } + hasHostnameIntersection = true + + var perHostRoutes []*ir.HTTPRoute + for _, host := range hosts { + var headerMatches []*ir.StringMatch + if routeContext.GetRouteType() == KindHTTPRoute { + // If the intersecting host is more specific than the Listener's hostname, + // add an additional header match to all of the routes for it + if host != "*" && (listener.Hostname == nil || string(*listener.Hostname) != host) { + headerMatches = append(headerMatches, &ir.StringMatch{ + Name: ":authority", + Exact: StringPtr(host), + }) } } - if !hasHostnameIntersection { - parentRef.SetCondition( - v1beta1.RouteConditionAccepted, - metav1.ConditionFalse, - v1beta1.RouteReasonNoMatchingListenerHostname, - "There were no hostname intersections between the HTTPRoute and this parent ref's Listener(s).", - ) - } else { - parentRef.SetCondition( - v1beta1.RouteConditionAccepted, - metav1.ConditionTrue, - v1beta1.RouteReasonAccepted, - "Route is accepted", - ) + for _, routeRoute := range routeRoutes { + perHostRoute := &ir.HTTPRoute{ + Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), + Destinations: routeRoute.Destinations, + } + + // For TLSRoutes, all would be nil except Name and Destinations. + if routeContext.GetRouteType() == KindHTTPRoute { + headerMatches = append(headerMatches, routeRoute.HeaderMatches...) + + perHostRoute.AddRequestHeaders = routeRoute.AddRequestHeaders + perHostRoute.RemoveRequestHeaders = routeRoute.RemoveRequestHeaders + perHostRoute.Destinations = routeRoute.Destinations + perHostRoute.PathMatch = routeRoute.PathMatch + perHostRoute.HeaderMatches = headerMatches + perHostRoute.QueryParamMatches = routeRoute.QueryParamMatches + perHostRoute.Redirect = routeRoute.Redirect + perHostRoute.DirectResponse = routeRoute.DirectResponse + } + + perHostRoutes = append(perHostRoutes, perHostRoute) } } + + irListener := xdsIR.GetListener(irListenerName(listener)) + if irListener != nil { + irListener.Routes = append(irListener.Routes, perHostRoutes...) + } + // Theoretically there should only be one parent ref per + // Route that attaches to a given Listener, so fine to just increment here, but we + // might want to check to ensure we're not double-counting. + if len(routeRoutes) > 0 { + listener.IncrementAttachedRoutes() + } } - return relevantHTTPRoutes + if !hasHostnameIntersection { + parentRef.SetCondition(routeContext, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + v1beta1.RouteReasonNoMatchingListenerHostname, + "There were no hostname intersections between the Route and this parent ref's Listener(s).", + ) + } else { + parentRef.SetCondition(routeContext, + v1beta1.RouteConditionAccepted, + metav1.ConditionTrue, + v1beta1.RouteReasonAccepted, + "Route is accepted", + ) + } +} + +// processAllowedListenersForParentRefs finds out if the route attaches to one of our +// Gateways' listeners, and if so, gets the list of listeners that allow it to +// attach for each parentRef. +func processAllowedListenersForParentRefs(routeContext RouteContext, gateways []*GatewayContext, resources *Resources) bool { + var relevantRoute bool + + for _, parentRef := range routeContext.GetParentReferences() { + isRelevantParentRef, selectedListeners := GetReferencedListeners(parentRef, gateways) + + // Parent ref is not to a Gateway that we control: skip it + if !isRelevantParentRef { + continue + } + relevantRoute = true + + parentRefCtx := routeContext.GetRouteParentContext(parentRef) + // Reset conditions since they will be recomputed during translation + parentRefCtx.ResetConditions(routeContext) + + if !HasReadyListener(selectedListeners) { + parentRefCtx.SetCondition(routeContext, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + "NoReadyListeners", + "There are no ready listeners for this parent ref", + ) + continue + } + + var allowedListeners []*ListenerContext + for _, listener := range selectedListeners { + acceptedKind := routeContext.GetRouteType() + if listener.AllowsKind(v1beta1.RouteGroupKind{Group: GroupPtr(v1beta1.GroupName), Kind: v1beta1.Kind(acceptedKind)}) && + listener.AllowsNamespace(resources.GetNamespace(routeContext.GetNamespace())) { + allowedListeners = append(allowedListeners, listener) + } + } + + if len(allowedListeners) == 0 { + parentRefCtx.SetCondition(routeContext, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + v1beta1.RouteReasonNotAllowedByListeners, + "No listeners included by this parent ref allowed this attachment.", + ) + continue + } + + parentRefCtx.SetListeners(allowedListeners...) + + parentRefCtx.SetCondition(routeContext, + v1beta1.RouteConditionAccepted, + metav1.ConditionTrue, + v1beta1.RouteReasonAccepted, + "Route is accepted", + ) + } + return relevantRoute } type crossNamespaceFrom struct { @@ -1229,19 +1490,21 @@ func irListenerName(listener *ListenerContext) string { return fmt.Sprintf("%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name) } -func routeName(httpRoute *HTTPRouteContext, ruleIdx, matchIdx int) string { - return fmt.Sprintf("%s-%s-rule-%d-match-%d", httpRoute.Namespace, httpRoute.Name, ruleIdx, matchIdx) +func routeName(route RouteContext, ruleIdx, matchIdx int) string { + return fmt.Sprintf("%s-%s-rule-%d-match-%d", route.GetNamespace(), route.GetName(), ruleIdx, matchIdx) } -func irTLSConfig(tlsSecret *v1.Secret) *ir.TLSListenerConfig { - if tlsSecret == nil { - return nil +func irTLSConfig(tlsMode v1beta1.TLSModeType, tlsSecret *v1.Secret) *ir.TLSListenerConfig { + tlsListenerConfig := &ir.TLSListenerConfig{ + TLSMode: tlsMode, } - return &ir.TLSListenerConfig{ - ServerCertificate: tlsSecret.Data[v1.TLSCertKey], - PrivateKey: tlsSecret.Data[v1.TLSPrivateKeyKey], + if tlsSecret != nil { + tlsListenerConfig.ServerCertificate = tlsSecret.Data[v1.TLSCertKey] + tlsListenerConfig.PrivateKey = tlsSecret.Data[v1.TLSPrivateKeyKey] } + + return tlsListenerConfig } // GatewayOwnerLabels returns the Gateway Owner labels using diff --git a/internal/ir/xds.go b/internal/ir/xds.go index d03de20b28..9c0f55cdc6 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -5,6 +5,7 @@ import ( "net" "github.com/tetratelabs/multierror" + "sigs.k8s.io/gateway-api/apis/v1beta1" ) var ( @@ -98,7 +99,7 @@ func (h HTTPListener) Validate() error { } } for _, route := range h.Routes { - if err := route.Validate(); err != nil { + if err := route.Validate(h.TLS); err != nil { errs = multierror.Append(errs, err) } } @@ -108,6 +109,9 @@ func (h HTTPListener) Validate() error { // TLSListenerConfig holds the configuration for downstream TLS context. // +k8s:deepcopy-gen=true type TLSListenerConfig struct { + // TLSMode could either be Terminate or Passthrough. If the TLS mode + // is Passthrough, certificate and private key information is not required. + TLSMode v1beta1.TLSModeType // ServerCertificate of the server. ServerCertificate []byte // PrivateKey for the server. @@ -117,6 +121,9 @@ type TLSListenerConfig struct { // Validate the fields within the TLSListenerConfig structure func (t TLSListenerConfig) Validate() error { var errs error + if t.TLSMode == v1beta1.TLSModePassthrough { + return errs + } if len(t.ServerCertificate) == 0 { errs = multierror.Append(errs, ErrTLSServerCertEmpty) } @@ -158,13 +165,16 @@ type HTTPRoute struct { } // Validate the fields within the HTTPRoute structure -func (h HTTPRoute) Validate() error { +func (h HTTPRoute) Validate(tls *TLSListenerConfig) error { var errs error if h.Name == "" { errs = multierror.Append(errs, ErrHTTPRouteNameEmpty) } - if h.PathMatch == nil && (len(h.HeaderMatches) == 0) && (len(h.QueryParamMatches) == 0) { - errs = multierror.Append(errs, ErrHTTPRouteMatchEmpty) + if tls == nil || tls.TLSMode != v1beta1.TLSModePassthrough { + // These fields could be nil in case of TLSModePassthrough. + if h.PathMatch == nil && (len(h.HeaderMatches) == 0) && (len(h.QueryParamMatches) == 0) { + errs = multierror.Append(errs, ErrHTTPRouteMatchEmpty) + } } if h.PathMatch != nil { if err := h.PathMatch.Validate(); err != nil { diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index da45b040cd..17e8be2a8a 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -473,9 +473,9 @@ func TestValidateHTTPRoute(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { if test.want == nil { - require.NoError(t, test.input.Validate()) + require.NoError(t, test.input.Validate(nil)) } else { - got := test.input.Validate() + got := test.input.Validate(nil) for _, w := range test.want { assert.ErrorContains(t, got, w.Error()) } diff --git a/internal/message/types.go b/internal/message/types.go index 6d7ec1a64d..4205a3d119 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -4,6 +4,7 @@ import ( "github.com/telepresenceio/watchable" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/envoyproxy/gateway/internal/ir" @@ -15,6 +16,7 @@ type ProviderResources struct { GatewayClasses watchable.Map[string, *gwapiv1b1.GatewayClass] Gateways watchable.Map[types.NamespacedName, *gwapiv1b1.Gateway] HTTPRoutes watchable.Map[types.NamespacedName, *gwapiv1b1.HTTPRoute] + TLSRoutes watchable.Map[types.NamespacedName, *gwapiv1a2.TLSRoute] Namespaces watchable.Map[string, *corev1.Namespace] Services watchable.Map[types.NamespacedName, *corev1.Service] @@ -56,6 +58,17 @@ func (p *ProviderResources) GetHTTPRoutes() []*gwapiv1b1.HTTPRoute { return res } +func (p *ProviderResources) GetTLSRoutes() []*gwapiv1a2.TLSRoute { + if p.TLSRoutes.Len() == 0 { + return nil + } + res := make([]*gwapiv1a2.TLSRoute, 0, p.TLSRoutes.Len()) + for _, v := range p.TLSRoutes.LoadAll() { + res = append(res, v) + } + return res +} + func (p *ProviderResources) GetNamespaces() []*corev1.Namespace { if p.Namespaces.Len() == 0 { return nil diff --git a/internal/provider/kubernetes/httproute.go b/internal/provider/kubernetes/httproute.go index 88eb98a591..82c852a6cc 100644 --- a/internal/provider/kubernetes/httproute.go +++ b/internal/provider/kubernetes/httproute.go @@ -288,6 +288,7 @@ func (r *httpRouteReconciler) Reconcile(ctx context.Context, request reconcile.R log.Info("reconciled httproute") + r.resources.RouteInitializedOnce.Do(r.resources.RoutesInitialized.Done) return reconcile.Result{}, nil } diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go index 1383078572..ec40d13636 100644 --- a/internal/provider/kubernetes/kubernetes.go +++ b/internal/provider/kubernetes/kubernetes.go @@ -50,9 +50,16 @@ func New(cfg *rest.Config, svr *config.Server, resources *message.ProviderResour if err := newGatewayController(mgr, svr, updateHandler.Writer(), resources); err != nil { return nil, fmt.Errorf("failed to create gateway controller: %w", err) } + + // Add once for all types of Routes that we watch for. We need at least one + // Route object to proceed with translation. + resources.RoutesInitialized.Add(1) if err := newHTTPRouteController(mgr, svr, updateHandler.Writer(), resources); err != nil { return nil, fmt.Errorf("failed to create httproute controller: %w", err) } + if err := newTLSRouteController(mgr, svr, resources); err != nil { + return nil, fmt.Errorf("failed to create tlsroute controller: %w", err) + } return &Provider{ manager: mgr, diff --git a/internal/provider/kubernetes/tlsroute.go b/internal/provider/kubernetes/tlsroute.go new file mode 100644 index 0000000000..77f60828c8 --- /dev/null +++ b/internal/provider/kubernetes/tlsroute.go @@ -0,0 +1,231 @@ +// Portions of this code are based on code from Contour, available at: +// https://github.com/projectcontour/contour/blob/main/internal/controller/tlsroute.go + +package kubernetes + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "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" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/gatewayapi" + "github.com/envoyproxy/gateway/internal/message" +) + +const ( + serviceTLSRouteIndex = "serviceTLSRouteBackendRef" +) + +type tlsRouteReconciler struct { + client client.Client + log logr.Logger + + resources *message.ProviderResources +} + +// newTLSRouteController creates the tlsroute controller from mgr. The controller will be pre-configured +// to watch for TLSRoute objects across all namespaces. +func newTLSRouteController(mgr manager.Manager, cfg *config.Server, resources *message.ProviderResources) error { + r := &tlsRouteReconciler{ + client: mgr.GetClient(), + log: cfg.Logger, + resources: resources, + } + + c, err := controller.New("tlsroute", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + r.log.Info("created tlsroute controller") + + if err := c.Watch(&source.Kind{Type: &gwapiv1a2.TLSRoute{}}, &handler.EnqueueRequestForObject{}); err != nil { + return err + } + // Add indexing on TLSRoute, for Service objects that are referenced in TLSRoute objects + // via `.spec.rules.backendRefs`. This helps in querying for TLSRoutes that are affected by + // a particular Service CRUD. + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &gwapiv1a2.TLSRoute{}, serviceTLSRouteIndex, func(rawObj client.Object) []string { + tlsRoute := rawObj.(*gwapiv1a2.TLSRoute) + var backendServices []string + for _, rule := range tlsRoute.Spec.Rules { + for _, backend := range rule.BackendRefs { + if string(*backend.Kind) == gatewayapi.KindService { + // If an explicit Service namespace is not provided, use the TLSRoute namespace to + // lookup the provided Service Name. + backendServices = append(backendServices, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOrAlpha(backend.Namespace, tlsRoute.Namespace), + Name: string(backend.Name), + }.String(), + ) + } + } + } + return backendServices + }); err != nil { + return err + } + + // Watch Service CRUDs and reconcile affected TLSRoutes. + if err := c.Watch( + &source.Kind{Type: &corev1.Service{}}, + handler.EnqueueRequestsFromMapFunc(r.getTLSRoutesForService), + ); err != nil { + return err + } + + r.log.Info("watching tlsroute objects") + return nil +} + +// getTLSRoutesForService uses a Service obj to fetch TLSRoutes that references +// the Service using `.spec.rules.backendRefs`. The affected TLSRoutes are then +// pushed for reconciliation. +func (r *tlsRouteReconciler) getTLSRoutesForService(obj client.Object) []reconcile.Request { + affectedTLSRouteList := &gwapiv1a2.TLSRouteList{} + + if err := r.client.List(context.Background(), affectedTLSRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(serviceTLSRouteIndex, NamespacedName(obj).String()), + }); err != nil { + return []reconcile.Request{} + } + + requests := make([]reconcile.Request, len(affectedTLSRouteList.Items)) + for i, item := range affectedTLSRouteList.Items { + requests[i] = reconcile.Request{ + NamespacedName: NamespacedName(item.DeepCopy()), + } + } + + return requests +} + +func (r *tlsRouteReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := r.log.WithValues("namespace", request.Namespace, "name", request.Name) + + log.Info("reconciling tlsroute") + + // Fetch all TLSRoutes from the cache. + routeList := &gwapiv1a2.TLSRouteList{} + if err := r.client.List(ctx, routeList); err != nil { + return reconcile.Result{}, fmt.Errorf("error listing tlsroutes") + } + + found := false + for i := range routeList.Items { + // See if this route from the list matched the reconciled route. + route := routeList.Items[i] + routeKey := NamespacedName(&route) + if routeKey == request.NamespacedName { + found = true + } + + // Store the tlsroute in the resource map. + r.resources.TLSRoutes.Store(routeKey, &route) + log.Info("added tlsroute to resource map") + + // Get the route's namespace from the cache. + nsKey := types.NamespacedName{Name: route.Namespace} + ns := new(corev1.Namespace) + if err := r.client.Get(ctx, nsKey, ns); err != nil { + if errors.IsNotFound(err) { + // The route's namespace doesn't exist in the cache, so remove it from + // the namespace resource map if it exists. + if _, ok := r.resources.Namespaces.Load(nsKey.Name); ok { + r.resources.Namespaces.Delete(nsKey.Name) + log.Info("deleted namespace from resource map") + } + } + return reconcile.Result{}, fmt.Errorf("failed to get namespace %s", nsKey.Name) + } + + // The route's namespace exists, so add it to the resource map. + r.resources.Namespaces.Store(nsKey.Name, ns) + log.Info("added namespace to resource map") + + // Get the route's backendRefs from the cache. Note that a Service is the + // only supported kind. + for i := range route.Spec.Rules { + for j := range route.Spec.Rules[i].BackendRefs { + ref := route.Spec.Rules[i].BackendRefs[j] + if err := validateTLSRouteBackendRef(&ref); err != nil { + return reconcile.Result{}, fmt.Errorf("invalid backendRef: %w", err) + } + + // The backendRef is valid, so get the referenced service from the cache. + svcKey := types.NamespacedName{Namespace: route.Namespace, Name: string(ref.Name)} + svc := new(corev1.Service) + if err := r.client.Get(ctx, svcKey, svc); err != nil { + if errors.IsNotFound(err) { + // The ref's service doesn't exist in the cache, so remove it from + // the resource map if it exists. + if _, ok := r.resources.Services.Load(svcKey); ok { + r.resources.Services.Delete(svcKey) + log.Info("deleted service from resource map") + } + } + return reconcile.Result{}, fmt.Errorf("failed to get service %s/%s", + svcKey.Namespace, svcKey.Name) + } + + // The backendRef Service exists, so add it to the resource map. + r.resources.Services.Store(svcKey, svc) + log.Info("added service to resource map") + } + } + } + + if !found { + // Delete the tlsroute from the resource map. + r.resources.TLSRoutes.Delete(request.NamespacedName) + log.Info("deleted tlsroute from resource map") + + // Delete the Namespace and Service from the resource maps if no other + // routes exist in the namespace. + routeList = &gwapiv1a2.TLSRouteList{} + if err := r.client.List(ctx, routeList, &client.ListOptions{Namespace: request.Namespace}); err != nil { + return reconcile.Result{}, fmt.Errorf("error listing tlsroutes") + } + if len(routeList.Items) == 0 { + r.resources.Namespaces.Delete(request.Namespace) + log.Info("deleted namespace from resource map") + r.resources.Services.Delete(request.NamespacedName) + log.Info("deleted service from resource map") + } + } + + log.Info("reconciled tlsroute") + + r.resources.RouteInitializedOnce.Do(r.resources.RoutesInitialized.Done) + return reconcile.Result{}, nil +} + +// validateTLSRouteBackendRef validates that ref is a reference to a local Service. +func validateTLSRouteBackendRef(ref *gwapiv1a2.BackendRef) error { + switch { + case ref == nil: + return nil + case ref.Group != nil && *ref.Group != corev1.GroupName: + return fmt.Errorf("invalid group; must be nil or empty string") + case ref.Kind != nil && *ref.Kind != gatewayapi.KindService: + return fmt.Errorf("invalid kind %q; must be %q", + *ref.BackendObjectReference.Kind, gatewayapi.KindService) + case ref.Namespace != nil: + return fmt.Errorf("invalid namespace; must be nil") + } + + return nil +} diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index 2a1dff9b30..b98d5dfe80 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -12,18 +12,18 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) -func buildXdsCluster(httpRoute *ir.HTTPRoute) (*cluster.Cluster, error) { +func buildXdsCluster(routeName string, destinations []*ir.RouteDestination) (*cluster.Cluster, error) { localities := make([]*endpoint.LocalityLbEndpoints, 0, 1) locality := &endpoint.LocalityLbEndpoints{ Locality: &core.Locality{}, - LbEndpoints: buildXdsEndpoints(httpRoute.Destinations), + LbEndpoints: buildXdsEndpoints(destinations), Priority: 0, // Each locality gets the same weight 1. There is a single locality // per priority, so the weight value does not really matter, but some // load balancers need the value to be set. LoadBalancingWeight: &wrapperspb.UInt32Value{Value: 1}} localities = append(localities, locality) - clusterName := getXdsClusterName(httpRoute.Name) + clusterName := getXdsClusterName(routeName) return &cluster.Cluster{ Name: clusterName, ConnectTimeout: durationpb.New(5 * time.Second), diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index baca1aae39..0ab37b64d3 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -6,7 +6,9 @@ import ( core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" + tls_inspector "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3" hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + tcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "google.golang.org/protobuf/types/known/anypb" @@ -71,6 +73,53 @@ func buildXdsListener(httpListener *ir.HTTPListener) (*listener.Listener, error) }, nil } +func buildXdsPassthroughListener(httpListener *ir.HTTPListener) (*listener.Listener, error) { + if httpListener == nil { + return nil, errors.New("http listener is nil") + } + + mgr := &tcp.TcpProxy{StatPrefix: "passthrough"} + mgrAny, err := anypb.New(mgr) + if err != nil { + return nil, err + } + + tlsInspector := &tls_inspector.TlsInspector{} + tlsInspectorAny, err := anypb.New(tlsInspector) + if err != nil { + return nil, err + } + + return &listener.Listener{ + Name: getXdsListenerName(httpListener.Name, httpListener.Port), + Address: &core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + Protocol: core.SocketAddress_TCP, + Address: httpListener.Address, + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: httpListener.Port, + }, + }, + }, + }, + FilterChains: []*listener.FilterChain{{ + Filters: []*listener.Filter{{ + Name: wellknown.TCPProxy, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: mgrAny, + }, + }}, + }}, + ListenerFilters: []*listener.ListenerFilter{{ + Name: wellknown.TlsInspector, + ConfigType: &listener.ListenerFilter_TypedConfig{ + TypedConfig: tlsInspectorAny, + }, + }}, + }, nil +} + func buildXdsDownstreamTLSSocket(listenerName string, tlsConfig *ir.TLSListenerConfig) (*core.TransportSocket, error) { tlsCtx := &tls.DownstreamTlsContext{ @@ -113,5 +162,4 @@ func buildXdsDownstreamTLSSecret(listenerName string, }, }, }, nil - } diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index d231a60697..a23578efbf 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -9,7 +9,13 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) -func buildXdsRoute(httpRoute *ir.HTTPRoute) (*route.Route, error) { +func buildXdsRoute(httpRoute *ir.HTTPRoute, tlsPassthrough bool) (*route.Route, error) { + if tlsPassthrough { + return &route.Route{ + Action: &route.Route_Route{Route: buildXdsRouteAction(httpRoute.Name)}, + }, nil + } + ret := &route.Route{ Match: buildXdsRouteMatch(httpRoute.PathMatch, httpRoute.HeaderMatches, httpRoute.QueryParamMatches), } diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 3c6bf26147..60c9c132f4 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -5,9 +5,11 @@ import ( "fmt" core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" resource "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/tetratelabs/multierror" + "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/xds/types" @@ -23,7 +25,14 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { for _, httpListener := range ir.HTTP { // 1:1 between IR HTTPListener and xDS Listener - xdsListener, err := buildXdsListener(httpListener) + var xdsListener *listener.Listener + var err error + tlsPassthrough := httpListener.TLS != nil && httpListener.TLS.TLSMode == v1beta1.TLSModePassthrough + if tlsPassthrough { + xdsListener, err = buildXdsPassthroughListener(httpListener) + } else { + xdsListener, err = buildXdsListener(httpListener) + } if err != nil { return nil, multierror.Append(err, errors.New("error building xds listener")) } @@ -54,7 +63,7 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { for _, httpRoute := range httpListener.Routes { // 1:1 between IR HTTPRoute and xDS config.route.v3.Route - xdsRoute, err := buildXdsRoute(httpRoute) + xdsRoute, err := buildXdsRoute(httpRoute, tlsPassthrough) if err != nil { return nil, multierror.Append(err, errors.New("error building xds route")) } @@ -64,7 +73,7 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { if len(httpRoute.Destinations) == 0 && httpRoute.BackendWeights.Invalid > 0 { continue } - xdsCluster, err := buildXdsCluster(httpRoute) + xdsCluster, err := buildXdsCluster(httpRoute.Name, httpRoute.Destinations) if err != nil { return nil, multierror.Append(err, errors.New("error building xds cluster")) } From b8033eca566acc9d394c7252d0015c1a27a7c73a Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Wed, 21 Sep 2022 18:56:18 +0530 Subject: [PATCH 02/30] Adding tlsroute experimental crd in testdata update gatewayclass/gateway/httproute experimental CRDs to use standard schemas Signed-off-by: Shubham Chauhan --- .../testdata/in/gateway-experimental-crd.yaml | 1417 ----------------- .../testdata/in/gateway-standard-crd.yaml | 1416 ++++++++++++++++ .../in/gatewayclass-experimental-crd.yaml | 430 ----- .../in/gatewayclass-standard-crd.yaml | 430 +++++ ...l-crd.yaml => httproute-standard-crd.yaml} | 700 ++------ .../in/tlsroute-experimental-crd.yaml | 542 +++++++ 6 files changed, 2477 insertions(+), 2458 deletions(-) delete mode 100644 internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml create mode 100644 internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml delete mode 100644 internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml create mode 100644 internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml rename internal/provider/kubernetes/testdata/in/{httproute-experimental-crd.yaml => httproute-standard-crd.yaml} (79%) create mode 100644 internal/provider/kubernetes/testdata/in/tlsroute-experimental-crd.yaml diff --git a/internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml deleted file mode 100644 index 46df0aed5c..0000000000 --- a/internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml +++ /dev/null @@ -1,1417 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 - gateway.networking.k8s.io/bundle-version: v0.5.0 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: gateways.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: Gateway - listKind: GatewayList - plural: gateways - shortNames: - - gtw - singular: gateway - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling - infrastructure by binding Listeners to a set of IP addresses. - 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 Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional - and behavior can depend on the implementation. If a value is set - in the spec and the requested address is invalid or unavailable, - the implementation MUST indicate this in the associated entry in - GatewayStatus.Addresses. \n The Addresses field represents a request - for the address(es) on the \"outside of the Gateway\", that traffic - bound for this Gateway will use. This could be the IP address or - hostname of an external load balancer or other networking infrastructure, - or some other address that traffic will be sent to. \n The .listener.hostname - field is used to route traffic that has already arrived at the Gateway - to the correct in-cluster destination. \n If no Addresses are specified, - the implementation MAY schedule the Gateway in an implementation-specific - manner, assigning an appropriate set of Addresses. \n The implementation - MUST bind all Listeners to every GatewayAddress that it assigns - to the Gateway and add a corresponding entry in GatewayStatus.Addresses. - \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name - of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define - logical endpoints that are bound on this Gateway's addresses. At - least one Listener MUST be specified. \n Each listener in a Gateway - must have a unique combination of Hostname, Port, and Protocol. - \n An implementation MAY group Listeners by Port and then collapse - each group of Listeners into a single Listener if the implementation - determines that the Listeners in the group are \"compatible\". An - implementation MAY also group together and collapse compatible Listeners - belonging to different Gateways. \n For example, an implementation - might consider Listeners to be compatible with each other if all - of the following conditions are met: \n 1. Either each Listener - within the group specifies the \"HTTP\" Protocol or each Listener - within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. - \n 2. Each Listener within the group specifies a Hostname that is - unique within the group. \n 3. As a special case, one Listener - within a group may omit Hostname, in which case this Listener - matches when no other Listener matches. \n If the implementation - does collapse compatible Listeners, the hostname provided in the - incoming client request MUST be matched to a Listener to find the - correct set of Routes. The incoming hostname MUST be matched using - the Hostname field for each Listener in order of most to least specific. - That is, exact matches must be processed before wildcard matches. - \n If this field specifies multiple Listeners that have the same - Port value but are not compatible, the implementation must raise - a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint - where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that - MAY be attached to a Listener and the trusted namespaces where - those Route resources MAY be present. \n Although a client - request may match multiple route rules, only one rule may - ultimately receive the request. Matching precedence MUST be - determined in order of the following criteria: \n * The most - specific match as defined by the Route type. * The oldest - Route based on creation timestamp. For example, a Route with - \ a creation timestamp of \"2020-09-08 01:02:03\" is given - precedence over a Route with a creation timestamp of \"2020-09-08 - 01:02:04\". * If everything else is equivalent, the Route - appearing first in alphabetical order (namespace/name) should - be given precedence. For example, foo/bar is given precedence - over foo/baz. \n All valid rules within a Route attached to - this Listener should be implemented. Invalid Route rules can - be ignored (sometimes that will mean the full Route). If a - Route rule transitions from valid to invalid, support for - that Route rule should be dropped to ensure consistency. For - example, even if a filter specified by a Route rule is invalid, - the rest of the rules within that Route should still be supported. - \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes - that are allowed to bind to this Gateway Listener. When - unspecified or empty, the kinds of Routes selected are - determined using the Listener protocol. \n A RouteGroupKind - MUST correspond to kinds of Routes that are compatible - with the application protocol specified in the Listener's - Protocol field. If an implementation does not support - or recognize this resource type, it MUST set the \"ResolvedRefs\" - condition to False for this Listener with the \"InvalidRouteKinds\" - reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind - of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which - Routes may be attached to this Listener. This is restricted - to the namespace of this Gateway by default. \n Support: - Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected - for this Gateway. Possible values are: * All: Routes - in all namespaces may be used by this Gateway. * Selector: - Routes in namespaces selected by the selector may - be used by this Gateway. * Same: Only Routes in - the same namespace may be used by this Gateway. \n - Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is - set to \"Selector\". In that case, only Routes in - Namespaces matching this Selector will be selected - by this Gateway. This field is ignored for other values - of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match - for protocol types that define this concept. When unspecified, - all hostnames are matched. This field is ignored for protocols - that don't require hostname based matching. \n Implementations - MUST apply Hostname matching appropriately for each of the - following protocols: \n * TLS: The Listener Hostname MUST - match the SNI. * HTTP: The Listener Hostname MUST match the - Host header of the request. * HTTPS: The Listener Hostname - SHOULD match at both the TLS and HTTP protocol layers as - described above. If an implementation does not ensure that - both the SNI and Host header match the Listener hostname, - \ it MUST clearly document that. \n For HTTPRoute and TLSRoute - resources, there is an interaction with the `spec.hostnames` - array. When both listener and route specify hostnames, there - MUST be an intersection between the values for a Route to - be accepted. For more information, refer to the Route specific - Hostnames documentation. \n Hostnames that are prefixed with - a wildcard label (`*.`) are interpreted as a suffix match. - That means that a match for `*.example.com` would match both - `test.example.com`, and `foo.test.example.com`, but not `example.com`. - \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 - name: - description: "Name is the name of the Listener. This name MUST - be unique within a 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 - port: - description: "Port is the network port. Multiple listeners may - use the same port, subject to the Listener compatibility rules. - \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener - expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. - This field is required if the Protocol field is \"HTTPS\" - or \"TLS\". It is invalid to set this field if the Protocol - field is \"HTTP\", \"TCP\", or \"UDP\". \n The association - of SNIs to Certificate defined in GatewayTLSConfig is defined - based on the Hostname field for this listener. \n The GatewayClass - MUST use the longest matching SNI out of all available certificates - for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references - to Kubernetes objects that contains TLS certificates and - private keys. These certificates are used to establish - a TLS handshake for requests that match the hostname of - the associated listener. \n A single CertificateRef to - a Kubernetes Secret has \"Core\" support. Implementations - MAY choose to support attaching multiple certificates - to a Listener, but this behavior is implementation-specific. - \n References to a resource in different namespace are - invalid UNLESS there is a ReferenceGrant in the target - namespace that allows the certificate to be attached. - If a ReferenceGrant does not allow this reference, the - \"ResolvedRefs\" condition MUST be set to False for this - listener with the \"InvalidCertificateRef\" reason. \n - This field is required to have at least one element when - the mode is set to \"Terminate\" (default) and is optional - otherwise. \n CertificateRefs can reference to standard - Kubernetes resources, i.e. Secret, or implementation-specific - custom resources. \n Support: Core - A single reference - to a Kubernetes Secret of type kubernetes.io/tls \n Support: - Implementation-specific (More than one reference or other - resource types)" - items: - description: "SecretObjectReference identifies an API - object including its namespace, defaulting to Secret. - \n 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: - default: "" - description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (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: - default: Secret - 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 - namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. - \n Note that when a different namespace is specified, - a ReferenceGrant object with ReferenceGrantTo.Kind=Secret - is required in the referent namespace to allow that - namespace's owner to accept the reference. See the - ReferenceGrant documentation for details. \n Support: - Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS - session initiated by the client. There are two possible - modes: \n - Terminate: The TLS session between the downstream - client and the Gateway is terminated at the Gateway. - This mode requires certificateRefs to be set and contain - at least one element. - Passthrough: The TLS session is - NOT terminated by the Gateway. This implies that the - Gateway can't decipher the TLS stream except for the - ClientHello message of the TLS protocol. CertificateRefs - field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation - in Gateway API. This is used for validation of maps - such as TLS options. This roughly matches Kubernetes - annotation validation, although the length validation - in that case is based on the entire size of the annotations - struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable - extended TLS configuration for each implementation. For - example, configuring the minimum TLS version or supported - cipher suites. \n A set of common keys MAY be defined - by the API in the future. To avoid any ambiguity, implementation-specific - definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. - Un-prefixed names are reserved for key names defined by - Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been - bound to the Gateway. These addresses may differ from the addresses - in the Spec, e.g. if the Gateway automatically assigns an address - from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: "Conditions describe the current conditions of the Gateway. - \n Implementations should prefer to express Gateway conditions using - the `GatewayConditionType` and `GatewayConditionReason` constants - so that operators and tools can converge on a common vocabulary - to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" - * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port - defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes - that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this - listener. - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status - corresponds to. - 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 - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds - supported by this listener. This MUST represent the kinds - an implementation supports for that Listener configuration. - \n If kinds are specified in Spec that are not supported, - they MUST NOT appear in this list and an implementation MUST - set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" - reason. If both valid and invalid Route kinds are specified, - the implementation MUST reference the valid Route kinds that - have been specified." - items: - description: RouteGroupKind indicates the group and kind of - a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling - infrastructure by binding Listeners to a set of IP addresses. - 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 Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional - and behavior can depend on the implementation. If a value is set - in the spec and the requested address is invalid or unavailable, - the implementation MUST indicate this in the associated entry in - GatewayStatus.Addresses. \n The Addresses field represents a request - for the address(es) on the \"outside of the Gateway\", that traffic - bound for this Gateway will use. This could be the IP address or - hostname of an external load balancer or other networking infrastructure, - or some other address that traffic will be sent to. \n The .listener.hostname - field is used to route traffic that has already arrived at the Gateway - to the correct in-cluster destination. \n If no Addresses are specified, - the implementation MAY schedule the Gateway in an implementation-specific - manner, assigning an appropriate set of Addresses. \n The implementation - MUST bind all Listeners to every GatewayAddress that it assigns - to the Gateway and add a corresponding entry in GatewayStatus.Addresses. - \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name - of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define - logical endpoints that are bound on this Gateway's addresses. At - least one Listener MUST be specified. \n Each listener in a Gateway - must have a unique combination of Hostname, Port, and Protocol. - \n An implementation MAY group Listeners by Port and then collapse - each group of Listeners into a single Listener if the implementation - determines that the Listeners in the group are \"compatible\". An - implementation MAY also group together and collapse compatible Listeners - belonging to different Gateways. \n For example, an implementation - might consider Listeners to be compatible with each other if all - of the following conditions are met: \n 1. Either each Listener - within the group specifies the \"HTTP\" Protocol or each Listener - within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. - \n 2. Each Listener within the group specifies a Hostname that is - unique within the group. \n 3. As a special case, one Listener - within a group may omit Hostname, in which case this Listener - matches when no other Listener matches. \n If the implementation - does collapse compatible Listeners, the hostname provided in the - incoming client request MUST be matched to a Listener to find the - correct set of Routes. The incoming hostname MUST be matched using - the Hostname field for each Listener in order of most to least specific. - That is, exact matches must be processed before wildcard matches. - \n If this field specifies multiple Listeners that have the same - Port value but are not compatible, the implementation must raise - a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint - where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that - MAY be attached to a Listener and the trusted namespaces where - those Route resources MAY be present. \n Although a client - request may match multiple route rules, only one rule may - ultimately receive the request. Matching precedence MUST be - determined in order of the following criteria: \n * The most - specific match as defined by the Route type. * The oldest - Route based on creation timestamp. For example, a Route with - \ a creation timestamp of \"2020-09-08 01:02:03\" is given - precedence over a Route with a creation timestamp of \"2020-09-08 - 01:02:04\". * If everything else is equivalent, the Route - appearing first in alphabetical order (namespace/name) should - be given precedence. For example, foo/bar is given precedence - over foo/baz. \n All valid rules within a Route attached to - this Listener should be implemented. Invalid Route rules can - be ignored (sometimes that will mean the full Route). If a - Route rule transitions from valid to invalid, support for - that Route rule should be dropped to ensure consistency. For - example, even if a filter specified by a Route rule is invalid, - the rest of the rules within that Route should still be supported. - \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes - that are allowed to bind to this Gateway Listener. When - unspecified or empty, the kinds of Routes selected are - determined using the Listener protocol. \n A RouteGroupKind - MUST correspond to kinds of Routes that are compatible - with the application protocol specified in the Listener's - Protocol field. If an implementation does not support - or recognize this resource type, it MUST set the \"ResolvedRefs\" - condition to False for this Listener with the \"InvalidRouteKinds\" - reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind - of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which - Routes may be attached to this Listener. This is restricted - to the namespace of this Gateway by default. \n Support: - Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected - for this Gateway. Possible values are: * All: Routes - in all namespaces may be used by this Gateway. * Selector: - Routes in namespaces selected by the selector may - be used by this Gateway. * Same: Only Routes in - the same namespace may be used by this Gateway. \n - Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is - set to \"Selector\". In that case, only Routes in - Namespaces matching this Selector will be selected - by this Gateway. This field is ignored for other values - of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match - for protocol types that define this concept. When unspecified, - all hostnames are matched. This field is ignored for protocols - that don't require hostname based matching. \n Implementations - MUST apply Hostname matching appropriately for each of the - following protocols: \n * TLS: The Listener Hostname MUST - match the SNI. * HTTP: The Listener Hostname MUST match the - Host header of the request. * HTTPS: The Listener Hostname - SHOULD match at both the TLS and HTTP protocol layers as - described above. If an implementation does not ensure that - both the SNI and Host header match the Listener hostname, - \ it MUST clearly document that. \n For HTTPRoute and TLSRoute - resources, there is an interaction with the `spec.hostnames` - array. When both listener and route specify hostnames, there - MUST be an intersection between the values for a Route to - be accepted. For more information, refer to the Route specific - Hostnames documentation. \n Hostnames that are prefixed with - a wildcard label (`*.`) are interpreted as a suffix match. - That means that a match for `*.example.com` would match both - `test.example.com`, and `foo.test.example.com`, but not `example.com`. - \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 - name: - description: "Name is the name of the Listener. This name MUST - be unique within a 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 - port: - description: "Port is the network port. Multiple listeners may - use the same port, subject to the Listener compatibility rules. - \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener - expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. - This field is required if the Protocol field is \"HTTPS\" - or \"TLS\". It is invalid to set this field if the Protocol - field is \"HTTP\", \"TCP\", or \"UDP\". \n The association - of SNIs to Certificate defined in GatewayTLSConfig is defined - based on the Hostname field for this listener. \n The GatewayClass - MUST use the longest matching SNI out of all available certificates - for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references - to Kubernetes objects that contains TLS certificates and - private keys. These certificates are used to establish - a TLS handshake for requests that match the hostname of - the associated listener. \n A single CertificateRef to - a Kubernetes Secret has \"Core\" support. Implementations - MAY choose to support attaching multiple certificates - to a Listener, but this behavior is implementation-specific. - \n References to a resource in different namespace are - invalid UNLESS there is a ReferenceGrant in the target - namespace that allows the certificate to be attached. - If a ReferenceGrant does not allow this reference, the - \"ResolvedRefs\" condition MUST be set to False for this - listener with the \"InvalidCertificateRef\" reason. \n - This field is required to have at least one element when - the mode is set to \"Terminate\" (default) and is optional - otherwise. \n CertificateRefs can reference to standard - Kubernetes resources, i.e. Secret, or implementation-specific - custom resources. \n Support: Core - A single reference - to a Kubernetes Secret of type kubernetes.io/tls \n Support: - Implementation-specific (More than one reference or other - resource types)" - items: - description: "SecretObjectReference identifies an API - object including its namespace, defaulting to Secret. - \n 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: - default: "" - description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (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: - default: Secret - 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 - namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. - \n Note that when a namespace is specified, a ReferenceGrant - object is required in the referent namespace to - allow that namespace's owner to accept the reference. - See the ReferenceGrant documentation for details. - \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS - session initiated by the client. There are two possible - modes: \n - Terminate: The TLS session between the downstream - client and the Gateway is terminated at the Gateway. - This mode requires certificateRefs to be set and contain - at least one element. - Passthrough: The TLS session is - NOT terminated by the Gateway. This implies that the - Gateway can't decipher the TLS stream except for the - ClientHello message of the TLS protocol. CertificateRefs - field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation - in Gateway API. This is used for validation of maps - such as TLS options. This roughly matches Kubernetes - annotation validation, although the length validation - in that case is based on the entire size of the annotations - struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable - extended TLS configuration for each implementation. For - example, configuring the minimum TLS version or supported - cipher suites. \n A set of common keys MAY be defined - by the API in the future. To avoid any ambiguity, implementation-specific - definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. - Un-prefixed names are reserved for key names defined by - Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been - bound to the Gateway. These addresses may differ from the addresses - in the Spec, e.g. if the Gateway automatically assigns an address - from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: "Conditions describe the current conditions of the Gateway. - \n Implementations should prefer to express Gateway conditions using - the `GatewayConditionType` and `GatewayConditionReason` constants - so that operators and tools can converge on a common vocabulary - to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" - * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port - defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes - that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this - listener. - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status - corresponds to. - 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 - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds - supported by this listener. This MUST represent the kinds - an implementation supports for that Listener configuration. - \n If kinds are specified in Spec that are not supported, - they MUST NOT appear in this list and an implementation MUST - set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" - reason. If both valid and invalid Route kinds are specified, - the implementation MUST reference the valid Route kinds that - have been specified." - items: - description: RouteGroupKind indicates the group and kind of - a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml b/internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml new file mode 100644 index 0000000000..dd8a372e99 --- /dev/null +++ b/internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml @@ -0,0 +1,1416 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 + gateway.networking.k8s.io/bundle-version: v0.6.0-dev + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling + infrastructure by binding Listeners to a set of IP addresses. + 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 Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional + and behavior can depend on the implementation. If a value is set + in the spec and the requested address is invalid or unavailable, + the implementation MUST indicate this in the associated entry in + GatewayStatus.Addresses. \n The Addresses field represents a request + for the address(es) on the \"outside of the Gateway\", that traffic + bound for this Gateway will use. This could be the IP address or + hostname of an external load balancer or other networking infrastructure, + or some other address that traffic will be sent to. \n The .listener.hostname + field is used to route traffic that has already arrived at the Gateway + to the correct in-cluster destination. \n If no Addresses are specified, + the implementation MAY schedule the Gateway in an implementation-specific + manner, assigning an appropriate set of Addresses. \n The implementation + MUST bind all Listeners to every GatewayAddress that it assigns + to the Gateway and add a corresponding entry in GatewayStatus.Addresses. + \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name + of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. At + least one Listener MUST be specified. \n Each listener in a Gateway + must have a unique combination of Hostname, Port, and Protocol. + \n An implementation MAY group Listeners by Port and then collapse + each group of Listeners into a single Listener if the implementation + determines that the Listeners in the group are \"compatible\". An + implementation MAY also group together and collapse compatible Listeners + belonging to different Gateways. \n For example, an implementation + might consider Listeners to be compatible with each other if all + of the following conditions are met: \n 1. Either each Listener + within the group specifies the \"HTTP\" Protocol or each Listener + within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. + \n 2. Each Listener within the group specifies a Hostname that is + unique within the group. \n 3. As a special case, one Listener + within a group may omit Hostname, in which case this Listener + matches when no other Listener matches. \n If the implementation + does collapse compatible Listeners, the hostname provided in the + incoming client request MUST be matched to a Listener to find the + correct set of Routes. The incoming hostname MUST be matched using + the Hostname field for each Listener in order of most to least specific. + That is, exact matches must be processed before wildcard matches. + \n If this field specifies multiple Listeners that have the same + Port value but are not compatible, the implementation must raise + a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint + where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that + MAY be attached to a Listener and the trusted namespaces where + those Route resources MAY be present. \n Although a client + request may match multiple route rules, only one rule may + ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: \n * The most + specific match as defined by the Route type. * The oldest + Route based on creation timestamp. For example, a Route with + \ a creation timestamp of \"2020-09-08 01:02:03\" is given + precedence over a Route with a creation timestamp of \"2020-09-08 + 01:02:04\". * If everything else is equivalent, the Route + appearing first in alphabetical order (namespace/name) should + be given precedence. For example, foo/bar is given precedence + over foo/baz. \n All valid rules within a Route attached to + this Listener should be implemented. Invalid Route rules can + be ignored (sometimes that will mean the full Route). If a + Route rule transitions from valid to invalid, support for + that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, + the rest of the rules within that Route should still be supported. + \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes + that are allowed to bind to this Gateway Listener. When + unspecified or empty, the kinds of Routes selected are + determined using the Listener protocol. \n A RouteGroupKind + MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's + Protocol field. If an implementation does not support + or recognize this resource type, it MUST set the \"ResolvedRefs\" + condition to False for this Listener with the \"InvalidRouteKinds\" + reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which + Routes may be attached to this Listener. This is restricted + to the namespace of this Gateway by default. \n Support: + Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected + for this Gateway. Possible values are: * All: Routes + in all namespaces may be used by this Gateway. * Selector: + Routes in namespaces selected by the selector may + be used by this Gateway. * Same: Only Routes in + the same namespace may be used by this Gateway. \n + Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is + set to \"Selector\". In that case, only Routes in + Namespaces matching this Selector will be selected + by this Gateway. This field is ignored for other values + of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match + for protocol types that define this concept. When unspecified, + all hostnames are matched. This field is ignored for protocols + that don't require hostname based matching. \n Implementations + MUST apply Hostname matching appropriately for each of the + following protocols: \n * TLS: The Listener Hostname MUST + match the SNI. * HTTP: The Listener Hostname MUST match the + Host header of the request. * HTTPS: The Listener Hostname + SHOULD match at both the TLS and HTTP protocol layers as + described above. If an implementation does not ensure that + both the SNI and Host header match the Listener hostname, + \ it MUST clearly document that. \n For HTTPRoute and TLSRoute + resources, there is an interaction with the `spec.hostnames` + array. When both listener and route specify hostnames, there + MUST be an intersection between the values for a Route to + be accepted. For more information, refer to the Route specific + Hostnames documentation. \n Hostnames that are prefixed with + a wildcard label (`*.`) are interpreted as a suffix match. + That means that a match for `*.example.com` would match both + `test.example.com`, and `foo.test.example.com`, but not `example.com`. + \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 + name: + description: "Name is the name of the Listener. This name MUST + be unique within a 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 + port: + description: "Port is the network port. Multiple listeners may + use the same port, subject to the Listener compatibility rules. + \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener + expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. + This field is required if the Protocol field is \"HTTPS\" + or \"TLS\". It is invalid to set this field if the Protocol + field is \"HTTP\", \"TCP\", or \"UDP\". \n The association + of SNIs to Certificate defined in GatewayTLSConfig is defined + based on the Hostname field for this listener. \n The GatewayClass + MUST use the longest matching SNI out of all available certificates + for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references + to Kubernetes objects that contains TLS certificates and + private keys. These certificates are used to establish + a TLS handshake for requests that match the hostname of + the associated listener. \n A single CertificateRef to + a Kubernetes Secret has \"Core\" support. Implementations + MAY choose to support attaching multiple certificates + to a Listener, but this behavior is implementation-specific. + \n References to a resource in different namespace are + invalid UNLESS there is a ReferenceGrant in the target + namespace that allows the certificate to be attached. + If a ReferenceGrant does not allow this reference, the + \"ResolvedRefs\" condition MUST be set to False for this + listener with the \"InvalidCertificateRef\" reason. \n + This field is required to have at least one element when + the mode is set to \"Terminate\" (default) and is optional + otherwise. \n CertificateRefs can reference to standard + Kubernetes resources, i.e. Secret, or implementation-specific + custom resources. \n Support: Core - A single reference + to a Kubernetes Secret of type kubernetes.io/tls \n Support: + Implementation-specific (More than one reference or other + resource types)" + items: + description: "SecretObjectReference identifies an API + object including its namespace, defaulting to Secret. + \n 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: + default: "" + description: Group is the group of the referent. For + example, "networking.k8s.io". When unspecified (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: + default: Secret + 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 + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. + \n Note that when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to + allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS + session initiated by the client. There are two possible + modes: \n - Terminate: The TLS session between the downstream + client and the Gateway is terminated at the Gateway. + This mode requires certificateRefs to be set and contain + at least one element. - Passthrough: The TLS session is + NOT terminated by the Gateway. This implies that the + Gateway can't decipher the TLS stream except for the + ClientHello message of the TLS protocol. CertificateRefs + field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation + in Gateway API. This is used for validation of maps + such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation + in that case is based on the entire size of the annotations + struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable + extended TLS configuration for each implementation. For + example, configuring the minimum TLS version or supported + cipher suites. \n A set of common keys MAY be defined + by the API in the future. To avoid any ambiguity, implementation-specific + definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by + Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been + bound to the Gateway. These addresses may differ from the addresses + in the Spec, e.g. if the Gateway automatically assigns an address + from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: "Conditions describe the current conditions of the Gateway. + \n Implementations should prefer to express Gateway conditions using + the `GatewayConditionType` and `GatewayConditionReason` constants + so that operators and tools can converge on a common vocabulary + to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" + * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes + that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, type FooStatus struct{ + \ // Represents the observations of a foo's current state. + \ // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // + +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + 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 + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds + supported by this listener. This MUST represent the kinds + an implementation supports for that Listener configuration. + \n If kinds are specified in Spec that are not supported, + they MUST NOT appear in this list and an implementation MUST + set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" + reason. If both valid and invalid Route kinds are specified, + the implementation MUST reference the valid Route kinds that + have been specified." + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: false + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling + infrastructure by binding Listeners to a set of IP addresses. + 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 Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional + and behavior can depend on the implementation. If a value is set + in the spec and the requested address is invalid or unavailable, + the implementation MUST indicate this in the associated entry in + GatewayStatus.Addresses. \n The Addresses field represents a request + for the address(es) on the \"outside of the Gateway\", that traffic + bound for this Gateway will use. This could be the IP address or + hostname of an external load balancer or other networking infrastructure, + or some other address that traffic will be sent to. \n The .listener.hostname + field is used to route traffic that has already arrived at the Gateway + to the correct in-cluster destination. \n If no Addresses are specified, + the implementation MAY schedule the Gateway in an implementation-specific + manner, assigning an appropriate set of Addresses. \n The implementation + MUST bind all Listeners to every GatewayAddress that it assigns + to the Gateway and add a corresponding entry in GatewayStatus.Addresses. + \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name + of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. At + least one Listener MUST be specified. \n Each listener in a Gateway + must have a unique combination of Hostname, Port, and Protocol. + \n An implementation MAY group Listeners by Port and then collapse + each group of Listeners into a single Listener if the implementation + determines that the Listeners in the group are \"compatible\". An + implementation MAY also group together and collapse compatible Listeners + belonging to different Gateways. \n For example, an implementation + might consider Listeners to be compatible with each other if all + of the following conditions are met: \n 1. Either each Listener + within the group specifies the \"HTTP\" Protocol or each Listener + within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. + \n 2. Each Listener within the group specifies a Hostname that is + unique within the group. \n 3. As a special case, one Listener + within a group may omit Hostname, in which case this Listener + matches when no other Listener matches. \n If the implementation + does collapse compatible Listeners, the hostname provided in the + incoming client request MUST be matched to a Listener to find the + correct set of Routes. The incoming hostname MUST be matched using + the Hostname field for each Listener in order of most to least specific. + That is, exact matches must be processed before wildcard matches. + \n If this field specifies multiple Listeners that have the same + Port value but are not compatible, the implementation must raise + a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint + where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that + MAY be attached to a Listener and the trusted namespaces where + those Route resources MAY be present. \n Although a client + request may match multiple route rules, only one rule may + ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: \n * The most + specific match as defined by the Route type. * The oldest + Route based on creation timestamp. For example, a Route with + \ a creation timestamp of \"2020-09-08 01:02:03\" is given + precedence over a Route with a creation timestamp of \"2020-09-08 + 01:02:04\". * If everything else is equivalent, the Route + appearing first in alphabetical order (namespace/name) should + be given precedence. For example, foo/bar is given precedence + over foo/baz. \n All valid rules within a Route attached to + this Listener should be implemented. Invalid Route rules can + be ignored (sometimes that will mean the full Route). If a + Route rule transitions from valid to invalid, support for + that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, + the rest of the rules within that Route should still be supported. + \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes + that are allowed to bind to this Gateway Listener. When + unspecified or empty, the kinds of Routes selected are + determined using the Listener protocol. \n A RouteGroupKind + MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's + Protocol field. If an implementation does not support + or recognize this resource type, it MUST set the \"ResolvedRefs\" + condition to False for this Listener with the \"InvalidRouteKinds\" + reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which + Routes may be attached to this Listener. This is restricted + to the namespace of this Gateway by default. \n Support: + Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected + for this Gateway. Possible values are: * All: Routes + in all namespaces may be used by this Gateway. * Selector: + Routes in namespaces selected by the selector may + be used by this Gateway. * Same: Only Routes in + the same namespace may be used by this Gateway. \n + Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is + set to \"Selector\". In that case, only Routes in + Namespaces matching this Selector will be selected + by this Gateway. This field is ignored for other values + of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match + for protocol types that define this concept. When unspecified, + all hostnames are matched. This field is ignored for protocols + that don't require hostname based matching. \n Implementations + MUST apply Hostname matching appropriately for each of the + following protocols: \n * TLS: The Listener Hostname MUST + match the SNI. * HTTP: The Listener Hostname MUST match the + Host header of the request. * HTTPS: The Listener Hostname + SHOULD match at both the TLS and HTTP protocol layers as + described above. If an implementation does not ensure that + both the SNI and Host header match the Listener hostname, + \ it MUST clearly document that. \n For HTTPRoute and TLSRoute + resources, there is an interaction with the `spec.hostnames` + array. When both listener and route specify hostnames, there + MUST be an intersection between the values for a Route to + be accepted. For more information, refer to the Route specific + Hostnames documentation. \n Hostnames that are prefixed with + a wildcard label (`*.`) are interpreted as a suffix match. + That means that a match for `*.example.com` would match both + `test.example.com`, and `foo.test.example.com`, but not `example.com`. + \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 + name: + description: "Name is the name of the Listener. This name MUST + be unique within a 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 + port: + description: "Port is the network port. Multiple listeners may + use the same port, subject to the Listener compatibility rules. + \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener + expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. + This field is required if the Protocol field is \"HTTPS\" + or \"TLS\". It is invalid to set this field if the Protocol + field is \"HTTP\", \"TCP\", or \"UDP\". \n The association + of SNIs to Certificate defined in GatewayTLSConfig is defined + based on the Hostname field for this listener. \n The GatewayClass + MUST use the longest matching SNI out of all available certificates + for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references + to Kubernetes objects that contains TLS certificates and + private keys. These certificates are used to establish + a TLS handshake for requests that match the hostname of + the associated listener. \n A single CertificateRef to + a Kubernetes Secret has \"Core\" support. Implementations + MAY choose to support attaching multiple certificates + to a Listener, but this behavior is implementation-specific. + \n References to a resource in different namespace are + invalid UNLESS there is a ReferenceGrant in the target + namespace that allows the certificate to be attached. + If a ReferenceGrant does not allow this reference, the + \"ResolvedRefs\" condition MUST be set to False for this + listener with the \"InvalidCertificateRef\" reason. \n + This field is required to have at least one element when + the mode is set to \"Terminate\" (default) and is optional + otherwise. \n CertificateRefs can reference to standard + Kubernetes resources, i.e. Secret, or implementation-specific + custom resources. \n Support: Core - A single reference + to a Kubernetes Secret of type kubernetes.io/tls \n Support: + Implementation-specific (More than one reference or other + resource types)" + items: + description: "SecretObjectReference identifies an API + object including its namespace, defaulting to Secret. + \n 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: + default: "" + description: Group is the group of the referent. For + example, "networking.k8s.io". When unspecified (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: + default: Secret + 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 + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. + \n Note that when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to + allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS + session initiated by the client. There are two possible + modes: \n - Terminate: The TLS session between the downstream + client and the Gateway is terminated at the Gateway. + This mode requires certificateRefs to be set and contain + at least one element. - Passthrough: The TLS session is + NOT terminated by the Gateway. This implies that the + Gateway can't decipher the TLS stream except for the + ClientHello message of the TLS protocol. CertificateRefs + field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation + in Gateway API. This is used for validation of maps + such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation + in that case is based on the entire size of the annotations + struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable + extended TLS configuration for each implementation. For + example, configuring the minimum TLS version or supported + cipher suites. \n A set of common keys MAY be defined + by the API in the future. To avoid any ambiguity, implementation-specific + definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by + Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been + bound to the Gateway. These addresses may differ from the addresses + in the Spec, e.g. if the Gateway automatically assigns an address + from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: "Conditions describe the current conditions of the Gateway. + \n Implementations should prefer to express Gateway conditions using + the `GatewayConditionType` and `GatewayConditionReason` constants + so that operators and tools can converge on a common vocabulary + to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" + * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes + that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, type FooStatus struct{ + \ // Represents the observations of a foo's current state. + \ // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // + +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + 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 + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds + supported by this listener. This MUST represent the kinds + an implementation supports for that Listener configuration. + \n If kinds are specified in Spec that are not supported, + they MUST NOT appear in this list and an implementation MUST + set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" + reason. If both valid and invalid Route kinds are specified, + the implementation MUST reference the valid Route kinds that + have been specified." + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml deleted file mode 100644 index 96153b352b..0000000000 --- a/internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml +++ /dev/null @@ -1,430 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 - gateway.networking.k8s.io/bundle-version: v0.5.0 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: gatewayclasses.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GatewayClass - listKind: GatewayClassList - plural: gatewayclasses - shortNames: - - gc - singular: gatewayclass - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the - user for creating Gateway resources. \n It is recommended that this resource - be used as a template for Gateways. This means that a Gateway is based on - the state of the GatewayClass at the time it was created and changes to - the GatewayClass or associated parameters are not propagated down to existing - Gateways. This recommendation is intended to limit the blast radius of changes - to GatewayClass or associated parameters. If implementations choose to propagate - GatewayClass changes to existing Gateways, that MUST be clearly documented - by the implementation. \n Whenever one or more Gateways are using a GatewayClass, - implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` - finalizer on the associated GatewayClass. This ensures that a GatewayClass - associated with a Gateway is not deleted while in use. \n GatewayClass is - a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is - managing Gateways of this class. The value of this field MUST be - a domain prefixed path. \n Example: \"example.net/gateway-controller\". - \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains - the configuration parameters corresponding to the GatewayClass. - This is optional if the controller does not require any additional - configuration. \n ParametersRef can reference a standard Kubernetes - resource, i.e. ConfigMap, or an implementation-specific custom resource. - The resource can be cluster-scoped or namespace-scoped. \n If the - referent cannot be found, the GatewayClass's \"InvalidParameters\" - status condition will be true. \n Support: Custom" - properties: - group: - description: Group is the group of the referent. - 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. - 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 - namespace: - description: Namespace is the namespace of the referent. This - field is required when referring to a Namespace-scoped resource - and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller - for this GatewayClass. \n Controllers should prefer to publish conditions - using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the - user for creating Gateway resources. \n It is recommended that this resource - be used as a template for Gateways. This means that a Gateway is based on - the state of the GatewayClass at the time it was created and changes to - the GatewayClass or associated parameters are not propagated down to existing - Gateways. This recommendation is intended to limit the blast radius of changes - to GatewayClass or associated parameters. If implementations choose to propagate - GatewayClass changes to existing Gateways, that MUST be clearly documented - by the implementation. \n Whenever one or more Gateways are using a GatewayClass, - implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` - finalizer on the associated GatewayClass. This ensures that a GatewayClass - associated with a Gateway is not deleted while in use. \n GatewayClass is - a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is - managing Gateways of this class. The value of this field MUST be - a domain prefixed path. \n Example: \"example.net/gateway-controller\". - \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains - the configuration parameters corresponding to the GatewayClass. - This is optional if the controller does not require any additional - configuration. \n ParametersRef can reference a standard Kubernetes - resource, i.e. ConfigMap, or an implementation-specific custom resource. - The resource can be cluster-scoped or namespace-scoped. \n If the - referent cannot be found, the GatewayClass's \"InvalidParameters\" - status condition will be true. \n Support: Custom" - properties: - group: - description: Group is the group of the referent. - 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. - 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 - namespace: - description: Namespace is the namespace of the referent. This - field is required when referring to a Namespace-scoped resource - and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller - for this GatewayClass. \n Controllers should prefer to publish conditions - using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml b/internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml new file mode 100644 index 0000000000..b85fc19dcf --- /dev/null +++ b/internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml @@ -0,0 +1,430 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 + gateway.networking.k8s.io/bundle-version: v0.6.0-dev + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the + user for creating Gateway resources. \n It is recommended that this resource + be used as a template for Gateways. This means that a Gateway is based on + the state of the GatewayClass at the time it was created and changes to + the GatewayClass or associated parameters are not propagated down to existing + Gateways. This recommendation is intended to limit the blast radius of changes + to GatewayClass or associated parameters. If implementations choose to propagate + GatewayClass changes to existing Gateways, that MUST be clearly documented + by the implementation. \n Whenever one or more Gateways are using a GatewayClass, + implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` + finalizer on the associated GatewayClass. This ensures that a GatewayClass + associated with a Gateway is not deleted while in use. \n GatewayClass is + a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is + managing Gateways of this class. The value of this field MUST be + a domain prefixed path. \n Example: \"example.net/gateway-controller\". + \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains + the configuration parameters corresponding to the GatewayClass. + This is optional if the controller does not require any additional + configuration. \n ParametersRef can reference a standard Kubernetes + resource, i.e. ConfigMap, or an implementation-specific custom resource. + The resource can be cluster-scoped or namespace-scoped. \n If the + referent cannot be found, the GatewayClass's \"InvalidParameters\" + status condition will be true. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + 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. + 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 + namespace: + description: Namespace is the namespace of the referent. This + field is required when referring to a Namespace-scoped resource + and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller + for this GatewayClass. \n Controllers should prefer to publish conditions + using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: false + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the + user for creating Gateway resources. \n It is recommended that this resource + be used as a template for Gateways. This means that a Gateway is based on + the state of the GatewayClass at the time it was created and changes to + the GatewayClass or associated parameters are not propagated down to existing + Gateways. This recommendation is intended to limit the blast radius of changes + to GatewayClass or associated parameters. If implementations choose to propagate + GatewayClass changes to existing Gateways, that MUST be clearly documented + by the implementation. \n Whenever one or more Gateways are using a GatewayClass, + implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` + finalizer on the associated GatewayClass. This ensures that a GatewayClass + associated with a Gateway is not deleted while in use. \n GatewayClass is + a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is + managing Gateways of this class. The value of this field MUST be + a domain prefixed path. \n Example: \"example.net/gateway-controller\". + \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains + the configuration parameters corresponding to the GatewayClass. + This is optional if the controller does not require any additional + configuration. \n ParametersRef can reference a standard Kubernetes + resource, i.e. ConfigMap, or an implementation-specific custom resource. + The resource can be cluster-scoped or namespace-scoped. \n If the + referent cannot be found, the GatewayClass's \"InvalidParameters\" + status condition will be true. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + 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. + 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 + namespace: + description: Namespace is the namespace of the referent. This + field is required when referring to a Namespace-scoped resource + and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller + for this GatewayClass. \n Controllers should prefer to publish conditions + using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/httproute-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/httproute-standard-crd.yaml similarity index 79% rename from internal/provider/kubernetes/testdata/in/httproute-experimental-crd.yaml rename to internal/provider/kubernetes/testdata/in/httproute-standard-crd.yaml index 3ebd153573..10ecb0152c 100644 --- a/internal/provider/kubernetes/testdata/in/httproute-experimental-crd.yaml +++ b/internal/provider/kubernetes/testdata/in/httproute-standard-crd.yaml @@ -4,7 +4,7 @@ metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 gateway.networking.k8s.io/bundle-version: v0.6.0-dev - gateway.networking.k8s.io/channel: experimental + gateway.networking.k8s.io/channel: standard creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: @@ -77,29 +77,19 @@ spec: have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding - RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute - is attached to a Listener and that listener already has another - Route (B) of the other type attached and the intersection of the - hostnames of A and B is non-empty, then the implementation MUST - accept exactly one of these two routes, determined by the following - criteria, in order: \n * The oldest Route based on creation timestamp. - * The Route appearing first in alphabetical order by \"{namespace}/{name}\". - \n The rejected Route MUST raise an 'Accepted' condition with a - status of 'False' in the corresponding RouteParentStatus. \n Support: - Core" + RouteParentStatus. \n Support: Core" items: description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname - may be prefixed with a wildcard label (`*.`). The wildcard \n - \tlabel must appear by itself as the first label. \n Hostname - can be \"precise\" which is a domain name without the terminating - dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", - which is a domain name prefixed with a single wildcard label (e.g. - `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* - must consist of lower case alphanumeric characters or '-', and - must start and end with an alphanumeric character. No other punctuation - is allowed." + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." maxLength: 253 minLength: 1 pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -154,38 +144,12 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" + unspecified, this refers to the local namespace 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 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 @@ -460,9 +424,8 @@ spec: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that - when a different namespace is specified, - a ReferenceGrant object with ReferenceGrantTo.Kind=Service - is required in the referent namespace + when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: @@ -475,7 +438,9 @@ spec: description: Port specifies the destination port number to use for this resource. Port is required when the referent is - a Kubernetes Service. For other resources, + a Kubernetes Service. In this case, the + port number is the service port number, + not the target port. For other resources, destination port might be derived from the referent resource or this field. format: int32 @@ -502,57 +467,6 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - path: - description: "Path defines parameters used to - modify the path of the incoming request. The - modified path is then used to construct the - `Location` header. When empty, the request - path is used as-is. \n Support: Extended \n - " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies - the value with which to replace the full - path of a request during a rewrite or - redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies - the value with which to replace the prefix - match of a request during a rewrite or - redirect. For example, a request to \"/foo/bar\" - with a prefix match of \"/foo\" would - be modified to \"/bar\". \n Note that - this matches the behavior of the PathPrefix - match type. This matches full path elements. - A path element refers to the list of labels - in the path split by the `/` separator. - When specified, a trailing `/` is ignored. - For example, the paths `/abc`, `/abc/`, - and `/abc/def` would all match the prefix - `/abc`, but the path `/abcd` would not. - \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path - modifier. Additional types may be added - in a future release of the API. \n Note - that values may be added to this enum, - implementations must ensure that unknown - values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the - Route to `status: False`, with a Reason - of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object port: description: "Port is the port to be used in the value of the `Location` header in the @@ -628,70 +542,8 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect - - URLRewrite - ExtensionRef type: string - urlRewrite: - description: "URLRewrite defines a schema for a - filter that modifies a request during forwarding. - \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used - to replace the Host header value during forwarding. - \n Support: Extended \n " - 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 - path: - description: "Path defines a path rewrite. \n - Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies - the value with which to replace the full - path of a request during a rewrite or - redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies - the value with which to replace the prefix - match of a request during a rewrite or - redirect. For example, a request to \"/foo/bar\" - with a prefix match of \"/foo\" would - be modified to \"/bar\". \n Note that - this matches the behavior of the PathPrefix - match type. This matches full path elements. - A path element refers to the list of labels - in the path split by the `/` separator. - When specified, a trailing `/` is ignored. - For example, the paths `/abc`, `/abc/`, - and `/abc/def` would all match the prefix - `/abc`, but the path `/abcd` would not. - \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path - modifier. Additional types may be added - in a future release of the API. \n Note - that values may be added to this enum, - implementations must ensure that unknown - values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the - Route to `status: False`, with a Reason - of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object required: - type type: object @@ -722,11 +574,11 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n - Note that when a different namespace is specified, a - ReferenceGrant object with ReferenceGrantTo.Kind=Service - is required in the referent namespace to allow that - namespace's owner to accept the reference. See the ReferenceGrant - documentation for details. \n Support: Core" + Note that when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow + that namespace's owner to accept the reference. See + the ReferenceGrant documentation for details. \n Support: + Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -734,9 +586,10 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -975,12 +828,11 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace - is inferred. \n Note that when a different namespace - is specified, a ReferenceGrant object with ReferenceGrantTo.Kind=Service - is required in the referent namespace to allow - that namespace's owner to accept the reference. - See the ReferenceGrant documentation for details. - \n Support: Core" + is inferred. \n Note that when a namespace is + specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -988,9 +840,11 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required - when the referent is a Kubernetes Service. For - other resources, destination port might be derived - from the referent resource or this field. + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -1015,52 +869,6 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - path: - description: "Path defines parameters used to modify - the path of the incoming request. The modified path - is then used to construct the `Location` header. - When empty, the request path is used as-is. \n Support: - Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value - with which to replace the full path of a request - during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the - value with which to replace the prefix match - of a request during a rewrite or redirect. For - example, a request to \"/foo/bar\" with a prefix - match of \"/foo\" would be modified to \"/bar\". - \n Note that this matches the behavior of the - PathPrefix match type. This matches full path - elements. A path element refers to the list - of labels in the path split by the `/` separator. - When specified, a trailing `/` is ignored. For - example, the paths `/abc`, `/abc/`, and `/abc/def` - would all match the prefix `/abc`, but the path - `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. - Additional types may be added in a future release - of the API. \n Note that values may be added - to this enum, implementations must ensure that - unknown values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the Route - to `status: False`, with a Reason of `UnsupportedValue`. - \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object port: description: "Port is the port to be used in the value of the `Location` header in the response. When empty, @@ -1131,66 +939,8 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect - - URLRewrite - ExtensionRef type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter - that modifies a request during forwarding. \n Support: - Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to - replace the Host header value during forwarding. - \n Support: Extended \n " - 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 - path: - description: "Path defines a path rewrite. \n Support: - Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value - with which to replace the full path of a request - during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the - value with which to replace the prefix match - of a request during a rewrite or redirect. For - example, a request to \"/foo/bar\" with a prefix - match of \"/foo\" would be modified to \"/bar\". - \n Note that this matches the behavior of the - PathPrefix match type. This matches full path - elements. A path element refers to the list - of labels in the path split by the `/` separator. - When specified, a trailing `/` is ignored. For - example, the paths `/abc`, `/abc/`, and `/abc/def` - would all match the prefix `/abc`, but the path - `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. - Additional types may be added in a future release - of the API. \n Note that values may be added - to this enum, implementations must ensure that - unknown values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the Route - to `status: False`, with a Reason of `UnsupportedValue`. - \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object required: - type type: object @@ -1226,10 +976,11 @@ spec: Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, - matching precedence MUST be granted to the first matching - rule meeting the above criteria. \n When no rules matching - a request have been successfully attached to the parent a - request is coming from, a HTTP 404 status code MUST be returned." + matching precedence MUST be granted to the FIRST matching + rule (in list order) meeting the above criteria. \n When no + rules matching a request have been successfully attached to + the parent a request is coming from, a HTTP 404 status code + MUST be returned." items: description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are @@ -1354,7 +1105,17 @@ spec: param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST - be ignored." + be ignored. \n If a query param is repeated in + an HTTP request, the behavior is purposely left + undefined, since different data planes have different + capabilities. However, it's *recommended* that + implementations should match against the first + value of the param if the data plane supports + it, as this behavior is expected in other load + balancing contexts outside of the Gateway API. + Users should not route traffic based on repeated + query params to guard themselves against potential + differences in the implementations." maxLength: 256 minLength: 1 type: string @@ -1550,40 +1311,12 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" + When unspecified, this refers to the local namespace 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 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 @@ -1623,8 +1356,8 @@ spec: required: - spec type: object - served: true - storage: true + served: false + storage: false subresources: status: {} - additionalPrinterColumns: @@ -1753,38 +1486,12 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" + unspecified, this refers to the local namespace 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 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 @@ -2073,7 +1780,9 @@ spec: description: Port specifies the destination port number to use for this resource. Port is required when the referent is - a Kubernetes Service. For other resources, + a Kubernetes Service. In this case, the + port number is the service port number, + not the target port. For other resources, destination port might be derived from the referent resource or this field. format: int32 @@ -2100,57 +1809,6 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - path: - description: "Path defines parameters used to - modify the path of the incoming request. The - modified path is then used to construct the - `Location` header. When empty, the request - path is used as-is. \n Support: Extended \n - " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies - the value with which to replace the full - path of a request during a rewrite or - redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies - the value with which to replace the prefix - match of a request during a rewrite or - redirect. For example, a request to \"/foo/bar\" - with a prefix match of \"/foo\" would - be modified to \"/bar\". \n Note that - this matches the behavior of the PathPrefix - match type. This matches full path elements. - A path element refers to the list of labels - in the path split by the `/` separator. - When specified, a trailing `/` is ignored. - For example, the paths `/abc`, `/abc/`, - and `/abc/def` would all match the prefix - `/abc`, but the path `/abcd` would not. - \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path - modifier. Additional types may be added - in a future release of the API. \n Note - that values may be added to this enum, - implementations must ensure that unknown - values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the - Route to `status: False`, with a Reason - of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object port: description: "Port is the port to be used in the value of the `Location` header in the @@ -2226,70 +1884,8 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect - - URLRewrite - ExtensionRef type: string - urlRewrite: - description: "URLRewrite defines a schema for a - filter that modifies a request during forwarding. - \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used - to replace the Host header value during forwarding. - \n Support: Extended \n " - 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 - path: - description: "Path defines a path rewrite. \n - Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies - the value with which to replace the full - path of a request during a rewrite or - redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies - the value with which to replace the prefix - match of a request during a rewrite or - redirect. For example, a request to \"/foo/bar\" - with a prefix match of \"/foo\" would - be modified to \"/bar\". \n Note that - this matches the behavior of the PathPrefix - match type. This matches full path elements. - A path element refers to the list of labels - in the path split by the `/` separator. - When specified, a trailing `/` is ignored. - For example, the paths `/abc`, `/abc/`, - and `/abc/def` would all match the prefix - `/abc`, but the path `/abcd` would not. - \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path - modifier. Additional types may be added - in a future release of the API. \n Note - that values may be added to this enum, - implementations must ensure that unknown - values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the - Route to `status: False`, with a Reason - of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object required: - type type: object @@ -2332,9 +1928,10 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -2585,9 +2182,11 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required - when the referent is a Kubernetes Service. For - other resources, destination port might be derived - from the referent resource or this field. + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -2612,52 +2211,6 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - path: - description: "Path defines parameters used to modify - the path of the incoming request. The modified path - is then used to construct the `Location` header. - When empty, the request path is used as-is. \n Support: - Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value - with which to replace the full path of a request - during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the - value with which to replace the prefix match - of a request during a rewrite or redirect. For - example, a request to \"/foo/bar\" with a prefix - match of \"/foo\" would be modified to \"/bar\". - \n Note that this matches the behavior of the - PathPrefix match type. This matches full path - elements. A path element refers to the list - of labels in the path split by the `/` separator. - When specified, a trailing `/` is ignored. For - example, the paths `/abc`, `/abc/`, and `/abc/def` - would all match the prefix `/abc`, but the path - `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. - Additional types may be added in a future release - of the API. \n Note that values may be added - to this enum, implementations must ensure that - unknown values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the Route - to `status: False`, with a Reason of `UnsupportedValue`. - \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object port: description: "Port is the port to be used in the value of the `Location` header in the response. When empty, @@ -2728,66 +2281,8 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect - - URLRewrite - ExtensionRef type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter - that modifies a request during forwarding. \n Support: - Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to - replace the Host header value during forwarding. - \n Support: Extended \n " - 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 - path: - description: "Path defines a path rewrite. \n Support: - Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value - with which to replace the full path of a request - during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the - value with which to replace the prefix match - of a request during a rewrite or redirect. For - example, a request to \"/foo/bar\" with a prefix - match of \"/foo\" would be modified to \"/bar\". - \n Note that this matches the behavior of the - PathPrefix match type. This matches full path - elements. A path element refers to the list - of labels in the path split by the `/` separator. - When specified, a trailing `/` is ignored. For - example, the paths `/abc`, `/abc/`, and `/abc/def` - would all match the prefix `/abc`, but the path - `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. - Additional types may be added in a future release - of the API. \n Note that values may be added - to this enum, implementations must ensure that - unknown values will not cause a crash. \n Unknown - values here must result in the implementation - setting the Attached Condition for the Route - to `status: False`, with a Reason of `UnsupportedValue`. - \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object required: - type type: object @@ -2823,10 +2318,11 @@ spec: Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, - matching precedence MUST be granted to the first matching - rule meeting the above criteria. \n When no rules matching - a request have been successfully attached to the parent a - request is coming from, a HTTP 404 status code MUST be returned." + matching precedence MUST be granted to the FIRST matching + rule (in list order) meeting the above criteria. \n When no + rules matching a request have been successfully attached to + the parent a request is coming from, a HTTP 404 status code + MUST be returned." items: description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are @@ -2951,7 +2447,17 @@ spec: param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST - be ignored." + be ignored. \n If a query param is repeated in + an HTTP request, the behavior is purposely left + undefined, since different data planes have different + capabilities. However, it's *recommended* that + implementations should match against the first + value of the param if the data plane supports + it, as this behavior is expected in other load + balancing contexts outside of the Gateway API. + Users should not route traffic based on repeated + query params to guard themselves against potential + differences in the implementations." maxLength: 256 minLength: 1 type: string @@ -3147,40 +2653,12 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" + When unspecified, this refers to the local namespace 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 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 @@ -3221,7 +2699,7 @@ spec: - spec type: object served: true - storage: false + storage: true subresources: status: {} status: diff --git a/internal/provider/kubernetes/testdata/in/tlsroute-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/tlsroute-experimental-crd.yaml new file mode 100644 index 0000000000..fb30827b00 --- /dev/null +++ b/internal/provider/kubernetes/testdata/in/tlsroute-experimental-crd.yaml @@ -0,0 +1,542 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 + gateway.networking.k8s.io/bundle-version: v0.6.0-dev + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured + to match against TLS-specific metadata. This allows more flexibility in + matching streams for a given TLS listener. \n If you need to forward traffic + to a single target for a TLS listener, you could choose to use a TCPRoute + with a TLS listener." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: "Hostnames defines a set of SNI names that should match + against the SNI attribute of TLS ClientHello message in TLS handshake. + This matches the RFC 1123 definition of a hostname with 2 notable + exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. + 2. A hostname may be prefixed with a wildcard label (`*.`). The + wildcard label must appear by itself as the first label. \n If + a hostname is specified by both the Listener and TLSRoute, there + must be at least one intersecting hostname for the TLSRoute to be + attached to the Listener. For example: \n * A Listener with `test.example.com` + as the hostname matches TLSRoutes that have either not specified + any hostnames, or have specified at least one of `test.example.com` + or `*.example.com`. * A Listener with `*.example.com` as the hostname + matches TLSRoutes that have either not specified any hostnames + or have specified at least one hostname that matches the Listener + hostname. For example, `test.example.com` and `*.example.com` + would both match. On the other hand, `example.com` and `test.example.net` + would not match. \n If both the Listener and TLSRoute have specified + hostnames, any TLSRoute hostnames that do not match the Listener + hostname MUST be ignored. For example, if a Listener specified `*.example.com`, + and the TLSRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. \n If both + the Listener and TLSRoute have specified hostnames, and none match + with the criteria above, then the TLSRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status + of `False` in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network + host. This matches the RFC 1123 definition of a hostname with + 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." + 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 + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. \n The only kind of parent resource + with \"Core\" support is Gateway. This API may be extended in the + future to support additional kinds of parent resources such as one + of the route kinds. \n It is invalid to reference an identical parent + more than once. It is valid to reference multiple distinct sections + within the same parent resource, such as 2 Listeners within a Gateway. + \n It is possible to separately reference multiple distinct objects + that may be collapsed by an implementation. For example, some implementations + may choose to merge compatible Gateway Listeners together. If that + is the case, the list of routes attached to those resources should + also be merged." + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). The only kind of parent resource with \"Core\" support + is Gateway. This API may be extended in the future to support + additional kinds of parent resources, such as HTTPRoute. \n 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." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. \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 Support: Core + (Gateway) \n Support: Custom (Other Resources)" + 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 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 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. \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 + maxItems: 32 + type: array + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. If unspecified or invalid (refers + to a non-existent resource or a Service with no endpoints), + the rule performs no forwarding; if no filters are specified + that would result in a response being sent, the underlying + implementation must actively reject request attempts to this + backend, by rejecting the connection or returning a 500 status + code. Request rejections must respect weight; if an invalid + backend is requested to have 80% of requests, then 80% of + requests must be rejected instead. \n Support: Core for Kubernetes + Service \n Support: Custom for any other resource \n Support + for weight: Extended" + items: + description: "BackendRef defines how a Route should forward + a request to a Kubernetes resource. \n Note that when a + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's owner + to accept the reference. See the ReferenceGrant documentation + for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, + "networking.k8s.io". When unspecified (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: + default: Service + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". Defaults to "Service" when + not specified. + 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 + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. \n + Note that when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow + that namespace's owner to accept the reference. See + the ReferenceGrant documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number + to use for this resource. Port is required when the + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1. \n Support for this field varies based + on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) + that are associated with the route, and the status of the route + with respect to each parent. When this route attaches to a parent, + the controller that manages the parent must add an entry to this + list when the controller first sees the route and should update + the entry as appropriate when the route or gateway is modified. + \n Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this + API can only populate Route status for the Gateways/parent resources + they are responsible for. \n A maximum of 32 Gateways will be represented + in this list. An empty list means the route has not been attached + to any Gateway." + items: + description: RouteParentStatus describes the status of a route with + respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with + respect to the Gateway. Note that the route's availability + is also subject to the Gateway's own status conditions and + listener status. \n If the Route's ParentRef specifies an + existing Gateway that supports Routes of this kind AND that + Gateway's controller has sufficient access, then that Gateway's + controller MUST set the \"Accepted\" condition on the Route, + to indicate whether the route has been accepted or rejected + by the Gateway, and why. \n A Route MUST be considered \"Accepted\" + if at least one of the Route's rules is implemented by the + Gateway. \n There are a number of cases where the \"Accepted\" + condition may not be set due to lack of controller visibility, + that includes when: \n * The Route refers to a non-existent + parent. * The Route is of a type that the controller does + not support. * The Route is in a namespace the controller + does not have access to." + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, type FooStatus struct{ + \ // Represents the observations of a foo's current state. + \ // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // + +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + 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 + parentRef: + description: ParentRef corresponds with a ParentRef in the spec + that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. \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 Support: + Core (Gateway) \n Support: Custom (Other Resources)" + 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 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 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. \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 + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] From 0b3f5be1dae38a8b4b33c7c6f72ec03772b49cbf Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 16:41:59 +0530 Subject: [PATCH 03/30] keep other testdata changes out of this PR Signed-off-by: Shubham Chauhan --- .../testdata/in/gateway-experimental-crd.yaml | 1417 +++++++++++++++++ .../testdata/in/gateway-standard-crd.yaml | 1416 ---------------- .../in/gatewayclass-experimental-crd.yaml | 430 +++++ .../in/gatewayclass-standard-crd.yaml | 430 ----- ...d.yaml => httproute-experimental-crd.yaml} | 700 ++++++-- 5 files changed, 2458 insertions(+), 1935 deletions(-) create mode 100644 internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml delete mode 100644 internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml create mode 100644 internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml delete mode 100644 internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml rename internal/provider/kubernetes/testdata/in/{httproute-standard-crd.yaml => httproute-experimental-crd.yaml} (79%) diff --git a/internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml new file mode 100644 index 0000000000..46df0aed5c --- /dev/null +++ b/internal/provider/kubernetes/testdata/in/gateway-experimental-crd.yaml @@ -0,0 +1,1417 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 + gateway.networking.k8s.io/bundle-version: v0.5.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling + infrastructure by binding Listeners to a set of IP addresses. + 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 Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional + and behavior can depend on the implementation. If a value is set + in the spec and the requested address is invalid or unavailable, + the implementation MUST indicate this in the associated entry in + GatewayStatus.Addresses. \n The Addresses field represents a request + for the address(es) on the \"outside of the Gateway\", that traffic + bound for this Gateway will use. This could be the IP address or + hostname of an external load balancer or other networking infrastructure, + or some other address that traffic will be sent to. \n The .listener.hostname + field is used to route traffic that has already arrived at the Gateway + to the correct in-cluster destination. \n If no Addresses are specified, + the implementation MAY schedule the Gateway in an implementation-specific + manner, assigning an appropriate set of Addresses. \n The implementation + MUST bind all Listeners to every GatewayAddress that it assigns + to the Gateway and add a corresponding entry in GatewayStatus.Addresses. + \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name + of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. At + least one Listener MUST be specified. \n Each listener in a Gateway + must have a unique combination of Hostname, Port, and Protocol. + \n An implementation MAY group Listeners by Port and then collapse + each group of Listeners into a single Listener if the implementation + determines that the Listeners in the group are \"compatible\". An + implementation MAY also group together and collapse compatible Listeners + belonging to different Gateways. \n For example, an implementation + might consider Listeners to be compatible with each other if all + of the following conditions are met: \n 1. Either each Listener + within the group specifies the \"HTTP\" Protocol or each Listener + within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. + \n 2. Each Listener within the group specifies a Hostname that is + unique within the group. \n 3. As a special case, one Listener + within a group may omit Hostname, in which case this Listener + matches when no other Listener matches. \n If the implementation + does collapse compatible Listeners, the hostname provided in the + incoming client request MUST be matched to a Listener to find the + correct set of Routes. The incoming hostname MUST be matched using + the Hostname field for each Listener in order of most to least specific. + That is, exact matches must be processed before wildcard matches. + \n If this field specifies multiple Listeners that have the same + Port value but are not compatible, the implementation must raise + a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint + where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that + MAY be attached to a Listener and the trusted namespaces where + those Route resources MAY be present. \n Although a client + request may match multiple route rules, only one rule may + ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: \n * The most + specific match as defined by the Route type. * The oldest + Route based on creation timestamp. For example, a Route with + \ a creation timestamp of \"2020-09-08 01:02:03\" is given + precedence over a Route with a creation timestamp of \"2020-09-08 + 01:02:04\". * If everything else is equivalent, the Route + appearing first in alphabetical order (namespace/name) should + be given precedence. For example, foo/bar is given precedence + over foo/baz. \n All valid rules within a Route attached to + this Listener should be implemented. Invalid Route rules can + be ignored (sometimes that will mean the full Route). If a + Route rule transitions from valid to invalid, support for + that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, + the rest of the rules within that Route should still be supported. + \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes + that are allowed to bind to this Gateway Listener. When + unspecified or empty, the kinds of Routes selected are + determined using the Listener protocol. \n A RouteGroupKind + MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's + Protocol field. If an implementation does not support + or recognize this resource type, it MUST set the \"ResolvedRefs\" + condition to False for this Listener with the \"InvalidRouteKinds\" + reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which + Routes may be attached to this Listener. This is restricted + to the namespace of this Gateway by default. \n Support: + Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected + for this Gateway. Possible values are: * All: Routes + in all namespaces may be used by this Gateway. * Selector: + Routes in namespaces selected by the selector may + be used by this Gateway. * Same: Only Routes in + the same namespace may be used by this Gateway. \n + Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is + set to \"Selector\". In that case, only Routes in + Namespaces matching this Selector will be selected + by this Gateway. This field is ignored for other values + of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match + for protocol types that define this concept. When unspecified, + all hostnames are matched. This field is ignored for protocols + that don't require hostname based matching. \n Implementations + MUST apply Hostname matching appropriately for each of the + following protocols: \n * TLS: The Listener Hostname MUST + match the SNI. * HTTP: The Listener Hostname MUST match the + Host header of the request. * HTTPS: The Listener Hostname + SHOULD match at both the TLS and HTTP protocol layers as + described above. If an implementation does not ensure that + both the SNI and Host header match the Listener hostname, + \ it MUST clearly document that. \n For HTTPRoute and TLSRoute + resources, there is an interaction with the `spec.hostnames` + array. When both listener and route specify hostnames, there + MUST be an intersection between the values for a Route to + be accepted. For more information, refer to the Route specific + Hostnames documentation. \n Hostnames that are prefixed with + a wildcard label (`*.`) are interpreted as a suffix match. + That means that a match for `*.example.com` would match both + `test.example.com`, and `foo.test.example.com`, but not `example.com`. + \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 + name: + description: "Name is the name of the Listener. This name MUST + be unique within a 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 + port: + description: "Port is the network port. Multiple listeners may + use the same port, subject to the Listener compatibility rules. + \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener + expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. + This field is required if the Protocol field is \"HTTPS\" + or \"TLS\". It is invalid to set this field if the Protocol + field is \"HTTP\", \"TCP\", or \"UDP\". \n The association + of SNIs to Certificate defined in GatewayTLSConfig is defined + based on the Hostname field for this listener. \n The GatewayClass + MUST use the longest matching SNI out of all available certificates + for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references + to Kubernetes objects that contains TLS certificates and + private keys. These certificates are used to establish + a TLS handshake for requests that match the hostname of + the associated listener. \n A single CertificateRef to + a Kubernetes Secret has \"Core\" support. Implementations + MAY choose to support attaching multiple certificates + to a Listener, but this behavior is implementation-specific. + \n References to a resource in different namespace are + invalid UNLESS there is a ReferenceGrant in the target + namespace that allows the certificate to be attached. + If a ReferenceGrant does not allow this reference, the + \"ResolvedRefs\" condition MUST be set to False for this + listener with the \"InvalidCertificateRef\" reason. \n + This field is required to have at least one element when + the mode is set to \"Terminate\" (default) and is optional + otherwise. \n CertificateRefs can reference to standard + Kubernetes resources, i.e. Secret, or implementation-specific + custom resources. \n Support: Core - A single reference + to a Kubernetes Secret of type kubernetes.io/tls \n Support: + Implementation-specific (More than one reference or other + resource types)" + items: + description: "SecretObjectReference identifies an API + object including its namespace, defaulting to Secret. + \n 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: + default: "" + description: Group is the group of the referent. For + example, "networking.k8s.io". When unspecified (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: + default: Secret + 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 + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. + \n Note that when a different namespace is specified, + a ReferenceGrant object with ReferenceGrantTo.Kind=Secret + is required in the referent namespace to allow that + namespace's owner to accept the reference. See the + ReferenceGrant documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS + session initiated by the client. There are two possible + modes: \n - Terminate: The TLS session between the downstream + client and the Gateway is terminated at the Gateway. + This mode requires certificateRefs to be set and contain + at least one element. - Passthrough: The TLS session is + NOT terminated by the Gateway. This implies that the + Gateway can't decipher the TLS stream except for the + ClientHello message of the TLS protocol. CertificateRefs + field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation + in Gateway API. This is used for validation of maps + such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation + in that case is based on the entire size of the annotations + struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable + extended TLS configuration for each implementation. For + example, configuring the minimum TLS version or supported + cipher suites. \n A set of common keys MAY be defined + by the API in the future. To avoid any ambiguity, implementation-specific + definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by + Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been + bound to the Gateway. These addresses may differ from the addresses + in the Spec, e.g. if the Gateway automatically assigns an address + from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: "Conditions describe the current conditions of the Gateway. + \n Implementations should prefer to express Gateway conditions using + the `GatewayConditionType` and `GatewayConditionReason` constants + so that operators and tools can converge on a common vocabulary + to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" + * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes + that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, type FooStatus struct{ + \ // Represents the observations of a foo's current state. + \ // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // + +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + 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 + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds + supported by this listener. This MUST represent the kinds + an implementation supports for that Listener configuration. + \n If kinds are specified in Spec that are not supported, + they MUST NOT appear in this list and an implementation MUST + set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" + reason. If both valid and invalid Route kinds are specified, + the implementation MUST reference the valid Route kinds that + have been specified." + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling + infrastructure by binding Listeners to a set of IP addresses. + 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 Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional + and behavior can depend on the implementation. If a value is set + in the spec and the requested address is invalid or unavailable, + the implementation MUST indicate this in the associated entry in + GatewayStatus.Addresses. \n The Addresses field represents a request + for the address(es) on the \"outside of the Gateway\", that traffic + bound for this Gateway will use. This could be the IP address or + hostname of an external load balancer or other networking infrastructure, + or some other address that traffic will be sent to. \n The .listener.hostname + field is used to route traffic that has already arrived at the Gateway + to the correct in-cluster destination. \n If no Addresses are specified, + the implementation MAY schedule the Gateway in an implementation-specific + manner, assigning an appropriate set of Addresses. \n The implementation + MUST bind all Listeners to every GatewayAddress that it assigns + to the Gateway and add a corresponding entry in GatewayStatus.Addresses. + \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name + of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. At + least one Listener MUST be specified. \n Each listener in a Gateway + must have a unique combination of Hostname, Port, and Protocol. + \n An implementation MAY group Listeners by Port and then collapse + each group of Listeners into a single Listener if the implementation + determines that the Listeners in the group are \"compatible\". An + implementation MAY also group together and collapse compatible Listeners + belonging to different Gateways. \n For example, an implementation + might consider Listeners to be compatible with each other if all + of the following conditions are met: \n 1. Either each Listener + within the group specifies the \"HTTP\" Protocol or each Listener + within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. + \n 2. Each Listener within the group specifies a Hostname that is + unique within the group. \n 3. As a special case, one Listener + within a group may omit Hostname, in which case this Listener + matches when no other Listener matches. \n If the implementation + does collapse compatible Listeners, the hostname provided in the + incoming client request MUST be matched to a Listener to find the + correct set of Routes. The incoming hostname MUST be matched using + the Hostname field for each Listener in order of most to least specific. + That is, exact matches must be processed before wildcard matches. + \n If this field specifies multiple Listeners that have the same + Port value but are not compatible, the implementation must raise + a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint + where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that + MAY be attached to a Listener and the trusted namespaces where + those Route resources MAY be present. \n Although a client + request may match multiple route rules, only one rule may + ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: \n * The most + specific match as defined by the Route type. * The oldest + Route based on creation timestamp. For example, a Route with + \ a creation timestamp of \"2020-09-08 01:02:03\" is given + precedence over a Route with a creation timestamp of \"2020-09-08 + 01:02:04\". * If everything else is equivalent, the Route + appearing first in alphabetical order (namespace/name) should + be given precedence. For example, foo/bar is given precedence + over foo/baz. \n All valid rules within a Route attached to + this Listener should be implemented. Invalid Route rules can + be ignored (sometimes that will mean the full Route). If a + Route rule transitions from valid to invalid, support for + that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, + the rest of the rules within that Route should still be supported. + \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes + that are allowed to bind to this Gateway Listener. When + unspecified or empty, the kinds of Routes selected are + determined using the Listener protocol. \n A RouteGroupKind + MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's + Protocol field. If an implementation does not support + or recognize this resource type, it MUST set the \"ResolvedRefs\" + condition to False for this Listener with the \"InvalidRouteKinds\" + reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which + Routes may be attached to this Listener. This is restricted + to the namespace of this Gateway by default. \n Support: + Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected + for this Gateway. Possible values are: * All: Routes + in all namespaces may be used by this Gateway. * Selector: + Routes in namespaces selected by the selector may + be used by this Gateway. * Same: Only Routes in + the same namespace may be used by this Gateway. \n + Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is + set to \"Selector\". In that case, only Routes in + Namespaces matching this Selector will be selected + by this Gateway. This field is ignored for other values + of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match + for protocol types that define this concept. When unspecified, + all hostnames are matched. This field is ignored for protocols + that don't require hostname based matching. \n Implementations + MUST apply Hostname matching appropriately for each of the + following protocols: \n * TLS: The Listener Hostname MUST + match the SNI. * HTTP: The Listener Hostname MUST match the + Host header of the request. * HTTPS: The Listener Hostname + SHOULD match at both the TLS and HTTP protocol layers as + described above. If an implementation does not ensure that + both the SNI and Host header match the Listener hostname, + \ it MUST clearly document that. \n For HTTPRoute and TLSRoute + resources, there is an interaction with the `spec.hostnames` + array. When both listener and route specify hostnames, there + MUST be an intersection between the values for a Route to + be accepted. For more information, refer to the Route specific + Hostnames documentation. \n Hostnames that are prefixed with + a wildcard label (`*.`) are interpreted as a suffix match. + That means that a match for `*.example.com` would match both + `test.example.com`, and `foo.test.example.com`, but not `example.com`. + \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 + name: + description: "Name is the name of the Listener. This name MUST + be unique within a 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 + port: + description: "Port is the network port. Multiple listeners may + use the same port, subject to the Listener compatibility rules. + \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener + expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. + This field is required if the Protocol field is \"HTTPS\" + or \"TLS\". It is invalid to set this field if the Protocol + field is \"HTTP\", \"TCP\", or \"UDP\". \n The association + of SNIs to Certificate defined in GatewayTLSConfig is defined + based on the Hostname field for this listener. \n The GatewayClass + MUST use the longest matching SNI out of all available certificates + for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references + to Kubernetes objects that contains TLS certificates and + private keys. These certificates are used to establish + a TLS handshake for requests that match the hostname of + the associated listener. \n A single CertificateRef to + a Kubernetes Secret has \"Core\" support. Implementations + MAY choose to support attaching multiple certificates + to a Listener, but this behavior is implementation-specific. + \n References to a resource in different namespace are + invalid UNLESS there is a ReferenceGrant in the target + namespace that allows the certificate to be attached. + If a ReferenceGrant does not allow this reference, the + \"ResolvedRefs\" condition MUST be set to False for this + listener with the \"InvalidCertificateRef\" reason. \n + This field is required to have at least one element when + the mode is set to \"Terminate\" (default) and is optional + otherwise. \n CertificateRefs can reference to standard + Kubernetes resources, i.e. Secret, or implementation-specific + custom resources. \n Support: Core - A single reference + to a Kubernetes Secret of type kubernetes.io/tls \n Support: + Implementation-specific (More than one reference or other + resource types)" + items: + description: "SecretObjectReference identifies an API + object including its namespace, defaulting to Secret. + \n 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: + default: "" + description: Group is the group of the referent. For + example, "networking.k8s.io". When unspecified (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: + default: Secret + 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 + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. + \n Note that when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to + allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS + session initiated by the client. There are two possible + modes: \n - Terminate: The TLS session between the downstream + client and the Gateway is terminated at the Gateway. + This mode requires certificateRefs to be set and contain + at least one element. - Passthrough: The TLS session is + NOT terminated by the Gateway. This implies that the + Gateway can't decipher the TLS stream except for the + ClientHello message of the TLS protocol. CertificateRefs + field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation + in Gateway API. This is used for validation of maps + such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation + in that case is based on the entire size of the annotations + struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable + extended TLS configuration for each implementation. For + example, configuring the minimum TLS version or supported + cipher suites. \n A set of common keys MAY be defined + by the API in the future. To avoid any ambiguity, implementation-specific + definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by + Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been + bound to the Gateway. These addresses may differ from the addresses + in the Spec, e.g. if the Gateway automatically assigns an address + from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Scheduled + description: "Conditions describe the current conditions of the Gateway. + \n Implementations should prefer to express Gateway conditions using + the `GatewayConditionType` and `GatewayConditionReason` constants + so that operators and tools can converge on a common vocabulary + to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" + * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes + that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, type FooStatus struct{ + \ // Represents the observations of a foo's current state. + \ // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // + +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + 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 + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds + supported by this listener. This MUST represent the kinds + an implementation supports for that Listener configuration. + \n If kinds are specified in Spec that are not supported, + they MUST NOT appear in this list and an implementation MUST + set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" + reason. If both valid and invalid Route kinds are specified, + the implementation MUST reference the valid Route kinds that + have been specified." + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + 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 the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml b/internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml deleted file mode 100644 index dd8a372e99..0000000000 --- a/internal/provider/kubernetes/testdata/in/gateway-standard-crd.yaml +++ /dev/null @@ -1,1416 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 - gateway.networking.k8s.io/bundle-version: v0.6.0-dev - gateway.networking.k8s.io/channel: standard - creationTimestamp: null - name: gateways.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: Gateway - listKind: GatewayList - plural: gateways - shortNames: - - gtw - singular: gateway - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling - infrastructure by binding Listeners to a set of IP addresses. - 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 Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional - and behavior can depend on the implementation. If a value is set - in the spec and the requested address is invalid or unavailable, - the implementation MUST indicate this in the associated entry in - GatewayStatus.Addresses. \n The Addresses field represents a request - for the address(es) on the \"outside of the Gateway\", that traffic - bound for this Gateway will use. This could be the IP address or - hostname of an external load balancer or other networking infrastructure, - or some other address that traffic will be sent to. \n The .listener.hostname - field is used to route traffic that has already arrived at the Gateway - to the correct in-cluster destination. \n If no Addresses are specified, - the implementation MAY schedule the Gateway in an implementation-specific - manner, assigning an appropriate set of Addresses. \n The implementation - MUST bind all Listeners to every GatewayAddress that it assigns - to the Gateway and add a corresponding entry in GatewayStatus.Addresses. - \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name - of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define - logical endpoints that are bound on this Gateway's addresses. At - least one Listener MUST be specified. \n Each listener in a Gateway - must have a unique combination of Hostname, Port, and Protocol. - \n An implementation MAY group Listeners by Port and then collapse - each group of Listeners into a single Listener if the implementation - determines that the Listeners in the group are \"compatible\". An - implementation MAY also group together and collapse compatible Listeners - belonging to different Gateways. \n For example, an implementation - might consider Listeners to be compatible with each other if all - of the following conditions are met: \n 1. Either each Listener - within the group specifies the \"HTTP\" Protocol or each Listener - within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. - \n 2. Each Listener within the group specifies a Hostname that is - unique within the group. \n 3. As a special case, one Listener - within a group may omit Hostname, in which case this Listener - matches when no other Listener matches. \n If the implementation - does collapse compatible Listeners, the hostname provided in the - incoming client request MUST be matched to a Listener to find the - correct set of Routes. The incoming hostname MUST be matched using - the Hostname field for each Listener in order of most to least specific. - That is, exact matches must be processed before wildcard matches. - \n If this field specifies multiple Listeners that have the same - Port value but are not compatible, the implementation must raise - a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint - where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that - MAY be attached to a Listener and the trusted namespaces where - those Route resources MAY be present. \n Although a client - request may match multiple route rules, only one rule may - ultimately receive the request. Matching precedence MUST be - determined in order of the following criteria: \n * The most - specific match as defined by the Route type. * The oldest - Route based on creation timestamp. For example, a Route with - \ a creation timestamp of \"2020-09-08 01:02:03\" is given - precedence over a Route with a creation timestamp of \"2020-09-08 - 01:02:04\". * If everything else is equivalent, the Route - appearing first in alphabetical order (namespace/name) should - be given precedence. For example, foo/bar is given precedence - over foo/baz. \n All valid rules within a Route attached to - this Listener should be implemented. Invalid Route rules can - be ignored (sometimes that will mean the full Route). If a - Route rule transitions from valid to invalid, support for - that Route rule should be dropped to ensure consistency. For - example, even if a filter specified by a Route rule is invalid, - the rest of the rules within that Route should still be supported. - \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes - that are allowed to bind to this Gateway Listener. When - unspecified or empty, the kinds of Routes selected are - determined using the Listener protocol. \n A RouteGroupKind - MUST correspond to kinds of Routes that are compatible - with the application protocol specified in the Listener's - Protocol field. If an implementation does not support - or recognize this resource type, it MUST set the \"ResolvedRefs\" - condition to False for this Listener with the \"InvalidRouteKinds\" - reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind - of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which - Routes may be attached to this Listener. This is restricted - to the namespace of this Gateway by default. \n Support: - Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected - for this Gateway. Possible values are: * All: Routes - in all namespaces may be used by this Gateway. * Selector: - Routes in namespaces selected by the selector may - be used by this Gateway. * Same: Only Routes in - the same namespace may be used by this Gateway. \n - Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is - set to \"Selector\". In that case, only Routes in - Namespaces matching this Selector will be selected - by this Gateway. This field is ignored for other values - of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match - for protocol types that define this concept. When unspecified, - all hostnames are matched. This field is ignored for protocols - that don't require hostname based matching. \n Implementations - MUST apply Hostname matching appropriately for each of the - following protocols: \n * TLS: The Listener Hostname MUST - match the SNI. * HTTP: The Listener Hostname MUST match the - Host header of the request. * HTTPS: The Listener Hostname - SHOULD match at both the TLS and HTTP protocol layers as - described above. If an implementation does not ensure that - both the SNI and Host header match the Listener hostname, - \ it MUST clearly document that. \n For HTTPRoute and TLSRoute - resources, there is an interaction with the `spec.hostnames` - array. When both listener and route specify hostnames, there - MUST be an intersection between the values for a Route to - be accepted. For more information, refer to the Route specific - Hostnames documentation. \n Hostnames that are prefixed with - a wildcard label (`*.`) are interpreted as a suffix match. - That means that a match for `*.example.com` would match both - `test.example.com`, and `foo.test.example.com`, but not `example.com`. - \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 - name: - description: "Name is the name of the Listener. This name MUST - be unique within a 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 - port: - description: "Port is the network port. Multiple listeners may - use the same port, subject to the Listener compatibility rules. - \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener - expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. - This field is required if the Protocol field is \"HTTPS\" - or \"TLS\". It is invalid to set this field if the Protocol - field is \"HTTP\", \"TCP\", or \"UDP\". \n The association - of SNIs to Certificate defined in GatewayTLSConfig is defined - based on the Hostname field for this listener. \n The GatewayClass - MUST use the longest matching SNI out of all available certificates - for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references - to Kubernetes objects that contains TLS certificates and - private keys. These certificates are used to establish - a TLS handshake for requests that match the hostname of - the associated listener. \n A single CertificateRef to - a Kubernetes Secret has \"Core\" support. Implementations - MAY choose to support attaching multiple certificates - to a Listener, but this behavior is implementation-specific. - \n References to a resource in different namespace are - invalid UNLESS there is a ReferenceGrant in the target - namespace that allows the certificate to be attached. - If a ReferenceGrant does not allow this reference, the - \"ResolvedRefs\" condition MUST be set to False for this - listener with the \"InvalidCertificateRef\" reason. \n - This field is required to have at least one element when - the mode is set to \"Terminate\" (default) and is optional - otherwise. \n CertificateRefs can reference to standard - Kubernetes resources, i.e. Secret, or implementation-specific - custom resources. \n Support: Core - A single reference - to a Kubernetes Secret of type kubernetes.io/tls \n Support: - Implementation-specific (More than one reference or other - resource types)" - items: - description: "SecretObjectReference identifies an API - object including its namespace, defaulting to Secret. - \n 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: - default: "" - description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (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: - default: Secret - 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 - namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. - \n Note that when a namespace is specified, a ReferenceGrant - object is required in the referent namespace to - allow that namespace's owner to accept the reference. - See the ReferenceGrant documentation for details. - \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS - session initiated by the client. There are two possible - modes: \n - Terminate: The TLS session between the downstream - client and the Gateway is terminated at the Gateway. - This mode requires certificateRefs to be set and contain - at least one element. - Passthrough: The TLS session is - NOT terminated by the Gateway. This implies that the - Gateway can't decipher the TLS stream except for the - ClientHello message of the TLS protocol. CertificateRefs - field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation - in Gateway API. This is used for validation of maps - such as TLS options. This roughly matches Kubernetes - annotation validation, although the length validation - in that case is based on the entire size of the annotations - struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable - extended TLS configuration for each implementation. For - example, configuring the minimum TLS version or supported - cipher suites. \n A set of common keys MAY be defined - by the API in the future. To avoid any ambiguity, implementation-specific - definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. - Un-prefixed names are reserved for key names defined by - Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been - bound to the Gateway. These addresses may differ from the addresses - in the Spec, e.g. if the Gateway automatically assigns an address - from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: "Conditions describe the current conditions of the Gateway. - \n Implementations should prefer to express Gateway conditions using - the `GatewayConditionType` and `GatewayConditionReason` constants - so that operators and tools can converge on a common vocabulary - to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" - * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port - defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes - that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this - listener. - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status - corresponds to. - 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 - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds - supported by this listener. This MUST represent the kinds - an implementation supports for that Listener configuration. - \n If kinds are specified in Spec that are not supported, - they MUST NOT appear in this list and an implementation MUST - set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" - reason. If both valid and invalid Route kinds are specified, - the implementation MUST reference the valid Route kinds that - have been specified." - items: - description: RouteGroupKind indicates the group and kind of - a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: false - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling - infrastructure by binding Listeners to a set of IP addresses. - 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 Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional - and behavior can depend on the implementation. If a value is set - in the spec and the requested address is invalid or unavailable, - the implementation MUST indicate this in the associated entry in - GatewayStatus.Addresses. \n The Addresses field represents a request - for the address(es) on the \"outside of the Gateway\", that traffic - bound for this Gateway will use. This could be the IP address or - hostname of an external load balancer or other networking infrastructure, - or some other address that traffic will be sent to. \n The .listener.hostname - field is used to route traffic that has already arrived at the Gateway - to the correct in-cluster destination. \n If no Addresses are specified, - the implementation MAY schedule the Gateway in an implementation-specific - manner, assigning an appropriate set of Addresses. \n The implementation - MUST bind all Listeners to every GatewayAddress that it assigns - to the Gateway and add a corresponding entry in GatewayStatus.Addresses. - \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name - of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define - logical endpoints that are bound on this Gateway's addresses. At - least one Listener MUST be specified. \n Each listener in a Gateway - must have a unique combination of Hostname, Port, and Protocol. - \n An implementation MAY group Listeners by Port and then collapse - each group of Listeners into a single Listener if the implementation - determines that the Listeners in the group are \"compatible\". An - implementation MAY also group together and collapse compatible Listeners - belonging to different Gateways. \n For example, an implementation - might consider Listeners to be compatible with each other if all - of the following conditions are met: \n 1. Either each Listener - within the group specifies the \"HTTP\" Protocol or each Listener - within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. - \n 2. Each Listener within the group specifies a Hostname that is - unique within the group. \n 3. As a special case, one Listener - within a group may omit Hostname, in which case this Listener - matches when no other Listener matches. \n If the implementation - does collapse compatible Listeners, the hostname provided in the - incoming client request MUST be matched to a Listener to find the - correct set of Routes. The incoming hostname MUST be matched using - the Hostname field for each Listener in order of most to least specific. - That is, exact matches must be processed before wildcard matches. - \n If this field specifies multiple Listeners that have the same - Port value but are not compatible, the implementation must raise - a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint - where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that - MAY be attached to a Listener and the trusted namespaces where - those Route resources MAY be present. \n Although a client - request may match multiple route rules, only one rule may - ultimately receive the request. Matching precedence MUST be - determined in order of the following criteria: \n * The most - specific match as defined by the Route type. * The oldest - Route based on creation timestamp. For example, a Route with - \ a creation timestamp of \"2020-09-08 01:02:03\" is given - precedence over a Route with a creation timestamp of \"2020-09-08 - 01:02:04\". * If everything else is equivalent, the Route - appearing first in alphabetical order (namespace/name) should - be given precedence. For example, foo/bar is given precedence - over foo/baz. \n All valid rules within a Route attached to - this Listener should be implemented. Invalid Route rules can - be ignored (sometimes that will mean the full Route). If a - Route rule transitions from valid to invalid, support for - that Route rule should be dropped to ensure consistency. For - example, even if a filter specified by a Route rule is invalid, - the rest of the rules within that Route should still be supported. - \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes - that are allowed to bind to this Gateway Listener. When - unspecified or empty, the kinds of Routes selected are - determined using the Listener protocol. \n A RouteGroupKind - MUST correspond to kinds of Routes that are compatible - with the application protocol specified in the Listener's - Protocol field. If an implementation does not support - or recognize this resource type, it MUST set the \"ResolvedRefs\" - condition to False for this Listener with the \"InvalidRouteKinds\" - reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind - of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which - Routes may be attached to this Listener. This is restricted - to the namespace of this Gateway by default. \n Support: - Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected - for this Gateway. Possible values are: * All: Routes - in all namespaces may be used by this Gateway. * Selector: - Routes in namespaces selected by the selector may - be used by this Gateway. * Same: Only Routes in - the same namespace may be used by this Gateway. \n - Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is - set to \"Selector\". In that case, only Routes in - Namespaces matching this Selector will be selected - by this Gateway. This field is ignored for other values - of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match - for protocol types that define this concept. When unspecified, - all hostnames are matched. This field is ignored for protocols - that don't require hostname based matching. \n Implementations - MUST apply Hostname matching appropriately for each of the - following protocols: \n * TLS: The Listener Hostname MUST - match the SNI. * HTTP: The Listener Hostname MUST match the - Host header of the request. * HTTPS: The Listener Hostname - SHOULD match at both the TLS and HTTP protocol layers as - described above. If an implementation does not ensure that - both the SNI and Host header match the Listener hostname, - \ it MUST clearly document that. \n For HTTPRoute and TLSRoute - resources, there is an interaction with the `spec.hostnames` - array. When both listener and route specify hostnames, there - MUST be an intersection between the values for a Route to - be accepted. For more information, refer to the Route specific - Hostnames documentation. \n Hostnames that are prefixed with - a wildcard label (`*.`) are interpreted as a suffix match. - That means that a match for `*.example.com` would match both - `test.example.com`, and `foo.test.example.com`, but not `example.com`. - \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 - name: - description: "Name is the name of the Listener. This name MUST - be unique within a 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 - port: - description: "Port is the network port. Multiple listeners may - use the same port, subject to the Listener compatibility rules. - \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener - expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. - This field is required if the Protocol field is \"HTTPS\" - or \"TLS\". It is invalid to set this field if the Protocol - field is \"HTTP\", \"TCP\", or \"UDP\". \n The association - of SNIs to Certificate defined in GatewayTLSConfig is defined - based on the Hostname field for this listener. \n The GatewayClass - MUST use the longest matching SNI out of all available certificates - for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references - to Kubernetes objects that contains TLS certificates and - private keys. These certificates are used to establish - a TLS handshake for requests that match the hostname of - the associated listener. \n A single CertificateRef to - a Kubernetes Secret has \"Core\" support. Implementations - MAY choose to support attaching multiple certificates - to a Listener, but this behavior is implementation-specific. - \n References to a resource in different namespace are - invalid UNLESS there is a ReferenceGrant in the target - namespace that allows the certificate to be attached. - If a ReferenceGrant does not allow this reference, the - \"ResolvedRefs\" condition MUST be set to False for this - listener with the \"InvalidCertificateRef\" reason. \n - This field is required to have at least one element when - the mode is set to \"Terminate\" (default) and is optional - otherwise. \n CertificateRefs can reference to standard - Kubernetes resources, i.e. Secret, or implementation-specific - custom resources. \n Support: Core - A single reference - to a Kubernetes Secret of type kubernetes.io/tls \n Support: - Implementation-specific (More than one reference or other - resource types)" - items: - description: "SecretObjectReference identifies an API - object including its namespace, defaulting to Secret. - \n 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: - default: "" - description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (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: - default: Secret - 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 - namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. - \n Note that when a namespace is specified, a ReferenceGrant - object is required in the referent namespace to - allow that namespace's owner to accept the reference. - See the ReferenceGrant documentation for details. - \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS - session initiated by the client. There are two possible - modes: \n - Terminate: The TLS session between the downstream - client and the Gateway is terminated at the Gateway. - This mode requires certificateRefs to be set and contain - at least one element. - Passthrough: The TLS session is - NOT terminated by the Gateway. This implies that the - Gateway can't decipher the TLS stream except for the - ClientHello message of the TLS protocol. CertificateRefs - field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation - in Gateway API. This is used for validation of maps - such as TLS options. This roughly matches Kubernetes - annotation validation, although the length validation - in that case is based on the entire size of the annotations - struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable - extended TLS configuration for each implementation. For - example, configuring the minimum TLS version or supported - cipher suites. \n A set of common keys MAY be defined - by the API in the future. To avoid any ambiguity, implementation-specific - definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. - Un-prefixed names are reserved for key names defined by - Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been - bound to the Gateway. These addresses may differ from the addresses - in the Spec, e.g. if the Gateway automatically assigns an address - from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: "Conditions describe the current conditions of the Gateway. - \n Implementations should prefer to express Gateway conditions using - the `GatewayConditionType` and `GatewayConditionReason` constants - so that operators and tools can converge on a common vocabulary - to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" - * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port - defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes - that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this - listener. - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status - corresponds to. - 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 - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds - supported by this listener. This MUST represent the kinds - an implementation supports for that Listener configuration. - \n If kinds are specified in Spec that are not supported, - they MUST NOT appear in this list and an implementation MUST - set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" - reason. If both valid and invalid Route kinds are specified, - the implementation MUST reference the valid Route kinds that - have been specified." - items: - description: RouteGroupKind indicates the group and kind of - a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - 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 the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml new file mode 100644 index 0000000000..96153b352b --- /dev/null +++ b/internal/provider/kubernetes/testdata/in/gatewayclass-experimental-crd.yaml @@ -0,0 +1,430 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 + gateway.networking.k8s.io/bundle-version: v0.5.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the + user for creating Gateway resources. \n It is recommended that this resource + be used as a template for Gateways. This means that a Gateway is based on + the state of the GatewayClass at the time it was created and changes to + the GatewayClass or associated parameters are not propagated down to existing + Gateways. This recommendation is intended to limit the blast radius of changes + to GatewayClass or associated parameters. If implementations choose to propagate + GatewayClass changes to existing Gateways, that MUST be clearly documented + by the implementation. \n Whenever one or more Gateways are using a GatewayClass, + implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` + finalizer on the associated GatewayClass. This ensures that a GatewayClass + associated with a Gateway is not deleted while in use. \n GatewayClass is + a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is + managing Gateways of this class. The value of this field MUST be + a domain prefixed path. \n Example: \"example.net/gateway-controller\". + \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains + the configuration parameters corresponding to the GatewayClass. + This is optional if the controller does not require any additional + configuration. \n ParametersRef can reference a standard Kubernetes + resource, i.e. ConfigMap, or an implementation-specific custom resource. + The resource can be cluster-scoped or namespace-scoped. \n If the + referent cannot be found, the GatewayClass's \"InvalidParameters\" + status condition will be true. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + 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. + 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 + namespace: + description: Namespace is the namespace of the referent. This + field is required when referring to a Namespace-scoped resource + and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller + for this GatewayClass. \n Controllers should prefer to publish conditions + using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the + user for creating Gateway resources. \n It is recommended that this resource + be used as a template for Gateways. This means that a Gateway is based on + the state of the GatewayClass at the time it was created and changes to + the GatewayClass or associated parameters are not propagated down to existing + Gateways. This recommendation is intended to limit the blast radius of changes + to GatewayClass or associated parameters. If implementations choose to propagate + GatewayClass changes to existing Gateways, that MUST be clearly documented + by the implementation. \n Whenever one or more Gateways are using a GatewayClass, + implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` + finalizer on the associated GatewayClass. This ensures that a GatewayClass + associated with a Gateway is not deleted while in use. \n GatewayClass is + a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is + managing Gateways of this class. The value of this field MUST be + a domain prefixed path. \n Example: \"example.net/gateway-controller\". + \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains + the configuration parameters corresponding to the GatewayClass. + This is optional if the controller does not require any additional + configuration. \n ParametersRef can reference a standard Kubernetes + resource, i.e. ConfigMap, or an implementation-specific custom resource. + The resource can be cluster-scoped or namespace-scoped. \n If the + referent cannot be found, the GatewayClass's \"InvalidParameters\" + status condition will be true. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + 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. + 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 + namespace: + description: Namespace is the namespace of the referent. This + field is required when referring to a Namespace-scoped resource + and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller + for this GatewayClass. \n Controllers should prefer to publish conditions + using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml b/internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml deleted file mode 100644 index b85fc19dcf..0000000000 --- a/internal/provider/kubernetes/testdata/in/gatewayclass-standard-crd.yaml +++ /dev/null @@ -1,430 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 - gateway.networking.k8s.io/bundle-version: v0.6.0-dev - gateway.networking.k8s.io/channel: standard - creationTimestamp: null - name: gatewayclasses.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GatewayClass - listKind: GatewayClassList - plural: gatewayclasses - shortNames: - - gc - singular: gatewayclass - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the - user for creating Gateway resources. \n It is recommended that this resource - be used as a template for Gateways. This means that a Gateway is based on - the state of the GatewayClass at the time it was created and changes to - the GatewayClass or associated parameters are not propagated down to existing - Gateways. This recommendation is intended to limit the blast radius of changes - to GatewayClass or associated parameters. If implementations choose to propagate - GatewayClass changes to existing Gateways, that MUST be clearly documented - by the implementation. \n Whenever one or more Gateways are using a GatewayClass, - implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` - finalizer on the associated GatewayClass. This ensures that a GatewayClass - associated with a Gateway is not deleted while in use. \n GatewayClass is - a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is - managing Gateways of this class. The value of this field MUST be - a domain prefixed path. \n Example: \"example.net/gateway-controller\". - \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains - the configuration parameters corresponding to the GatewayClass. - This is optional if the controller does not require any additional - configuration. \n ParametersRef can reference a standard Kubernetes - resource, i.e. ConfigMap, or an implementation-specific custom resource. - The resource can be cluster-scoped or namespace-scoped. \n If the - referent cannot be found, the GatewayClass's \"InvalidParameters\" - status condition will be true. \n Support: Custom" - properties: - group: - description: Group is the group of the referent. - 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. - 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 - namespace: - description: Namespace is the namespace of the referent. This - field is required when referring to a Namespace-scoped resource - and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller - for this GatewayClass. \n Controllers should prefer to publish conditions - using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: false - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the - user for creating Gateway resources. \n It is recommended that this resource - be used as a template for Gateways. This means that a Gateway is based on - the state of the GatewayClass at the time it was created and changes to - the GatewayClass or associated parameters are not propagated down to existing - Gateways. This recommendation is intended to limit the blast radius of changes - to GatewayClass or associated parameters. If implementations choose to propagate - GatewayClass changes to existing Gateways, that MUST be clearly documented - by the implementation. \n Whenever one or more Gateways are using a GatewayClass, - implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` - finalizer on the associated GatewayClass. This ensures that a GatewayClass - associated with a Gateway is not deleted while in use. \n GatewayClass is - a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is - managing Gateways of this class. The value of this field MUST be - a domain prefixed path. \n Example: \"example.net/gateway-controller\". - \n This field is not mutable and cannot be empty. \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])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains - the configuration parameters corresponding to the GatewayClass. - This is optional if the controller does not require any additional - configuration. \n ParametersRef can reference a standard Kubernetes - resource, i.e. ConfigMap, or an implementation-specific custom resource. - The resource can be cluster-scoped or namespace-scoped. \n If the - referent cannot be found, the GatewayClass's \"InvalidParameters\" - status condition will be true. \n Support: Custom" - properties: - group: - description: Group is the group of the referent. - 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. - 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 - namespace: - description: Namespace is the namespace of the referent. This - field is required when referring to a Namespace-scoped resource - and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller - for this GatewayClass. \n Controllers should prefer to publish conditions - using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/internal/provider/kubernetes/testdata/in/httproute-standard-crd.yaml b/internal/provider/kubernetes/testdata/in/httproute-experimental-crd.yaml similarity index 79% rename from internal/provider/kubernetes/testdata/in/httproute-standard-crd.yaml rename to internal/provider/kubernetes/testdata/in/httproute-experimental-crd.yaml index 10ecb0152c..3ebd153573 100644 --- a/internal/provider/kubernetes/testdata/in/httproute-standard-crd.yaml +++ b/internal/provider/kubernetes/testdata/in/httproute-experimental-crd.yaml @@ -4,7 +4,7 @@ metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1086 gateway.networking.k8s.io/bundle-version: v0.6.0-dev - gateway.networking.k8s.io/channel: standard + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: @@ -77,19 +77,29 @@ spec: have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding - RouteParentStatus. \n Support: Core" + RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute + is attached to a Listener and that listener already has another + Route (B) of the other type attached and the intersection of the + hostnames of A and B is non-empty, then the implementation MUST + accept exactly one of these two routes, determined by the following + criteria, in order: \n * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by \"{namespace}/{name}\". + \n The rejected Route MUST raise an 'Accepted' condition with a + status of 'False' in the corresponding RouteParentStatus. \n Support: + Core" items: description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname - may be prefixed with a wildcard label (`*.`). The wildcard label - must appear by itself as the first label. \n Hostname can be \"precise\" - which is a domain name without the terminating dot of a network - host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain - name prefixed with a single wildcard label (e.g. `*.example.com`). - \n Note that as per RFC1035 and RFC1123, a *label* must consist - of lower case alphanumeric characters or '-', and must start and - end with an alphanumeric character. No other punctuation is allowed." + may be prefixed with a wildcard label (`*.`). The wildcard \n + \tlabel must appear by itself as the first label. \n Hostname + can be \"precise\" which is a domain name without the terminating + dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", + which is a domain name prefixed with a single wildcard label (e.g. + `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* + must consist of lower case alphanumeric characters or '-', and + must start and end with an alphanumeric character. No other punctuation + is allowed." maxLength: 253 minLength: 1 pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -144,12 +154,38 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified, this refers to the local namespace of the Route. - \n Support: Core" + unspecified (or empty string), this refers to the local namespace + 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 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 @@ -424,8 +460,9 @@ spec: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that - when a namespace is specified, a ReferenceGrant - object is required in the referent namespace + when a different namespace is specified, + a ReferenceGrant object with ReferenceGrantTo.Kind=Service + is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: @@ -438,9 +475,7 @@ spec: description: Port specifies the destination port number to use for this resource. Port is required when the referent is - a Kubernetes Service. In this case, the - port number is the service port number, - not the target port. For other resources, + a Kubernetes Service. For other resources, destination port might be derived from the referent resource or this field. format: int32 @@ -467,6 +502,57 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + path: + description: "Path defines parameters used to + modify the path of the incoming request. The + modified path is then used to construct the + `Location` header. When empty, the request + path is used as-is. \n Support: Extended \n + " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies + the value with which to replace the full + path of a request during a rewrite or + redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" would + be modified to \"/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object port: description: "Port is the port to be used in the value of the `Location` header in the @@ -542,8 +628,70 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect + - URLRewrite - ExtensionRef type: string + urlRewrite: + description: "URLRewrite defines a schema for a + filter that modifies a request during forwarding. + \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used + to replace the Host header value during forwarding. + \n Support: Extended \n " + 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 + path: + description: "Path defines a path rewrite. \n + Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies + the value with which to replace the full + path of a request during a rewrite or + redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" would + be modified to \"/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object @@ -574,11 +722,11 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferenceGrant - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferenceGrant documentation for details. \n Support: - Core" + Note that when a different namespace is specified, a + ReferenceGrant object with ReferenceGrantTo.Kind=Service + is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -586,10 +734,9 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. In this case, the - port number is the service port number, not the target - port. For other resources, destination port might be - derived from the referent resource or this field. + referent is a Kubernetes Service. For other resources, + destination port might be derived from the referent + resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -828,11 +975,12 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace - is inferred. \n Note that when a namespace is - specified, a ReferenceGrant object is required - in the referent namespace to allow that namespace's - owner to accept the reference. See the ReferenceGrant - documentation for details. \n Support: Core" + is inferred. \n Note that when a different namespace + is specified, a ReferenceGrant object with ReferenceGrantTo.Kind=Service + is required in the referent namespace to allow + that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. + \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -840,11 +988,9 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required - when the referent is a Kubernetes Service. In - this case, the port number is the service port - number, not the target port. For other resources, - destination port might be derived from the referent - resource or this field. + when the referent is a Kubernetes Service. For + other resources, destination port might be derived + from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -869,6 +1015,52 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`. + \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object port: description: "Port is the port to be used in the value of the `Location` header in the response. When empty, @@ -939,8 +1131,66 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect + - URLRewrite - ExtensionRef type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended \n " + 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 + path: + description: "Path defines a path rewrite. \n Support: + Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`. + \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object @@ -976,11 +1226,10 @@ spec: Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, - matching precedence MUST be granted to the FIRST matching - rule (in list order) meeting the above criteria. \n When no - rules matching a request have been successfully attached to - the parent a request is coming from, a HTTP 404 status code - MUST be returned." + matching precedence MUST be granted to the first matching + rule meeting the above criteria. \n When no rules matching + a request have been successfully attached to the parent a + request is coming from, a HTTP 404 status code MUST be returned." items: description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are @@ -1105,17 +1354,7 @@ spec: param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST - be ignored. \n If a query param is repeated in - an HTTP request, the behavior is purposely left - undefined, since different data planes have different - capabilities. However, it's *recommended* that - implementations should match against the first - value of the param if the data plane supports - it, as this behavior is expected in other load - balancing contexts outside of the Gateway API. - Users should not route traffic based on repeated - query params to guard themselves against potential - differences in the implementations." + be ignored." maxLength: 256 minLength: 1 type: string @@ -1311,12 +1550,40 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified, this refers to the local namespace of - the Route. \n Support: Core" + When unspecified (or empty string), this refers to the + local namespace 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 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 @@ -1356,8 +1623,8 @@ spec: required: - spec type: object - served: false - storage: false + served: true + storage: true subresources: status: {} - additionalPrinterColumns: @@ -1486,12 +1753,38 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified, this refers to the local namespace of the Route. - \n Support: Core" + unspecified (or empty string), this refers to the local namespace + 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 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 @@ -1780,9 +2073,7 @@ spec: description: Port specifies the destination port number to use for this resource. Port is required when the referent is - a Kubernetes Service. In this case, the - port number is the service port number, - not the target port. For other resources, + a Kubernetes Service. For other resources, destination port might be derived from the referent resource or this field. format: int32 @@ -1809,6 +2100,57 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + path: + description: "Path defines parameters used to + modify the path of the incoming request. The + modified path is then used to construct the + `Location` header. When empty, the request + path is used as-is. \n Support: Extended \n + " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies + the value with which to replace the full + path of a request during a rewrite or + redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" would + be modified to \"/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object port: description: "Port is the port to be used in the value of the `Location` header in the @@ -1884,8 +2226,70 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect + - URLRewrite - ExtensionRef type: string + urlRewrite: + description: "URLRewrite defines a schema for a + filter that modifies a request during forwarding. + \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used + to replace the Host header value during forwarding. + \n Support: Extended \n " + 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 + path: + description: "Path defines a path rewrite. \n + Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies + the value with which to replace the full + path of a request during a rewrite or + redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" would + be modified to \"/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object @@ -1928,10 +2332,9 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. In this case, the - port number is the service port number, not the target - port. For other resources, destination port might be - derived from the referent resource or this field. + referent is a Kubernetes Service. For other resources, + destination port might be derived from the referent + resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -2182,11 +2585,9 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required - when the referent is a Kubernetes Service. In - this case, the port number is the service port - number, not the target port. For other resources, - destination port might be derived from the referent - resource or this field. + when the referent is a Kubernetes Service. For + other resources, destination port might be derived + from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -2211,6 +2612,52 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`. + \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object port: description: "Port is the port to be used in the value of the `Location` header in the response. When empty, @@ -2281,8 +2728,66 @@ spec: - RequestHeaderModifier - RequestMirror - RequestRedirect + - URLRewrite - ExtensionRef type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended \n " + 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 + path: + description: "Path defines a path rewrite. \n Support: + Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Attached Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`. + \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object @@ -2318,11 +2823,10 @@ spec: Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, - matching precedence MUST be granted to the FIRST matching - rule (in list order) meeting the above criteria. \n When no - rules matching a request have been successfully attached to - the parent a request is coming from, a HTTP 404 status code - MUST be returned." + matching precedence MUST be granted to the first matching + rule meeting the above criteria. \n When no rules matching + a request have been successfully attached to the parent a + request is coming from, a HTTP 404 status code MUST be returned." items: description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are @@ -2447,17 +2951,7 @@ spec: param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST - be ignored. \n If a query param is repeated in - an HTTP request, the behavior is purposely left - undefined, since different data planes have different - capabilities. However, it's *recommended* that - implementations should match against the first - value of the param if the data plane supports - it, as this behavior is expected in other load - balancing contexts outside of the Gateway API. - Users should not route traffic based on repeated - query params to guard themselves against potential - differences in the implementations." + be ignored." maxLength: 256 minLength: 1 type: string @@ -2653,12 +3147,40 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified, this refers to the local namespace of - the Route. \n Support: Core" + When unspecified (or empty string), this refers to the + local namespace 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 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 @@ -2699,7 +3221,7 @@ spec: - spec type: object served: true - storage: true + storage: false subresources: status: {} status: From 7473cfbc0743b783fcb9dd6a02184dbe33a3cbf2 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 19:20:36 +0530 Subject: [PATCH 04/30] added testcases for tlsroutes, include serviceport in irInfraPortName Signed-off-by: Shubham Chauhan --- ...with-tls-terminate-and-passthrough.in.yaml | 70 ++++++++ ...ith-tls-terminate-and-passthrough.out.yaml | 154 ++++++++++++++++++ ...nd-tlsroute-same-hostname-and-port.in.yaml | 56 +++++++ ...d-tlsroute-same-hostname-and-port.out.yaml | 117 +++++++++++++ .../tlsroute-attaching-to-gateway.in.yaml | 31 ++++ .../tlsroute-attaching-to-gateway.out.yaml | 83 ++++++++++ ...ing-to-gateway-with-incorrect-mode.in.yaml | 31 ++++ ...ng-to-gateway-with-incorrect-mode.out.yaml | 64 ++++++++ ...-attaching-to-gateway-with-no-mode.in.yaml | 29 ++++ ...attaching-to-gateway-with-no-mode.out.yaml | 62 +++++++ internal/gatewayapi/translator.go | 21 +-- internal/ir/infra.go | 3 + 12 files changed, 711 insertions(+), 10 deletions(-) create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml new file mode 100644 index 0000000000..4ea509e3cf --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml @@ -0,0 +1,70 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls-passthrough + protocol: TLS + port: 90 + hostname: foo.com + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + - name: tls-terminate + protocol: HTTPS + port: 443 + hostname: foo.com + tls: + mode: Terminate + certificateRefs: + - name: tls-secret-1 + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8080 +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +secrets: + - apiVersion: v1 + kind: Secret + metadata: + namespace: envoy-gateway + name: tls-secret-1 + type: kubernetes.io/tls + data: + tls.crt: Zm9vCg== + tls.key: YmFyCg== \ No newline at end of file diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml new file mode 100644 index 0000000000..387166793f --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -0,0 +1,154 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls-passthrough + protocol: TLS + port: 90 + hostname: foo.com + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + - name: tls-terminate + protocol: HTTPS + port: 443 + hostname: foo.com + tls: + mode: Terminate + certificateRefs: + - name: tls-secret-1 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls-passthrough + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 1 + conditions: + - type: Ready + status: "True" + reason: Ready + message: Listener is ready + - name: tls-terminate + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + attachedRoutes: 1 + conditions: + - type: Ready + status: "True" + reason: Ready + message: Listener is ready +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + http: + - name: envoy-gateway-gateway-1-tls-passthrough + address: 0.0.0.0 + port: 10090 + hostnames: + - "foo.com" + tls: + tlsMode: Passthrough + routes: + - name: default-tlsroute-1-rule-0-match-0-foo.com + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 + - name: envoy-gateway-gateway-1-tls-terminate + address: 0.0.0.0 + port: 10443 + hostnames: + - "foo.com" + tls: + tlsMode: Terminate + serverCertificate: Zm9vCg== + privateKey: YmFyCg== + routes: + - name: default-httproute-1-rule-0-match-0-foo.com + pathMatch: + prefix: "/" + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" + ports: + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 + - name: envoy-gateway-gateway-1-443 + protocol: "HTTPS" + servicePort: 443 + containerPort: 10443 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.in.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.in.yaml new file mode 100644 index 0000000000..e3bfa50235 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.in.yaml @@ -0,0 +1,56 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-1 + protocol: HTTP + port: 80 + hostname: foo.com + allowedRoutes: + namespaces: + from: All + - name: tls-1 + protocol: TLS + tls: + mode: Passthrough + port: 80 + hostname: foo.com + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +tlsRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml new file mode 100644 index 0000000000..fd31a3e131 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml @@ -0,0 +1,117 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-1 + protocol: HTTP + port: 80 + hostname: foo.com + allowedRoutes: + namespaces: + from: All + - name: tls-1 + protocol: TLS + tls: + mode: Passthrough + port: 80 + hostname: foo.com + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: http-1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + conditions: + - type: Conflicted + status: "True" + reason: HostnameConflict + message: All listeners for a given port must use a unique hostname + - type: Ready + status: "False" + reason: Invalid + message: Listener is invalid, see other Conditions for details. + - name: tls-1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + conditions: + - type: Conflicted + status: "True" + reason: HostnameConflict + message: All listeners for a given port must use a unique hostname + - type: Ready + status: "False" + reason: Invalid + message: Listener is invalid, see other Conditions for details. +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref +tlsRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref + +xdsIR: {} +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml new file mode 100644 index 0000000000..09f2b84a80 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml @@ -0,0 +1,31 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 90 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml new file mode 100644 index 0000000000..eaa1e23960 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -0,0 +1,83 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 90 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 1 + conditions: + - type: Ready + status: "True" + reason: Ready + message: Listener is ready +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + http: + - name: envoy-gateway-gateway-1-tls + address: 0.0.0.0 + port: 10090 + hostnames: + - "*" + tls: + tlsMode: Passthrough + routes: + - name: default-tlsroute-1-rule-0-match-0-* + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" + ports: + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.in.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.in.yaml new file mode 100644 index 0000000000..d8a62c2adf --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.in.yaml @@ -0,0 +1,31 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + tls: + mode: Terminate + port: 90 + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml new file mode 100644 index 0000000000..7f13015eb2 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml @@ -0,0 +1,64 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + tls: + mode: Terminate + port: 90 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 0 + conditions: + - type: Ready + status: "False" + reason: UnsupportedTLSMode + message: TLS Terminate mode is not supported, TLS mode must be Passthrough. +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref +xdsIR: {} +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.in.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.in.yaml new file mode 100644 index 0000000000..38395404cd --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.in.yaml @@ -0,0 +1,29 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 90 + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml new file mode 100644 index 0000000000..3b073b4bea --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml @@ -0,0 +1,62 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 90 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 0 + conditions: + - type: Ready + status: "False" + reason: Invalid + message: Listener must have TLS set when protocol is TLS. +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref +xdsIR: {} +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index ea3bc79990..ce04c0a6bf 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -556,9 +556,14 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap // Add the listener to the Infra IR. Infra IR ports must have a unique port number. if !slices.Contains(foundPorts, servicePort) { foundPorts = append(foundPorts, servicePort) - proto := ir.HTTPProtocolType - if listener.Protocol == v1beta1.HTTPSProtocolType { + var proto ir.ProtocolType + switch listener.Protocol { + case v1beta1.HTTPProtocolType: + proto = ir.HTTPProtocolType + case v1beta1.HTTPSProtocolType: proto = ir.HTTPSProtocolType + case v1beta1.TLSProtocolType: + proto = ir.TLSProtocolType } infraPort := ir.ListenerPort{ Name: string(listener.Name), @@ -1145,8 +1150,7 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ for _, backendRef := range rule.BackendRefs { if backendRef.Group != nil && *backendRef.Group != "" { - parentRef.SetCondition( - tlsRoute, + parentRef.SetCondition(tlsRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonInvalidKind, @@ -1156,8 +1160,7 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ } if backendRef.Kind != nil && *backendRef.Kind != KindService { - parentRef.SetCondition( - tlsRoute, + parentRef.SetCondition(tlsRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonInvalidKind, @@ -1205,8 +1208,7 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ serviceNamespace := NamespaceDerefOrAlpha(backendRef.Namespace, tlsRoute.Namespace) service := resources.GetService(serviceNamespace, string(backendRef.Name)) if service == nil { - parentRef.SetCondition( - tlsRoute, + parentRef.SetCondition(tlsRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, v1beta1.RouteReasonBackendNotFound, @@ -1224,8 +1226,7 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ } if !portFound { - parentRef.SetCondition( - tlsRoute, + parentRef.SetCondition(tlsRoute, v1beta1.RouteConditionResolvedRefs, metav1.ConditionFalse, "PortNotFound", diff --git a/internal/ir/infra.go b/internal/ir/infra.go index 52e05ea7c3..99d91f103a 100644 --- a/internal/ir/infra.go +++ b/internal/ir/infra.go @@ -79,6 +79,9 @@ const ( // HTTPSProtocolType accepts HTTP/1.1 or HTTP/2 sessions over TLS. HTTPSProtocolType ProtocolType = "HTTPS" + + // Accepts TLS sessions over TCP. + TLSProtocolType ProtocolType = "TLS" ) // NewInfra returns a new Infra with default parameters. From e4563ba9f6763a1a9e7a3849414f9ccae69a4dbe Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 19:31:10 +0530 Subject: [PATCH 05/30] lintfix Signed-off-by: Shubham Chauhan --- ...way-with-listener-with-tls-terminate-and-passthrough.in.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml index 4ea509e3cf..bf254c658b 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.in.yaml @@ -67,4 +67,4 @@ secrets: type: kubernetes.io/tls data: tls.crt: Zm9vCg== - tls.key: YmFyCg== \ No newline at end of file + tls.key: YmFyCg== From 9edaa0393e43d1a0eb3e1f66b044082cf5c2e133 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 20:01:20 +0530 Subject: [PATCH 06/30] tlroute kubernetes provider test Signed-off-by: Shubham Chauhan --- .../provider/kubernetes/kubernetes_test.go | 207 ++++++++++++++---- 1 file changed, 159 insertions(+), 48 deletions(-) diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go index fec19e3d45..9290e4ef77 100644 --- a/internal/provider/kubernetes/kubernetes_test.go +++ b/internal/provider/kubernetes/kubernetes_test.go @@ -62,6 +62,7 @@ func TestProvider(t *testing.T) { "gatewayclass accepted status": testGatewayClassAcceptedStatus, "gateway scheduled status": testGatewayScheduledStatus, "httproute": testHTTPRoute, + "tlsroute": testTLSRoute, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { @@ -83,17 +84,40 @@ func startEnv() (*envtest.Environment, *rest.Config, error) { return env, cfg, nil } -func testGatewayClassController(ctx context.Context, t *testing.T, provider *Provider, resources *message.ProviderResources) { - cli := provider.manager.GetClient() - - gc := &gwapiv1b1.GatewayClass{ +func getGatewayClass(name string) *gwapiv1b1.GatewayClass { + return &gwapiv1b1.GatewayClass{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-gc-controllername", + Name: name, }, Spec: gwapiv1b1.GatewayClassSpec{ - ControllerName: v1alpha1.GatewayControllerName, + ControllerName: gwapiv1b1.GatewayController(v1alpha1.GatewayControllerName), + }, + } +} + +func getService(name, namespace string, ports map[string]int) *corev1.Service { + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{}, }, } + for name, port := range ports { + service.Spec.Ports = append(service.Spec.Ports, corev1.ServicePort{ + Name: name, + Port: port, + }) + } + return service +} + +func testGatewayClassController(ctx context.Context, t *testing.T, provider *Provider, resources *message.ProviderResources) { + cli := provider.manager.GetClient() + + gc := getGatewayClass("test-gc-controllername") require.NoError(t, cli.Create(ctx, gc)) defer func() { @@ -109,14 +133,7 @@ func testGatewayClassController(ctx context.Context, t *testing.T, provider *Pro func testGatewayClassAcceptedStatus(ctx context.Context, t *testing.T, provider *Provider, resources *message.ProviderResources) { cli := provider.manager.GetClient() - gc := &gwapiv1b1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gc-accepted-status", - }, - Spec: gwapiv1b1.GatewayClassSpec{ - ControllerName: v1alpha1.GatewayControllerName, - }, - } + gc := getGatewayClass("test-gc-accepted-status") require.NoError(t, cli.Create(ctx, gc)) defer func() { @@ -145,14 +162,7 @@ func testGatewayClassAcceptedStatus(ctx context.Context, t *testing.T, provider func testGatewayScheduledStatus(ctx context.Context, t *testing.T, provider *Provider, resources *message.ProviderResources) { cli := provider.manager.GetClient() - gc := &gwapiv1b1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gc-scheduled-status-test", - }, - Spec: gwapiv1b1.GatewayClassSpec{ - ControllerName: gwapiv1b1.GatewayController(v1alpha1.GatewayControllerName), - }, - } + gc := getGatewayClass("gc-scheduled-status-test") require.NoError(t, cli.Create(ctx, gc)) // Ensure the GatewayClass reports "Ready". @@ -241,14 +251,7 @@ func testGatewayScheduledStatus(ctx context.Context, t *testing.T, provider *Pro func testHTTPRoute(ctx context.Context, t *testing.T, provider *Provider, resources *message.ProviderResources) { cli := provider.manager.GetClient() - gc := &gwapiv1b1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "httproute-test", - }, - Spec: gwapiv1b1.GatewayClassSpec{ - ControllerName: gwapiv1b1.GatewayController(v1alpha1.GatewayControllerName), - }, - } + gc := getGatewayClass("httproute-test") require.NoError(t, cli.Create(ctx, gc)) // Ensure the GatewayClass reports ready. @@ -296,24 +299,10 @@ func testHTTPRoute(ctx context.Context, t *testing.T, provider *Provider, resour require.NoError(t, cli.Delete(ctx, gw)) }() - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ns.Name, - Name: "test", - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "http", - Port: 80, - }, - { - Name: "https", - Port: 443, - }, - }, - }, - } + svc := getService(ns.Name, "test", map[string]int{ + "http": 80, + "https": 443 + }) require.NoError(t, cli.Create(ctx, svc)) @@ -579,3 +568,125 @@ func testHTTPRoute(ctx context.Context, t *testing.T, provider *Provider, resour }) } } + +func testTLSRoute(ctx context.Context, t *testing.T, provider *Provider, resources *message.ProviderResources) { + cli := provider.manager.GetClient() + + gc := getGatewayClass("tlsroute-test") + require.NoError(t, cli.Create(ctx, gc)) + + defer func() { + require.NoError(t, cli.Delete(ctx, gc)) + }() + + // Create the namespace for the Gateway under test. + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "tlsroute-test"}} + require.NoError(t, cli.Create(ctx, ns)) + + gw := &gwapiv1b1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlsroute-test", + Namespace: ns.Name, + }, + Spec: gwapiv1b1.GatewaySpec{ + GatewayClassName: gwapiv1b1.ObjectName(gc.Name), + Listeners: []gwapiv1b1.Listener{ + { + Name: "test", + Port: gwapiv1b1.PortNumber(int32(8080)), + Protocol: gwapiv1b1.TLSProtocolType, + }, + }, + }, + } + require.NoError(t, cli.Create(ctx, gw)) + + defer func() { + require.NoError(t, cli.Delete(ctx, gw)) + }() + + svc := getService(ns.Name, "test", map[string]int{ + "tls": 90, + }) + + require.NoError(t, cli.Create(ctx, svc)) + + defer func() { + require.NoError(t, cli.Delete(ctx, svc)) + }() + + var testCases = []struct { + name string + route gwapiv1a2.TLSRoute + }{ + { + name: "tlsroute", + route: gwapiv1a2.TLSRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlsroute-test", + Namespace: ns.Name, + }, + Spec: gwapiv1a2.TLSRouteSpec{ + CommonRouteSpec: gwapiv1a2.CommonRouteSpec{ + ParentRefs: []gwapiv1a2.ParentReference{ + { + Name: gwapiv1a2.ObjectName(gw.Name), + }, + }, + }, + Hostnames: []gwapiv1a2.Hostname{"test.hostname.local"}, + Rules: []gwapiv1a2.TLSRouteRule{ + { + BackendRefs: []gwapiv1a2.TLSBackendRef{ + { + BackendRef: gwapiv1a2.BackendRef{ + BackendObjectReference: gwapiv1a2.BackendObjectReference{ + Name: "test", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + require.NoError(t, cli.Create(ctx, &testCase.route)) + defer func() { + require.NoError(t, cli.Delete(ctx, &testCase.route)) + }() + + require.Eventually(t, func() bool { + return resources.TLSRoutes.Len() == 1 + }, defaultWait, defaultTick) + + // Ensure the test TLSRoute in the TLSRoute resources is as expected. + key := types.NamespacedName{ + Namespace: testCase.route.Namespace, + Name: testCase.route.Name, + } + require.Eventually(t, func() bool { + return cli.Get(ctx, key, &testCase.route) == nil + }, defaultWait, defaultTick) + troutes, _ := resources.TLSRoutes.Load(key) + assert.Equal(t, &testCase.route, troutes) + + // Ensure the TLSRoute Namespace is in the Namespace resource map. + require.Eventually(t, func() bool { + _, ok := resources.Namespaces.Load(testCase.route.Namespace) + return ok + }, defaultWait, defaultTick) + + // Ensure the Service is in the resource map. + svcKey := NamespacedName(svc) + require.Eventually(t, func() bool { + _, ok := resources.Services.Load(svcKey) + return ok + }, defaultWait, defaultTick) + }) + } +} From 813ced855fb1c3a3f763c94c08ffdf060b796247 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 20:08:39 +0530 Subject: [PATCH 07/30] added xds tls config validate test for passthrough Signed-off-by: Shubham Chauhan --- internal/ir/xds_test.go | 7 +++++++ internal/provider/kubernetes/kubernetes_test.go | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 17e8be2a8a..884be887c8 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -365,6 +365,13 @@ func TestValidateTLSListenerConfig(t *testing.T) { }, want: ErrTLSPrivateKey, }, + { + name: "passthrough", + input: TLSListenerConfig{ + TLSMode: "Passthrough", + }, + want: nil, + }, } for _, test := range tests { test := test diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go index 9290e4ef77..855175a2ba 100644 --- a/internal/provider/kubernetes/kubernetes_test.go +++ b/internal/provider/kubernetes/kubernetes_test.go @@ -300,8 +300,8 @@ func testHTTPRoute(ctx context.Context, t *testing.T, provider *Provider, resour }() svc := getService(ns.Name, "test", map[string]int{ - "http": 80, - "https": 443 + "http": 80, + "https": 443, }) require.NoError(t, cli.Create(ctx, svc)) From 499b752c902410b7812895186b8acc306ea3c394 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 20:19:17 +0530 Subject: [PATCH 08/30] types test tlsroute Signed-off-by: Shubham Chauhan --- internal/message/types_test.go | 32 +++++++++++++++++++ .../provider/kubernetes/kubernetes_test.go | 1 + 2 files changed, 33 insertions(+) diff --git a/internal/message/types_test.go b/internal/message/types_test.go index 36e1649547..4b2faa409b 100644 --- a/internal/message/types_test.go +++ b/internal/message/types_test.go @@ -7,6 +7,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -34,6 +35,12 @@ func TestProviderResources(t *testing.T) { Namespace: "test", }, } + t1 := &gwapiv1a2.TLSRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlsroute1", + Namespace: "test", + }, + } s1 := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "service1", @@ -46,6 +53,7 @@ func TestProviderResources(t *testing.T) { assert.Nil(t, resources.GetGatewayClasses()) assert.Nil(t, resources.GetGateways()) assert.Nil(t, resources.GetHTTPRoutes()) + assert.Nil(t, resources.GetTLSRoutes()) assert.Nil(t, resources.GetServices()) // Add resources @@ -64,6 +72,12 @@ func TestProviderResources(t *testing.T) { } resources.HTTPRoutes.Store(r1Key, r1) + t1Key := types.NamespacedName{ + Namespace: t1.GetNamespace(), + Name: t1.GetName(), + } + resources.TLSRoutes.Store(t1Key, t1) + s1Key := types.NamespacedName{ Namespace: s1.GetNamespace(), Name: s1.GetName(), @@ -83,6 +97,9 @@ func TestProviderResources(t *testing.T) { hrs := resources.GetHTTPRoutes() assert.Equal(t, len(hrs), 1) + trs := resources.GetTLSRoutes() + assert.Equal(t, len(trs), 1) + svcs := resources.GetServices() assert.Equal(t, len(svcs), 1) @@ -110,6 +127,12 @@ func TestProviderResources(t *testing.T) { Namespace: "test", }, } + t2 := &gwapiv1a2.TLSRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlsroute2", + Namespace: "test", + }, + } s2 := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "service2", @@ -131,6 +154,12 @@ func TestProviderResources(t *testing.T) { } resources.HTTPRoutes.Store(r2Key, r2) + t2Key := types.NamespacedName{ + Namespace: t2.GetNamespace(), + Name: t2.GetName(), + } + resources.TLSRoutes.Store(t2Key, t2) + s2Key := types.NamespacedName{ Namespace: s2.GetNamespace(), Name: s2.GetName(), @@ -150,6 +179,9 @@ func TestProviderResources(t *testing.T) { hrs = resources.GetHTTPRoutes() assert.ElementsMatch(t, hrs, []*gwapiv1b1.HTTPRoute{r1, r2}) + trs = resources.GetTLSRoutes() + assert.ElementsMatch(t, trs, []*gwapiv1a2.TLSRoute{t1, t2}) + svcs = resources.GetServices() assert.ElementsMatch(t, svcs, []*corev1.Service{s1, s2}) } diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go index 855175a2ba..9a189895dd 100644 --- a/internal/provider/kubernetes/kubernetes_test.go +++ b/internal/provider/kubernetes/kubernetes_test.go @@ -21,6 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/envoyproxy/gateway/api/config/v1alpha1" From f8024b8036bba5a2632b43e6d9eb4e244eb5c67d Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 20:25:59 +0530 Subject: [PATCH 09/30] test fixes Signed-off-by: Shubham Chauhan --- internal/provider/kubernetes/kubernetes_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go index 9a189895dd..c1b2ce00e6 100644 --- a/internal/provider/kubernetes/kubernetes_test.go +++ b/internal/provider/kubernetes/kubernetes_test.go @@ -96,7 +96,7 @@ func getGatewayClass(name string) *gwapiv1b1.GatewayClass { } } -func getService(name, namespace string, ports map[string]int) *corev1.Service { +func getService(name, namespace string, ports map[string]int32) *corev1.Service { service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -300,7 +300,7 @@ func testHTTPRoute(ctx context.Context, t *testing.T, provider *Provider, resour require.NoError(t, cli.Delete(ctx, gw)) }() - svc := getService(ns.Name, "test", map[string]int{ + svc := getService("test", ns.Name, map[string]int32{ "http": 80, "https": 443, }) @@ -606,7 +606,7 @@ func testTLSRoute(ctx context.Context, t *testing.T, provider *Provider, resourc require.NoError(t, cli.Delete(ctx, gw)) }() - svc := getService(ns.Name, "test", map[string]int{ + svc := getService("test", ns.Name, map[string]int32{ "tls": 90, }) @@ -638,12 +638,10 @@ func testTLSRoute(ctx context.Context, t *testing.T, provider *Provider, resourc Hostnames: []gwapiv1a2.Hostname{"test.hostname.local"}, Rules: []gwapiv1a2.TLSRouteRule{ { - BackendRefs: []gwapiv1a2.TLSBackendRef{ + BackendRefs: []gwapiv1a2.BackendRef{ { - BackendRef: gwapiv1a2.BackendRef{ - BackendObjectReference: gwapiv1a2.BackendObjectReference{ - Name: "test", - }, + BackendObjectReference: gwapiv1a2.BackendObjectReference{ + Name: "test", }, }, }, From 33e9f1baa071b6f9b5f2677cd27d08f6053812b4 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 21:20:26 +0530 Subject: [PATCH 10/30] xds config tests for tls passthrough Signed-off-by: Shubham Chauhan --- .../in/xds-ir/tls-route-passthrough.yaml | 13 ++++++ .../tls-route-passthrough.clusters.yaml | 18 ++++++++ .../tls-route-passthrough.listeners.yaml | 42 +++++++++++++++++++ .../xds-ir/tls-route-passthrough.routes.yaml | 10 +++++ internal/xds/translator/translator.go | 2 +- internal/xds/translator/translator_test.go | 3 ++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml new file mode 100644 index 0000000000..880ab9cb5f --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -0,0 +1,13 @@ +http: +- name: "tls-passthrough" + address: "0.0.0.0" + port: 10080 + tls: + tlsMode: passthrough + hostnames: + - "*" + routes: + - name: "first-route" + destinations: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml new file mode 100644 index 0000000000..c65cb16a6a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml @@ -0,0 +1,18 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: cluster_first-route + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + locality: {} + name: cluster_first-route + outlierDetection: {} + type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml new file mode 100644 index 0000000000..8c1fa471fa --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml @@ -0,0 +1,42 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + rds: + configSource: + apiConfigSource: + apiType: GRPC + grpcServices: + - envoyGrpc: + clusterName: xds_cluster + setNodeOnFirstMessageOnly: true + transportApiVersion: V3 + resourceApiVersion: V3 + routeConfigName: route_tls-passthrough + statPrefix: http + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + commonTlsContext: + tlsCertificateSdsSecretConfigs: + - name: secret_tls-passthrough + sdsConfig: + apiConfigSource: + apiType: GRPC + grpcServices: + - envoyGrpc: + clusterName: xds_cluster + setNodeOnFirstMessageOnly: true + transportApiVersion: V3 + resourceApiVersion: V3 + name: listener_tls-passthrough_10080 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml new file mode 100644 index 0000000000..d5811a65fc --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml @@ -0,0 +1,10 @@ +- name: route_tls-passthrough + virtualHosts: + - domains: + - '*' + name: route_tls-passthrough + routes: + - match: + prefix: / + route: + cluster: cluster_first-route diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 60c9c132f4..e904a3a4cb 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -38,7 +38,7 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { } // 1:1 between IR TLSListenerConfig and xDS Secret - if httpListener.TLS != nil { + if httpListener.TLS != nil && httpListener.TLS.TLSMode != v1beta1.TLSModePassthrough { // Build downstream TLS details. tSocket, err := buildXdsDownstreamTLSSocket(httpListener.Name, httpListener.TLS) if err != nil { diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 90cea49670..0102b6d84f 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -50,6 +50,9 @@ func TestTranslate(t *testing.T) { name: "simple-tls", requireSecrets: true, }, + { + name: "tls-route-passthrough", + }, } for _, tc := range testCases { From 441ed9e6622980dded0abf3cf9906b96c0d7bc8c Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 22:11:14 +0530 Subject: [PATCH 11/30] increase test coverage Signed-off-by: Shubham Chauhan --- ...ith-invalid-allowed-tls-route-kind.in.yaml | 34 ++++++++++ ...th-invalid-allowed-tls-route-kind.out.yaml | 68 +++++++++++++++++++ ...ner-both-passthrough-and-cert-data.in.yaml | 43 ++++++++++++ ...er-both-passthrough-and-cert-data.out.yaml | 66 ++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml new file mode 100644 index 0000000000..692adbfd82 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml @@ -0,0 +1,34 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 80 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + kinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml new file mode 100644 index 0000000000..f3c61f31de --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml @@ -0,0 +1,68 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 80 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + kinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + status: + listeners: + - name: tls + attachedRoutes: 0 + conditions: + - type: ResolvedRefs + status: "False" + reason: InvalidRouteKinds + message: "Kind is not supported, kind must be TLSRoute" + - type: Ready + status: "False" + reason: Invalid + message: Listener is invalid, see other Conditions for details. +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref +xdsIR: {} +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml new file mode 100644 index 0000000000..d190f65e47 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml @@ -0,0 +1,43 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + tls: + mode: Passthrough + certificateRefs: + - name: tls-secret-1 + port: 90 + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 +secrets: + - apiVersion: v1 + kind: Secret + metadata: + namespace: envoy-gateway + name: tls-secret-1 + type: kubernetes.io/tls + data: + tls.crt: Zm9vCg== + tls.key: YmFyCg== diff --git a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml new file mode 100644 index 0000000000..7ff12f029c --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml @@ -0,0 +1,66 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + tls: + mode: Passthrough + certificateRefs: + - name: tls-secret-1 + port: 90 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 0 + conditions: + - type: Ready + status: "False" + reason: Invalid + message: Listener must not have TLS certificate refs set for TLS mode Passthrough +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + # controllerName: envoyproxy.io/gateway-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref +xdsIR: {} +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" From 14f9a468bf3be2d3faee6bc4d06ff13f259ee5ed Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Thu, 22 Sep 2022 22:45:46 +0530 Subject: [PATCH 12/30] testfix Signed-off-by: Shubham Chauhan --- ...stener-with-tls-terminate-and-passthrough.out.yaml | 8 ++++---- internal/gatewayapi/translator_test.go | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index 387166793f..55689bd2b5 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -144,11 +144,11 @@ infraIR: listeners: - address: "" ports: - - name: envoy-gateway-gateway-1-90 - protocol: "TLS" - servicePort: 90 - containerPort: 10090 - name: envoy-gateway-gateway-1-443 protocol: "HTTPS" servicePort: 443 containerPort: 10443 + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index b62664e1e8..ae797957b0 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strconv" "strings" "testing" @@ -76,6 +77,16 @@ func TestTranslate(t *testing.T) { got := translator.Translate(resources) + envoyGatewayNsName := "envoy-gateway-gateway-1" + sort.Slice(got.XdsIR[envoyGatewayNsName].HTTP, func(i, j int) bool { + return got.XdsIR[envoyGatewayNsName].HTTP[i].Name < got.XdsIR[envoyGatewayNsName].HTTP[j].Name + }) + // Only 1 listener is supported + sort.Slice(got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports, + func(i, j int) bool { + return got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports[i].Name < got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports[j].Name + }) + opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") require.Empty(t, cmp.Diff(want, got, opts)) }) From aba3fd2666fbb7ec1ca69401771461bf53698925 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 24 Sep 2022 10:57:42 +0530 Subject: [PATCH 13/30] separate xds tls listener Signed-off-by: Shubham Chauhan testfix Signed-off-by: Shubham Chauhan --- internal/gatewayapi/contexts.go | 32 +-- ...th-invalid-allowed-tls-route-kind.out.yaml | 2 +- ...ith-tls-terminate-and-passthrough.out.yaml | 28 +- ...ener-with-valid-tls-configuration.out.yaml | 1 - ...d-tlsroute-same-hostname-and-port.out.yaml | 4 +- ...to-gateway-with-wildcard-hostname.out.yaml | 2 +- .../tlsroute-attaching-to-gateway.out.yaml | 6 +- ...ng-to-gateway-with-incorrect-mode.out.yaml | 2 +- ...attaching-to-gateway-with-no-mode.out.yaml | 2 +- ...er-both-passthrough-and-cert-data.out.yaml | 2 +- internal/gatewayapi/translator.go | 241 ++++++++++-------- internal/gatewayapi/translator_test.go | 5 +- internal/ir/xds.go | 127 ++++++--- internal/ir/xds_test.go | 20 +- internal/ir/zz_generated.deepcopy.go | 68 +++++ internal/provider/kubernetes/httproute.go | 1 - .../provider/kubernetes/kubernetes_test.go | 2 +- internal/provider/kubernetes/tlsroute.go | 10 +- internal/xds/translator/listener.go | 10 +- internal/xds/translator/route.go | 14 +- .../in/xds-ir/tls-route-passthrough.yaml | 4 +- .../tls-route-passthrough.listeners.yaml | 41 +-- .../xds-ir/tls-route-passthrough.routes.yaml | 4 +- internal/xds/translator/translator.go | 55 +++- 24 files changed, 420 insertions(+), 263 deletions(-) diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index 048cc01bcb..b40a780d8b 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -53,10 +53,10 @@ func (g *GatewayContext) SetCondition(conditionType v1beta1.GatewayConditionType } } -// GetListenerContext returns the ListenerContext with its name matching -// listenerName from GatewayContext. If the listener exists in the Gateway Spec -// but NOT yet in the GatewayContext, this creates a new ListenerContext for the -// listener and attaches it to the GatewayContext. +// GetListenerContext returns the ListenerContext with listenerName. +// If the listener exists in the Gateway Spec but NOT yet in the GatewayContext, +// this creates a new ListenerContext for the listener and attaches it to the +// GatewayContext. func (g *GatewayContext) GetListenerContext(listenerName v1beta1.SectionName) *ListenerContext { if g.listeners == nil { g.listeners = make(map[v1beta1.SectionName]*ListenerContext) @@ -107,12 +107,7 @@ type ListenerContext struct { gateway *v1beta1.Gateway listenerStatusIdx int namespaceSelector labels.Selector - tls listenerContextTLSConfig -} - -type listenerContextTLSConfig struct { - mode v1beta1.TLSModeType - secret *v1.Secret + tlsSecret *v1.Secret } func (l *ListenerContext) SetCondition(conditionType v1beta1.ListenerConditionType, status metav1.ConditionStatus, reason v1beta1.ListenerConditionReason, message string) { @@ -205,10 +200,6 @@ func (l *ListenerContext) GetConditions() []metav1.Condition { return l.gateway.Status.Listeners[l.listenerStatusIdx].Conditions } -func (l *ListenerContext) SetTLSConfig(mode v1beta1.TLSModeType, secret *v1.Secret) { - l.tls = listenerContextTLSConfig{mode, secret} -} - // RouteContext represents a generic Route object (HTTPRoute, TLSRoute, etc.) // that can reference Gateway objects. type RouteContext interface { @@ -285,12 +276,11 @@ func (h *HTTPRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefe } } if routeParentStatusIdx == -1 { - rParentStatus := v1beta1.RouteParentStatus{ + h.Status.Parents = append(h.Status.Parents, v1beta1.RouteParentStatus{ + ParentRef: forParentRef, // TODO: get this value from the config ControllerName: v1beta1.GatewayController(egv1alpha1.GatewayControllerName), - ParentRef: forParentRef, - } - h.Status.Parents = append(h.Status.Parents, rParentStatus) + }) routeParentStatusIdx = len(h.Status.Parents) - 1 } @@ -362,7 +352,11 @@ func (t *TLSRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefer } } if routeParentStatusIdx == -1 { - t.Status.Parents = append(t.Status.Parents, v1alpha2.RouteParentStatus{ParentRef: DowngradeParentReference(forParentRef)}) + t.Status.Parents = append(t.Status.Parents, v1alpha2.RouteParentStatus{ + ParentRef: DowngradeParentReference(forParentRef), + // TODO: get this value from the config + ControllerName: v1alpha2.GatewayController(egv1alpha1.GatewayControllerName), + }) routeParentStatusIdx = len(t.Status.Parents) - 1 } diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml index f3c61f31de..1afc2670f9 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml @@ -50,7 +50,7 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "False" diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index 55689bd2b5..9d224bf173 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -68,7 +68,7 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "True" @@ -96,7 +96,7 @@ httpRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "True" @@ -104,32 +104,30 @@ httpRoutes: message: Route is accepted xdsIR: http: - - name: envoy-gateway-gateway-1-tls-passthrough + - name: envoy-gateway-gateway-1-tls-terminate address: 0.0.0.0 - port: 10090 + port: 10443 hostnames: - "foo.com" tls: - tlsMode: Passthrough + serverCertificate: Zm9vCg== + privateKey: YmFyCg== routes: - - name: default-tlsroute-1-rule-0-match-0-foo.com + - name: default-httproute-1-rule-0-match-0-foo.com + pathMatch: + prefix: "/" destinations: - host: 7.7.7.7 port: 8080 weight: 1 - - name: envoy-gateway-gateway-1-tls-terminate + tls: + - name: envoy-gateway-gateway-1-tls-passthrough address: 0.0.0.0 - port: 10443 + port: 10090 hostnames: - "foo.com" - tls: - tlsMode: Terminate - serverCertificate: Zm9vCg== - privateKey: YmFyCg== routes: - - name: default-httproute-1-rule-0-match-0-foo.com - pathMatch: - prefix: "/" + - name: default-tlsroute-1-rule-0-match-0-foo.com destinations: - host: 7.7.7.7 port: 8080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml index ba0e15761d..fb780f18da 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml @@ -66,7 +66,6 @@ xdsIR: hostnames: - "*" tls: - tlsMode: Terminate serverCertificate: Zm9vCg== privateKey: YmFyCg== routes: diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml index fd31a3e131..a0d9082051 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml @@ -73,7 +73,7 @@ httpRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "False" @@ -98,7 +98,7 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "False" diff --git a/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml b/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml index d27e79946e..3bb183f147 100644 --- a/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.out.yaml @@ -55,7 +55,7 @@ httpRoutes: - type: Accepted status: "False" reason: NoMatchingListenerHostname - message: There were no hostname intersections between the Route and this parent ref's Listener(s). + message: There were no hostname intersections between the HTTPRoute and this parent ref's Listener(s). xdsIR: envoy-gateway-gateway-1: http: diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index eaa1e23960..0d6fa5bcc6 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -46,21 +46,19 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "True" reason: Accepted message: Route is accepted xdsIR: - http: + tls: - name: envoy-gateway-gateway-1-tls address: 0.0.0.0 port: 10090 hostnames: - "*" - tls: - tlsMode: Passthrough routes: - name: default-tlsroute-1-rule-0-match-0-* destinations: diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml index 7f13015eb2..bb7cfa5cd3 100644 --- a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml @@ -46,7 +46,7 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "False" diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml index 3b073b4bea..9dca364f35 100644 --- a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml @@ -44,7 +44,7 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "False" diff --git a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml index 7ff12f029c..447f1401d3 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml @@ -48,7 +48,7 @@ tlsRoutes: - parentRef: namespace: envoy-gateway name: gateway-1 - # controllerName: envoyproxy.io/gateway-controller + controllerName: gateway.envoyproxy.io/gatewayclass-controller conditions: - type: Accepted status: "False" diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index ce04c0a6bf..d265b7432e 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -472,7 +472,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap break } - listener.SetTLSConfig(v1beta1.TLSModeTerminate, secret) + listener.tlsSecret = secret case v1beta1.TLSProtocolType: if listener.TLS == nil { listener.SetCondition( @@ -503,8 +503,6 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap ) break } - - listener.SetTLSConfig(v1beta1.TLSModePassthrough, nil) } lConditions := listener.GetConditions() @@ -532,24 +530,38 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap continue } + // Add the listener to the Xds IR. servicePort := int32(listener.Port) containerPort := servicePortToContainerPort(servicePort) - // Add the listener to the Xds IR. - irListener := &ir.HTTPListener{ - Name: irListenerName(listener), - Address: "0.0.0.0", - Port: uint32(containerPort), - } - if listener.TLS != nil { - irListener.TLS = irTLSConfig(listener.tls.mode, listener.tls.secret) - } - if listener.Hostname != nil { - irListener.Hostnames = append(irListener.Hostnames, string(*listener.Hostname)) - } else { - // Hostname specifies the virtual hostname to match for protocol types that define this concept. - // When unspecified, all hostnames are matched. This field is ignored for protocols that don’t require hostname based matching. - // see more https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.Listener. - irListener.Hostnames = append(irListener.Hostnames, "*") + switch listener.Protocol { + case v1beta1.HTTPProtocolType, v1beta1.HTTPSProtocolType: + irListener := &ir.HTTPListener{ + Name: irListenerName(listener), + Address: "0.0.0.0", + Port: uint32(containerPort), + TLS: irTLSConfig(listener.tlsSecret), + } + if listener.Hostname != nil { + irListener.Hostnames = append(irListener.Hostnames, string(*listener.Hostname)) + } else { + // Hostname specifies the virtual hostname to match for protocol types that define this concept. + // When unspecified, all hostnames are matched. This field is ignored for protocols that don’t require hostname based matching. + // see more https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.Listener. + irListener.Hostnames = append(irListener.Hostnames, "*") + } + xdsIR.HTTP = append(xdsIR.HTTP, irListener) + case v1beta1.TLSProtocolType: + irListener := &ir.TLSListener{ + Name: irListenerName(listener), + Address: "0.0.0.0", + Port: uint32(containerPort), + } + if listener.Hostname != nil { + irListener.Hostnames = append(irListener.Hostnames, string(*listener.Hostname)) + } else { + irListener.Hostnames = append(irListener.Hostnames, "*") + } + xdsIR.TLS = append(xdsIR.TLS, irListener) } gwXdsIR.HTTP = append(gwXdsIR.HTTP, irListener) @@ -1105,7 +1117,69 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways routeRoutes = append(routeRoutes, ruleRoutes...) } - addRoutesToListener(httpRoute, parentRef, routeRoutes, xdsIR) + var hasHostnameIntersection bool + for _, listener := range parentRef.listeners { + hosts := computeHosts(httpRoute.GetHostnames(), listener.Hostname) + if len(hosts) == 0 { + continue + } + hasHostnameIntersection = true + + var perHostRoutes []*ir.HTTPRoute + for _, host := range hosts { + var headerMatches []*ir.StringMatch + + // If the intersecting host is more specific than the Listener's hostname, + // add an additional header match to all of the routes for it + if host != "*" && (listener.Hostname == nil || string(*listener.Hostname) != host) { + headerMatches = append(headerMatches, &ir.StringMatch{ + Name: ":authority", + Exact: StringPtr(host), + }) + } + + for _, routeRoute := range routeRoutes { + perHostRoutes = append(perHostRoutes, &ir.HTTPRoute{ + Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), + PathMatch: routeRoute.PathMatch, + HeaderMatches: append(headerMatches, routeRoute.HeaderMatches...), + QueryParamMatches: routeRoute.QueryParamMatches, + AddRequestHeaders: routeRoute.AddRequestHeaders, + RemoveRequestHeaders: routeRoute.RemoveRequestHeaders, + Destinations: routeRoute.Destinations, + Redirect: routeRoute.Redirect, + DirectResponse: routeRoute.DirectResponse, + }) + } + } + + irListener := xdsIR.GetListener(irListenerName(listener)) + if irListener != nil { + irListener.Routes = append(irListener.Routes, perHostRoutes...) + } + // Theoretically there should only be one parent ref per + // Route that attaches to a given Listener, so fine to just increment here, but we + // might want to check to ensure we're not double-counting. + if len(routeRoutes) > 0 { + listener.IncrementAttachedRoutes() + } + } + + if !hasHostnameIntersection { + parentRef.SetCondition(httpRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + v1beta1.RouteReasonNoMatchingListenerHostname, + "There were no hostname intersections between the HTTPRoute and this parent ref's Listener(s).", + ) + } else { + parentRef.SetCondition(httpRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionTrue, + v1beta1.RouteReasonAccepted, + "Route is accepted", + ) + } } } @@ -1256,87 +1330,55 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ routeRoutes = append(routeRoutes, ruleRoute) } - addRoutesToListener(tlsRoute, parentRef, routeRoutes, xdsIR) - } - } - - return relevantTLSRoutes -} - -func addRoutesToListener(routeContext RouteContext, parentRef *RouteParentContext, routeRoutes []*ir.HTTPRoute, xdsIR XdsIRMap) { - var hasHostnameIntersection bool - for _, listener := range parentRef.listeners { - hosts := computeHosts(routeContext.GetHostnames(), listener.Hostname) - if len(hosts) == 0 { - continue - } - hasHostnameIntersection = true - - var perHostRoutes []*ir.HTTPRoute - for _, host := range hosts { - var headerMatches []*ir.StringMatch - if routeContext.GetRouteType() == KindHTTPRoute { - // If the intersecting host is more specific than the Listener's hostname, - // add an additional header match to all of the routes for it - if host != "*" && (listener.Hostname == nil || string(*listener.Hostname) != host) { - headerMatches = append(headerMatches, &ir.StringMatch{ - Name: ":authority", - Exact: StringPtr(host), - }) + var hasHostnameIntersection bool + for _, listener := range parentRef.listeners { + hosts := computeHosts(tlsRoute.GetHostnames(), listener.Hostname) + if len(hosts) == 0 { + continue } - } - - for _, routeRoute := range routeRoutes { - perHostRoute := &ir.HTTPRoute{ - Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), - Destinations: routeRoute.Destinations, + hasHostnameIntersection = true + + var perHostRoutes []*ir.TLSRoute + for _, host := range hosts { + for _, routeRoute := range routeRoutes { + perHostRoutes = append(perHostRoutes, &ir.TLSRoute{ + Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), + Destinations: routeRoute.Destinations, + }) + } } - // For TLSRoutes, all would be nil except Name and Destinations. - if routeContext.GetRouteType() == KindHTTPRoute { - headerMatches = append(headerMatches, routeRoute.HeaderMatches...) - - perHostRoute.AddRequestHeaders = routeRoute.AddRequestHeaders - perHostRoute.RemoveRequestHeaders = routeRoute.RemoveRequestHeaders - perHostRoute.Destinations = routeRoute.Destinations - perHostRoute.PathMatch = routeRoute.PathMatch - perHostRoute.HeaderMatches = headerMatches - perHostRoute.QueryParamMatches = routeRoute.QueryParamMatches - perHostRoute.Redirect = routeRoute.Redirect - perHostRoute.DirectResponse = routeRoute.DirectResponse + irListener := xdsIR.GetTLSListener(irListenerName(listener)) + if irListener != nil { + irListener.Routes = append(irListener.Routes, perHostRoutes...) + } + // Theoretically there should only be one parent ref per + // Route that attaches to a given Listener, so fine to just increment here, but we + // might want to check to ensure we're not double-counting. + if len(routeRoutes) > 0 { + listener.IncrementAttachedRoutes() } - - perHostRoutes = append(perHostRoutes, perHostRoute) } - } - irListener := xdsIR.GetListener(irListenerName(listener)) - if irListener != nil { - irListener.Routes = append(irListener.Routes, perHostRoutes...) - } - // Theoretically there should only be one parent ref per - // Route that attaches to a given Listener, so fine to just increment here, but we - // might want to check to ensure we're not double-counting. - if len(routeRoutes) > 0 { - listener.IncrementAttachedRoutes() + if !hasHostnameIntersection { + parentRef.SetCondition(tlsRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + v1beta1.RouteReasonNoMatchingListenerHostname, + "There were no hostname intersections between the HTTPRoute and this parent ref's Listener(s).", + ) + } else { + parentRef.SetCondition(tlsRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionTrue, + v1beta1.RouteReasonAccepted, + "Route is accepted", + ) + } } } - if !hasHostnameIntersection { - parentRef.SetCondition(routeContext, - v1beta1.RouteConditionAccepted, - metav1.ConditionFalse, - v1beta1.RouteReasonNoMatchingListenerHostname, - "There were no hostname intersections between the Route and this parent ref's Listener(s).", - ) - } else { - parentRef.SetCondition(routeContext, - v1beta1.RouteConditionAccepted, - metav1.ConditionTrue, - v1beta1.RouteReasonAccepted, - "Route is accepted", - ) - } + return relevantTLSRoutes } // processAllowedListenersForParentRefs finds out if the route attaches to one of our @@ -1455,7 +1497,6 @@ func isValidCrossNamespaceRef(from crossNamespaceFrom, to crossNamespaceTo, refe // Checks if a hostname is valid according to RFC 1123 and gateway API's requirement that it not be an IP address func isValidHostname(hostname string) error { - if errs := validation.IsDNS1123Subdomain(hostname); errs != nil { return fmt.Errorf("hostname %q is invalid for a redirect filter: %v", hostname, errs) } @@ -1495,17 +1536,15 @@ func routeName(route RouteContext, ruleIdx, matchIdx int) string { return fmt.Sprintf("%s-%s-rule-%d-match-%d", route.GetNamespace(), route.GetName(), ruleIdx, matchIdx) } -func irTLSConfig(tlsMode v1beta1.TLSModeType, tlsSecret *v1.Secret) *ir.TLSListenerConfig { - tlsListenerConfig := &ir.TLSListenerConfig{ - TLSMode: tlsMode, +func irTLSConfig(tlsSecret *v1.Secret) *ir.TLSListenerConfig { + if tlsSecret == nil { + return nil } - if tlsSecret != nil { - tlsListenerConfig.ServerCertificate = tlsSecret.Data[v1.TLSCertKey] - tlsListenerConfig.PrivateKey = tlsSecret.Data[v1.TLSPrivateKeyKey] + return &ir.TLSListenerConfig{ + ServerCertificate: tlsSecret.Data[v1.TLSCertKey], + PrivateKey: tlsSecret.Data[v1.TLSPrivateKeyKey], } - - return tlsListenerConfig } // GatewayOwnerLabels returns the Gateway Owner labels using diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index ae797957b0..5ff445d4ed 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -24,7 +24,7 @@ func mustUnmarshal(t *testing.T, val string, out interface{}) { } func TestTranslate(t *testing.T) { - inputFiles, err := filepath.Glob(filepath.Join("testdata", "*.in.yaml")) + inputFiles, err := filepath.Glob(filepath.Join("testdata", "httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.in.yaml")) require.NoError(t, err) for _, inputFile := range inputFiles { @@ -81,6 +81,9 @@ func TestTranslate(t *testing.T) { sort.Slice(got.XdsIR[envoyGatewayNsName].HTTP, func(i, j int) bool { return got.XdsIR[envoyGatewayNsName].HTTP[i].Name < got.XdsIR[envoyGatewayNsName].HTTP[j].Name }) + sort.Slice(got.XdsIR[envoyGatewayNsName].TLS, func(i, j int) bool { + return got.XdsIR[envoyGatewayNsName].TLS[i].Name < got.XdsIR[envoyGatewayNsName].TLS[j].Name + }) // Only 1 listener is supported sort.Slice(got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports, func(i, j int) bool { diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 9c0f55cdc6..60ace0e4fd 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -5,14 +5,13 @@ import ( "net" "github.com/tetratelabs/multierror" - "sigs.k8s.io/gateway-api/apis/v1beta1" ) var ( - ErrHTTPListenerNameEmpty = errors.New("field Name must be specified") - ErrHTTPListenerAddressInvalid = errors.New("field Address must be a valid IP address") - ErrHTTPListenerPortInvalid = errors.New("field Port specified is invalid") - ErrHTTPListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") + ErrListenerNameEmpty = errors.New("field Name must be specified") + ErrListenerAddressInvalid = errors.New("field Address must be a valid IP address") + ErrListenerPortInvalid = errors.New("field Port specified is invalid") + ErrListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified") ErrTLSPrivateKey = errors.New("field PrivateKey must be specified") ErrHTTPRouteNameEmpty = errors.New("field Name must be specified") @@ -36,6 +35,8 @@ var ( type Xds struct { // HTTP listeners exposed by the gateway. HTTP []*HTTPListener + // TLS Listeners exposed by the gateway. + TLS []*TLSListener } // Validate the fields within the Xds structure. @@ -49,6 +50,24 @@ func (x Xds) Validate() error { return errs } +func (x Xds) GetListener(name string) *HTTPListener { + for _, listener := range x.HTTP { + if listener.Name == name { + return listener + } + } + return nil +} + +func (x Xds) GetTLSListener(name string) *TLSListener { + for _, listener := range x.TLS { + if listener.Name == name { + return listener + } + } + return nil +} + // HTTPListener holds the listener configuration. // +k8s:deepcopy-gen=true type HTTPListener struct { @@ -69,29 +88,20 @@ type HTTPListener struct { Routes []*HTTPRoute } -func (x Xds) GetListener(name string) *HTTPListener { - for _, listener := range x.HTTP { - if listener.Name == name { - return listener - } - } - return nil -} - // Validate the fields within the HTTPListener structure func (h HTTPListener) Validate() error { var errs error if h.Name == "" { - errs = multierror.Append(errs, ErrHTTPListenerNameEmpty) + errs = multierror.Append(errs, ErrListenerNameEmpty) } if ip := net.ParseIP(h.Address); ip == nil { - errs = multierror.Append(errs, ErrHTTPListenerAddressInvalid) + errs = multierror.Append(errs, ErrListenerAddressInvalid) } if h.Port == 0 { - errs = multierror.Append(errs, ErrHTTPListenerPortInvalid) + errs = multierror.Append(errs, ErrListenerPortInvalid) } if len(h.Hostnames) == 0 { - errs = multierror.Append(errs, ErrHTTPListenerHostnamesEmpty) + errs = multierror.Append(errs, ErrListenerHostnamesEmpty) } if h.TLS != nil { if err := h.TLS.Validate(); err != nil { @@ -99,7 +109,7 @@ func (h HTTPListener) Validate() error { } } for _, route := range h.Routes { - if err := route.Validate(h.TLS); err != nil { + if err := route.Validate(); err != nil { errs = multierror.Append(errs, err) } } @@ -109,9 +119,6 @@ func (h HTTPListener) Validate() error { // TLSListenerConfig holds the configuration for downstream TLS context. // +k8s:deepcopy-gen=true type TLSListenerConfig struct { - // TLSMode could either be Terminate or Passthrough. If the TLS mode - // is Passthrough, certificate and private key information is not required. - TLSMode v1beta1.TLSModeType // ServerCertificate of the server. ServerCertificate []byte // PrivateKey for the server. @@ -121,9 +128,6 @@ type TLSListenerConfig struct { // Validate the fields within the TLSListenerConfig structure func (t TLSListenerConfig) Validate() error { var errs error - if t.TLSMode == v1beta1.TLSModePassthrough { - return errs - } if len(t.ServerCertificate) == 0 { errs = multierror.Append(errs, ErrTLSServerCertEmpty) } @@ -165,16 +169,13 @@ type HTTPRoute struct { } // Validate the fields within the HTTPRoute structure -func (h HTTPRoute) Validate(tls *TLSListenerConfig) error { +func (h HTTPRoute) Validate() error { var errs error if h.Name == "" { errs = multierror.Append(errs, ErrHTTPRouteNameEmpty) } - if tls == nil || tls.TLSMode != v1beta1.TLSModePassthrough { - // These fields could be nil in case of TLSModePassthrough. - if h.PathMatch == nil && (len(h.HeaderMatches) == 0) && (len(h.QueryParamMatches) == 0) { - errs = multierror.Append(errs, ErrHTTPRouteMatchEmpty) - } + if h.PathMatch == nil && (len(h.HeaderMatches) == 0) && (len(h.QueryParamMatches) == 0) { + errs = multierror.Append(errs, ErrHTTPRouteMatchEmpty) } if h.PathMatch != nil { if err := h.PathMatch.Validate(); err != nil { @@ -394,3 +395,67 @@ func (s StringMatch) Validate() error { return errs } + +// TLSListener holds the listener configuration. +// +k8s:deepcopy-gen=true +type TLSListener struct { + // Name of the TLSListener + Name string + // Address that the listener should listen on. + Address string + // Port on which the service can be expected to be accessed by clients. + Port uint32 + // Hostnames (Host/Authority header value) with which the service can be expected to be accessed by clients. + // This field is required. Wildcard hosts are supported in the suffix or prefix form. + // Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-virtualhost + // for more info. + Hostnames []string + // Routes associated with TLS passthrough traffic to the service. + Routes []*TLSRoute +} + +// Validate the fields within the TLSListener structure +func (h TLSListener) Validate() error { + var errs error + if h.Name == "" { + errs = multierror.Append(errs, ErrListenerNameEmpty) + } + if ip := net.ParseIP(h.Address); ip == nil { + errs = multierror.Append(errs, ErrListenerAddressInvalid) + } + if h.Port == 0 { + errs = multierror.Append(errs, ErrListenerPortInvalid) + } + if len(h.Hostnames) == 0 { + errs = multierror.Append(errs, ErrListenerHostnamesEmpty) + } + for _, route := range h.Routes { + if err := route.Validate(); err != nil { + errs = multierror.Append(errs, err) + } + } + return errs +} + +// TLSRoute holds the route information associated with the HTTP Route +// +k8s:deepcopy-gen=true +type TLSRoute struct { + // Name of the TLSRoute + Name string + // Destinations associated with this matched route. + Destinations []*RouteDestination +} + +// Validate the fields within the TLSRoute structure +func (h TLSRoute) Validate() error { + var errs error + if h.Name == "" { + errs = multierror.Append(errs, ErrHTTPRouteNameEmpty) + } + for _, dest := range h.Destinations { + if err := dest.Validate(); err != nil { + errs = multierror.Append(errs, err) + } + } + return errs +} diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 884be887c8..14c1e46a9c 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -249,7 +249,7 @@ func TestValidateXds(t *testing.T) { input: Xds{ HTTP: []*HTTPListener{&happyHTTPListener, &invalidAddrHTTPListener, &invalidRouteMatchHTTPListener}, }, - want: []error{ErrHTTPListenerAddressInvalid, ErrHTTPRouteMatchEmpty}, + want: []error{ErrListenerAddressInvalid, ErrHTTPRouteMatchEmpty}, }, { name: "invalid backend", @@ -300,12 +300,12 @@ func TestValidateHTTPListener(t *testing.T) { Hostnames: []string{"example.com"}, Routes: []*HTTPRoute{&happyHTTPRoute}, }, - want: []error{ErrHTTPListenerNameEmpty}, + want: []error{ErrListenerNameEmpty}, }, { name: "invalid addr", input: invalidAddrHTTPListener, - want: []error{ErrHTTPListenerAddressInvalid}, + want: []error{ErrListenerAddressInvalid}, }, { name: "invalid port and hostnames", @@ -314,7 +314,7 @@ func TestValidateHTTPListener(t *testing.T) { Address: "1.0.0", Routes: []*HTTPRoute{&happyHTTPRoute}, }, - want: []error{ErrHTTPListenerPortInvalid, ErrHTTPListenerHostnamesEmpty}, + want: []error{ErrListenerPortInvalid, ErrListenerHostnamesEmpty}, }, { name: "invalid route match", @@ -365,13 +365,6 @@ func TestValidateTLSListenerConfig(t *testing.T) { }, want: ErrTLSPrivateKey, }, - { - name: "passthrough", - input: TLSListenerConfig{ - TLSMode: "Passthrough", - }, - want: nil, - }, } for _, test := range tests { test := test @@ -383,7 +376,6 @@ func TestValidateTLSListenerConfig(t *testing.T) { } }) } - } func TestValidateHTTPRoute(t *testing.T) { @@ -480,9 +472,9 @@ func TestValidateHTTPRoute(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { if test.want == nil { - require.NoError(t, test.input.Validate(nil)) + require.NoError(t, test.input.Validate()) } else { - got := test.input.Validate(nil) + got := test.input.Validate() for _, w := range test.want { assert.ErrorContains(t, got, w.Error()) } diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index b765727d61..b1354ebe69 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -358,6 +358,37 @@ func (in *StringMatch) DeepCopy() *StringMatch { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSListener) DeepCopyInto(out *TLSListener) { + *out = *in + if in.Hostnames != nil { + in, out := &in.Hostnames, &out.Hostnames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Routes != nil { + in, out := &in.Routes, &out.Routes + *out = make([]*TLSRoute, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TLSRoute) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSListener. +func (in *TLSListener) DeepCopy() *TLSListener { + if in == nil { + return nil + } + out := new(TLSListener) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSListenerConfig) DeepCopyInto(out *TLSListenerConfig) { *out = *in @@ -383,6 +414,32 @@ func (in *TLSListenerConfig) DeepCopy() *TLSListenerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSRoute) DeepCopyInto(out *TLSRoute) { + *out = *in + if in.Destinations != nil { + in, out := &in.Destinations, &out.Destinations + *out = make([]*RouteDestination, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RouteDestination) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSRoute. +func (in *TLSRoute) DeepCopy() *TLSRoute { + if in == nil { + return nil + } + out := new(TLSRoute) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Xds) DeepCopyInto(out *Xds) { *out = *in @@ -397,6 +454,17 @@ func (in *Xds) DeepCopyInto(out *Xds) { } } } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = make([]*TLSListener, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TLSListener) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Xds. diff --git a/internal/provider/kubernetes/httproute.go b/internal/provider/kubernetes/httproute.go index 82c852a6cc..88eb98a591 100644 --- a/internal/provider/kubernetes/httproute.go +++ b/internal/provider/kubernetes/httproute.go @@ -288,7 +288,6 @@ func (r *httpRouteReconciler) Reconcile(ctx context.Context, request reconcile.R log.Info("reconciled httproute") - r.resources.RouteInitializedOnce.Do(r.resources.RoutesInitialized.Done) return reconcile.Result{}, nil } diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go index c1b2ce00e6..5a7dc33693 100644 --- a/internal/provider/kubernetes/kubernetes_test.go +++ b/internal/provider/kubernetes/kubernetes_test.go @@ -681,7 +681,7 @@ func testTLSRoute(ctx context.Context, t *testing.T, provider *Provider, resourc }, defaultWait, defaultTick) // Ensure the Service is in the resource map. - svcKey := NamespacedName(svc) + svcKey := utils.NamespacedName(svc) require.Eventually(t, func() bool { _, ok := resources.Services.Load(svcKey) return ok diff --git a/internal/provider/kubernetes/tlsroute.go b/internal/provider/kubernetes/tlsroute.go index 77f60828c8..90203b56fe 100644 --- a/internal/provider/kubernetes/tlsroute.go +++ b/internal/provider/kubernetes/tlsroute.go @@ -23,6 +23,7 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/message" + "github.com/envoyproxy/gateway/internal/provider/utils" ) const ( @@ -98,7 +99,7 @@ func (r *tlsRouteReconciler) getTLSRoutesForService(obj client.Object) []reconci affectedTLSRouteList := &gwapiv1a2.TLSRouteList{} if err := r.client.List(context.Background(), affectedTLSRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(serviceTLSRouteIndex, NamespacedName(obj).String()), + FieldSelector: fields.OneTermEqualSelector(serviceTLSRouteIndex, utils.NamespacedName(obj).String()), }); err != nil { return []reconcile.Request{} } @@ -106,7 +107,7 @@ func (r *tlsRouteReconciler) getTLSRoutesForService(obj client.Object) []reconci requests := make([]reconcile.Request, len(affectedTLSRouteList.Items)) for i, item := range affectedTLSRouteList.Items { requests[i] = reconcile.Request{ - NamespacedName: NamespacedName(item.DeepCopy()), + NamespacedName: utils.NamespacedName(item.DeepCopy()), } } @@ -118,6 +119,8 @@ func (r *tlsRouteReconciler) Reconcile(ctx context.Context, request reconcile.Re log.Info("reconciling tlsroute") + defer r.resources.RouteInitializedOnce.Do(r.resources.RoutesInitialized.Done) + // Fetch all TLSRoutes from the cache. routeList := &gwapiv1a2.TLSRouteList{} if err := r.client.List(ctx, routeList); err != nil { @@ -128,7 +131,7 @@ func (r *tlsRouteReconciler) Reconcile(ctx context.Context, request reconcile.Re for i := range routeList.Items { // See if this route from the list matched the reconciled route. route := routeList.Items[i] - routeKey := NamespacedName(&route) + routeKey := utils.NamespacedName(&route) if routeKey == request.NamespacedName { found = true } @@ -209,7 +212,6 @@ func (r *tlsRouteReconciler) Reconcile(ctx context.Context, request reconcile.Re log.Info("reconciled tlsroute") - r.resources.RouteInitializedOnce.Do(r.resources.RoutesInitialized.Done) return reconcile.Result{}, nil } diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 0ab37b64d3..4737ca6faa 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -73,8 +73,8 @@ func buildXdsListener(httpListener *ir.HTTPListener) (*listener.Listener, error) }, nil } -func buildXdsPassthroughListener(httpListener *ir.HTTPListener) (*listener.Listener, error) { - if httpListener == nil { +func buildXdsPassthroughListener(tlsListener *ir.TLSListener) (*listener.Listener, error) { + if tlsListener == nil { return nil, errors.New("http listener is nil") } @@ -91,14 +91,14 @@ func buildXdsPassthroughListener(httpListener *ir.HTTPListener) (*listener.Liste } return &listener.Listener{ - Name: getXdsListenerName(httpListener.Name, httpListener.Port), + Name: getXdsListenerName(tlsListener.Name, tlsListener.Port), Address: &core.Address{ Address: &core.Address_SocketAddress{ SocketAddress: &core.SocketAddress{ Protocol: core.SocketAddress_TCP, - Address: httpListener.Address, + Address: tlsListener.Address, PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: httpListener.Port, + PortValue: tlsListener.Port, }, }, }, diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index a23578efbf..332252e110 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -9,13 +9,7 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) -func buildXdsRoute(httpRoute *ir.HTTPRoute, tlsPassthrough bool) (*route.Route, error) { - if tlsPassthrough { - return &route.Route{ - Action: &route.Route_Route{Route: buildXdsRouteAction(httpRoute.Name)}, - }, nil - } - +func buildXdsRoute(httpRoute *ir.HTTPRoute) (*route.Route, error) { ret := &route.Route{ Match: buildXdsRouteMatch(httpRoute.PathMatch, httpRoute.HeaderMatches, httpRoute.QueryParamMatches), } @@ -44,6 +38,12 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute, tlsPassthrough bool) (*route.Route, return ret, nil } +func buildXdsPassthroughRoute(tlsRoute *ir.TLSRoute) (*route.Route, error) { + return &route.Route{ + Action: &route.Route_Route{Route: buildXdsRouteAction(tlsRoute.Name)}, + }, nil +} + func buildXdsRouteMatch(pathMatch *ir.StringMatch, headerMatches []*ir.StringMatch, queryParamMatches []*ir.StringMatch) *route.RouteMatch { outMatch := &route.RouteMatch{} diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index 880ab9cb5f..e4c6aebb3d 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -1,9 +1,7 @@ -http: +tls: - name: "tls-passthrough" address: "0.0.0.0" port: 10080 - tls: - tlsMode: passthrough hostnames: - "*" routes: diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml index 8c1fa471fa..960133476d 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml @@ -4,39 +4,12 @@ portValue: 10080 filterChains: - filters: - - name: envoy.filters.network.http_connection_manager + - name: envoy.filters.network.tcp_proxy typedConfig: - '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - httpFilters: - - name: envoy.filters.http.router - typedConfig: - '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - rds: - configSource: - apiConfigSource: - apiType: GRPC - grpcServices: - - envoyGrpc: - clusterName: xds_cluster - setNodeOnFirstMessageOnly: true - transportApiVersion: V3 - resourceApiVersion: V3 - routeConfigName: route_tls-passthrough - statPrefix: http - transportSocket: - name: envoy.transport_sockets.tls - typedConfig: - '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext - commonTlsContext: - tlsCertificateSdsSecretConfigs: - - name: secret_tls-passthrough - sdsConfig: - apiConfigSource: - apiType: GRPC - grpcServices: - - envoyGrpc: - clusterName: xds_cluster - setNodeOnFirstMessageOnly: true - transportApiVersion: V3 - resourceApiVersion: V3 + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + statPrefix: passthrough + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector name: listener_tls-passthrough_10080 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml index d5811a65fc..a6520fb42a 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml @@ -4,7 +4,5 @@ - '*' name: route_tls-passthrough routes: - - match: - prefix: / - route: + - route: cluster: cluster_first-route diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index e904a3a4cb..e9e83df7e6 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -5,11 +5,9 @@ import ( "fmt" core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" resource "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/tetratelabs/multierror" - "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/xds/types" @@ -25,20 +23,13 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { for _, httpListener := range ir.HTTP { // 1:1 between IR HTTPListener and xDS Listener - var xdsListener *listener.Listener - var err error - tlsPassthrough := httpListener.TLS != nil && httpListener.TLS.TLSMode == v1beta1.TLSModePassthrough - if tlsPassthrough { - xdsListener, err = buildXdsPassthroughListener(httpListener) - } else { - xdsListener, err = buildXdsListener(httpListener) - } + xdsListener, err := buildXdsListener(httpListener) if err != nil { return nil, multierror.Append(err, errors.New("error building xds listener")) } // 1:1 between IR TLSListenerConfig and xDS Secret - if httpListener.TLS != nil && httpListener.TLS.TLSMode != v1beta1.TLSModePassthrough { + if httpListener.TLS != nil { // Build downstream TLS details. tSocket, err := buildXdsDownstreamTLSSocket(httpListener.Name, httpListener.TLS) if err != nil { @@ -63,7 +54,7 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { for _, httpRoute := range httpListener.Routes { // 1:1 between IR HTTPRoute and xDS config.route.v3.Route - xdsRoute, err := buildXdsRoute(httpRoute, tlsPassthrough) + xdsRoute, err := buildXdsRoute(httpRoute) if err != nil { return nil, multierror.Append(err, errors.New("error building xds route")) } @@ -90,6 +81,46 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { tCtx.AddXdsResource(resource.RouteType, xdsRouteCfg) } + for _, tlsListener := range ir.TLS { + // 1:1 between IR TLSListener and xDS Listener + xdsListener, err := buildXdsPassthroughListener(tlsListener) + if err != nil { + return nil, multierror.Append(err, errors.New("error building xds listener")) + } + + // Allocate virtual host for this tlsListener. + // 1:1 between IR TLSListener and xDS VirtualHost + routeName := getXdsRouteName(tlsListener.Name) + vHost := &route.VirtualHost{ + Name: routeName, + Domains: tlsListener.Hostnames, + } + + for _, tlsListener := range tlsListener.Routes { + // 1:1 between IR HTTPRoute and xDS config.route.v3.Route + xdsRoute, err := buildXdsPassthroughRoute(tlsListener) + if err != nil { + return nil, multierror.Append(err, errors.New("error building xds route")) + } + vHost.Routes = append(vHost.Routes, xdsRoute) + + // 1:1 between IR TLSRoute and xDS Cluster + xdsCluster, err := buildXdsCluster(tlsListener.Name, tlsListener.Destinations) + if err != nil { + return nil, multierror.Append(err, errors.New("error building xds cluster")) + } + tCtx.AddXdsResource(resource.ClusterType, xdsCluster) + + } + + xdsRouteCfg := &route.RouteConfiguration{ + Name: routeName, + } + xdsRouteCfg.VirtualHosts = append(xdsRouteCfg.VirtualHosts, vHost) + + tCtx.AddXdsResource(resource.ListenerType, xdsListener) + tCtx.AddXdsResource(resource.RouteType, xdsRouteCfg) + } return tCtx, nil } From 5275facc3072a2c23148696aa64816fd7dbf2753 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 24 Sep 2022 11:21:45 +0530 Subject: [PATCH 14/30] additional xds validate tests Signed-off-by: Shubham Chauhan --- internal/ir/xds.go | 34 ++++++------ internal/ir/xds_test.go | 118 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 19 deletions(-) diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 60ace0e4fd..c622612d32 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -14,7 +14,7 @@ var ( ErrListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified") ErrTLSPrivateKey = errors.New("field PrivateKey must be specified") - ErrHTTPRouteNameEmpty = errors.New("field Name must be specified") + ErrRouteNameEmpty = errors.New("field Name must be specified") ErrHTTPRouteMatchEmpty = errors.New("either PathMatch, HeaderMatches or QueryParamMatches fields must be specified") ErrRouteDestinationHostInvalid = errors.New("field Address must be a valid IP address") ErrRouteDestinationPortInvalid = errors.New("field Port specified is invalid") @@ -172,7 +172,7 @@ type HTTPRoute struct { func (h HTTPRoute) Validate() error { var errs error if h.Name == "" { - errs = multierror.Append(errs, ErrHTTPRouteNameEmpty) + errs = multierror.Append(errs, ErrRouteNameEmpty) } if h.PathMatch == nil && (len(h.HeaderMatches) == 0) && (len(h.QueryParamMatches) == 0) { errs = multierror.Append(errs, ErrHTTPRouteMatchEmpty) @@ -245,6 +245,20 @@ type RouteDestination struct { Weight uint32 } +// Validate the fields within the RouteDestination structure +func (r RouteDestination) Validate() error { + var errs error + // Only support IP hosts for now + if ip := net.ParseIP(r.Host); ip == nil { + errs = multierror.Append(errs, ErrRouteDestinationHostInvalid) + } + if r.Port == 0 { + errs = multierror.Append(errs, ErrRouteDestinationPortInvalid) + } + + return errs +} + // Add header configures a headder to be added to a request. // +k8s:deepcopy-gen=true type AddHeader struct { @@ -347,20 +361,6 @@ func (r HTTPPathModifier) Validate() error { return errs } -// Validate the fields within the RouteDestination structure -func (r RouteDestination) Validate() error { - var errs error - // Only support IP hosts for now - if ip := net.ParseIP(r.Host); ip == nil { - errs = multierror.Append(errs, ErrRouteDestinationHostInvalid) - } - if r.Port == 0 { - errs = multierror.Append(errs, ErrRouteDestinationPortInvalid) - } - - return errs -} - // StringMatch holds the various match conditions. // Only one of Exact, Prefix or SafeRegex can be set. // +k8s:deepcopy-gen=true @@ -450,7 +450,7 @@ type TLSRoute struct { func (h TLSRoute) Validate() error { var errs error if h.Name == "" { - errs = multierror.Append(errs, ErrHTTPRouteNameEmpty) + errs = multierror.Append(errs, ErrRouteNameEmpty) } for _, dest := range h.Destinations { if err := dest.Validate(); err != nil { diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 14c1e46a9c..75ca82fb5b 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -45,6 +45,22 @@ var ( Routes: []*HTTPRoute{&weightedInvalidBackendsHTTPRoute}, } + // TLSListener + happyTLSListener = TLSListener{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + Hostnames: []string{"example.com"}, + Routes: []*TLSRoute{&happyTLSRoute}, + } + invalidAddrTLSListener = TLSListener{ + Name: "invalid-addr", + Address: "1.0.0", + Port: 80, + Hostnames: []string{"example.com"}, + Routes: []*TLSRoute{&happyTLSRoute}, + } + // HTTPRoute happyHTTPRoute = HTTPRoute{ Name: "happy", @@ -219,6 +235,12 @@ var ( }, } + // TLSRoute + happyTLSRoute = TLSRoute{ + Name: "happy", + Destinations: []*RouteDestination{&happyRouteDestination}, + } + // RouteDestination happyRouteDestination = RouteDestination{ Host: "10.11.12.13", @@ -244,6 +266,13 @@ func TestValidateXds(t *testing.T) { }, want: nil, }, + { + name: "happy tls", + input: Xds{ + TLS: []*TLSListener{&happyTLSListener}, + }, + want: nil, + }, { name: "invalid listener", input: Xds{ @@ -337,6 +366,57 @@ func TestValidateHTTPListener(t *testing.T) { } } +func TestValidateTLSListener(t *testing.T) { + tests := []struct { + name string + input TLSListener + want []error + }{ + { + name: "happy", + input: happyTLSListener, + want: nil, + }, + { + name: "invalid name", + input: TLSListener{ + Address: "0.0.0.0", + Port: 80, + Hostnames: []string{"example.com"}, + Routes: []*TLSRoute{&happyTLSRoute}, + }, + want: []error{ErrListenerNameEmpty}, + }, + { + name: "invalid addr", + input: invalidAddrTLSListener, + want: []error{ErrListenerAddressInvalid}, + }, + { + name: "invalid port and hostnames", + input: TLSListener{ + Name: "invalid-port-and-hostnames", + Address: "1.0.0", + Routes: []*TLSRoute{&happyTLSRoute}, + }, + want: []error{ErrListenerPortInvalid, ErrListenerHostnamesEmpty}, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + if test.want == nil { + require.NoError(t, test.input.Validate()) + } else { + got := test.input.Validate() + for _, w := range test.want { + assert.ErrorContains(t, got, w.Error()) + } + } + }) + } +} + func TestValidateTLSListenerConfig(t *testing.T) { tests := []struct { name string @@ -397,7 +477,7 @@ func TestValidateHTTPRoute(t *testing.T) { }, Destinations: []*RouteDestination{&happyRouteDestination}, }, - want: []error{ErrHTTPRouteNameEmpty}, + want: []error{ErrRouteNameEmpty}, }, { name: "empty match", @@ -420,7 +500,7 @@ func TestValidateHTTPRoute(t *testing.T) { HeaderMatches: []*StringMatch{ptrTo(StringMatch{})}, Destinations: []*RouteDestination{&happyRouteDestination}, }, - want: []error{ErrHTTPRouteNameEmpty, ErrStringMatchConditionInvalid}, + want: []error{ErrRouteNameEmpty, ErrStringMatchConditionInvalid}, }, { name: "redirect-httproute", @@ -483,6 +563,40 @@ func TestValidateHTTPRoute(t *testing.T) { } } +func TestValidateTLSRoute(t *testing.T) { + tests := []struct { + name string + input TLSRoute + want []error + }{ + { + name: "happy", + input: happyTLSRoute, + want: nil, + }, + { + name: "invalid name", + input: TLSRoute{ + Destinations: []*RouteDestination{&happyRouteDestination}, + }, + want: []error{ErrRouteNameEmpty}, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + if test.want == nil { + require.NoError(t, test.input.Validate()) + } else { + got := test.input.Validate() + for _, w := range test.want { + assert.ErrorContains(t, got, w.Error()) + } + } + }) + } +} + func TestValidateRouteDestination(t *testing.T) { tests := []struct { name string From 5803a54e6628b27aea013c0bc3f43043407a3e03 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 24 Sep 2022 11:31:59 +0530 Subject: [PATCH 15/30] tlsroute refgrant test Signed-off-by: Shubham Chauhan --- ...ther-namespace-allowed-by-refgrant.in.yaml | 56 +++++++++++++ ...her-namespace-allowed-by-refgrant.out.yaml | 82 +++++++++++++++++++ internal/gatewayapi/translator_test.go | 2 +- 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml new file mode 100644 index 0000000000..636dc9fa3a --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml @@ -0,0 +1,56 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 90 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + namespace: test-service-namespace + port: 8080 +services: + - apiVersion: v1 + kind: Service + metadata: + namespace: test-service-namespace + name: service-1 + spec: + clusterIP: 7.7.7.7 + ports: + - port: 8080 +referenceGrants: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + namespace: test-service-namespace + name: referencegrant-1 + spec: + from: + - group: gateway.networking.k8s.io + kind: TLSRoute + namespace: default + to: + - group: "" + kind: Service diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml new file mode 100644 index 0000000000..ae45f1dbde --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -0,0 +1,82 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 90 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 1 + conditions: + - type: Ready + status: "True" + reason: Ready + message: Listener is ready +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + namespace: test-service-namespace + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + tls: + - name: envoy-gateway-gateway-1-tls + address: 0.0.0.0 + port: 10090 + hostnames: + - "*" + routes: + - name: default-tlsroute-1-rule-0-match-0-* + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 +infraIR: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" + ports: + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index 5ff445d4ed..e0efc6ff68 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -24,7 +24,7 @@ func mustUnmarshal(t *testing.T, val string, out interface{}) { } func TestTranslate(t *testing.T) { - inputFiles, err := filepath.Glob(filepath.Join("testdata", "httproute-with-non-matching-specific-hostname-attaching-to-gateway-with-wildcard-hostname.in.yaml")) + inputFiles, err := filepath.Glob(filepath.Join("testdata", "*.in.yaml")) require.NoError(t, err) for _, inputFile := range inputFiles { From 754083f5e4f3df16ea44d50809d3150b44250178 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Tue, 27 Sep 2022 00:54:55 +0530 Subject: [PATCH 16/30] add rbac permissions for tlsroute Signed-off-by: Shubham Chauhan --- internal/provider/kubernetes/config/rbac/role.yaml | 2 ++ internal/provider/kubernetes/rbac.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/provider/kubernetes/config/rbac/role.yaml b/internal/provider/kubernetes/config/rbac/role.yaml index ba7e139e0d..66a48f7f1d 100644 --- a/internal/provider/kubernetes/config/rbac/role.yaml +++ b/internal/provider/kubernetes/config/rbac/role.yaml @@ -31,6 +31,7 @@ rules: - httproutes - referencegrants - referencepolicies + - tlsroutes verbs: - get - list @@ -42,5 +43,6 @@ rules: - gatewayclasses/status - gateways/status - httproutes/status + - tlsroutes/status verbs: - update diff --git a/internal/provider/kubernetes/rbac.go b/internal/provider/kubernetes/rbac.go index 32a72ee193..4015f40550 100644 --- a/internal/provider/kubernetes/rbac.go +++ b/internal/provider/kubernetes/rbac.go @@ -1,7 +1,7 @@ package kubernetes -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;referencepolicies;referencegrants,verbs=get;list;watch;update -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status,verbs=update +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;tlsroutes;referencepolicies;referencegrants,verbs=get;list;watch;update +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status;tlsroutes/status,verbs=update // RBAC for watched resources of Gateway API controllers. // +kubebuilder:rbac:groups="",resources=secrets;services;namespaces,verbs=get;list;watch From 48df046839690f0ca22600087c00b40d75cc5873 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Wed, 5 Oct 2022 20:23:18 +0530 Subject: [PATCH 17/30] updates post rebase Signed-off-by: Shubham Chauhan --- internal/gatewayapi/translator.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index d265b7432e..cf299d93eb 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -549,7 +549,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap // see more https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.Listener. irListener.Hostnames = append(irListener.Hostnames, "*") } - xdsIR.HTTP = append(xdsIR.HTTP, irListener) + gwXdsIR.HTTP = append(gwXdsIR.HTTP, irListener) case v1beta1.TLSProtocolType: irListener := &ir.TLSListener{ Name: irListenerName(listener), @@ -561,9 +561,8 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap } else { irListener.Hostnames = append(irListener.Hostnames, "*") } - xdsIR.TLS = append(xdsIR.TLS, irListener) + gwXdsIR.TLS = append(gwXdsIR.TLS, irListener) } - gwXdsIR.HTTP = append(gwXdsIR.HTTP, irListener) // Add the listener to the Infra IR. Infra IR ports must have a unique port number. if !slices.Contains(foundPorts, servicePort) { @@ -1152,8 +1151,8 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways }) } } - - irListener := xdsIR.GetListener(irListenerName(listener)) + irKey := irStringKey(listener.gateway) + irListener := xdsIR[irKey].GetListener(irListenerName(listener)) if irListener != nil { irListener.Routes = append(irListener.Routes, perHostRoutes...) } @@ -1347,8 +1346,8 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ }) } } - - irListener := xdsIR.GetTLSListener(irListenerName(listener)) + irKey := irStringKey(listener.gateway) + irListener := xdsIR[irKey].GetTLSListener(irListenerName(listener)) if irListener != nil { irListener.Routes = append(irListener.Routes, perHostRoutes...) } From 8a5632fb2026fb01ce62ba489c19855f51afba46 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Wed, 5 Oct 2022 20:48:15 +0530 Subject: [PATCH 18/30] add status updater, gateway watcher for tlsroute Signed-off-by: Shubham Chauhan --- internal/gatewayapi/helpers_v1alpha2.go | 10 +++ internal/provider/kubernetes/helpers.go | 52 ++++++++++++++ internal/provider/kubernetes/httproute.go | 47 +------------ .../provider/kubernetes/httproute_test.go | 2 +- internal/provider/kubernetes/kubernetes.go | 5 +- internal/provider/kubernetes/tlsroute.go | 70 ++++++++++++++++--- 6 files changed, 128 insertions(+), 58 deletions(-) create mode 100644 internal/provider/kubernetes/helpers.go diff --git a/internal/gatewayapi/helpers_v1alpha2.go b/internal/gatewayapi/helpers_v1alpha2.go index 1f21260e8f..e3e4a43c73 100644 --- a/internal/gatewayapi/helpers_v1alpha2.go +++ b/internal/gatewayapi/helpers_v1alpha2.go @@ -1,3 +1,5 @@ +// Portions of this code are based on code from Contour. + package gatewayapi import ( @@ -33,6 +35,14 @@ func PortNumPtrV1Alpha2(port int) *v1alpha2.PortNumber { return &pn } +func UpgradeParentReferences(old []v1alpha2.ParentReference) []v1beta1.ParentReference { + newParentReferences := make([]v1beta1.ParentReference, len(old)) + for _, o := range old { + newParentReferences = append(newParentReferences, UpgradeParentReference(o)) + } + return newParentReferences +} + // UpgradeParentReference converts v1alpha2.ParentReference to v1beta1.ParentReference func UpgradeParentReference(old v1alpha2.ParentReference) v1beta1.ParentReference { upgraded := v1beta1.ParentReference{} diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go new file mode 100644 index 0000000000..20059da5a6 --- /dev/null +++ b/internal/provider/kubernetes/helpers.go @@ -0,0 +1,52 @@ +package kubernetes + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// validateParentRefs validates parentRefs for the provided route, returning the +// referenced Gateways managed by Envoy Gateway. The only supported parentRef +// is a Gateway. +func validateParentRefs(ctx context.Context, client client.Client, namespace string, + gatewayClassController gwapiv1b1.GatewayController, + routeParentReferences []gwapiv1b1.ParentReference) ([]gwapiv1b1.Gateway, error) { + var ret []gwapiv1b1.Gateway + for i := range routeParentReferences { + ref := routeParentReferences[i] + if ref.Kind != nil && *ref.Kind != "Gateway" { + return nil, fmt.Errorf("invalid Kind %q", *ref.Kind) + } + if ref.Group != nil && *ref.Group != gwapiv1b1.GroupName { + return nil, fmt.Errorf("invalid Group %q", *ref.Group) + } + // Ensure the referenced Gateway exists, using the route's namespace unless + // specified by the parentRef. + ns := namespace + if ref.Namespace != nil { + ns = string(*ref.Namespace) + } + gwKey := types.NamespacedName{ + Namespace: ns, + Name: string(ref.Name), + } + gw := new(gwapiv1b1.Gateway) + if err := client.Get(ctx, gwKey, gw); err != nil { + return nil, fmt.Errorf("failed to get gateway %s/%s: %v", gwKey.Namespace, gwKey.Name, err) + } + gcKey := types.NamespacedName{Name: string(gw.Spec.GatewayClassName)} + gc := new(gwapiv1b1.GatewayClass) + if err := client.Get(ctx, gcKey, gc); err != nil { + return nil, fmt.Errorf("failed to get gatewayclass %s: %v", gcKey.Name, err) + } + if gc.Spec.ControllerName == gatewayClassController { + ret = append(ret, *gw) + } + } + + return ret, nil +} diff --git a/internal/provider/kubernetes/httproute.go b/internal/provider/kubernetes/httproute.go index 88eb98a591..7af55acc28 100644 --- a/internal/provider/kubernetes/httproute.go +++ b/internal/provider/kubernetes/httproute.go @@ -129,7 +129,7 @@ func (r *httpRouteReconciler) getHTTPRoutesForGateway(obj client.Object) []recon requests := []reconcile.Request{} for i := range routes.Items { route := routes.Items[i] - gateways, err := r.validateParentRefs(ctx, &route) + gateways, err := validateParentRefs(ctx, r.client, route.Namespace, r.classController, route.Spec.ParentRefs) if err != nil { r.log.Info("invalid parentRefs for httproute, bypassing reconciliation", "object", obj) continue @@ -195,7 +195,7 @@ func (r *httpRouteReconciler) Reconcile(ctx context.Context, request reconcile.R } // Validate the route. - gws, err := r.validateParentRefs(ctx, &route) + gws, err := validateParentRefs(ctx, r.client, route.Namespace, r.classController, route.Spec.ParentRefs) if err != nil { // Remove the route from the watchable map since it's invalid. r.resources.HTTPRoutes.Delete(routeKey) @@ -344,46 +344,3 @@ func (r *httpRouteReconciler) subscribeAndUpdateStatus(ctx context.Context) { } r.log.Info("status subscriber shutting down") } - -// validateParentRefs validates parentRefs for the provided route, returning the referenced Gateways -// managed by Envoy Gateway. The only supported parentRef is a Gateway. -func (r *httpRouteReconciler) validateParentRefs(ctx context.Context, route *gwapiv1b1.HTTPRoute) ([]gwapiv1b1.Gateway, error) { - if route == nil { - return nil, fmt.Errorf("httproute is nil") - } - - var ret []gwapiv1b1.Gateway - for i := range route.Spec.ParentRefs { - ref := route.Spec.ParentRefs[i] - if ref.Kind != nil && *ref.Kind != "Gateway" { - return nil, fmt.Errorf("invalid Kind %q", *ref.Kind) - } - if ref.Group != nil && *ref.Group != gwapiv1b1.GroupName { - return nil, fmt.Errorf("invalid Group %q", *ref.Group) - } - // Ensure the referenced Gateway exists, using the route's namespace unless - // specified by the parentRef. - ns := route.Namespace - if ref.Namespace != nil { - ns = string(*ref.Namespace) - } - gwKey := types.NamespacedName{ - Namespace: ns, - Name: string(ref.Name), - } - gw := new(gwapiv1b1.Gateway) - if err := r.client.Get(ctx, gwKey, gw); err != nil { - return nil, fmt.Errorf("failed to get gateway %s/%s: %v", gwKey.Namespace, gwKey.Name, err) - } - gcKey := types.NamespacedName{Name: string(gw.Spec.GatewayClassName)} - gc := new(gwapiv1b1.GatewayClass) - if err := r.client.Get(ctx, gcKey, gc); err != nil { - return nil, fmt.Errorf("failed to get gatewayclass %s: %v", gcKey.Name, err) - } - if gc.Spec.ControllerName == r.classController { - ret = append(ret, *gw) - } - } - - return ret, nil -} diff --git a/internal/provider/kubernetes/httproute_test.go b/internal/provider/kubernetes/httproute_test.go index 6fd509c78b..c504d31215 100644 --- a/internal/provider/kubernetes/httproute_test.go +++ b/internal/provider/kubernetes/httproute_test.go @@ -732,7 +732,7 @@ func TestValidateParentRefs(t *testing.T) { objs = append(objs, tc.gateways[i]) } r.client = fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).WithObjects(objs...).Build() - gws, err := r.validateParentRefs(ctx, tc.route) + gws, err := validateParentRefs(ctx, r.client, tc.route.Namespace, r.classController, tc.route.Spec.ParentRefs) if tc.expected { require.NoError(t, err) } else { diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go index ec40d13636..127febf547 100644 --- a/internal/provider/kubernetes/kubernetes.go +++ b/internal/provider/kubernetes/kubernetes.go @@ -51,13 +51,10 @@ func New(cfg *rest.Config, svr *config.Server, resources *message.ProviderResour return nil, fmt.Errorf("failed to create gateway controller: %w", err) } - // Add once for all types of Routes that we watch for. We need at least one - // Route object to proceed with translation. - resources.RoutesInitialized.Add(1) if err := newHTTPRouteController(mgr, svr, updateHandler.Writer(), resources); err != nil { return nil, fmt.Errorf("failed to create httproute controller: %w", err) } - if err := newTLSRouteController(mgr, svr, resources); err != nil { + if err := newTLSRouteController(mgr, svr, updateHandler.Writer(), resources); err != nil { return nil, fmt.Errorf("failed to create tlsroute controller: %w", err) } diff --git a/internal/provider/kubernetes/tlsroute.go b/internal/provider/kubernetes/tlsroute.go index 90203b56fe..af8e90701d 100644 --- a/internal/provider/kubernetes/tlsroute.go +++ b/internal/provider/kubernetes/tlsroute.go @@ -19,11 +19,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/message" "github.com/envoyproxy/gateway/internal/provider/utils" + "github.com/envoyproxy/gateway/internal/status" ) const ( @@ -31,19 +33,23 @@ const ( ) type tlsRouteReconciler struct { - client client.Client - log logr.Logger + client client.Client + log logr.Logger + statusUpdater status.Updater + classController gwapiv1b1.GatewayController resources *message.ProviderResources } // newTLSRouteController creates the tlsroute controller from mgr. The controller will be pre-configured // to watch for TLSRoute objects across all namespaces. -func newTLSRouteController(mgr manager.Manager, cfg *config.Server, resources *message.ProviderResources) error { +func newTLSRouteController(mgr manager.Manager, cfg *config.Server, su status.Updater, resources *message.ProviderResources) error { r := &tlsRouteReconciler{ - client: mgr.GetClient(), - log: cfg.Logger, - resources: resources, + client: mgr.GetClient(), + log: cfg.Logger, + classController: gwapiv1b1.GatewayController(cfg.EnvoyGateway.Gateway.ControllerName), + statusUpdater: su, + resources: resources, } c, err := controller.New("tlsroute", mgr, controller.Options{Reconciler: r}) @@ -80,6 +86,14 @@ func newTLSRouteController(mgr manager.Manager, cfg *config.Server, resources *m return err } + // Watch Gateway CRUDs and reconcile affected TLSRoutes. + if err := c.Watch( + &source.Kind{Type: &gwapiv1b1.Gateway{}}, + handler.EnqueueRequestsFromMapFunc(r.getTLSRoutesForGateway), + ); err != nil { + return err + } + // Watch Service CRUDs and reconcile affected TLSRoutes. if err := c.Watch( &source.Kind{Type: &corev1.Service{}}, @@ -92,6 +106,48 @@ func newTLSRouteController(mgr manager.Manager, cfg *config.Server, resources *m return nil } +// getTLSRoutesForGateway uses a Gateway obj to fetch TLSRoutes, iterating +// through them and creating a reconciliation request for each valid TLSRoute +// that references obj. +func (r *tlsRouteReconciler) getTLSRoutesForGateway(obj client.Object) []reconcile.Request { + ctx := context.Background() + + gw, ok := obj.(*gwapiv1b1.Gateway) + if !ok { + r.log.Info("unexpected object type, bypassing reconciliation", "object", obj) + return []reconcile.Request{} + } + + routes := &gwapiv1a2.TLSRouteList{} + if err := r.client.List(ctx, routes); err != nil { + return []reconcile.Request{} + } + + requests := []reconcile.Request{} + for i := range routes.Items { + route := routes.Items[i] + gateways, err := validateParentRefs(ctx, r.client, route.Namespace, r.classController, gatewayapi.UpgradeParentReferences(route.Spec.ParentRefs)) + if err != nil { + r.log.Info("invalid parentRefs for tlsroute, bypassing reconciliation", "object", obj) + continue + } + for j := range gateways { + if gateways[j].Namespace == gw.Namespace && gateways[j].Name == gw.Name { + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: route.Namespace, + Name: route.Name, + }, + } + requests = append(requests, req) + break + } + } + } + + return requests +} + // getTLSRoutesForService uses a Service obj to fetch TLSRoutes that references // the Service using `.spec.rules.backendRefs`. The affected TLSRoutes are then // pushed for reconciliation. @@ -119,8 +175,6 @@ func (r *tlsRouteReconciler) Reconcile(ctx context.Context, request reconcile.Re log.Info("reconciling tlsroute") - defer r.resources.RouteInitializedOnce.Do(r.resources.RoutesInitialized.Done) - // Fetch all TLSRoutes from the cache. routeList := &gwapiv1a2.TLSRouteList{} if err := r.client.List(ctx, routeList); err != nil { From fa840ed8eeabc482abf4cf15a57b8105d6779ecb Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Fri, 7 Oct 2022 01:25:36 +0530 Subject: [PATCH 19/30] add status update framework for tlsroute Signed-off-by: Shubham Chauhan --- internal/cmd/server.go | 1 + internal/gatewayapi/contexts.go | 10 ++++++- internal/gatewayapi/helpers_v1alpha2.go | 4 +-- internal/gatewayapi/runner/runner.go | 7 ++++- internal/message/types.go | 1 + internal/provider/kubernetes/helpers.go | 4 +++ internal/provider/kubernetes/tlsroute.go | 37 +++++++++++++++++++++++- internal/status/status.go | 8 +++++ 8 files changed, 67 insertions(+), 5 deletions(-) diff --git a/internal/cmd/server.go b/internal/cmd/server.go index eff0454846..052d0229e4 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -154,6 +154,7 @@ func setupRunners(cfg *config.Server) error { pResources.GatewayStatuses.Close() pResources.HTTPRouteStatuses.Close() pResources.TLSRoutes.Close() + pResources.TLSRouteStatuses.Close() xdsIR.Close() infraIR.Close() xds.Close() diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index b40a780d8b..4143df39db 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -334,7 +334,15 @@ func (t *TLSRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefer var parentRef *v1beta1.ParentReference for i, p := range t.Spec.ParentRefs { p := UpgradeParentReference(p) - if *p.Namespace == *forParentRef.Namespace && p.Name == forParentRef.Name { + defaultNamespace := v1beta1.Namespace("default") + if p.Namespace == nil { + p.Namespace = &defaultNamespace + } + if forParentRef.Namespace == nil { + forParentRef.Namespace = &defaultNamespace + } + if *p.Namespace == *forParentRef.Namespace && + p.Name == forParentRef.Name { upgraded := UpgradeParentReference(t.Spec.ParentRefs[i]) parentRef = &upgraded break diff --git a/internal/gatewayapi/helpers_v1alpha2.go b/internal/gatewayapi/helpers_v1alpha2.go index e3e4a43c73..c4b2153f2a 100644 --- a/internal/gatewayapi/helpers_v1alpha2.go +++ b/internal/gatewayapi/helpers_v1alpha2.go @@ -37,8 +37,8 @@ func PortNumPtrV1Alpha2(port int) *v1alpha2.PortNumber { func UpgradeParentReferences(old []v1alpha2.ParentReference) []v1beta1.ParentReference { newParentReferences := make([]v1beta1.ParentReference, len(old)) - for _, o := range old { - newParentReferences = append(newParentReferences, UpgradeParentReference(o)) + for i, o := range old { + newParentReferences[i] = UpgradeParentReference(o) } return newParentReferences } diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 95a9dcaf50..2d6cf70d72 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -3,8 +3,8 @@ package runner import ( "context" + "gopkg.in/yaml.v2" "sigs.k8s.io/gateway-api/apis/v1beta1" - "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" @@ -102,6 +102,7 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { newKeys = append(newKeys, key) } } + for key, val := range result.XdsIR { if err := val.Validate(); err != nil { r.Logger.Error(err, "unable to validate xds ir, skipped sending it") @@ -127,6 +128,10 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { key := utils.NamespacedName(httpRoute) r.ProviderResources.HTTPRouteStatuses.Store(key, httpRoute) } + for _, tlsRoute := range result.TLSRoutes { + key := utils.NamespacedName(tlsRoute) + r.ProviderResources.TLSRouteStatuses.Store(key, tlsRoute) + } } } r.Logger.Info("shutting down") diff --git a/internal/message/types.go b/internal/message/types.go index 4205a3d119..c3f5675559 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -22,6 +22,7 @@ type ProviderResources struct { GatewayStatuses watchable.Map[types.NamespacedName, *gwapiv1b1.Gateway] HTTPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1b1.HTTPRoute] + TLSRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.TLSRoute] } func (p *ProviderResources) GetGatewayClasses() []*gwapiv1b1.GatewayClass { diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go index 20059da5a6..f23c1d7300 100644 --- a/internal/provider/kubernetes/helpers.go +++ b/internal/provider/kubernetes/helpers.go @@ -15,6 +15,7 @@ import ( func validateParentRefs(ctx context.Context, client client.Client, namespace string, gatewayClassController gwapiv1b1.GatewayController, routeParentReferences []gwapiv1b1.ParentReference) ([]gwapiv1b1.Gateway, error) { + var ret []gwapiv1b1.Gateway for i := range routeParentReferences { ref := routeParentReferences[i] @@ -24,6 +25,7 @@ func validateParentRefs(ctx context.Context, client client.Client, namespace str if ref.Group != nil && *ref.Group != gwapiv1b1.GroupName { return nil, fmt.Errorf("invalid Group %q", *ref.Group) } + // Ensure the referenced Gateway exists, using the route's namespace unless // specified by the parentRef. ns := namespace @@ -34,10 +36,12 @@ func validateParentRefs(ctx context.Context, client client.Client, namespace str Namespace: ns, Name: string(ref.Name), } + gw := new(gwapiv1b1.Gateway) if err := client.Get(ctx, gwKey, gw); err != nil { return nil, fmt.Errorf("failed to get gateway %s/%s: %v", gwKey.Namespace, gwKey.Name, err) } + gcKey := types.NamespacedName{Name: string(gw.Spec.GatewayClassName)} gc := new(gwapiv1b1.GatewayClass) if err := client.Get(ctx, gcKey, gc); err != nil { diff --git a/internal/provider/kubernetes/tlsroute.go b/internal/provider/kubernetes/tlsroute.go index af8e90701d..7efa704a2d 100644 --- a/internal/provider/kubernetes/tlsroute.go +++ b/internal/provider/kubernetes/tlsroute.go @@ -58,7 +58,13 @@ func newTLSRouteController(mgr manager.Manager, cfg *config.Server, su status.Up } r.log.Info("created tlsroute controller") - if err := c.Watch(&source.Kind{Type: &gwapiv1a2.TLSRoute{}}, &handler.EnqueueRequestForObject{}); err != nil { + // Subscribe to status updates + go r.subscribeAndUpdateStatus(context.Background()) + + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.TLSRoute{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { return err } // Add indexing on TLSRoute, for Service objects that are referenced in TLSRoute objects @@ -285,3 +291,32 @@ func validateTLSRouteBackendRef(ref *gwapiv1a2.BackendRef) error { return nil } + +// subscribeAndUpdateStatus subscribes to tlsroute status updates and writes it into the +// Kubernetes API Server +func (r *tlsRouteReconciler) subscribeAndUpdateStatus(ctx context.Context) { + // Subscribe to resources + for snapshot := range r.resources.TLSRouteStatuses.Subscribe(ctx) { + r.log.Info("received a status notification") + updates := snapshot.Updates + for _, update := range updates { + // skip delete updates. + if update.Delete { + continue + } + // key := update.Key + // val := update.Value + // r.statusUpdater.Send(status.Update{ + // NamespacedName: key, + // Resource: new(gwapiv1a2.TLSRoute), + // Mutator: status.MutatorFunc(func(obj client.Object) client.Object { + // if _, ok := obj.(*gwapiv1a2.TLSRoute); !ok { + // panic(fmt.Sprintf("unsupported object type %T", obj)) + // } + // return val + // }), + // }) + } + } + r.log.Info("status subscriber shutting down") +} diff --git a/internal/status/status.go b/internal/status/status.go index c58a488c0b..373a19b394 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -143,6 +144,7 @@ func (u *UpdateWriter) Send(update Update) { // GatewayClasses // Gateway // HTTPRoute +// TLSRoute func isStatusEqual(objA, objB interface{}) bool { opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime", "ObservedGeneration") switch a := objA.(type) { @@ -164,6 +166,12 @@ func isStatusEqual(objA, objB interface{}) bool { return true } } + case *gwapiv1a2.TLSRoute: + if b, ok := objB.(*gwapiv1a2.TLSRoute); ok { + if cmp.Equal(a.Status, b.Status, opts) { + return true + } + } } return false } From b674402aa615a8384f364af3cc9687155155fb0f Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Fri, 7 Oct 2022 11:21:10 +0530 Subject: [PATCH 20/30] lintfix, testfix, fix post rebase Signed-off-by: Shubham Chauhan --- ...th-invalid-allowed-tls-route-kind.out.yaml | 21 ++-- ...ith-tls-terminate-and-passthrough.out.yaml | 95 ++++++++++--------- ...ener-with-valid-tls-configuration.out.yaml | 32 +++---- ...d-tlsroute-same-hostname-and-port.out.yaml | 21 ++-- ...with-invalid-backend-ref-bad-port.out.yaml | 2 +- ...-invalid-backend-ref-invalid-kind.out.yaml | 2 +- ...-with-invalid-backend-ref-no-port.out.yaml | 2 +- ...th-invalid-backend-ref-no-service.out.yaml | 2 +- .../tlsroute-attaching-to-gateway.out.yaml | 53 ++++++----- ...ng-to-gateway-with-incorrect-mode.out.yaml | 21 ++-- ...attaching-to-gateway-with-no-mode.out.yaml | 21 ++-- ...her-namespace-allowed-by-refgrant.out.yaml | 53 ++++++----- ...er-both-passthrough-and-cert-data.out.yaml | 21 ++-- internal/gatewayapi/translator.go | 10 +- 14 files changed, 193 insertions(+), 163 deletions(-) diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml index 1afc2670f9..eacb1696ff 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml @@ -56,13 +56,16 @@ tlsRoutes: status: "False" reason: NoReadyListeners message: There are no ready listeners for this parent ref -xdsIR: {} +xdsIR: + envoy-gateway-gateway-1: {} infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index 9d224bf173..6fa0cd0c79 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -103,50 +103,53 @@ httpRoutes: reason: Accepted message: Route is accepted xdsIR: - http: - - name: envoy-gateway-gateway-1-tls-terminate - address: 0.0.0.0 - port: 10443 - hostnames: - - "foo.com" - tls: - serverCertificate: Zm9vCg== - privateKey: YmFyCg== - routes: - - name: default-httproute-1-rule-0-match-0-foo.com - pathMatch: - prefix: "/" - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 - tls: - - name: envoy-gateway-gateway-1-tls-passthrough - address: 0.0.0.0 - port: 10090 - hostnames: - - "foo.com" - routes: - - name: default-tlsroute-1-rule-0-match-0-foo.com - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + envoy-gateway-gateway-1: + http: + - name: envoy-gateway-gateway-1-tls-terminate + address: 0.0.0.0 + port: 10443 + hostnames: + - "foo.com" + tls: + serverCertificate: Zm9vCg== + privateKey: YmFyCg== + routes: + - name: default-httproute-1-rule-0-match-0-foo.com + pathMatch: + prefix: "/" + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 + tls: + - name: envoy-gateway-gateway-1-tls-passthrough + address: 0.0.0.0 + port: 10090 + hostnames: + - "foo.com" + routes: + - name: default-tlsroute-1-rule-0-match-0-foo.com + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" - ports: - - name: envoy-gateway-gateway-1-443 - protocol: "HTTPS" - servicePort: 443 - containerPort: 10443 - - name: envoy-gateway-gateway-1-90 - protocol: "TLS" - servicePort: 90 - containerPort: 10090 + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" + ports: + - name: envoy-gateway-gateway-1-443 + protocol: "HTTPS" + servicePort: 443 + containerPort: 10443 + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml index fb780f18da..ecd190d5e1 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-valid-tls-configuration.out.yaml @@ -61,28 +61,28 @@ xdsIR: envoy-gateway-gateway-1: http: - name: envoy-gateway-gateway-1-tls - address: 0.0.0.0 - port: 10443 - hostnames: - - "*" - tls: - serverCertificate: Zm9vCg== - privateKey: YmFyCg== - routes: - - name: default-httproute-1-rule-0-match-0-* - pathMatch: - prefix: "/" - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + address: 0.0.0.0 + port: 10443 + hostnames: + - "*" + tls: + serverCertificate: Zm9vCg== + privateKey: YmFyCg== + routes: + - name: default-httproute-1-rule-0-match-0-* + pathMatch: + prefix: "/" + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: envoy-gateway-gateway-1: proxy: metadata: labels: - gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway-gateway-1 image: envoyproxy/envoy:v1.23-latest listeners: diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml index a0d9082051..9f9aa90323 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml @@ -105,13 +105,16 @@ tlsRoutes: reason: NoReadyListeners message: There are no ready listeners for this parent ref -xdsIR: {} +xdsIR: + envoy-gateway-gateway-1: {} infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-bad-port.out.yaml b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-bad-port.out.yaml index 18fc9ff2c8..3218c2096e 100644 --- a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-bad-port.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-bad-port.out.yaml @@ -79,8 +79,8 @@ infraIR: proxy: metadata: labels: - gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway-gateway-1 image: envoyproxy/envoy:v1.23-latest listeners: diff --git a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-invalid-kind.out.yaml b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-invalid-kind.out.yaml index a0b7631900..7b0ad031a9 100644 --- a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-invalid-kind.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-invalid-kind.out.yaml @@ -80,8 +80,8 @@ infraIR: proxy: metadata: labels: - gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway-gateway-1 image: envoyproxy/envoy:v1.23-latest listeners: diff --git a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-port.out.yaml b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-port.out.yaml index 634ed1b1bc..5e41249f9d 100644 --- a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-port.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-port.out.yaml @@ -78,8 +78,8 @@ infraIR: proxy: metadata: labels: - gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway-gateway-1 image: envoyproxy/envoy:v1.23-latest listeners: diff --git a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-service.out.yaml b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-service.out.yaml index 4dfdd8dd66..27f0f389c4 100644 --- a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-service.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-no-service.out.yaml @@ -79,8 +79,8 @@ infraIR: proxy: metadata: labels: - gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway-gateway-1 image: envoyproxy/envoy:v1.23-latest listeners: diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index 0d6fa5bcc6..8abc0121d6 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -53,29 +53,32 @@ tlsRoutes: reason: Accepted message: Route is accepted xdsIR: - tls: - - name: envoy-gateway-gateway-1-tls - address: 0.0.0.0 - port: 10090 - hostnames: - - "*" - routes: - - name: default-tlsroute-1-rule-0-match-0-* - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + envoy-gateway-gateway-1: + tls: + - name: envoy-gateway-gateway-1-tls + address: 0.0.0.0 + port: 10090 + hostnames: + - "*" + routes: + - name: default-tlsroute-1-rule-0-match-0-* + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" - ports: - - name: envoy-gateway-gateway-1-90 - protocol: "TLS" - servicePort: 90 - containerPort: 10090 + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" + ports: + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml index bb7cfa5cd3..90c6250061 100644 --- a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-incorrect-mode.out.yaml @@ -52,13 +52,16 @@ tlsRoutes: status: "False" reason: NoReadyListeners message: There are no ready listeners for this parent ref -xdsIR: {} +xdsIR: + envoy-gateway-gateway-1: {} infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml index 9dca364f35..dbc22d0a5d 100644 --- a/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-not-attaching-to-gateway-with-no-mode.out.yaml @@ -50,13 +50,16 @@ tlsRoutes: status: "False" reason: NoReadyListeners message: There are no ready listeners for this parent ref -xdsIR: {} +xdsIR: + envoy-gateway-gateway-1: {} infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml index ae45f1dbde..97d7bea8bb 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -54,29 +54,32 @@ tlsRoutes: reason: Accepted message: Route is accepted xdsIR: - tls: - - name: envoy-gateway-gateway-1-tls - address: 0.0.0.0 - port: 10090 - hostnames: - - "*" - routes: - - name: default-tlsroute-1-rule-0-match-0-* - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + envoy-gateway-gateway-1: + tls: + - name: envoy-gateway-gateway-1-tls + address: 0.0.0.0 + port: 10090 + hostnames: + - "*" + routes: + - name: default-tlsroute-1-rule-0-match-0-* + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" - ports: - - name: envoy-gateway-gateway-1-90 - protocol: "TLS" - servicePort: 90 - containerPort: 10090 + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" + ports: + - name: envoy-gateway-gateway-1-90 + protocol: "TLS" + servicePort: 90 + containerPort: 10090 diff --git a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml index 447f1401d3..a8ec24c2dc 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml @@ -54,13 +54,16 @@ tlsRoutes: status: "False" reason: NoReadyListeners message: There are no ready listeners for this parent ref -xdsIR: {} +xdsIR: + envoy-gateway-gateway-1: {} infraIR: - proxy: - metadata: - labels: - gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class - name: envoy-gateway-class - image: envoyproxy/envoy:v1.23-latest - listeners: - - address: "" + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index cf299d93eb..77e752c1a7 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -1138,7 +1138,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } for _, routeRoute := range routeRoutes { - perHostRoutes = append(perHostRoutes, &ir.HTTPRoute{ + hostRoute := &ir.HTTPRoute{ Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), PathMatch: routeRoute.PathMatch, HeaderMatches: append(headerMatches, routeRoute.HeaderMatches...), @@ -1148,9 +1148,15 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways Destinations: routeRoute.Destinations, Redirect: routeRoute.Redirect, DirectResponse: routeRoute.DirectResponse, - }) + } + // Don't bother copying over the weights unless the route has invalid backends. + if routeRoute.BackendWeights.Invalid > 0 { + hostRoute.BackendWeights = routeRoute.BackendWeights + } + perHostRoutes = append(perHostRoutes, hostRoute) } } + irKey := irStringKey(listener.gateway) irListener := xdsIR[irKey].GetListener(irListenerName(listener)) if irListener != nil { From 3f348b8fb777416599bc56c8fa187e7d2b69927f Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Fri, 7 Oct 2022 11:26:37 +0530 Subject: [PATCH 21/30] yet another lintfix Signed-off-by: Shubham Chauhan --- ...ers-with-http-and-tlsroute-same-hostname-and-port.out.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml index 9f9aa90323..f0c6ae9bc5 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml @@ -112,8 +112,8 @@ infraIR: proxy: metadata: labels: - gateway.envoyproxy.io/owning-gateway-name: gateway-1 - gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway-gateway-1 image: envoyproxy/envoy:v1.23-latest listeners: From bf204fae11dd14c7560882ec009fdc494dfae333 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 8 Oct 2022 00:32:09 +0530 Subject: [PATCH 22/30] refactor tlslistener/route -> tcplistener/route, xds updates Signed-off-by: Shubham Chauhan --- internal/gatewayapi/contexts.go | 38 +++++--- ...ith-tls-terminate-and-passthrough.out.yaml | 14 ++- .../tlsroute-attaching-to-gateway.out.yaml | 14 ++- ...her-namespace-allowed-by-refgrant.out.yaml | 14 ++- internal/gatewayapi/translator.go | 37 +++---- internal/gatewayapi/translator_test.go | 4 +- internal/ir/xds.go | 64 ++++-------- internal/ir/xds_test.go | 97 +++++-------------- internal/ir/zz_generated.deepcopy.go | 56 +++-------- internal/provider/kubernetes/helpers.go | 2 +- internal/provider/kubernetes/tlsroute.go | 31 +++--- internal/xds/translator/route.go | 6 -- .../in/xds-ir/tls-route-passthrough.yaml | 12 +-- .../tls-route-passthrough.clusters.yaml | 9 +- .../tls-route-passthrough.listeners.yaml | 6 +- .../xds-ir/tls-route-passthrough.routes.yaml | 9 +- internal/xds/translator/translator.go | 42 ++------ 17 files changed, 156 insertions(+), 299 deletions(-) diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index 4143df39db..5be9a2cd24 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -200,6 +200,10 @@ func (l *ListenerContext) GetConditions() []metav1.Condition { return l.gateway.Status.Listeners[l.listenerStatusIdx].Conditions } +func (l *ListenerContext) SetTLSSecret(tlsSecret *v1.Secret) { + l.tlsSecret = tlsSecret +} + // RouteContext represents a generic Route object (HTTPRoute, TLSRoute, etc.) // that can reference Gateway objects. type RouteContext interface { @@ -276,11 +280,12 @@ func (h *HTTPRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefe } } if routeParentStatusIdx == -1 { - h.Status.Parents = append(h.Status.Parents, v1beta1.RouteParentStatus{ - ParentRef: forParentRef, + rParentStatus := v1beta1.RouteParentStatus{ // TODO: get this value from the config ControllerName: v1beta1.GatewayController(egv1alpha1.GatewayControllerName), - }) + ParentRef: forParentRef, + } + h.Status.Parents = append(h.Status.Parents, rParentStatus) routeParentStatusIdx = len(h.Status.Parents) - 1 } @@ -334,15 +339,7 @@ func (t *TLSRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefer var parentRef *v1beta1.ParentReference for i, p := range t.Spec.ParentRefs { p := UpgradeParentReference(p) - defaultNamespace := v1beta1.Namespace("default") - if p.Namespace == nil { - p.Namespace = &defaultNamespace - } - if forParentRef.Namespace == nil { - forParentRef.Namespace = &defaultNamespace - } - if *p.Namespace == *forParentRef.Namespace && - p.Name == forParentRef.Name { + if reflect.DeepEqual(p, forParentRef) { upgraded := UpgradeParentReference(t.Spec.ParentRefs[i]) parentRef = &upgraded break @@ -354,17 +351,26 @@ func (t *TLSRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefer routeParentStatusIdx := -1 for i := range t.Status.Parents { - if UpgradeParentReference(t.Status.Parents[i].ParentRef) == forParentRef { + p := UpgradeParentReference(t.Status.Parents[i].ParentRef) + defaultNamespace := v1beta1.Namespace(metav1.NamespaceDefault) + if forParentRef.Namespace == nil { + forParentRef.Namespace = &defaultNamespace + } + if p.Namespace == nil { + p.Namespace = &defaultNamespace + } + if reflect.DeepEqual(p, forParentRef) { routeParentStatusIdx = i break } } if routeParentStatusIdx == -1 { - t.Status.Parents = append(t.Status.Parents, v1alpha2.RouteParentStatus{ - ParentRef: DowngradeParentReference(forParentRef), + rParentStatus := v1alpha2.RouteParentStatus{ // TODO: get this value from the config ControllerName: v1alpha2.GatewayController(egv1alpha1.GatewayControllerName), - }) + ParentRef: DowngradeParentReference(forParentRef), + } + t.Status.Parents = append(t.Status.Parents, rParentStatus) routeParentStatusIdx = len(t.Status.Parents) - 1 } diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index 6fa0cd0c79..f715d7adec 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -121,18 +121,16 @@ xdsIR: - host: 7.7.7.7 port: 8080 weight: 1 - tls: + tcp: - name: envoy-gateway-gateway-1-tls-passthrough address: 0.0.0.0 port: 10090 - hostnames: + snis: - "foo.com" - routes: - - name: default-tlsroute-1-rule-0-match-0-foo.com - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: envoy-gateway-gateway-1: proxy: diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index 8abc0121d6..62c6f1fe11 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -54,18 +54,16 @@ tlsRoutes: message: Route is accepted xdsIR: envoy-gateway-gateway-1: - tls: + tcp: - name: envoy-gateway-gateway-1-tls address: 0.0.0.0 port: 10090 - hostnames: + snis: - "*" - routes: - - name: default-tlsroute-1-rule-0-match-0-* - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: envoy-gateway-gateway-1: proxy: diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml index 97d7bea8bb..4f5a43713e 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -55,18 +55,16 @@ tlsRoutes: message: Route is accepted xdsIR: envoy-gateway-gateway-1: - tls: + tcp: - name: envoy-gateway-gateway-1-tls address: 0.0.0.0 port: 10090 - hostnames: + snis: - "*" - routes: - - name: default-tlsroute-1-rule-0-match-0-* - destinations: - - host: 7.7.7.7 - port: 8080 - weight: 1 + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 infraIR: envoy-gateway-gateway-1: proxy: diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 77e752c1a7..f785b7b1df 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -472,7 +472,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap break } - listener.tlsSecret = secret + listener.SetTLSSecret(secret) case v1beta1.TLSProtocolType: if listener.TLS == nil { listener.SetCondition( @@ -551,17 +551,17 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap } gwXdsIR.HTTP = append(gwXdsIR.HTTP, irListener) case v1beta1.TLSProtocolType: - irListener := &ir.TLSListener{ + irListener := &ir.TCPListener{ Name: irListenerName(listener), Address: "0.0.0.0", Port: uint32(containerPort), } if listener.Hostname != nil { - irListener.Hostnames = append(irListener.Hostnames, string(*listener.Hostname)) + irListener.SNIs = append(irListener.SNIs, string(*listener.Hostname)) } else { - irListener.Hostnames = append(irListener.Hostnames, "*") + irListener.SNIs = append(irListener.SNIs, "*") } - gwXdsIR.TLS = append(gwXdsIR.TLS, irListener) + gwXdsIR.TCP = append(gwXdsIR.TCP, irListener) } // Add the listener to the Infra IR. Infra IR ports must have a unique port number. @@ -1219,14 +1219,10 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ // Need to compute Route rules within the parentRef loop because // any conditions that come out of it have to go on each RouteParentStatus, // not on the Route as a whole. - var routeRoutes []*ir.HTTPRoute + var routeDestinations []*ir.RouteDestination // compute backends - for ruleIdx, rule := range tlsRoute.Spec.Rules { - ruleRoute := &ir.HTTPRoute{ - Name: routeName(tlsRoute, ruleIdx, 0), - } - + for _, rule := range tlsRoute.Spec.Rules { for _, backendRef := range rule.BackendRefs { if backendRef.Group != nil && *backendRef.Group != "" { parentRef.SetCondition(tlsRoute, @@ -1319,7 +1315,7 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ weight = uint32(*backendRef.Weight) } - ruleRoute.Destinations = append(ruleRoute.Destinations, &ir.RouteDestination{ + routeDestinations = append(routeDestinations, &ir.RouteDestination{ Host: service.Spec.ClusterIP, Port: uint32(*backendRef.Port), Weight: weight, @@ -1331,8 +1327,6 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ // - sum of weights for valid backend refs is 0 // - returning 500's for invalid backend refs // - etc. - - routeRoutes = append(routeRoutes, ruleRoute) } var hasHostnameIntersection bool @@ -1343,24 +1337,15 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ } hasHostnameIntersection = true - var perHostRoutes []*ir.TLSRoute - for _, host := range hosts { - for _, routeRoute := range routeRoutes { - perHostRoutes = append(perHostRoutes, &ir.TLSRoute{ - Name: fmt.Sprintf("%s-%s", routeRoute.Name, host), - Destinations: routeRoute.Destinations, - }) - } - } irKey := irStringKey(listener.gateway) - irListener := xdsIR[irKey].GetTLSListener(irListenerName(listener)) + irListener := xdsIR[irKey].GetTCPListener(irListenerName(listener)) if irListener != nil { - irListener.Routes = append(irListener.Routes, perHostRoutes...) + irListener.Destinations = routeDestinations } // Theoretically there should only be one parent ref per // Route that attaches to a given Listener, so fine to just increment here, but we // might want to check to ensure we're not double-counting. - if len(routeRoutes) > 0 { + if len(routeDestinations) > 0 { listener.IncrementAttachedRoutes() } } diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index e0efc6ff68..c4a005a372 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -81,8 +81,8 @@ func TestTranslate(t *testing.T) { sort.Slice(got.XdsIR[envoyGatewayNsName].HTTP, func(i, j int) bool { return got.XdsIR[envoyGatewayNsName].HTTP[i].Name < got.XdsIR[envoyGatewayNsName].HTTP[j].Name }) - sort.Slice(got.XdsIR[envoyGatewayNsName].TLS, func(i, j int) bool { - return got.XdsIR[envoyGatewayNsName].TLS[i].Name < got.XdsIR[envoyGatewayNsName].TLS[j].Name + sort.Slice(got.XdsIR[envoyGatewayNsName].TCP, func(i, j int) bool { + return got.XdsIR[envoyGatewayNsName].TCP[i].Name < got.XdsIR[envoyGatewayNsName].TCP[j].Name }) // Only 1 listener is supported sort.Slice(got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports, diff --git a/internal/ir/xds.go b/internal/ir/xds.go index c622612d32..b6c8057d3f 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -11,7 +11,7 @@ var ( ErrListenerNameEmpty = errors.New("field Name must be specified") ErrListenerAddressInvalid = errors.New("field Address must be a valid IP address") ErrListenerPortInvalid = errors.New("field Port specified is invalid") - ErrListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") + ErrHTTPListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified") ErrTLSPrivateKey = errors.New("field PrivateKey must be specified") ErrRouteNameEmpty = errors.New("field Name must be specified") @@ -35,8 +35,8 @@ var ( type Xds struct { // HTTP listeners exposed by the gateway. HTTP []*HTTPListener - // TLS Listeners exposed by the gateway. - TLS []*TLSListener + // TCP Listeners exposed by the gateway. + TCP []*TCPListener } // Validate the fields within the Xds structure. @@ -59,8 +59,8 @@ func (x Xds) GetListener(name string) *HTTPListener { return nil } -func (x Xds) GetTLSListener(name string) *TLSListener { - for _, listener := range x.TLS { +func (x Xds) GetTCPListener(name string) *TCPListener { + for _, listener := range x.TCP { if listener.Name == name { return listener } @@ -101,7 +101,7 @@ func (h HTTPListener) Validate() error { errs = multierror.Append(errs, ErrListenerPortInvalid) } if len(h.Hostnames) == 0 { - errs = multierror.Append(errs, ErrListenerHostnamesEmpty) + errs = multierror.Append(errs, ErrHTTPListenerHostnamesEmpty) } if h.TLS != nil { if err := h.TLS.Validate(); err != nil { @@ -396,26 +396,26 @@ func (s StringMatch) Validate() error { return errs } -// TLSListener holds the listener configuration. +// TCPListener holds the listener configuration. // +k8s:deepcopy-gen=true -type TLSListener struct { - // Name of the TLSListener +type TCPListener struct { + // Name of the TCPListener Name string // Address that the listener should listen on. Address string // Port on which the service can be expected to be accessed by clients. Port uint32 - // Hostnames (Host/Authority header value) with which the service can be expected to be accessed by clients. - // This field is required. Wildcard hosts are supported in the suffix or prefix form. - // Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-virtualhost - // for more info. - Hostnames []string - // Routes associated with TLS passthrough traffic to the service. - Routes []*TLSRoute + // Server names that are compared against the server names of a new connection. + // Wildcard hosts are supported in the prefix form. Partial wildcards are not + // supported, and values like *w.example.com are invalid. + // SNIs are used only in case of TLS Passthrough. + SNIs []string + // Destinations associated with TCP traffic to the service. + Destinations []*RouteDestination } -// Validate the fields within the TLSListener structure -func (h TLSListener) Validate() error { +// Validate the fields within the TCPListener structure +func (h TCPListener) Validate() error { var errs error if h.Name == "" { errs = multierror.Append(errs, ErrListenerNameEmpty) @@ -426,36 +426,10 @@ func (h TLSListener) Validate() error { if h.Port == 0 { errs = multierror.Append(errs, ErrListenerPortInvalid) } - if len(h.Hostnames) == 0 { - errs = multierror.Append(errs, ErrListenerHostnamesEmpty) - } - for _, route := range h.Routes { + for _, route := range h.Destinations { if err := route.Validate(); err != nil { errs = multierror.Append(errs, err) } } return errs } - -// TLSRoute holds the route information associated with the HTTP Route -// +k8s:deepcopy-gen=true -type TLSRoute struct { - // Name of the TLSRoute - Name string - // Destinations associated with this matched route. - Destinations []*RouteDestination -} - -// Validate the fields within the TLSRoute structure -func (h TLSRoute) Validate() error { - var errs error - if h.Name == "" { - errs = multierror.Append(errs, ErrRouteNameEmpty) - } - for _, dest := range h.Destinations { - if err := dest.Validate(); err != nil { - errs = multierror.Append(errs, err) - } - } - return errs -} diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 75ca82fb5b..88fb525d26 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -45,20 +45,20 @@ var ( Routes: []*HTTPRoute{&weightedInvalidBackendsHTTPRoute}, } - // TLSListener - happyTLSListener = TLSListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, - Hostnames: []string{"example.com"}, - Routes: []*TLSRoute{&happyTLSRoute}, + // TCPListener + happyTCPListener = TCPListener{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + SNIs: []string{"example.com"}, + Destinations: []*RouteDestination{&happyRouteDestination}, } - invalidAddrTLSListener = TLSListener{ - Name: "invalid-addr", - Address: "1.0.0", - Port: 80, - Hostnames: []string{"example.com"}, - Routes: []*TLSRoute{&happyTLSRoute}, + invalidAddrTCPListener = TCPListener{ + Name: "invalid-addr", + Address: "1.0.0", + Port: 80, + SNIs: []string{"example.com"}, + Destinations: []*RouteDestination{&happyRouteDestination}, } // HTTPRoute @@ -235,12 +235,6 @@ var ( }, } - // TLSRoute - happyTLSRoute = TLSRoute{ - Name: "happy", - Destinations: []*RouteDestination{&happyRouteDestination}, - } - // RouteDestination happyRouteDestination = RouteDestination{ Host: "10.11.12.13", @@ -269,7 +263,7 @@ func TestValidateXds(t *testing.T) { { name: "happy tls", input: Xds{ - TLS: []*TLSListener{&happyTLSListener}, + TCP: []*TCPListener{&happyTCPListener}, }, want: nil, }, @@ -343,7 +337,7 @@ func TestValidateHTTPListener(t *testing.T) { Address: "1.0.0", Routes: []*HTTPRoute{&happyHTTPRoute}, }, - want: []error{ErrListenerPortInvalid, ErrListenerHostnamesEmpty}, + want: []error{ErrListenerPortInvalid, ErrHTTPListenerHostnamesEmpty}, }, { name: "invalid route match", @@ -366,41 +360,32 @@ func TestValidateHTTPListener(t *testing.T) { } } -func TestValidateTLSListener(t *testing.T) { +func TestValidateTCPListener(t *testing.T) { tests := []struct { name string - input TLSListener + input TCPListener want []error }{ { name: "happy", - input: happyTLSListener, + input: happyTCPListener, want: nil, }, { name: "invalid name", - input: TLSListener{ - Address: "0.0.0.0", - Port: 80, - Hostnames: []string{"example.com"}, - Routes: []*TLSRoute{&happyTLSRoute}, + input: TCPListener{ + Address: "0.0.0.0", + Port: 80, + SNIs: []string{"example.com"}, + Destinations: []*RouteDestination{&happyRouteDestination}, }, want: []error{ErrListenerNameEmpty}, }, { name: "invalid addr", - input: invalidAddrTLSListener, + input: invalidAddrTCPListener, want: []error{ErrListenerAddressInvalid}, }, - { - name: "invalid port and hostnames", - input: TLSListener{ - Name: "invalid-port-and-hostnames", - Address: "1.0.0", - Routes: []*TLSRoute{&happyTLSRoute}, - }, - want: []error{ErrListenerPortInvalid, ErrListenerHostnamesEmpty}, - }, } for _, test := range tests { test := test @@ -563,40 +548,6 @@ func TestValidateHTTPRoute(t *testing.T) { } } -func TestValidateTLSRoute(t *testing.T) { - tests := []struct { - name string - input TLSRoute - want []error - }{ - { - name: "happy", - input: happyTLSRoute, - want: nil, - }, - { - name: "invalid name", - input: TLSRoute{ - Destinations: []*RouteDestination{&happyRouteDestination}, - }, - want: []error{ErrRouteNameEmpty}, - }, - } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - if test.want == nil { - require.NoError(t, test.input.Validate()) - } else { - got := test.input.Validate() - for _, w := range test.want { - assert.ErrorContains(t, got, w.Error()) - } - } - }) - } -} - func TestValidateRouteDestination(t *testing.T) { tests := []struct { name string diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index b1354ebe69..35a629e8a2 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -359,32 +359,32 @@ func (in *StringMatch) DeepCopy() *StringMatch { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSListener) DeepCopyInto(out *TLSListener) { +func (in *TCPListener) DeepCopyInto(out *TCPListener) { *out = *in - if in.Hostnames != nil { - in, out := &in.Hostnames, &out.Hostnames + if in.SNIs != nil { + in, out := &in.SNIs, &out.SNIs *out = make([]string, len(*in)) copy(*out, *in) } - if in.Routes != nil { - in, out := &in.Routes, &out.Routes - *out = make([]*TLSRoute, len(*in)) + if in.Destinations != nil { + in, out := &in.Destinations, &out.Destinations + *out = make([]*RouteDestination, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(TLSRoute) - (*in).DeepCopyInto(*out) + *out = new(RouteDestination) + **out = **in } } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSListener. -func (in *TLSListener) DeepCopy() *TLSListener { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPListener. +func (in *TCPListener) DeepCopy() *TCPListener { if in == nil { return nil } - out := new(TLSListener) + out := new(TCPListener) in.DeepCopyInto(out) return out } @@ -414,32 +414,6 @@ func (in *TLSListenerConfig) DeepCopy() *TLSListenerConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSRoute) DeepCopyInto(out *TLSRoute) { - *out = *in - if in.Destinations != nil { - in, out := &in.Destinations, &out.Destinations - *out = make([]*RouteDestination, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(RouteDestination) - **out = **in - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSRoute. -func (in *TLSRoute) DeepCopy() *TLSRoute { - if in == nil { - return nil - } - out := new(TLSRoute) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Xds) DeepCopyInto(out *Xds) { *out = *in @@ -454,13 +428,13 @@ func (in *Xds) DeepCopyInto(out *Xds) { } } } - if in.TLS != nil { - in, out := &in.TLS, &out.TLS - *out = make([]*TLSListener, len(*in)) + if in.TCP != nil { + in, out := &in.TCP, &out.TCP + *out = make([]*TCPListener, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(TLSListener) + *out = new(TCPListener) (*in).DeepCopyInto(*out) } } diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go index f23c1d7300..86d0a3c681 100644 --- a/internal/provider/kubernetes/helpers.go +++ b/internal/provider/kubernetes/helpers.go @@ -9,7 +9,7 @@ import ( gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -// validateParentRefs validates parentRefs for the provided route, returning the +// validateParentRefs validates the provided routeParentReferences, returning the // referenced Gateways managed by Envoy Gateway. The only supported parentRef // is a Gateway. func validateParentRefs(ctx context.Context, client client.Client, namespace string, diff --git a/internal/provider/kubernetes/tlsroute.go b/internal/provider/kubernetes/tlsroute.go index 7efa704a2d..11fd90d272 100644 --- a/internal/provider/kubernetes/tlsroute.go +++ b/internal/provider/kubernetes/tlsroute.go @@ -58,15 +58,16 @@ func newTLSRouteController(mgr manager.Manager, cfg *config.Server, su status.Up } r.log.Info("created tlsroute controller") - // Subscribe to status updates - go r.subscribeAndUpdateStatus(context.Background()) - if err := c.Watch( &source.Kind{Type: &gwapiv1a2.TLSRoute{}}, &handler.EnqueueRequestForObject{}, ); err != nil { return err } + + // Subscribe to status updates + go r.subscribeAndUpdateStatus(context.Background()) + // Add indexing on TLSRoute, for Service objects that are referenced in TLSRoute objects // via `.spec.rules.backendRefs`. This helps in querying for TLSRoutes that are affected by // a particular Service CRUD. @@ -304,18 +305,18 @@ func (r *tlsRouteReconciler) subscribeAndUpdateStatus(ctx context.Context) { if update.Delete { continue } - // key := update.Key - // val := update.Value - // r.statusUpdater.Send(status.Update{ - // NamespacedName: key, - // Resource: new(gwapiv1a2.TLSRoute), - // Mutator: status.MutatorFunc(func(obj client.Object) client.Object { - // if _, ok := obj.(*gwapiv1a2.TLSRoute); !ok { - // panic(fmt.Sprintf("unsupported object type %T", obj)) - // } - // return val - // }), - // }) + key := update.Key + val := update.Value + r.statusUpdater.Send(status.Update{ + NamespacedName: key, + Resource: new(gwapiv1a2.TLSRoute), + Mutator: status.MutatorFunc(func(obj client.Object) client.Object { + if _, ok := obj.(*gwapiv1a2.TLSRoute); !ok { + panic(fmt.Sprintf("unsupported object type %T", obj)) + } + return val + }), + }) } } r.log.Info("status subscriber shutting down") diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index 332252e110..d231a60697 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -38,12 +38,6 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*route.Route, error) { return ret, nil } -func buildXdsPassthroughRoute(tlsRoute *ir.TLSRoute) (*route.Route, error) { - return &route.Route{ - Action: &route.Route_Route{Route: buildXdsRouteAction(tlsRoute.Name)}, - }, nil -} - func buildXdsRouteMatch(pathMatch *ir.StringMatch, headerMatches []*ir.StringMatch, queryParamMatches []*ir.StringMatch) *route.RouteMatch { outMatch := &route.RouteMatch{} diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index e4c6aebb3d..7de900858c 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -1,11 +1,11 @@ -tls: +tcp: - name: "tls-passthrough" address: "0.0.0.0" port: 10080 - hostnames: - - "*" - routes: - - name: "first-route" - destinations: + snis: + - "www.example.com" + destinations: - host: "1.2.3.4" port: 50000 + - host: "5.6.7.8" + port: 50001 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml index c65cb16a6a..95ed98685b 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.clusters.yaml @@ -3,7 +3,7 @@ connectTimeout: 5s dnsLookupFamily: V4_ONLY loadAssignment: - clusterName: cluster_first-route + clusterName: cluster_tls-passthrough endpoints: - lbEndpoints: - endpoint: @@ -11,8 +11,13 @@ socketAddress: address: 1.2.3.4 portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 loadBalancingWeight: 1 locality: {} - name: cluster_first-route + name: cluster_tls-passthrough outlierDetection: {} type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml index 960133476d..226cc03400 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml @@ -3,10 +3,14 @@ address: 0.0.0.0 portValue: 10080 filterChains: - - filters: + - filterChainMatch: + serverNames: + - www.example.com + filters: - name: envoy.filters.network.tcp_proxy typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: cluster_tls-passthrough statPrefix: passthrough listenerFilters: - name: envoy.filters.listener.tls_inspector diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml index a6520fb42a..fe51488c70 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.routes.yaml @@ -1,8 +1 @@ -- name: route_tls-passthrough - virtualHosts: - - domains: - - '*' - name: route_tls-passthrough - routes: - - route: - cluster: cluster_first-route +[] diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index e9e83df7e6..2fc149d015 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -81,45 +81,21 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { tCtx.AddXdsResource(resource.RouteType, xdsRouteCfg) } - for _, tlsListener := range ir.TLS { - // 1:1 between IR TLSListener and xDS Listener - xdsListener, err := buildXdsPassthroughListener(tlsListener) + for _, tcpListener := range ir.TCP { + // 1:1 between IR TCPListener and xDS Cluster + xdsCluster, err := buildXdsCluster(tcpListener.Name, tcpListener.Destinations) if err != nil { - return nil, multierror.Append(err, errors.New("error building xds listener")) - } - - // Allocate virtual host for this tlsListener. - // 1:1 between IR TLSListener and xDS VirtualHost - routeName := getXdsRouteName(tlsListener.Name) - vHost := &route.VirtualHost{ - Name: routeName, - Domains: tlsListener.Hostnames, - } - - for _, tlsListener := range tlsListener.Routes { - // 1:1 between IR HTTPRoute and xDS config.route.v3.Route - xdsRoute, err := buildXdsPassthroughRoute(tlsListener) - if err != nil { - return nil, multierror.Append(err, errors.New("error building xds route")) - } - vHost.Routes = append(vHost.Routes, xdsRoute) - - // 1:1 between IR TLSRoute and xDS Cluster - xdsCluster, err := buildXdsCluster(tlsListener.Name, tlsListener.Destinations) - if err != nil { - return nil, multierror.Append(err, errors.New("error building xds cluster")) - } - tCtx.AddXdsResource(resource.ClusterType, xdsCluster) - + return nil, multierror.Append(err, errors.New("error building xds cluster")) } + tCtx.AddXdsResource(resource.ClusterType, xdsCluster) - xdsRouteCfg := &route.RouteConfiguration{ - Name: routeName, + // 1:1 between IR TCPListener and xDS Listener + xdsListener, err := buildXdsPassthroughListener(xdsCluster.Name, tcpListener) + if err != nil { + return nil, multierror.Append(err, errors.New("error building xds listener")) } - xdsRouteCfg.VirtualHosts = append(xdsRouteCfg.VirtualHosts, vHost) tCtx.AddXdsResource(resource.ListenerType, xdsListener) - tCtx.AddXdsResource(resource.RouteType, xdsRouteCfg) } return tCtx, nil } From 6a8e114955d9ca1a1780bb9c6e484dc82b51bc78 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 8 Oct 2022 00:36:37 +0530 Subject: [PATCH 23/30] missed a file Signed-off-by: Shubham Chauhan --- internal/xds/translator/listener.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 4737ca6faa..82c25210c8 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -73,12 +73,17 @@ func buildXdsListener(httpListener *ir.HTTPListener) (*listener.Listener, error) }, nil } -func buildXdsPassthroughListener(tlsListener *ir.TLSListener) (*listener.Listener, error) { - if tlsListener == nil { +func buildXdsPassthroughListener(clusterName string, tcpListener *ir.TCPListener) (*listener.Listener, error) { + if tcpListener == nil { return nil, errors.New("http listener is nil") } - mgr := &tcp.TcpProxy{StatPrefix: "passthrough"} + mgr := &tcp.TcpProxy{ + StatPrefix: "passthrough", + ClusterSpecifier: &tcp.TcpProxy_Cluster{ + Cluster: clusterName, + }, + } mgrAny, err := anypb.New(mgr) if err != nil { return nil, err @@ -91,19 +96,22 @@ func buildXdsPassthroughListener(tlsListener *ir.TLSListener) (*listener.Listene } return &listener.Listener{ - Name: getXdsListenerName(tlsListener.Name, tlsListener.Port), + Name: getXdsListenerName(tcpListener.Name, tcpListener.Port), Address: &core.Address{ Address: &core.Address_SocketAddress{ SocketAddress: &core.SocketAddress{ Protocol: core.SocketAddress_TCP, - Address: tlsListener.Address, + Address: tcpListener.Address, PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: tlsListener.Port, + PortValue: tcpListener.Port, }, }, }, }, FilterChains: []*listener.FilterChain{{ + FilterChainMatch: &listener.FilterChainMatch{ + ServerNames: tcpListener.SNIs, + }, Filters: []*listener.Filter{{ Name: wellknown.TCPProxy, ConfigType: &listener.Filter_TypedConfig{ From 0ca160bbd1a00cfebabc7c149d9db7a3a2b9d24f Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 8 Oct 2022 00:45:07 +0530 Subject: [PATCH 24/30] lintfix Signed-off-by: Shubham Chauhan --- .../testdata/in/xds-ir/tls-route-passthrough.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index 7de900858c..7a90d22e75 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -5,7 +5,7 @@ tcp: snis: - "www.example.com" destinations: - - host: "1.2.3.4" - port: 50000 - - host: "5.6.7.8" - port: 50001 + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 From 8bf39b3cc16c5c1caed2c8b0cbef0a1021299771 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 8 Oct 2022 10:42:46 +0530 Subject: [PATCH 25/30] rebase, review comments Signed-off-by: Shubham Chauhan --- ...ith-tls-terminate-and-passthrough.out.yaml | 10 ++-- .../tlsroute-attaching-to-gateway.out.yaml | 2 +- ...her-namespace-allowed-by-refgrant.out.yaml | 2 +- internal/gatewayapi/translator.go | 9 ++- internal/ir/xds.go | 37 ++++++++++--- internal/ir/xds_test.go | 46 ++++++++++------ internal/ir/zz_generated.deepcopy.go | 28 ++++++++-- internal/xds/translator/listener.go | 55 ++++++++++++------- internal/xds/translator/translator.go | 2 +- 9 files changed, 131 insertions(+), 60 deletions(-) diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index f715d7adec..44c0a2cf65 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -143,11 +143,11 @@ infraIR: listeners: - address: "" ports: - - name: envoy-gateway-gateway-1-443 - protocol: "HTTPS" - servicePort: 443 - containerPort: 10443 - - name: envoy-gateway-gateway-1-90 + - name: tls-passthrough protocol: "TLS" servicePort: 90 containerPort: 10090 + - name: tls-terminate + protocol: "HTTPS" + servicePort: 443 + containerPort: 10443 diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index 62c6f1fe11..4e7fa88424 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -76,7 +76,7 @@ infraIR: listeners: - address: "" ports: - - name: envoy-gateway-gateway-1-90 + - name: tls protocol: "TLS" servicePort: 90 containerPort: 10090 diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml index 4f5a43713e..a6669e589f 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -77,7 +77,7 @@ infraIR: listeners: - address: "" ports: - - name: envoy-gateway-gateway-1-90 + - name: tls protocol: "TLS" servicePort: 90 containerPort: 10090 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index f785b7b1df..1ccff007b2 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -555,11 +555,14 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap Name: irListenerName(listener), Address: "0.0.0.0", Port: uint32(containerPort), + TLS: &ir.TLSInspectorConfig{ + SNIs: []string{}, + }, } if listener.Hostname != nil { - irListener.SNIs = append(irListener.SNIs, string(*listener.Hostname)) + irListener.TLS.SNIs = append(irListener.TLS.SNIs, string(*listener.Hostname)) } else { - irListener.SNIs = append(irListener.SNIs, "*") + irListener.TLS.SNIs = append(irListener.TLS.SNIs, "*") } gwXdsIR.TCP = append(gwXdsIR.TCP, irListener) } @@ -1158,7 +1161,7 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways } irKey := irStringKey(listener.gateway) - irListener := xdsIR[irKey].GetListener(irListenerName(listener)) + irListener := xdsIR[irKey].GetHTTPListener(irListenerName(listener)) if irListener != nil { irListener.Routes = append(irListener.Routes, perHostRoutes...) } diff --git a/internal/ir/xds.go b/internal/ir/xds.go index b6c8057d3f..dd3613208b 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -12,6 +12,7 @@ var ( ErrListenerAddressInvalid = errors.New("field Address must be a valid IP address") ErrListenerPortInvalid = errors.New("field Port specified is invalid") ErrHTTPListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") + ErrTCPListenesSNIsEmpty = errors.New("field SNIs must be specified with at least a single server name entry") ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified") ErrTLSPrivateKey = errors.New("field PrivateKey must be specified") ErrRouteNameEmpty = errors.New("field Name must be specified") @@ -50,7 +51,7 @@ func (x Xds) Validate() error { return errs } -func (x Xds) GetListener(name string) *HTTPListener { +func (x Xds) GetHTTPListener(name string) *HTTPListener { for _, listener := range x.HTTP { if listener.Name == name { return listener @@ -396,7 +397,7 @@ func (s StringMatch) Validate() error { return errs } -// TCPListener holds the listener configuration. +// TCPListener holds the TCP listener configuration. // +k8s:deepcopy-gen=true type TCPListener struct { // Name of the TCPListener @@ -405,11 +406,9 @@ type TCPListener struct { Address string // Port on which the service can be expected to be accessed by clients. Port uint32 - // Server names that are compared against the server names of a new connection. - // Wildcard hosts are supported in the prefix form. Partial wildcards are not - // supported, and values like *w.example.com are invalid. - // SNIs are used only in case of TLS Passthrough. - SNIs []string + // TLS information required for TLS Passthrough, If provided, incoming + // connections' server names are inspected and routed to backends accordingly. + TLS *TLSInspectorConfig // Destinations associated with TCP traffic to the service. Destinations []*RouteDestination } @@ -426,6 +425,11 @@ func (h TCPListener) Validate() error { if h.Port == 0 { errs = multierror.Append(errs, ErrListenerPortInvalid) } + if h.TLS != nil { + if err := h.TLS.Validate(); err != nil { + errs = multierror.Append(errs, err) + } + } for _, route := range h.Destinations { if err := route.Validate(); err != nil { errs = multierror.Append(errs, err) @@ -433,3 +437,22 @@ func (h TCPListener) Validate() error { } return errs } + +// TLSInspectorConfig holds the configuration required for inspecting TLS +// passthrough connections. +// +k8s:deepcopy-gen=true +type TLSInspectorConfig struct { + // Server names that are compared against the server names of a new connection. + // Wildcard hosts are supported in the prefix form. Partial wildcards are not + // supported, and values like *w.example.com are invalid. + // SNIs are used only in case of TLS Passthrough. + SNIs []string +} + +func (t TLSInspectorConfig) Validate() error { + var errs error + if len(t.SNIs) == 0 { + errs = multierror.Append(errs, ErrTCPListenesSNIsEmpty) + } + return errs +} diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 88fb525d26..cf302bd575 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -46,18 +46,30 @@ var ( } // TCPListener - happyTCPListener = TCPListener{ + happyTCPListenerTLSPassthrough = TCPListener{ Name: "happy", Address: "0.0.0.0", Port: 80, - SNIs: []string{"example.com"}, + TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, Destinations: []*RouteDestination{&happyRouteDestination}, } - invalidAddrTCPListener = TCPListener{ + invalidNameTCPListenerTLSPassthrough = TCPListener{ + Address: "0.0.0.0", + Port: 80, + TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, + Destinations: []*RouteDestination{&happyRouteDestination}, + } + invalidAddrTCPListenerTLSPassthrough = TCPListener{ Name: "invalid-addr", Address: "1.0.0", Port: 80, - SNIs: []string{"example.com"}, + TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, + Destinations: []*RouteDestination{&happyRouteDestination}, + } + invalidSNITCPListenerTLSPassthrough = TCPListener{ + Address: "0.0.0.0", + Port: 80, + TLS: &TLSInspectorConfig{SNIs: []string{}}, Destinations: []*RouteDestination{&happyRouteDestination}, } @@ -263,7 +275,7 @@ func TestValidateXds(t *testing.T) { { name: "happy tls", input: Xds{ - TCP: []*TCPListener{&happyTCPListener}, + TCP: []*TCPListener{&happyTCPListenerTLSPassthrough}, }, want: nil, }, @@ -367,25 +379,25 @@ func TestValidateTCPListener(t *testing.T) { want []error }{ { - name: "happy", - input: happyTCPListener, + name: "tls passthrough happy", + input: happyTCPListenerTLSPassthrough, want: nil, }, { - name: "invalid name", - input: TCPListener{ - Address: "0.0.0.0", - Port: 80, - SNIs: []string{"example.com"}, - Destinations: []*RouteDestination{&happyRouteDestination}, - }, - want: []error{ErrListenerNameEmpty}, + name: "tls passthrough invalid name", + input: invalidNameTCPListenerTLSPassthrough, + want: []error{ErrListenerNameEmpty}, }, { - name: "invalid addr", - input: invalidAddrTCPListener, + name: "tls passthrough invalid addr", + input: invalidAddrTCPListenerTLSPassthrough, want: []error{ErrListenerAddressInvalid}, }, + { + name: "tls passthrough empty SNIs", + input: invalidSNITCPListenerTLSPassthrough, + want: []error{ErrTCPListenesSNIsEmpty}, + }, } for _, test := range tests { test := test diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 35a629e8a2..16c3477e5b 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -361,10 +361,10 @@ func (in *StringMatch) DeepCopy() *StringMatch { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPListener) DeepCopyInto(out *TCPListener) { *out = *in - if in.SNIs != nil { - in, out := &in.SNIs, &out.SNIs - *out = make([]string, len(*in)) - copy(*out, *in) + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSInspectorConfig) + (*in).DeepCopyInto(*out) } if in.Destinations != nil { in, out := &in.Destinations, &out.Destinations @@ -389,6 +389,26 @@ func (in *TCPListener) DeepCopy() *TCPListener { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSInspectorConfig) DeepCopyInto(out *TLSInspectorConfig) { + *out = *in + if in.SNIs != nil { + in, out := &in.SNIs, &out.SNIs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSInspectorConfig. +func (in *TLSInspectorConfig) DeepCopy() *TLSInspectorConfig { + if in == nil { + return nil + } + out := new(TLSInspectorConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSListenerConfig) DeepCopyInto(out *TLSListenerConfig) { *out = *in diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 82c25210c8..e3ea4c82fe 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -73,13 +73,17 @@ func buildXdsListener(httpListener *ir.HTTPListener) (*listener.Listener, error) }, nil } -func buildXdsPassthroughListener(clusterName string, tcpListener *ir.TCPListener) (*listener.Listener, error) { +func buildXdsTCPListener(clusterName string, tcpListener *ir.TCPListener) (*listener.Listener, error) { if tcpListener == nil { return nil, errors.New("http listener is nil") } + statPrefix := "tcp" + if tcpListener.TLS != nil { + statPrefix = "passthrough" + } mgr := &tcp.TcpProxy{ - StatPrefix: "passthrough", + StatPrefix: statPrefix, ClusterSpecifier: &tcp.TcpProxy_Cluster{ Cluster: clusterName, }, @@ -89,13 +93,21 @@ func buildXdsPassthroughListener(clusterName string, tcpListener *ir.TCPListener return nil, err } - tlsInspector := &tls_inspector.TlsInspector{} - tlsInspectorAny, err := anypb.New(tlsInspector) - if err != nil { - return nil, err + filterChain := &listener.FilterChain{ + Filters: []*listener.Filter{{ + Name: wellknown.TCPProxy, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: mgrAny, + }, + }}, + } + if tcpListener.TLS != nil { + filterChain.FilterChainMatch = &listener.FilterChainMatch{ + ServerNames: tcpListener.TLS.SNIs, + } } - return &listener.Listener{ + xdsListener := &listener.Listener{ Name: getXdsListenerName(tcpListener.Name, tcpListener.Port), Address: &core.Address{ Address: &core.Address_SocketAddress{ @@ -108,24 +120,25 @@ func buildXdsPassthroughListener(clusterName string, tcpListener *ir.TCPListener }, }, }, - FilterChains: []*listener.FilterChain{{ - FilterChainMatch: &listener.FilterChainMatch{ - ServerNames: tcpListener.SNIs, - }, - Filters: []*listener.Filter{{ - Name: wellknown.TCPProxy, - ConfigType: &listener.Filter_TypedConfig{ - TypedConfig: mgrAny, - }, - }}, - }}, - ListenerFilters: []*listener.ListenerFilter{{ + FilterChains: []*listener.FilterChain{filterChain}, + } + + if tcpListener.TLS != nil { + tlsInspector := &tls_inspector.TlsInspector{} + tlsInspectorAny, err := anypb.New(tlsInspector) + if err != nil { + return nil, err + } + + xdsListener.ListenerFilters = []*listener.ListenerFilter{{ Name: wellknown.TlsInspector, ConfigType: &listener.ListenerFilter_TypedConfig{ TypedConfig: tlsInspectorAny, }, - }}, - }, nil + }} + } + + return xdsListener, nil } func buildXdsDownstreamTLSSocket(listenerName string, diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 2fc149d015..434b0e62e8 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -90,7 +90,7 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { tCtx.AddXdsResource(resource.ClusterType, xdsCluster) // 1:1 between IR TCPListener and xDS Listener - xdsListener, err := buildXdsPassthroughListener(xdsCluster.Name, tcpListener) + xdsListener, err := buildXdsTCPListener(xdsCluster.Name, tcpListener) if err != nil { return nil, multierror.Append(err, errors.New("error building xds listener")) } From 932334c859e82562d67632574827447954fec5f5 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 8 Oct 2022 10:55:56 +0530 Subject: [PATCH 26/30] minor testfix Signed-off-by: Shubham Chauhan --- .../translator/testdata/in/xds-ir/tls-route-passthrough.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index 7a90d22e75..b1e087cf33 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -2,8 +2,9 @@ tcp: - name: "tls-passthrough" address: "0.0.0.0" port: 10080 - snis: - - "www.example.com" + tls: + snis: + - "www.example.com" destinations: - host: "1.2.3.4" port: 50000 From 991b869821959c1e672d79fff3d53534a583159d Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Sat, 8 Oct 2022 11:03:25 +0530 Subject: [PATCH 27/30] more Signed-off-by: Shubham Chauhan --- internal/gatewayapi/contexts.go | 31 ------------------- ...ith-tls-terminate-and-passthrough.out.yaml | 5 +-- .../tlsroute-attaching-to-gateway.out.yaml | 5 +-- ...her-namespace-allowed-by-refgrant.out.yaml | 5 +-- internal/ir/xds.go | 4 +-- internal/ir/xds_test.go | 4 +-- 6 files changed, 13 insertions(+), 41 deletions(-) diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index 5be9a2cd24..abfb0d130e 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -22,37 +22,6 @@ type GatewayContext struct { listeners map[v1beta1.SectionName]*ListenerContext } -func (g *GatewayContext) SetCondition(conditionType v1beta1.GatewayConditionType, status metav1.ConditionStatus, reason v1beta1.GatewayConditionReason, message string) { - cond := metav1.Condition{ - Type: string(conditionType), - Status: status, - Reason: string(reason), - Message: message, - ObservedGeneration: g.Generation, - LastTransitionTime: metav1.NewTime(time.Now()), - } - - idx := -1 - for i, existing := range g.Status.Conditions { - if existing.Type == cond.Type { - // return early if the condition is unchanged - if existing.Status == cond.Status && - existing.Reason == cond.Reason && - existing.Message == cond.Message { - return - } - idx = i - break - } - } - - if idx > -1 { - g.Status.Conditions[idx] = cond - } else { - g.Status.Conditions = append(g.Status.Conditions, cond) - } -} - // GetListenerContext returns the ListenerContext with listenerName. // If the listener exists in the Gateway Spec but NOT yet in the GatewayContext, // this creates a new ListenerContext for the listener and attaches it to the diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index 44c0a2cf65..f47a7e3a1f 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -125,8 +125,9 @@ xdsIR: - name: envoy-gateway-gateway-1-tls-passthrough address: 0.0.0.0 port: 10090 - snis: - - "foo.com" + tls: + snis: + - "foo.com" destinations: - host: 7.7.7.7 port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index 4e7fa88424..ad6b334f8c 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -58,8 +58,9 @@ xdsIR: - name: envoy-gateway-gateway-1-tls address: 0.0.0.0 port: 10090 - snis: - - "*" + tls: + snis: + - "*" destinations: - host: 7.7.7.7 port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml index a6669e589f..fab2100044 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -59,8 +59,9 @@ xdsIR: - name: envoy-gateway-gateway-1-tls address: 0.0.0.0 port: 10090 - snis: - - "*" + tls: + snis: + - "*" destinations: - host: 7.7.7.7 port: 8080 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index dd3613208b..9224762d59 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -15,7 +15,7 @@ var ( ErrTCPListenesSNIsEmpty = errors.New("field SNIs must be specified with at least a single server name entry") ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified") ErrTLSPrivateKey = errors.New("field PrivateKey must be specified") - ErrRouteNameEmpty = errors.New("field Name must be specified") + ErrHTTPRouteNameEmpty = errors.New("field Name must be specified") ErrHTTPRouteMatchEmpty = errors.New("either PathMatch, HeaderMatches or QueryParamMatches fields must be specified") ErrRouteDestinationHostInvalid = errors.New("field Address must be a valid IP address") ErrRouteDestinationPortInvalid = errors.New("field Port specified is invalid") @@ -173,7 +173,7 @@ type HTTPRoute struct { func (h HTTPRoute) Validate() error { var errs error if h.Name == "" { - errs = multierror.Append(errs, ErrRouteNameEmpty) + errs = multierror.Append(errs, ErrHTTPRouteNameEmpty) } if h.PathMatch == nil && (len(h.HeaderMatches) == 0) && (len(h.QueryParamMatches) == 0) { errs = multierror.Append(errs, ErrHTTPRouteMatchEmpty) diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index cf302bd575..537a01aa0d 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -474,7 +474,7 @@ func TestValidateHTTPRoute(t *testing.T) { }, Destinations: []*RouteDestination{&happyRouteDestination}, }, - want: []error{ErrRouteNameEmpty}, + want: []error{ErrHTTPRouteNameEmpty}, }, { name: "empty match", @@ -497,7 +497,7 @@ func TestValidateHTTPRoute(t *testing.T) { HeaderMatches: []*StringMatch{ptrTo(StringMatch{})}, Destinations: []*RouteDestination{&happyRouteDestination}, }, - want: []error{ErrRouteNameEmpty, ErrStringMatchConditionInvalid}, + want: []error{ErrHTTPRouteNameEmpty, ErrStringMatchConditionInvalid}, }, { name: "redirect-httproute", From b54f2fefab91e78b804c73df284e46690a2f83f9 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Mon, 10 Oct 2022 22:22:53 +0530 Subject: [PATCH 28/30] review comments, status deepcopy, check routes in ns Signed-off-by: Shubham Chauhan --- internal/gatewayapi/translator.go | 24 ++++++++++++++++++++--- internal/gatewayapi/translator_test.go | 14 ------------- internal/provider/kubernetes/helpers.go | 20 +++++++++++++++++++ internal/provider/kubernetes/httproute.go | 10 +++++----- internal/provider/kubernetes/tlsroute.go | 17 +++++++++------- 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 1ccff007b2..6a6ff75e1d 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -494,6 +494,18 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap break } + // With TLS Passthrough, partial wildcards are not allowed in xDS config, so "*", "*w.abc.com" are + // invalid configurations. + if listener.Hostname == nil || *listener.Hostname == "" { + listener.SetCondition( + v1beta1.ListenerConditionReady, + metav1.ConditionFalse, + v1beta1.ListenerReasonInvalid, + "Hostname must not be empty with TLS mode Passthrough.", + ) + break + } + if len(listener.TLS.CertificateRefs) > 0 { listener.SetCondition( v1beta1.ListenerConditionReady, @@ -559,10 +571,16 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap SNIs: []string{}, }, } - if listener.Hostname != nil { + if listener.Hostname == nil || *listener.Hostname == "" { + listener.SetCondition( + v1beta1.ListenerConditionReady, + metav1.ConditionFalse, + v1beta1.ListenerReasonInvalid, + "Listener is invalid, see other Conditions for details.", + ) + } + if listener.Hostname != nil && *listener.Hostname != "" { irListener.TLS.SNIs = append(irListener.TLS.SNIs, string(*listener.Hostname)) - } else { - irListener.TLS.SNIs = append(irListener.TLS.SNIs, "*") } gwXdsIR.TCP = append(gwXdsIR.TCP, irListener) } diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index c4a005a372..b62664e1e8 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "sort" "strconv" "strings" "testing" @@ -77,19 +76,6 @@ func TestTranslate(t *testing.T) { got := translator.Translate(resources) - envoyGatewayNsName := "envoy-gateway-gateway-1" - sort.Slice(got.XdsIR[envoyGatewayNsName].HTTP, func(i, j int) bool { - return got.XdsIR[envoyGatewayNsName].HTTP[i].Name < got.XdsIR[envoyGatewayNsName].HTTP[j].Name - }) - sort.Slice(got.XdsIR[envoyGatewayNsName].TCP, func(i, j int) bool { - return got.XdsIR[envoyGatewayNsName].TCP[i].Name < got.XdsIR[envoyGatewayNsName].TCP[j].Name - }) - // Only 1 listener is supported - sort.Slice(got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports, - func(i, j int) bool { - return got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports[i].Name < got.InfraIR[envoyGatewayNsName].Proxy.Listeners[0].Ports[j].Name - }) - opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") require.Empty(t, cmp.Diff(want, got, opts)) }) diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go index 86d0a3c681..8ca309cecf 100644 --- a/internal/provider/kubernetes/helpers.go +++ b/internal/provider/kubernetes/helpers.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -54,3 +55,22 @@ func validateParentRefs(ctx context.Context, client client.Client, namespace str return ret, nil } + +// isRoutePresentInNamespace checks if any kind of Routes - HTTPRoute, TLSRoute +// exists in the namespace ns. +func isRoutePresentInNamespace(ctx context.Context, c client.Client, ns string) (bool, error) { + tlsRouteList := &gwapiv1a2.TLSRouteList{} + if err := c.List(ctx, tlsRouteList, &client.ListOptions{Namespace: ns}); err != nil { + return false, fmt.Errorf("error listing tlsroutes") + } + + httpRouteList := &gwapiv1b1.HTTPRouteList{} + if err := c.List(ctx, httpRouteList, &client.ListOptions{Namespace: ns}); err != nil { + return false, fmt.Errorf("error listing httproutes") + } + + if len(tlsRouteList.Items)+len(httpRouteList.Items) > 0 { + return true, nil + } + return false, nil +} diff --git a/internal/provider/kubernetes/httproute.go b/internal/provider/kubernetes/httproute.go index 7af55acc28..3a3798e5a1 100644 --- a/internal/provider/kubernetes/httproute.go +++ b/internal/provider/kubernetes/httproute.go @@ -273,12 +273,12 @@ func (r *httpRouteReconciler) Reconcile(ctx context.Context, request reconcile.R log.Info("deleted httproute from resource map") // Delete the Namespace and Service from the resource maps if no other - // routes exist in the namespace. - routeList = &gwapiv1b1.HTTPRouteList{} - if err := r.client.List(ctx, routeList, &client.ListOptions{Namespace: request.Namespace}); err != nil { - return reconcile.Result{}, fmt.Errorf("error listing httproutes") + // routes (TLSRoute or HTTPRoute) exist in the namespace. + found, err := isRoutePresentInNamespace(ctx, r.client, request.NamespacedName.Namespace) + if err != nil { + return reconcile.Result{}, err } - if len(routeList.Items) == 0 { + if !found { r.resources.Namespaces.Delete(request.Namespace) log.Info("deleted namespace from resource map") r.resources.Services.Delete(request.NamespacedName) diff --git a/internal/provider/kubernetes/tlsroute.go b/internal/provider/kubernetes/tlsroute.go index 11fd90d272..ff4296b53a 100644 --- a/internal/provider/kubernetes/tlsroute.go +++ b/internal/provider/kubernetes/tlsroute.go @@ -258,12 +258,12 @@ func (r *tlsRouteReconciler) Reconcile(ctx context.Context, request reconcile.Re log.Info("deleted tlsroute from resource map") // Delete the Namespace and Service from the resource maps if no other - // routes exist in the namespace. - routeList = &gwapiv1a2.TLSRouteList{} - if err := r.client.List(ctx, routeList, &client.ListOptions{Namespace: request.Namespace}); err != nil { - return reconcile.Result{}, fmt.Errorf("error listing tlsroutes") + // routes (TLSRoute or HTTPRoute) exist in the namespace. + found, err := isRoutePresentInNamespace(ctx, r.client, request.NamespacedName.Namespace) + if err != nil { + return reconcile.Result{}, err } - if len(routeList.Items) == 0 { + if !found { r.resources.Namespaces.Delete(request.Namespace) log.Info("deleted namespace from resource map") r.resources.Services.Delete(request.NamespacedName) @@ -311,10 +311,13 @@ func (r *tlsRouteReconciler) subscribeAndUpdateStatus(ctx context.Context) { NamespacedName: key, Resource: new(gwapiv1a2.TLSRoute), Mutator: status.MutatorFunc(func(obj client.Object) client.Object { - if _, ok := obj.(*gwapiv1a2.TLSRoute); !ok { + t, ok := obj.(*gwapiv1a2.TLSRoute) + if !ok { panic(fmt.Sprintf("unsupported object type %T", obj)) } - return val + tCopy := t.DeepCopy() + tCopy.Status.Parents = val.Status.Parents + return tCopy }), }) } From c09d71c68e462476e0e93ad1a9972862ebe28696 Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Mon, 10 Oct 2022 23:05:33 +0530 Subject: [PATCH 29/30] revert bad import, testfix, new test Signed-off-by: Shubham Chauhan --- internal/gatewayapi/runner/runner.go | 2 +- internal/gatewayapi/sort.go | 2 + ...ith-invalid-allowed-tls-route-kind.in.yaml | 1 + ...th-invalid-allowed-tls-route-kind.out.yaml | 1 + ...lid-tls-configuration-invalid-mode.in.yaml | 1 + ...id-tls-configuration-invalid-mode.out.yaml | 1 + .../tlsroute-attaching-to-gateway.in.yaml | 1 + .../tlsroute-attaching-to-gateway.out.yaml | 3 +- ...ther-namespace-allowed-by-refgrant.in.yaml | 1 + ...her-namespace-allowed-by-refgrant.out.yaml | 3 +- ...ner-both-passthrough-and-cert-data.in.yaml | 1 + ...er-both-passthrough-and-cert-data.out.yaml | 1 + ...ute-with-partial-wildcard-hostname.in.yaml | 52 ++++++++++++++ ...te-with-partial-wildcard-hostname.out.yaml | 68 +++++++++++++++++++ internal/gatewayapi/translator.go | 1 + .../in/xds-ir/tls-route-passthrough.yaml | 2 +- .../tls-route-passthrough.listeners.yaml | 2 +- 17 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.out.yaml diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 2d6cf70d72..d4793d5ecd 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -3,8 +3,8 @@ package runner import ( "context" - "gopkg.in/yaml.v2" "sigs.k8s.io/gateway-api/apis/v1beta1" + "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" diff --git a/internal/gatewayapi/sort.go b/internal/gatewayapi/sort.go index 035d2f6093..7539778fff 100644 --- a/internal/gatewayapi/sort.go +++ b/internal/gatewayapi/sort.go @@ -50,6 +50,8 @@ func sortXdsIRMap(xdsIR XdsIRMap) { // descending order sort.Sort(sort.Reverse(XdsIRRoutes(http.Routes))) } + + sort.SliceStable(ir.TCP, func(i, j int) bool { return ir.TCP[i].Name < ir.TCP[j].Name }) } } diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml index 692adbfd82..c5762afbaf 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.in.yaml @@ -8,6 +8,7 @@ gateways: gatewayClassName: envoy-gateway-class listeners: - name: tls + hostname: foo.com protocol: TLS port: 80 tls: diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml index eacb1696ff..59c61cc716 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-allowed-tls-route-kind.out.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com port: 80 tls: mode: Passthrough diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.in.yaml index c17a2ef492..a4793ddeff 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.in.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.in.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: HTTPS + hostname: foo.com port: 443 allowedRoutes: namespaces: diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.out.yaml index 40a696060d..13c19dc555 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-invalid-tls-configuration-invalid-mode.out.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: HTTPS + hostname: foo.com port: 443 allowedRoutes: namespaces: diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml index 09f2b84a80..71db6c0ece 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.in.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com port: 90 tls: mode: Passthrough diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index ad6b334f8c..9a82becdbc 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com port: 90 tls: mode: Passthrough @@ -60,7 +61,7 @@ xdsIR: port: 10090 tls: snis: - - "*" + - foo.com destinations: - host: 7.7.7.7 port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml index 636dc9fa3a..d18aca10b8 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.in.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com port: 90 tls: mode: Passthrough diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml index fab2100044..9bc5dc2c00 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com port: 90 tls: mode: Passthrough @@ -61,7 +62,7 @@ xdsIR: port: 10090 tls: snis: - - "*" + - foo.com destinations: - host: 7.7.7.7 port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml index d190f65e47..81afa2331b 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.in.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com tls: mode: Passthrough certificateRefs: diff --git a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml index a8ec24c2dc..b8b1322044 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-listener-both-passthrough-and-cert-data.out.yaml @@ -9,6 +9,7 @@ gateways: listeners: - name: tls protocol: TLS + hostname: foo.com tls: mode: Passthrough certificateRefs: diff --git a/internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.in.yaml b/internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.in.yaml new file mode 100644 index 0000000000..ac35ef2672 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.in.yaml @@ -0,0 +1,52 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + # TODO: add test for partial wildcard + # - name: tls-1 + # protocol: TLS + # hostname: "*w.example.com" + # port: 90 + # tls: + # mode: Passthrough + # allowedRoutes: + # namespaces: + # from: All + - name: tls + protocol: TLS + port: 91 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + namespace: test-service-namespace + port: 8080 +services: + - apiVersion: v1 + kind: Service + metadata: + namespace: default + name: service-1 + spec: + clusterIP: 7.7.7.7 + ports: + - port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.out.yaml new file mode 100644 index 0000000000..feb5f80714 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-partial-wildcard-hostname.out.yaml @@ -0,0 +1,68 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 91 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 0 + conditions: + - type: Ready + status: "False" + reason: Invalid + message: Hostname must not be empty with TLS mode Passthrough. +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + namespace: test-service-namespace + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "False" + reason: NoReadyListeners + message: There are no ready listeners for this parent ref +xdsIR: + envoy-gateway-gateway-1: {} +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:v1.23-latest + listeners: + - address: "" diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 6a6ff75e1d..71e3ef9a11 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -496,6 +496,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap // With TLS Passthrough, partial wildcards are not allowed in xDS config, so "*", "*w.abc.com" are // invalid configurations. + // TODO: add regex match to detect partial wildcards like *w.abc.com if listener.Hostname == nil || *listener.Hostname == "" { listener.SetCondition( v1beta1.ListenerConditionReady, diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index b1e087cf33..c7f5963306 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -4,7 +4,7 @@ tcp: port: 10080 tls: snis: - - "www.example.com" + - foo.com destinations: - host: "1.2.3.4" port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml index 226cc03400..b52cb5d8d2 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml @@ -5,7 +5,7 @@ filterChains: - filterChainMatch: serverNames: - - www.example.com + - foo.com filters: - name: envoy.filters.network.tcp_proxy typedConfig: From 5475f5b350b85f87c0e2509de64aa84f97ab770f Mon Sep 17 00:00:00 2001 From: Shubham Chauhan Date: Mon, 10 Oct 2022 23:27:25 +0530 Subject: [PATCH 30/30] rev sort Signed-off-by: Shubham Chauhan --- internal/gatewayapi/sort.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/gatewayapi/sort.go b/internal/gatewayapi/sort.go index 7539778fff..035d2f6093 100644 --- a/internal/gatewayapi/sort.go +++ b/internal/gatewayapi/sort.go @@ -50,8 +50,6 @@ func sortXdsIRMap(xdsIR XdsIRMap) { // descending order sort.Sort(sort.Reverse(XdsIRRoutes(http.Routes))) } - - sort.SliceStable(ir.TCP, func(i, j int) bool { return ir.TCP[i].Name < ir.TCP[j].Name }) } }