2020-12-15 15:40:05 +00:00
package gateway
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"net"
2022-12-22 14:02:05 +00:00
"net/http"
2020-12-15 15:40:05 +00:00
"os"
2022-11-28 14:48:05 +00:00
"regexp"
2024-05-01 04:38:03 +00:00
"slices"
2020-12-15 15:40:05 +00:00
"sort"
"strconv"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/hashstructure"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2020-12-15 15:40:05 +00:00
ptypes "github.com/traefik/paerser/types"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/job"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
2023-03-21 11:00:46 +00:00
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
2023-05-17 09:07:09 +00:00
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/tls"
2024-01-11 16:06:06 +00:00
"github.com/traefik/traefik/v3/pkg/types"
2020-12-15 15:40:05 +00:00
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
2024-05-01 04:38:03 +00:00
ktypes "k8s.io/apimachinery/pkg/types"
2024-01-09 09:28:05 +00:00
"k8s.io/utils/ptr"
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
2024-01-30 15:44:05 +00:00
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
2020-12-15 15:40:05 +00:00
)
2021-02-02 18:36:04 +00:00
const (
2024-05-01 04:38:03 +00:00
providerName = "kubernetesgateway"
controllerName = "traefik.io/gateway-controller"
2021-11-09 10:34:06 +00:00
2024-01-30 15:44:05 +00:00
groupCore = "core"
2021-11-09 10:34:06 +00:00
kindGateway = "Gateway"
kindTraefikService = "TraefikService"
kindHTTPRoute = "HTTPRoute"
kindTCPRoute = "TCPRoute"
kindTLSRoute = "TLSRoute"
2021-02-02 18:36:04 +00:00
)
2020-12-15 15:40:05 +00:00
// Provider holds configurations of the provider.
type Provider struct {
2024-04-02 15:32:04 +00:00
Endpoint string ` description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" `
Token types . FileOrContent ` description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false" `
CertAuthFilePath string ` description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty" `
Namespaces [ ] string ` description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true" `
LabelSelector string ` description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true" `
ThrottleDuration ptypes . Duration ` description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true" `
ExperimentalChannel bool ` description:"Toggles Experimental Channel resources support (TCPRoute, TLSRoute...)." json:"experimentalChannel,omitempty" toml:"experimentalChannel,omitempty" yaml:"experimentalChannel,omitempty" export:"true" `
2024-04-10 07:34:07 +00:00
StatusAddress * StatusAddress ` description:"Defines the Kubernetes Gateway status address." json:"statusAddress,omitempty" toml:"statusAddress,omitempty" yaml:"statusAddress,omitempty" export:"true" `
2024-04-02 15:32:04 +00:00
EntryPoints map [ string ] Entrypoint ` json:"-" toml:"-" yaml:"-" label:"-" file:"-" `
2020-12-15 15:40:05 +00:00
2024-03-25 13:38:04 +00:00
// groupKindFilterFuncs is the list of allowed Group and Kinds for the Filter ExtensionRef objects.
groupKindFilterFuncs map [ string ] map [ string ] BuildFilterFunc
// groupKindBackendFuncs is the list of allowed Group and Kinds for the Backend ExtensionRef objects.
groupKindBackendFuncs map [ string ] map [ string ] BuildBackendFunc
2020-12-15 15:40:05 +00:00
lastConfiguration safe . Safe
2023-05-15 14:38:05 +00:00
routerTransform k8s . RouterTransform
}
2024-05-01 04:38:03 +00:00
// Entrypoint defines the available entry points.
type Entrypoint struct {
Address string
HasHTTPTLSConf bool
}
2024-04-10 07:34:07 +00:00
// StatusAddress holds the Gateway Status address configuration.
type StatusAddress struct {
IP string ` description:"IP used to set Kubernetes Gateway status address." json:"ip,omitempty" toml:"ip,omitempty" yaml:"ip,omitempty" `
Hostname string ` description:"Hostname used for Kubernetes Gateway status address." json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty" `
Service ServiceRef ` description:"Published Kubernetes Service to copy status addresses from." json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" `
}
// ServiceRef holds a Kubernetes service reference.
type ServiceRef struct {
Name string ` description:"Name of the Kubernetes service." json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" `
Namespace string ` description:"Namespace of the Kubernetes service." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty" `
}
2024-03-25 13:38:04 +00:00
// BuildFilterFunc returns the name of the filter and the related dynamic.Middleware if needed.
type BuildFilterFunc func ( name , namespace string ) ( string , * dynamic . Middleware , error )
// BuildBackendFunc returns the name of the backend and the related dynamic.Service if needed.
type BuildBackendFunc func ( name , namespace string ) ( string , * dynamic . Service , error )
type ExtensionBuilderRegistry interface {
RegisterFilterFuncs ( group , kind string , builderFunc BuildFilterFunc )
RegisterBackendFuncs ( group , kind string , builderFunc BuildBackendFunc )
}
// RegisterFilterFuncs registers an allowed Group, Kind, and builder for the Filter ExtensionRef objects.
func ( p * Provider ) RegisterFilterFuncs ( group , kind string , builderFunc BuildFilterFunc ) {
if p . groupKindFilterFuncs == nil {
p . groupKindFilterFuncs = map [ string ] map [ string ] BuildFilterFunc { }
}
if p . groupKindFilterFuncs [ group ] == nil {
p . groupKindFilterFuncs [ group ] = map [ string ] BuildFilterFunc { }
}
p . groupKindFilterFuncs [ group ] [ kind ] = builderFunc
}
// RegisterBackendFuncs registers an allowed Group, Kind, and builder for the Backend ExtensionRef objects.
func ( p * Provider ) RegisterBackendFuncs ( group , kind string , builderFunc BuildBackendFunc ) {
if p . groupKindBackendFuncs == nil {
p . groupKindBackendFuncs = map [ string ] map [ string ] BuildBackendFunc { }
}
if p . groupKindBackendFuncs [ group ] == nil {
p . groupKindBackendFuncs [ group ] = map [ string ] BuildBackendFunc { }
}
p . groupKindBackendFuncs [ group ] [ kind ] = builderFunc
}
2023-05-15 14:38:05 +00:00
func ( p * Provider ) SetRouterTransform ( routerTransform k8s . RouterTransform ) {
p . routerTransform = routerTransform
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) applyRouterTransform ( ctx context . Context , rt * dynamic . Router , route * gatev1 . HTTPRoute ) {
2023-05-15 14:38:05 +00:00
if p . routerTransform == nil {
return
}
2024-03-15 08:24:03 +00:00
err := p . routerTransform . Apply ( ctx , rt , route )
2023-05-15 14:38:05 +00:00
if err != nil {
2023-05-17 09:07:09 +00:00
log . Ctx ( ctx ) . Error ( ) . Err ( err ) . Msg ( "Apply router transform" )
2023-05-15 14:38:05 +00:00
}
2020-12-15 15:40:05 +00:00
}
func ( p * Provider ) newK8sClient ( ctx context . Context ) ( * clientWrapper , error ) {
// Label selector validation
_ , err := labels . Parse ( p . LabelSelector )
if err != nil {
return nil , fmt . Errorf ( "invalid label selector: %q" , p . LabelSelector )
}
2021-07-15 15:20:08 +00:00
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
logger . Info ( ) . Msgf ( "Label selector is: %q" , p . LabelSelector )
2020-12-15 15:40:05 +00:00
var client * clientWrapper
switch {
case os . Getenv ( "KUBERNETES_SERVICE_HOST" ) != "" && os . Getenv ( "KUBERNETES_SERVICE_PORT" ) != "" :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Str ( "endpoint" , p . Endpoint ) . Msg ( "Creating in-cluster Provider client" )
2020-12-15 15:40:05 +00:00
client , err = newInClusterClient ( p . Endpoint )
case os . Getenv ( "KUBECONFIG" ) != "" :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msgf ( "Creating cluster-external Provider client from KUBECONFIG %s" , os . Getenv ( "KUBECONFIG" ) )
2020-12-15 15:40:05 +00:00
client , err = newExternalClusterClientFromFile ( os . Getenv ( "KUBECONFIG" ) )
default :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Str ( "endpoint" , p . Endpoint ) . Msg ( "Creating cluster-external Provider client" )
2024-01-11 16:06:06 +00:00
client , err = newExternalClusterClient ( p . Endpoint , p . CertAuthFilePath , p . Token )
2020-12-15 15:40:05 +00:00
}
if err != nil {
return nil , err
}
2021-07-15 15:20:08 +00:00
2020-12-15 15:40:05 +00:00
client . labelSelector = p . LabelSelector
2024-04-02 15:32:04 +00:00
client . experimentalChannel = p . ExperimentalChannel
2020-12-15 15:40:05 +00:00
return client , nil
}
// Init the provider.
func ( p * Provider ) Init ( ) error {
return nil
}
2021-11-09 10:34:06 +00:00
// Provide allows the k8s provider to provide configurations to traefik using the given configuration channel.
2020-12-15 15:40:05 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2022-11-21 17:36:05 +00:00
logger := log . With ( ) . Str ( logs . ProviderName , providerName ) . Logger ( )
ctxLog := logger . WithContext ( context . Background ( ) )
2020-12-15 15:40:05 +00:00
k8sClient , err := p . newK8sClient ( ctxLog )
if err != nil {
return err
}
pool . GoCtx ( func ( ctxPool context . Context ) {
operation := func ( ) error {
eventsChan , err := k8sClient . WatchAll ( p . Namespaces , ctxPool . Done ( ) )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error watching kubernetes events" )
2020-12-15 15:40:05 +00:00
timer := time . NewTimer ( 1 * time . Second )
select {
case <- timer . C :
return err
case <- ctxPool . Done ( ) :
return nil
}
}
throttleDuration := time . Duration ( p . ThrottleDuration )
throttledChan := throttleEvents ( ctxLog , throttleDuration , pool , eventsChan )
if throttledChan != nil {
eventsChan = throttledChan
}
for {
select {
case <- ctxPool . Done ( ) :
return nil
case event := <- eventsChan :
// Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events.
// This is fine, because we don't treat different event types differently.
// But if we do in the future, we'll need to track more information about the dropped events.
conf := p . loadConfigurationFromGateway ( ctxLog , k8sClient )
confHash , err := hashstructure . Hash ( conf , nil )
switch {
case err != nil :
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Msg ( "Unable to hash the configuration" )
2020-12-15 15:40:05 +00:00
case p . lastConfiguration . Get ( ) == confHash :
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Skipping Kubernetes event kind %T" , event )
2020-12-15 15:40:05 +00:00
default :
p . lastConfiguration . Set ( confHash )
configurationChan <- dynamic . Message {
ProviderName : providerName ,
Configuration : conf ,
}
}
// If we're throttling,
// we sleep here for the throttle duration to enforce that we don't refresh faster than our throttle.
// time.Sleep returns immediately if p.ThrottleDuration is 0 (no throttle).
time . Sleep ( throttleDuration )
}
}
}
notify := func ( err error , time time . Duration ) {
2022-11-30 08:50:05 +00:00
logger . Error ( ) . Err ( err ) . Msgf ( "Provider error, retrying in %s" , time )
2020-12-15 15:40:05 +00:00
}
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , backoff . WithContext ( job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , ctxPool ) , notify )
if err != nil {
2022-11-30 08:50:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Cannot retrieve data" )
2020-12-15 15:40:05 +00:00
}
} )
return nil
}
// TODO Handle errors and update resources statuses (gatewayClass, gateway).
func ( p * Provider ) loadConfigurationFromGateway ( ctx context . Context , client Client ) * dynamic . Configuration {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2020-12-15 15:40:05 +00:00
gatewayClassNames := map [ string ] struct { } { }
gatewayClasses , err := client . GetGatewayClasses ( )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Cannot find GatewayClasses" )
2020-12-15 15:40:05 +00:00
return & dynamic . Configuration {
2022-12-09 08:58:05 +00:00
HTTP : & dynamic . HTTPConfiguration {
Routers : map [ string ] * dynamic . Router { } ,
Middlewares : map [ string ] * dynamic . Middleware { } ,
Services : map [ string ] * dynamic . Service { } ,
ServersTransports : map [ string ] * dynamic . ServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
TCP : & dynamic . TCPConfiguration {
2022-12-09 08:58:05 +00:00
Routers : map [ string ] * dynamic . TCPRouter { } ,
Middlewares : map [ string ] * dynamic . TCPMiddleware { } ,
Services : map [ string ] * dynamic . TCPService { } ,
ServersTransports : map [ string ] * dynamic . TCPServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
2022-12-09 08:58:05 +00:00
UDP : & dynamic . UDPConfiguration {
Routers : map [ string ] * dynamic . UDPRouter { } ,
Services : map [ string ] * dynamic . UDPService { } ,
2020-12-15 15:40:05 +00:00
} ,
TLS : & dynamic . TLSConfiguration { } ,
}
}
for _ , gatewayClass := range gatewayClasses {
2024-05-01 04:38:03 +00:00
if gatewayClass . Spec . ControllerName == controllerName {
2020-12-15 15:40:05 +00:00
gatewayClassNames [ gatewayClass . Name ] = struct { } { }
err := client . UpdateGatewayClassStatus ( gatewayClass , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayClassConditionStatusAccepted ) ,
2020-12-15 15:40:05 +00:00
Status : metav1 . ConditionTrue ,
2024-01-22 14:30:05 +00:00
ObservedGeneration : gatewayClass . Generation ,
2020-12-15 15:40:05 +00:00
Reason : "Handled" ,
Message : "Handled by Traefik controller" ,
LastTransitionTime : metav1 . Now ( ) ,
} )
if err != nil {
2024-01-09 09:28:05 +00:00
logger . Error ( ) . Err ( err ) . Msgf ( "Failed to update %s condition" , gatev1 . GatewayClassConditionStatusAccepted )
2020-12-15 15:40:05 +00:00
}
}
}
cfgs := map [ string ] * dynamic . Configuration { }
// TODO check if we can only use the default filtering mechanism
for _ , gateway := range client . GetGateways ( ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx ) . With ( ) . Str ( "gateway" , gateway . Name ) . Str ( "namespace" , gateway . Namespace ) . Logger ( )
ctxLog := logger . WithContext ( ctx )
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
if _ , ok := gatewayClassNames [ string ( gateway . Spec . GatewayClassName ) ] ; ! ok {
2020-12-15 15:40:05 +00:00
continue
}
2021-05-20 09:50:12 +00:00
cfg , err := p . createGatewayConf ( ctxLog , client , gateway )
2020-12-15 15:40:05 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2020-12-15 15:40:05 +00:00
continue
}
cfgs [ gateway . Name + gateway . Namespace ] = cfg
}
conf := provider . Merge ( ctx , cfgs )
conf . TLS = & dynamic . TLSConfiguration { }
for _ , cfg := range cfgs {
if conf . TLS == nil {
conf . TLS = & dynamic . TLSConfiguration { }
}
conf . TLS . Certificates = append ( conf . TLS . Certificates , cfg . TLS . Certificates ... )
for name , options := range cfg . TLS . Options {
if conf . TLS . Options == nil {
conf . TLS . Options = map [ string ] tls . Options { }
}
conf . TLS . Options [ name ] = options
}
for name , store := range cfg . TLS . Stores {
if conf . TLS . Stores == nil {
conf . TLS . Stores = map [ string ] tls . Store { }
}
conf . TLS . Stores [ name ] = store
}
}
return conf
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) createGatewayConf ( ctx context . Context , client Client , gateway * gatev1 . Gateway ) ( * dynamic . Configuration , error ) {
2024-05-01 04:38:03 +00:00
addresses , err := p . gatewayAddresses ( client )
if err != nil {
return nil , fmt . Errorf ( "get Gateway status addresses: %w" , err )
}
2020-12-15 15:40:05 +00:00
conf := & dynamic . Configuration {
2022-12-09 08:58:05 +00:00
HTTP : & dynamic . HTTPConfiguration {
Routers : map [ string ] * dynamic . Router { } ,
Middlewares : map [ string ] * dynamic . Middleware { } ,
Services : map [ string ] * dynamic . Service { } ,
ServersTransports : map [ string ] * dynamic . ServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
TCP : & dynamic . TCPConfiguration {
2022-12-09 08:58:05 +00:00
Routers : map [ string ] * dynamic . TCPRouter { } ,
Middlewares : map [ string ] * dynamic . TCPMiddleware { } ,
Services : map [ string ] * dynamic . TCPService { } ,
ServersTransports : map [ string ] * dynamic . TCPServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
2022-12-09 08:58:05 +00:00
UDP : & dynamic . UDPConfiguration {
Routers : map [ string ] * dynamic . UDPRouter { } ,
Services : map [ string ] * dynamic . UDPService { } ,
2020-12-15 15:40:05 +00:00
} ,
TLS : & dynamic . TLSConfiguration { } ,
}
tlsConfigs := make ( map [ string ] * tls . CertAndStores )
// GatewayReasonListenersNotValid is used when one or more
// Listeners have an invalid or unsupported configuration
// and cannot be configured on the Gateway.
2024-05-01 04:38:03 +00:00
listenerStatuses , httpRouteParentStatuses := p . fillGatewayConf ( ctx , client , gateway , conf , tlsConfigs )
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
if len ( tlsConfigs ) > 0 {
conf . TLS . Certificates = append ( conf . TLS . Certificates , getTLSConfig ( tlsConfigs ) ... )
2024-04-10 07:34:07 +00:00
}
2024-05-01 04:38:03 +00:00
httpRouteStatuses := makeHTTPRouteStatuses ( gateway . Namespace , httpRouteParentStatuses )
for nsName , status := range httpRouteStatuses {
if err := client . UpdateHTTPRouteStatus ( ctx , gateway , nsName , status ) ; err != nil {
log . Error ( ) .
Err ( err ) .
Str ( "namespace" , nsName . Namespace ) .
Str ( "name" , nsName . Name ) .
Msg ( "Unable to update HTTPRoute status" )
}
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
gatewayStatus , errG := p . makeGatewayStatus ( gateway , listenerStatuses , addresses )
if err = client . UpdateGatewayStatus ( gateway , gatewayStatus ) ; err != nil {
log . Error ( ) .
Err ( err ) .
Str ( "namespace" , gateway . Namespace ) .
Str ( "name" , gateway . Name ) .
Msg ( "Unable to update Gateway status" )
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if errG != nil {
return nil , fmt . Errorf ( "creating gateway status: %w" , errG )
2020-12-15 15:40:05 +00:00
}
return conf , nil
}
2024-05-01 04:38:03 +00:00
func ( p * Provider ) fillGatewayConf ( ctx context . Context , client Client , gateway * gatev1 . Gateway , conf * dynamic . Configuration , tlsConfigs map [ string ] * tls . CertAndStores ) ( [ ] gatev1 . ListenerStatus , map [ ktypes . NamespacedName ] [ ] gatev1 . RouteParentStatus ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2022-06-23 09:58:09 +00:00
allocatedListeners := make ( map [ string ] struct { } )
2024-05-01 04:38:03 +00:00
listenerStatuses := make ( [ ] gatev1 . ListenerStatus , len ( gateway . Spec . Listeners ) )
httpRouteParentStatuses := make ( map [ ktypes . NamespacedName ] [ ] gatev1 . RouteParentStatus )
2020-12-15 15:40:05 +00:00
for i , listener := range gateway . Spec . Listeners {
2024-01-09 09:28:05 +00:00
listenerStatuses [ i ] = gatev1 . ListenerStatus {
2021-11-09 10:34:06 +00:00
Name : listener . Name ,
2024-01-09 09:28:05 +00:00
SupportedKinds : [ ] gatev1 . RouteGroupKind { } ,
2021-11-09 10:34:06 +00:00
Conditions : [ ] metav1 . Condition { } ,
2024-01-30 15:44:05 +00:00
// AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status
2020-12-15 15:40:05 +00:00
}
2024-04-02 15:32:04 +00:00
supportedKinds , conditions := supportedRouteKinds ( listener . Protocol , p . ExperimentalChannel )
2021-11-09 10:34:06 +00:00
if len ( conditions ) > 0 {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , conditions ... )
2020-12-15 15:40:05 +00:00
continue
}
2024-01-09 09:28:05 +00:00
routeKinds , conditions := getAllowedRouteKinds ( gateway , listener , supportedKinds )
2024-01-30 15:44:05 +00:00
listenerStatuses [ i ] . SupportedKinds = routeKinds
2021-11-09 10:34:06 +00:00
if len ( conditions ) > 0 {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , conditions ... )
2021-05-20 09:50:12 +00:00
continue
}
2022-06-23 09:58:09 +00:00
listenerKey := makeListenerKey ( listener )
if _ , ok := allocatedListeners [ listenerKey ] ; ok {
2021-05-20 09:50:12 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionConflicted ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionTrue ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2022-06-23 09:58:09 +00:00
Reason : "DuplicateListener" ,
Message : "A listener with same protocol, port and hostname already exists" ,
2021-05-20 09:50:12 +00:00
} )
continue
}
2022-06-23 09:58:09 +00:00
allocatedListeners [ listenerKey ] = struct { } { }
2021-11-09 10:34:06 +00:00
ep , err := p . entryPointName ( listener . Port , listener . Protocol )
if err != nil {
// update "Detached" status with "PortUnavailable" reason
2021-05-20 09:50:12 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonPortUnavailable ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Cannot find entryPoint for Gateway: %v" , err ) ,
2021-05-20 09:50:12 +00:00
} )
continue
}
2024-01-09 09:28:05 +00:00
if ( listener . Protocol == gatev1 . HTTPProtocolType || listener . Protocol == gatev1 . TCPProtocolType ) && listener . TLS != nil {
2020-12-15 15:40:05 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidTLSConfiguration" , // TODO check the spec if a proper reason is introduced at some point
Message : "TLS configuration must no be defined when using HTTP or TCP protocol" ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-05-20 09:50:12 +00:00
// TLS
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . HTTPSProtocolType || listener . Protocol == gatev1 . TLSProtocolType {
if listener . TLS == nil || ( len ( listener . TLS . CertificateRefs ) == 0 && listener . TLS . Mode != nil && * listener . TLS . Mode != gatev1 . TLSModePassthrough ) {
2020-12-15 15:40:05 +00:00
// update "Detached" status with "UnsupportedProtocol" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidTLSConfiguration" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "No TLS configuration for Gateway Listener %s:%d and protocol %q" ,
listener . Name , listener . Port , listener . Protocol ) ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2024-01-09 09:28:05 +00:00
var tlsModeType gatev1 . TLSModeType
2021-07-15 15:20:08 +00:00
if listener . TLS . Mode != nil {
tlsModeType = * listener . TLS . Mode
}
2024-01-09 09:28:05 +00:00
isTLSPassthrough := tlsModeType == gatev1 . TLSModePassthrough
2021-11-09 10:34:06 +00:00
if isTLSPassthrough && len ( listener . TLS . CertificateRefs ) > 0 {
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayTLSConfig
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msg ( "In case of Passthrough TLS mode, no TLS settings take effect as the TLS session from the client is NOT terminated at the Gateway" )
2021-05-20 09:50:12 +00:00
}
// Allowed configurations:
2021-11-09 10:34:06 +00:00
// Protocol TLS -> Passthrough -> TLSRoute/TCPRoute
// Protocol TLS -> Terminate -> TLSRoute/TCPRoute
2021-05-20 09:50:12 +00:00
// Protocol HTTPS -> Terminate -> HTTPRoute
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . HTTPSProtocolType && isTLSPassthrough {
2020-12-15 15:40:05 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonUnsupportedProtocol ) ,
2021-11-09 10:34:06 +00:00
Message : "HTTPS protocol is not supported with TLS mode Passthrough" ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-05-20 09:50:12 +00:00
if ! isTLSPassthrough {
2021-11-09 10:34:06 +00:00
if len ( listener . TLS . CertificateRefs ) == 0 {
2020-12-15 15:40:05 +00:00
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2020-12-15 15:40:05 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
2021-11-09 10:34:06 +00:00
Message : "One TLS CertificateRef is required in Terminate mode" ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-11-09 10:34:06 +00:00
// TODO Should we support multiple certificates?
certificateRef := listener . TLS . CertificateRefs [ 0 ]
if certificateRef . Kind == nil || * certificateRef . Kind != "Secret" ||
2024-01-30 15:44:05 +00:00
certificateRef . Group == nil || ( * certificateRef . Group != "" && * certificateRef . Group != groupCore ) {
2021-11-09 10:34:06 +00:00
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
2024-01-30 15:44:05 +00:00
Message : fmt . Sprintf ( "Unsupported TLS CertificateRef group/kind: %s/%s" , groupToString ( certificateRef . Group ) , kindToString ( certificateRef . Kind ) ) ,
2021-11-09 10:34:06 +00:00
} )
continue
}
2024-01-30 15:44:05 +00:00
certificateNamespace := gateway . Namespace
2021-11-09 10:34:06 +00:00
if certificateRef . Namespace != nil && string ( * certificateRef . Namespace ) != gateway . Namespace {
2024-01-30 15:44:05 +00:00
certificateNamespace = string ( * certificateRef . Namespace )
2021-11-09 10:34:06 +00:00
}
2024-01-30 15:44:05 +00:00
if certificateNamespace != gateway . Namespace {
referenceGrants , err := client . GetReferenceGrants ( certificateNamespace )
2021-05-20 09:50:12 +00:00
if err != nil {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-30 15:44:05 +00:00
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
Message : fmt . Sprintf ( "Cannot find any ReferenceGrant: %v" , err ) ,
} )
continue
}
referenceGrants = filterReferenceGrantsFrom ( referenceGrants , "gateway.networking.k8s.io" , "Gateway" , gateway . Namespace )
referenceGrants = filterReferenceGrantsTo ( referenceGrants , groupCore , "Secret" , string ( certificateRef . Name ) )
if len ( referenceGrants ) == 0 {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
Message : "Required ReferenceGrant for cross namespace secret reference is missing" ,
2021-05-20 09:50:12 +00:00
} )
continue
}
2024-01-30 15:44:05 +00:00
}
configKey := certificateNamespace + "/" + string ( certificateRef . Name )
if _ , tlsExists := tlsConfigs [ configKey ] ; ! tlsExists {
tlsConf , err := getTLS ( client , certificateRef . Name , certificateNamespace )
if err != nil {
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
// update "Programmed" status false with "Invalid" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions ,
metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
Message : fmt . Sprintf ( "Error while retrieving certificate: %v" , err ) ,
} ,
metav1 . Condition {
Type : string ( gatev1 . ListenerConditionProgrammed ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonInvalid ) ,
Message : fmt . Sprintf ( "Error while retrieving certificate: %v" , err ) ,
} ,
)
2021-05-20 09:50:12 +00:00
2024-01-30 15:44:05 +00:00
continue
}
2021-05-20 09:50:12 +00:00
tlsConfigs [ configKey ] = tlsConf
}
2020-12-15 15:40:05 +00:00
}
}
2021-11-09 10:34:06 +00:00
for _ , routeKind := range routeKinds {
switch routeKind . Kind {
case kindHTTPRoute :
2024-05-01 04:38:03 +00:00
listenerConditions , routeStatuses := p . gatewayHTTPRouteToHTTPConf ( ctx , ep , listener , gateway , client , conf )
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , listenerConditions ... )
for nsName , status := range routeStatuses {
httpRouteParentStatuses [ nsName ] = append ( httpRouteParentStatuses [ nsName ] , status )
}
2021-11-09 10:34:06 +00:00
case kindTCPRoute :
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , gatewayTCPRouteToTCPConf ( ctx , ep , listener , gateway , client , conf ) ... )
case kindTLSRoute :
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , gatewayTLSRouteToTCPConf ( ctx , ep , listener , gateway , client , conf ) ... )
}
2020-12-15 15:40:05 +00:00
}
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
return listenerStatuses , httpRouteParentStatuses
2021-05-20 09:50:12 +00:00
}
2020-12-15 15:40:05 +00:00
2024-04-10 07:34:07 +00:00
func ( p * Provider ) makeGatewayStatus ( gateway * gatev1 . Gateway , listenerStatuses [ ] gatev1 . ListenerStatus , addresses [ ] gatev1 . GatewayStatusAddress ) ( gatev1 . GatewayStatus , error ) {
gatewayStatus := gatev1 . GatewayStatus { Addresses : addresses }
2021-10-04 13:46:08 +00:00
2021-11-09 10:34:06 +00:00
var result error
for i , listener := range listenerStatuses {
if len ( listener . Conditions ) == 0 {
2024-01-30 15:44:05 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions ,
metav1 . Condition {
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonAccepted ) ,
Message : "No error found" ,
} ,
metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonResolvedRefs ) ,
Message : "No error found" ,
} ,
metav1 . Condition {
Type : string ( gatev1 . ListenerConditionProgrammed ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonProgrammed ) ,
Message : "No error found" ,
} ,
)
2021-11-09 10:34:06 +00:00
continue
}
for _ , condition := range listener . Conditions {
result = multierror . Append ( result , errors . New ( condition . Message ) )
2021-10-04 13:46:08 +00:00
}
2021-07-15 15:20:08 +00:00
}
2024-01-30 15:44:05 +00:00
gatewayStatus . Listeners = listenerStatuses
2021-07-15 15:20:08 +00:00
2021-11-09 10:34:06 +00:00
if result != nil {
// GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid"
gatewayStatus . Conditions = append ( gatewayStatus . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayConditionAccepted ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . GatewayReasonListenersNotValid ) ,
2021-11-09 10:34:06 +00:00
Message : "All Listeners must be valid" ,
} )
return gatewayStatus , result
}
gatewayStatus . Conditions = append ( gatewayStatus . Conditions ,
2024-01-09 09:28:05 +00:00
// update "Accepted" status with "Accepted" reason
2021-11-09 10:34:06 +00:00
metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayConditionAccepted ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionTrue ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2024-01-22 14:30:05 +00:00
Reason : string ( gatev1 . GatewayReasonAccepted ) ,
Message : "Gateway successfully scheduled" ,
LastTransitionTime : metav1 . Now ( ) ,
} ,
// update "Programmed" status with "Programmed" reason
metav1 . Condition {
Type : string ( gatev1 . GatewayConditionProgrammed ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : gateway . Generation ,
Reason : string ( gatev1 . GatewayReasonProgrammed ) ,
2024-01-09 09:28:05 +00:00
Message : "Gateway successfully scheduled" ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
} ,
)
return gatewayStatus , nil
}
2024-04-10 07:34:07 +00:00
func ( p * Provider ) gatewayAddresses ( client Client ) ( [ ] gatev1 . GatewayStatusAddress , error ) {
if p . StatusAddress == nil {
return nil , nil
}
if p . StatusAddress . IP != "" {
return [ ] gatev1 . GatewayStatusAddress { {
Type : ptr . To ( gatev1 . IPAddressType ) ,
Value : p . StatusAddress . IP ,
} } , nil
}
if p . StatusAddress . Hostname != "" {
return [ ] gatev1 . GatewayStatusAddress { {
Type : ptr . To ( gatev1 . HostnameAddressType ) ,
Value : p . StatusAddress . Hostname ,
} } , nil
}
svcRef := p . StatusAddress . Service
if svcRef . Name != "" && svcRef . Namespace != "" {
svc , exists , err := client . GetService ( svcRef . Namespace , svcRef . Name )
if err != nil {
return nil , fmt . Errorf ( "unable to get service: %w" , err )
}
if ! exists {
return nil , fmt . Errorf ( "could not find a service with name %s in namespace %s" , svcRef . Name , svcRef . Namespace )
}
var addresses [ ] gatev1 . GatewayStatusAddress
for _ , addr := range svc . Status . LoadBalancer . Ingress {
switch {
case addr . IP != "" :
addresses = append ( addresses , gatev1 . GatewayStatusAddress {
Type : ptr . To ( gatev1 . IPAddressType ) ,
Value : addr . IP ,
} )
case addr . Hostname != "" :
addresses = append ( addresses , gatev1 . GatewayStatusAddress {
Type : ptr . To ( gatev1 . HostnameAddressType ) ,
Value : addr . Hostname ,
} )
}
}
return addresses , nil
}
return nil , errors . New ( "empty Gateway status address configuration" )
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) entryPointName ( port gatev1 . PortNumber , protocol gatev1 . ProtocolType ) ( string , error ) {
2021-11-09 10:34:06 +00:00
portStr := strconv . FormatInt ( int64 ( port ) , 10 )
for name , entryPoint := range p . EntryPoints {
if strings . HasSuffix ( entryPoint . Address , ":" + portStr ) {
// If the protocol is HTTP the entryPoint must have no TLS conf
2024-01-09 09:28:05 +00:00
// Not relevant for gatev1.TLSProtocolType && gatev1.TCPProtocolType
if protocol == gatev1 . HTTPProtocolType && entryPoint . HasHTTPTLSConf {
2021-11-09 10:34:06 +00:00
continue
}
return name , nil
}
}
return "" , fmt . Errorf ( "no matching entryPoint for port %d and protocol %q" , port , protocol )
}
2024-05-01 04:38:03 +00:00
func ( p * Provider ) gatewayHTTPRouteToHTTPConf ( ctx context . Context , ep string , listener gatev1 . Listener , gateway * gatev1 . Gateway , client Client , conf * dynamic . Configuration ) ( [ ] metav1 . Condition , map [ ktypes . NamespacedName ] gatev1 . RouteParentStatus ) {
// Should not happen due to validation.
2021-11-09 10:34:06 +00:00
if listener . AllowedRoutes == nil {
2024-05-01 04:38:03 +00:00
return nil , nil
2021-11-09 10:34:06 +00:00
}
namespaces , err := getRouteBindingSelectorNamespace ( client , gateway . Namespace , listener . AllowedRoutes . Namespaces )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouteNamespacesSelector" , // Should never happen as the selector is validated by kubernetes
2021-10-04 13:46:08 +00:00
Message : fmt . Sprintf ( "Invalid route namespaces selector: %v" , err ) ,
2024-05-01 04:38:03 +00:00
} } , nil
2021-10-04 13:46:08 +00:00
}
2021-11-09 10:34:06 +00:00
routes , err := client . GetHTTPRoutes ( namespaces )
2021-10-04 13:46:08 +00:00
if err != nil {
2024-01-30 15:44:05 +00:00
// update "ResolvedRefs" status true with "RefNotPermitted" reason
2021-10-04 13:46:08 +00:00
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-10-04 13:46:08 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-10-04 13:46:08 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Cannot fetch HTTPRoutes: %v" , err ) ,
2024-05-01 04:38:03 +00:00
} } , nil
2021-05-20 09:50:12 +00:00
}
2021-11-09 10:34:06 +00:00
if len ( routes ) == 0 {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No HTTPRoutes found" )
2024-05-01 04:38:03 +00:00
return nil , nil
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
routeStatuses := map [ ktypes . NamespacedName ] gatev1 . RouteParentStatus { }
2021-11-09 10:34:06 +00:00
for _ , route := range routes {
2024-05-22 15:20:04 +00:00
routeNsName := ktypes . NamespacedName { Namespace : route . Namespace , Name : route . Name }
2024-05-01 04:38:03 +00:00
parentRef , ok := shouldAttach ( gateway , listener , route . Namespace , route . Spec . CommonRouteSpec )
if ! ok {
2024-05-22 15:20:04 +00:00
// TODO: to add an invalid HTTPRoute status when no parent is matching,
// we have to start the attachment evaluation from the route not from the listeners.
// This will fix the HTTPRouteInvalidParentRefNotMatchingSectionName test.
2021-11-09 10:34:06 +00:00
continue
}
2024-05-22 15:20:04 +00:00
routeConditions := [ ] metav1 . Condition {
{
Type : string ( gatev1 . RouteConditionAccepted ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : route . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . RouteReasonAccepted ) ,
} ,
{
Type : string ( gatev1 . RouteConditionResolvedRefs ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : route . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . RouteConditionResolvedRefs ) ,
} ,
}
2021-11-09 10:34:06 +00:00
hostnames := matchingHostnames ( listener , route . Spec . Hostnames )
if len ( hostnames ) == 0 && listener . Hostname != nil && * listener . Hostname != "" && len ( route . Spec . Hostnames ) > 0 {
2024-05-22 15:20:04 +00:00
// TODO update the corresponding route parent status.
2021-11-09 10:34:06 +00:00
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute
continue
}
hostRule , err := hostRule ( hostnames )
2020-12-15 15:40:05 +00:00
if err != nil {
2024-05-22 15:20:04 +00:00
// TODO update the route status condition.
2020-12-15 15:40:05 +00:00
continue
}
2021-11-09 10:34:06 +00:00
for _ , routeRule := range route . Spec . Rules {
2021-05-20 09:50:12 +00:00
rule , err := extractRule ( routeRule , hostRule )
if err != nil {
2024-05-22 15:20:04 +00:00
// TODO update the route status condition.
2024-05-01 04:38:03 +00:00
continue
2021-05-20 09:50:12 +00:00
}
router := dynamic . Router {
Rule : rule ,
2024-01-23 10:34:05 +00:00
RuleSyntax : "v3" ,
2021-05-20 09:50:12 +00:00
EntryPoints : [ ] string { ep } ,
2020-12-15 15:40:05 +00:00
}
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . HTTPSProtocolType && listener . TLS != nil {
2024-05-22 15:20:04 +00:00
// TODO support let's encrypt.
2021-05-20 09:50:12 +00:00
router . TLS = & dynamic . RouterTLSConfig { }
}
2021-11-09 10:34:06 +00:00
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
routerName := route . Name + "-" + gateway . Name + "-" + ep
routerKey , err := makeRouterKey ( router . Rule , makeID ( route . Namespace , routerName ) )
2021-04-29 15:18:04 +00:00
if err != nil {
2024-05-22 15:20:04 +00:00
// TODO update the route status condition.
2021-04-29 15:18:04 +00:00
continue
}
2020-12-15 15:40:05 +00:00
2024-03-25 13:38:04 +00:00
middlewares , err := p . loadMiddlewares ( listener , route . Namespace , routerKey , routeRule . Filters )
2022-12-22 14:02:05 +00:00
if err != nil {
2024-05-22 15:20:04 +00:00
// TODO update the route status condition.
2022-12-22 14:02:05 +00:00
continue
}
for middlewareName , middleware := range middlewares {
2024-03-25 13:38:04 +00:00
// If the middleware is not defined in the return of the loadMiddlewares function, it means we just need a reference to that middleware.
if middleware != nil {
conf . HTTP . Middlewares [ middlewareName ] = middleware
}
2022-12-22 14:02:05 +00:00
router . Middlewares = append ( router . Middlewares , middlewareName )
}
2021-11-09 10:34:06 +00:00
if len ( routeRule . BackendRefs ) == 0 {
2021-05-20 09:50:12 +00:00
continue
}
2021-11-09 10:34:06 +00:00
// Traefik internal service can be used only if there is only one BackendRef service reference.
if len ( routeRule . BackendRefs ) == 1 && isInternalService ( routeRule . BackendRefs [ 0 ] . BackendRef ) {
router . Service = string ( routeRule . BackendRefs [ 0 ] . Name )
2021-05-20 09:50:12 +00:00
} else {
2024-05-22 15:20:04 +00:00
var wrr dynamic . WeightedRoundRobin
for _ , backendRef := range routeRule . BackendRefs {
weight := ptr . To ( int ( ptr . Deref ( backendRef . Weight , 1 ) ) )
name , svc , errCondition := p . loadHTTPService ( client , route , backendRef )
if errCondition != nil {
routeConditions = appendCondition ( routeConditions , * errCondition )
wrr . Services = append ( wrr . Services , dynamic . WRRService {
Name : name ,
Weight : weight ,
Status : ptr . To ( 500 ) ,
} )
continue
}
2020-12-15 15:40:05 +00:00
2024-03-25 13:38:04 +00:00
if svc != nil {
2024-05-22 15:20:04 +00:00
conf . HTTP . Services [ name ] = svc
2024-03-25 13:38:04 +00:00
}
2024-05-22 15:20:04 +00:00
wrr . Services = append ( wrr . Services , dynamic . WRRService {
Name : name ,
Weight : weight ,
} )
2020-12-15 15:40:05 +00:00
}
2024-05-22 15:20:04 +00:00
wrrName := provider . Normalize ( routerKey + "-wrr" )
conf . HTTP . Services [ wrrName ] = & dynamic . Service { Weighted : & wrr }
2020-12-15 15:40:05 +00:00
2024-05-22 15:20:04 +00:00
router . Service = wrrName
2021-05-20 09:50:12 +00:00
}
2020-12-15 15:40:05 +00:00
2023-05-15 14:38:05 +00:00
rt := & router
p . applyRouterTransform ( ctx , rt , route )
2021-05-20 09:50:12 +00:00
routerKey = provider . Normalize ( routerKey )
2023-05-15 14:38:05 +00:00
conf . HTTP . Routers [ routerKey ] = rt
2021-05-20 09:50:12 +00:00
}
2020-12-15 15:40:05 +00:00
2024-05-22 15:20:04 +00:00
routeStatuses [ routeNsName ] = gatev1 . RouteParentStatus {
2024-05-01 04:38:03 +00:00
ParentRef : parentRef ,
ControllerName : controllerName ,
2024-05-22 15:20:04 +00:00
Conditions : routeConditions ,
2024-05-01 04:38:03 +00:00
}
2021-10-04 13:46:08 +00:00
}
2024-05-22 15:20:04 +00:00
return nil , routeStatuses
2024-05-01 04:38:03 +00:00
}
2021-07-15 15:20:08 +00:00
2024-05-22 15:20:04 +00:00
// loadHTTPService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
func ( p * Provider ) loadHTTPService ( client Client , route * gatev1 . HTTPRoute , backendRef gatev1 . HTTPBackendRef ) ( string , * dynamic . Service , * metav1 . Condition ) {
group := groupCore
if backendRef . Group != nil && * backendRef . Group != "" {
group = string ( * backendRef . Group )
2021-05-20 09:50:12 +00:00
}
2020-12-15 15:40:05 +00:00
2024-05-22 15:20:04 +00:00
kind := ptr . Deref ( backendRef . Kind , "Service" )
namespace := ptr . Deref ( backendRef . Namespace , gatev1 . Namespace ( route . Namespace ) )
namespaceStr := string ( namespace )
serviceName := provider . Normalize ( makeID ( namespaceStr , string ( backendRef . Name ) ) )
2021-05-20 09:50:12 +00:00
2024-05-22 15:20:04 +00:00
if group != groupCore || kind != "Service" {
// TODO support cross namespace through ReferencePolicy.
if namespaceStr != route . Namespace {
return serviceName , nil , & metav1 . Condition {
Type : string ( gatev1 . RouteConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : route . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . RouteReasonRefNotPermitted ) ,
Message : fmt . Sprintf ( "Cannot load HTTPBackendRef %s/%s/%s/%s namespace not allowed" , group , kind , namespace , backendRef . Name ) ,
2024-05-01 04:38:03 +00:00
}
2021-11-09 10:34:06 +00:00
}
2021-05-20 09:50:12 +00:00
2024-05-22 15:20:04 +00:00
name , service , err := p . loadHTTPBackendRef ( namespaceStr , backendRef )
2024-05-01 04:38:03 +00:00
if err != nil {
2024-05-22 15:20:04 +00:00
return serviceName , nil , & metav1 . Condition {
Type : string ( gatev1 . RouteConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : route . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . RouteReasonInvalidKind ) ,
Message : fmt . Sprintf ( "Cannot load HTTPBackendRef %s/%s/%s/%s: %s" , group , kind , namespace , backendRef . Name , err ) ,
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
}
2021-05-20 09:50:12 +00:00
2024-05-22 15:20:04 +00:00
return name , service , nil
}
2024-05-01 04:38:03 +00:00
2024-05-22 15:20:04 +00:00
port := ptr . Deref ( backendRef . Port , gatev1 . PortNumber ( 0 ) )
if port == 0 {
return serviceName , nil , & metav1 . Condition {
Type : string ( gatev1 . RouteConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : route . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . RouteReasonUnsupportedProtocol ) ,
Message : fmt . Sprintf ( "Cannot load HTTPBackendRef %s/%s/%s/%s port is required" , group , kind , namespace , backendRef . Name ) ,
2024-05-01 04:38:03 +00:00
}
2024-05-22 15:20:04 +00:00
}
2024-05-01 04:38:03 +00:00
2024-05-22 15:20:04 +00:00
portStr := strconv . FormatInt ( int64 ( port ) , 10 )
serviceName = provider . Normalize ( serviceName + "-" + portStr )
2024-05-01 04:38:03 +00:00
2024-05-22 15:20:04 +00:00
lb , err := loadHTTPServers ( client , namespaceStr , backendRef )
if err != nil {
return serviceName , nil , & metav1 . Condition {
Type : string ( gatev1 . RouteConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : route . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . RouteReasonBackendNotFound ) ,
Message : fmt . Sprintf ( "Cannot load HTTPBackendRef %s/%s/%s/%s: %s" , group , kind , namespace , backendRef . Name , err ) ,
2021-11-09 10:34:06 +00:00
}
2024-05-01 04:38:03 +00:00
}
2024-05-22 15:20:04 +00:00
return serviceName , & dynamic . Service { LoadBalancer : lb } , nil
2024-05-01 04:38:03 +00:00
}
func ( p * Provider ) loadHTTPBackendRef ( namespace string , backendRef gatev1 . HTTPBackendRef ) ( string , * dynamic . Service , error ) {
// Support for cross-provider references (e.g: api@internal).
// This provides the same behavior as for IngressRoutes.
if * backendRef . Kind == "TraefikService" && strings . Contains ( string ( backendRef . Name ) , "@" ) {
return string ( backendRef . Name ) , nil , nil
}
backendFunc , ok := p . groupKindBackendFuncs [ string ( * backendRef . Group ) ] [ string ( * backendRef . Kind ) ]
if ! ok {
return "" , nil , fmt . Errorf ( "unsupported HTTPBackendRef %s/%s/%s" , * backendRef . Group , * backendRef . Kind , backendRef . Name )
}
if backendFunc == nil {
return "" , nil , fmt . Errorf ( "undefined backendFunc for HTTPBackendRef %s/%s/%s" , * backendRef . Group , * backendRef . Kind , backendRef . Name )
}
return backendFunc ( string ( backendRef . Name ) , namespace )
}
func ( p * Provider ) loadMiddlewares ( listener gatev1 . Listener , namespace string , prefix string , filters [ ] gatev1 . HTTPRouteFilter ) ( map [ string ] * dynamic . Middleware , error ) {
middlewares := make ( map [ string ] * dynamic . Middleware )
// The spec allows for an empty string in which case we should use the
// scheme of the request which in this case is the listener scheme.
var listenerScheme string
switch listener . Protocol {
case gatev1 . HTTPProtocolType :
listenerScheme = "http"
case gatev1 . HTTPSProtocolType :
listenerScheme = "https"
default :
return nil , fmt . Errorf ( "invalid listener protocol %s" , listener . Protocol )
}
for i , filter := range filters {
var middleware * dynamic . Middleware
switch filter . Type {
case gatev1 . HTTPRouteFilterRequestRedirect :
var err error
middleware , err = createRedirectRegexMiddleware ( listenerScheme , filter . RequestRedirect )
if err != nil {
return nil , fmt . Errorf ( "creating RedirectRegex middleware: %w" , err )
}
middlewareName := provider . Normalize ( fmt . Sprintf ( "%s-%s-%d" , prefix , strings . ToLower ( string ( filter . Type ) ) , i ) )
middlewares [ middlewareName ] = middleware
case gatev1 . HTTPRouteFilterExtensionRef :
name , middleware , err := p . loadHTTPRouteFilterExtensionRef ( namespace , filter . ExtensionRef )
if err != nil {
return nil , fmt . Errorf ( "unsupported filter %s: %w" , filter . Type , err )
}
middlewares [ name ] = middleware
case gatev1 . HTTPRouteFilterRequestHeaderModifier :
middlewareName := provider . Normalize ( fmt . Sprintf ( "%s-%s-%d" , prefix , strings . ToLower ( string ( filter . Type ) ) , i ) )
middlewares [ middlewareName ] = createRequestHeaderModifier ( filter . RequestHeaderModifier )
default :
// As per the spec:
// https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
// In all cases where incompatible or unsupported filters are
// specified, implementations MUST add a warning condition to
// status.
return nil , fmt . Errorf ( "unsupported filter %s" , filter . Type )
}
}
return middlewares , nil
}
func ( p * Provider ) loadHTTPRouteFilterExtensionRef ( namespace string , extensionRef * gatev1 . LocalObjectReference ) ( string , * dynamic . Middleware , error ) {
if extensionRef == nil {
return "" , nil , errors . New ( "filter extension ref undefined" )
}
filterFunc , ok := p . groupKindFilterFuncs [ string ( extensionRef . Group ) ] [ string ( extensionRef . Kind ) ]
if ! ok {
return "" , nil , fmt . Errorf ( "unsupported filter extension ref %s/%s/%s" , extensionRef . Group , extensionRef . Kind , extensionRef . Name )
}
if filterFunc == nil {
return "" , nil , fmt . Errorf ( "undefined filterFunc for filter extension ref %s/%s/%s" , extensionRef . Group , extensionRef . Kind , extensionRef . Name )
}
return filterFunc ( string ( extensionRef . Name ) , namespace )
}
2024-05-22 15:20:04 +00:00
// TODO support cross namespace through ReferencePolicy.
func loadHTTPServers ( client Client , namespace string , backendRef gatev1 . HTTPBackendRef ) ( * dynamic . ServersLoadBalancer , error ) {
service , exists , err := client . GetService ( namespace , string ( backendRef . Name ) )
if err != nil {
return nil , fmt . Errorf ( "getting service: %w" , err )
}
if ! exists {
return nil , errors . New ( "service not found" )
}
var portSpec corev1 . ServicePort
var match bool
for _ , p := range service . Spec . Ports {
if backendRef . Port == nil || p . Port == int32 ( * backendRef . Port ) {
portSpec = p
match = true
break
}
}
if ! match {
return nil , errors . New ( "service port not found" )
}
endpoints , endpointsExists , err := client . GetEndpoints ( namespace , string ( backendRef . Name ) )
if err != nil {
return nil , fmt . Errorf ( "getting endpoints: %w" , err )
}
if ! endpointsExists {
return nil , errors . New ( "endpoints not found" )
}
if len ( endpoints . Subsets ) == 0 {
return nil , errors . New ( "subset not found" )
}
lb := & dynamic . ServersLoadBalancer { }
lb . SetDefaults ( )
var port int32
var portStr string
for _ , subset := range endpoints . Subsets {
for _ , p := range subset . Ports {
if portSpec . Name == p . Name {
port = p . Port
break
}
}
if port == 0 {
return nil , errors . New ( "cannot define a port" )
}
protocol := getProtocol ( portSpec )
portStr = strconv . FormatInt ( int64 ( port ) , 10 )
for _ , addr := range subset . Addresses {
lb . Servers = append ( lb . Servers , dynamic . Server {
URL : fmt . Sprintf ( "%s://%s" , protocol , net . JoinHostPort ( addr . IP , portStr ) ) ,
} )
}
}
return lb , nil
}
2024-05-01 04:38:03 +00:00
// loadTCPServices is generating a WRR service, even when there is only one target.
func loadTCPServices ( client Client , namespace string , backendRefs [ ] gatev1 . BackendRef ) ( * dynamic . TCPService , map [ string ] * dynamic . TCPService , error ) {
services := map [ string ] * dynamic . TCPService { }
wrrSvc := & dynamic . TCPService {
Weighted : & dynamic . TCPWeightedRoundRobin {
Services : [ ] dynamic . TCPWRRService { } ,
} ,
}
for _ , backendRef := range backendRefs {
if backendRef . Group == nil || backendRef . Kind == nil {
// Should not happen as this is validated by kubernetes
2021-11-09 10:34:06 +00:00
continue
2021-05-20 09:50:12 +00:00
}
2021-11-09 10:34:06 +00:00
2024-05-01 04:38:03 +00:00
if isInternalService ( backendRef ) {
return nil , nil , fmt . Errorf ( "traefik internal service %s is not allowed in a WRR loadbalancer" , backendRef . Name )
}
2021-11-09 10:34:06 +00:00
2024-05-01 04:38:03 +00:00
weight := int ( ptr . Deref ( backendRef . Weight , 1 ) )
2021-11-09 10:34:06 +00:00
2024-05-01 04:38:03 +00:00
if isTraefikService ( backendRef ) {
wrrSvc . Weighted . Services = append ( wrrSvc . Weighted . Services , dynamic . TCPWRRService { Name : string ( backendRef . Name ) , Weight : & weight } )
continue
2021-11-09 10:34:06 +00:00
}
2024-05-01 04:38:03 +00:00
if * backendRef . Group != "" && * backendRef . Group != groupCore && * backendRef . Kind != "Service" {
return nil , nil , fmt . Errorf ( "unsupported BackendRef %s/%s/%s" , * backendRef . Group , * backendRef . Kind , backendRef . Name )
}
2021-11-09 10:34:06 +00:00
2024-05-01 04:38:03 +00:00
svc := dynamic . TCPService {
LoadBalancer : & dynamic . TCPServersLoadBalancer { } ,
}
service , exists , err := client . GetService ( namespace , string ( backendRef . Name ) )
if err != nil {
return nil , nil , err
}
if ! exists {
return nil , nil , errors . New ( "service not found" )
}
if len ( service . Spec . Ports ) > 1 && backendRef . Port == nil {
// If the port is unspecified and the backend is a Service
// object consisting of multiple port definitions, the route
// must be dropped from the Gateway. The controller should
// raise the "ResolvedRefs" condition on the Gateway with the
// "DroppedRoutes" reason. The gateway status for this route
// should be updated with a condition that describes the error
// more specifically.
log . Error ( ) . Msg ( "A multiple ports Kubernetes Service cannot be used if unspecified backendRef.Port" )
continue
}
var portSpec corev1 . ServicePort
var match bool
for _ , p := range service . Spec . Ports {
if backendRef . Port == nil || p . Port == int32 ( * backendRef . Port ) {
portSpec = p
match = true
break
}
}
if ! match {
return nil , nil , errors . New ( "service port not found" )
}
endpoints , endpointsExists , endpointsErr := client . GetEndpoints ( namespace , string ( backendRef . Name ) )
if endpointsErr != nil {
return nil , nil , endpointsErr
}
if ! endpointsExists {
return nil , nil , errors . New ( "endpoints not found" )
}
if len ( endpoints . Subsets ) == 0 {
return nil , nil , errors . New ( "subset not found" )
}
var port int32
var portStr string
for _ , subset := range endpoints . Subsets {
for _ , p := range subset . Ports {
if portSpec . Name == p . Name {
port = p . Port
break
}
}
if port == 0 {
return nil , nil , errors . New ( "cannot define a port" )
}
portStr = strconv . FormatInt ( int64 ( port ) , 10 )
for _ , addr := range subset . Addresses {
svc . LoadBalancer . Servers = append ( svc . LoadBalancer . Servers , dynamic . TCPServer {
Address : net . JoinHostPort ( addr . IP , portStr ) ,
} )
}
}
serviceName := provider . Normalize ( makeID ( service . Namespace , service . Name ) + "-" + portStr )
services [ serviceName ] = & svc
wrrSvc . Weighted . Services = append ( wrrSvc . Weighted . Services , dynamic . TCPWRRService { Name : serviceName , Weight : & weight } )
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
if len ( wrrSvc . Weighted . Services ) == 0 {
return nil , nil , errors . New ( "no service has been created" )
}
return wrrSvc , services , nil
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
func supportedRouteKinds ( protocol gatev1 . ProtocolType , experimentalChannel bool ) ( [ ] gatev1 . RouteGroupKind , [ ] metav1 . Condition ) {
group := gatev1 . Group ( gatev1 . GroupName )
switch protocol {
case gatev1 . TCPProtocolType :
if experimentalChannel {
return [ ] gatev1 . RouteGroupKind { { Kind : kindTCPRoute , Group : & group } } , nil
}
return nil , [ ] metav1 . Condition { {
Type : string ( gatev1 . ListenerConditionConflicted ) ,
Status : metav1 . ConditionFalse ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonInvalidRouteKinds ) ,
Message : fmt . Sprintf ( "Protocol %q requires the experimental channel support to be enabled, please use the `experimentalChannel` option" , protocol ) ,
} }
case gatev1 . HTTPProtocolType , gatev1 . HTTPSProtocolType :
return [ ] gatev1 . RouteGroupKind { { Kind : kindHTTPRoute , Group : & group } } , nil
case gatev1 . TLSProtocolType :
if experimentalChannel {
return [ ] gatev1 . RouteGroupKind {
{ Kind : kindTCPRoute , Group : & group } ,
{ Kind : kindTLSRoute , Group : & group } ,
} , nil
}
return nil , [ ] metav1 . Condition { {
Type : string ( gatev1 . ListenerConditionConflicted ) ,
Status : metav1 . ConditionFalse ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonInvalidRouteKinds ) ,
Message : fmt . Sprintf ( "Protocol %q requires the experimental channel support to be enabled, please use the `experimentalChannel` option" , protocol ) ,
} }
}
return nil , [ ] metav1 . Condition { {
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonUnsupportedProtocol ) ,
Message : fmt . Sprintf ( "Unsupported listener protocol %q" , protocol ) ,
} }
}
func getAllowedRouteKinds ( gateway * gatev1 . Gateway , listener gatev1 . Listener , supportedKinds [ ] gatev1 . RouteGroupKind ) ( [ ] gatev1 . RouteGroupKind , [ ] metav1 . Condition ) {
if listener . AllowedRoutes == nil || len ( listener . AllowedRoutes . Kinds ) == 0 {
return supportedKinds , nil
}
var (
routeKinds = [ ] gatev1 . RouteGroupKind { }
conditions [ ] metav1 . Condition
)
uniqRouteKinds := map [ gatev1 . Kind ] struct { } { }
for _ , routeKind := range listener . AllowedRoutes . Kinds {
var isSupported bool
for _ , kind := range supportedKinds {
if routeKind . Kind == kind . Kind && routeKind . Group != nil && * routeKind . Group == * kind . Group {
isSupported = true
break
}
}
if ! isSupported {
conditions = append ( conditions , metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonInvalidRouteKinds ) ,
Message : fmt . Sprintf ( "Listener protocol %q does not support RouteGroupKind %s/%s" , listener . Protocol , groupToString ( routeKind . Group ) , routeKind . Kind ) ,
} )
continue
}
if _ , exists := uniqRouteKinds [ routeKind . Kind ] ; ! exists {
routeKinds = append ( routeKinds , routeKind )
uniqRouteKinds [ routeKind . Kind ] = struct { } { }
}
}
return routeKinds , conditions
}
func gatewayTCPRouteToTCPConf ( ctx context . Context , ep string , listener gatev1 . Listener , gateway * gatev1 . Gateway , client Client , conf * dynamic . Configuration ) [ ] metav1 . Condition {
2021-11-09 10:34:06 +00:00
if listener . AllowedRoutes == nil {
// Should not happen due to validation.
return nil
2021-07-15 15:20:08 +00:00
}
2021-11-09 10:34:06 +00:00
namespaces , err := getRouteBindingSelectorNamespace ( client , gateway . Namespace , listener . AllowedRoutes . Namespaces )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouteNamespacesSelector" , // TODO should never happen as the selector is validated by Kubernetes
2021-10-04 13:46:08 +00:00
Message : fmt . Sprintf ( "Invalid route namespaces selector: %v" , err ) ,
} }
}
2024-05-01 04:38:03 +00:00
routes , err := client . GetTCPRoutes ( namespaces )
2021-10-04 13:46:08 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-10-04 13:46:08 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-10-04 13:46:08 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
2024-05-01 04:38:03 +00:00
Message : fmt . Sprintf ( "Cannot fetch TCPRoutes: %v" , err ) ,
2021-05-20 09:50:12 +00:00
} }
}
2024-05-01 04:38:03 +00:00
if len ( routes ) == 0 {
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No TCPRoutes found" )
return nil
}
var conditions [ ] metav1 . Condition
for _ , route := range routes {
if _ , ok := shouldAttach ( gateway , listener , route . Namespace , route . Spec . CommonRouteSpec ) ; ! ok {
2021-11-09 10:34:06 +00:00
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
router := dynamic . TCPRouter {
2024-05-01 04:38:03 +00:00
Rule : "HostSNI(`*`)" ,
2021-11-09 10:34:06 +00:00
EntryPoints : [ ] string { ep } ,
2024-05-01 04:38:03 +00:00
RuleSyntax : "v3" ,
}
if listener . Protocol == gatev1 . TLSProtocolType && listener . TLS != nil {
// TODO support let's encrypt
router . TLS = & dynamic . RouterTCPTLSConfig {
2024-01-09 09:28:05 +00:00
Passthrough : listener . TLS . Mode != nil && * listener . TLS . Mode == gatev1 . TLSModePassthrough ,
2024-05-01 04:38:03 +00:00
}
2021-11-09 10:34:06 +00:00
}
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
routerName := route . Name + "-" + gateway . Name + "-" + ep
2024-05-01 04:38:03 +00:00
routerKey , err := makeRouterKey ( "" , makeID ( route . Namespace , routerName ) )
2021-11-09 10:34:06 +00:00
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidRouterKey" , // Should never happen
2024-05-01 04:38:03 +00:00
Message : fmt . Sprintf ( "Skipping TCPRoute %s: cannot make router's key with rule %s: %v" , route . Name , router . Rule , err ) ,
2021-11-09 10:34:06 +00:00
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
routerKey = provider . Normalize ( routerKey )
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
var ruleServiceNames [ ] string
2024-05-01 04:38:03 +00:00
for i , rule := range route . Spec . Rules {
if rule . BackendRefs == nil {
2021-11-09 10:34:06 +00:00
// Should not happen due to validation.
2024-05-01 04:38:03 +00:00
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tcproute_types.go#L76
2021-05-20 09:50:12 +00:00
continue
}
2024-05-01 04:38:03 +00:00
wrrService , subServices , err := loadTCPServices ( client , route . Namespace , rule . BackendRefs )
2021-05-20 09:50:12 +00:00
if err != nil {
2024-05-01 04:38:03 +00:00
// update "ResolvedRefs" status true with "DroppedRoutes" reason
2021-05-20 09:50:12 +00:00
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidBackendRefs" , // TODO check the spec if a proper reason is introduced at some point
2024-05-01 04:38:03 +00:00
Message : fmt . Sprintf ( "Cannot load TCPRoute service %s/%s: %v" , route . Namespace , route . Name , err ) ,
2021-05-20 09:50:12 +00:00
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
for svcName , svc := range subServices {
conf . TCP . Services [ svcName ] = svc
}
2021-11-09 10:34:06 +00:00
serviceName := fmt . Sprintf ( "%s-wrr-%d" , routerKey , i )
2021-05-20 09:50:12 +00:00
conf . TCP . Services [ serviceName ] = wrrService
2021-11-09 10:34:06 +00:00
ruleServiceNames = append ( ruleServiceNames , serviceName )
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
if len ( ruleServiceNames ) == 1 {
2024-05-01 04:38:03 +00:00
router . Service = ruleServiceNames [ 0 ]
conf . TCP . Routers [ routerKey ] = & router
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
routeServiceKey := routerKey + "-wrr"
routeService := & dynamic . TCPService { Weighted : & dynamic . TCPWeightedRoundRobin { } }
for _ , name := range ruleServiceNames {
service := dynamic . TCPWRRService { Name : name }
service . SetDefaults ( )
routeService . Weighted . Services = append ( routeService . Weighted . Services , service )
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
conf . TCP . Services [ routeServiceKey ] = routeService
router . Service = routeServiceKey
conf . TCP . Routers [ routerKey ] = & router
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
return conditions
}
func gatewayTLSRouteToTCPConf ( ctx context . Context , ep string , listener gatev1 . Listener , gateway * gatev1 . Gateway , client Client , conf * dynamic . Configuration ) [ ] metav1 . Condition {
if listener . AllowedRoutes == nil {
// Should not happen due to validation.
return nil
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
namespaces , err := getRouteBindingSelectorNamespace ( client , gateway . Namespace , listener . AllowedRoutes . Namespaces )
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidRouteNamespacesSelector" , // TODO should never happen as the selector is validated by Kubernetes
Message : fmt . Sprintf ( "Invalid route namespaces selector: %v" , err ) ,
} }
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
routes , err := client . GetTLSRoutes ( namespaces )
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
Message : fmt . Sprintf ( "Cannot fetch TLSRoutes: %v" , err ) ,
} }
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if len ( routes ) == 0 {
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No TLSRoutes found" )
return nil
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
var conditions [ ] metav1 . Condition
for _ , route := range routes {
if _ , ok := shouldAttach ( gateway , listener , route . Namespace , route . Spec . CommonRouteSpec ) ; ! ok {
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
hostnames := matchingHostnames ( listener , route . Spec . Hostnames )
if len ( hostnames ) == 0 && listener . Hostname != nil && * listener . Hostname != "" && len ( route . Spec . Hostnames ) > 0 {
for _ , parent := range route . Status . Parents {
parent . Conditions = append ( parent . Conditions , metav1 . Condition {
Type : string ( gatev1 . GatewayClassConditionStatusAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
Reason : string ( gatev1 . ListenerReasonHostnameConflict ) ,
Message : fmt . Sprintf ( "No hostname match between listener: %v and route: %v" , listener . Hostname , route . Spec . Hostnames ) ,
LastTransitionTime : metav1 . Now ( ) ,
} )
}
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
rule , err := hostSNIRule ( hostnames )
if err != nil {
// update "ResolvedRefs" status true with "InvalidHostnames" reason
conditions = append ( conditions , metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidHostnames" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Skipping TLSRoute %s: cannot make route's SNI match: %v" , route . Name , err ) ,
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
router := dynamic . TCPRouter {
Rule : rule ,
RuleSyntax : "v3" ,
EntryPoints : [ ] string { ep } ,
TLS : & dynamic . RouterTCPTLSConfig {
Passthrough : listener . TLS . Mode != nil && * listener . TLS . Mode == gatev1 . TLSModePassthrough ,
} ,
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
routerName := route . Name + "-" + gateway . Name + "-" + ep
routerKey , err := makeRouterKey ( rule , makeID ( route . Namespace , routerName ) )
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidRouterKey" , // Should never happen
Message : fmt . Sprintf ( "Skipping TLSRoute %s: cannot make router's key with rule %s: %v" , route . Name , router . Rule , err ) ,
} )
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
routerKey = provider . Normalize ( routerKey )
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
var ruleServiceNames [ ] string
for i , routeRule := range route . Spec . Rules {
if len ( routeRule . BackendRefs ) == 0 {
// Should not happen due to validation.
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tlsroute_types.go#L120
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
wrrService , subServices , err := loadTCPServices ( client , route . Namespace , routeRule . BackendRefs )
if err != nil {
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
conditions = append ( conditions , metav1 . Condition {
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidBackendRefs" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Cannot load TLSRoute service %s/%s: %v" , route . Namespace , route . Name , err ) ,
} )
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
for svcName , svc := range subServices {
conf . TCP . Services [ svcName ] = svc
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
serviceName := fmt . Sprintf ( "%s-wrr-%d" , routerKey , i )
conf . TCP . Services [ serviceName ] = wrrService
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
ruleServiceNames = append ( ruleServiceNames , serviceName )
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
if len ( ruleServiceNames ) == 1 {
router . Service = ruleServiceNames [ 0 ]
conf . TCP . Routers [ routerKey ] = & router
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
routeServiceKey := routerKey + "-wrr"
routeService := & dynamic . TCPService { Weighted : & dynamic . TCPWeightedRoundRobin { } }
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
for _ , name := range ruleServiceNames {
service := dynamic . TCPWRRService { Name : name }
service . SetDefaults ( )
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
routeService . Weighted . Services = append ( routeService . Weighted . Services , service )
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
conf . TCP . Services [ routeServiceKey ] = routeService
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
router . Service = routeServiceKey
conf . TCP . Routers [ routerKey ] = & router
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
return conditions
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
// Because of Kubernetes validation we admit that the given Hostnames are valid.
// https://github.com/kubernetes-sigs/gateway-api/blob/ff9883da4cad8554cd300394f725ab3a27502785/apis/v1alpha2/shared_types.go#L252
func matchingHostnames ( listener gatev1 . Listener , hostnames [ ] gatev1 . Hostname ) [ ] gatev1 . Hostname {
if listener . Hostname == nil || * listener . Hostname == "" {
return hostnames
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if len ( hostnames ) == 0 {
return [ ] gatev1 . Hostname { * listener . Hostname }
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
listenerLabels := strings . Split ( string ( * listener . Hostname ) , "." )
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
var matches [ ] gatev1 . Hostname
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
for _ , hostname := range hostnames {
if hostname == * listener . Hostname {
matches = append ( matches , hostname )
2021-11-09 10:34:06 +00:00
continue
}
2021-02-02 18:36:04 +00:00
2024-05-01 04:38:03 +00:00
hostnameLabels := strings . Split ( string ( hostname ) , "." )
if len ( listenerLabels ) != len ( hostnameLabels ) {
continue
2021-11-09 10:34:06 +00:00
}
2021-02-02 18:36:04 +00:00
2024-05-01 04:38:03 +00:00
if ! slices . Equal ( listenerLabels [ 1 : ] , hostnameLabels [ 1 : ] ) {
continue
}
2024-03-25 13:38:04 +00:00
2024-05-01 04:38:03 +00:00
if listenerLabels [ 0 ] == "*" {
matches = append ( matches , hostname )
2024-03-25 13:38:04 +00:00
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if hostnameLabels [ 0 ] == "*" {
matches = append ( matches , * listener . Hostname )
continue
}
}
2022-11-16 10:38:07 +00:00
2024-05-01 04:38:03 +00:00
return matches
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
func shouldAttach ( gateway * gatev1 . Gateway , listener gatev1 . Listener , routeNamespace string , routeSpec gatev1 . CommonRouteSpec ) ( gatev1 . ParentReference , bool ) {
for _ , parentRef := range routeSpec . ParentRefs {
if parentRef . Group == nil || * parentRef . Group != gatev1 . GroupName {
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if parentRef . Kind == nil || * parentRef . Kind != kindGateway {
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if parentRef . SectionName != nil && * parentRef . SectionName != listener . Name {
2020-12-15 15:40:05 +00:00
continue
}
2024-05-01 04:38:03 +00:00
namespace := routeNamespace
if parentRef . Namespace != nil {
namespace = string ( * parentRef . Namespace )
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
if namespace == gateway . Namespace && string ( parentRef . Name ) == gateway . Name {
return parentRef , true
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
return gatev1 . ParentReference { } , false
}
func getRouteBindingSelectorNamespace ( client Client , gatewayNamespace string , routeNamespaces * gatev1 . RouteNamespaces ) ( [ ] string , error ) {
if routeNamespaces == nil || routeNamespaces . From == nil {
return [ ] string { gatewayNamespace } , nil
}
switch * routeNamespaces . From {
case gatev1 . NamespacesFromAll :
return [ ] string { metav1 . NamespaceAll } , nil
case gatev1 . NamespacesFromSame :
return [ ] string { gatewayNamespace } , nil
case gatev1 . NamespacesFromSelector :
selector , err := metav1 . LabelSelectorAsSelector ( routeNamespaces . Selector )
if err != nil {
return nil , fmt . Errorf ( "malformed selector: %w" , err )
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
return client . GetNamespaces ( selector )
}
return nil , fmt . Errorf ( "unsupported RouteSelectType: %q" , * routeNamespaces . From )
}
func hostRule ( hostnames [ ] gatev1 . Hostname ) ( string , error ) {
var rules [ ] string
for _ , hostname := range hostnames {
host := string ( hostname )
// When unspecified, "", or *, all hostnames are matched.
// This field can be omitted for protocols that don't require hostname based matching.
// TODO Refactor this when building support for TLS options.
if host == "*" || host == "" {
return "" , nil
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
wildcard := strings . Count ( host , "*" )
if wildcard == 0 {
rules = append ( rules , fmt . Sprintf ( "Host(`%s`)" , host ) )
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Hostname
if ! strings . HasPrefix ( host , "*." ) || wildcard > 1 {
return "" , fmt . Errorf ( "invalid rule: %q" , host )
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
host = strings . Replace ( regexp . QuoteMeta ( host ) , ` \*\. ` , ` [a-zA-Z0-9-]+\. ` , 1 )
rules = append ( rules , fmt . Sprintf ( "HostRegexp(`^%s$`)" , host ) )
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
switch len ( rules ) {
case 0 :
return "" , nil
case 1 :
return rules [ 0 ] , nil
default :
return fmt . Sprintf ( "(%s)" , strings . Join ( rules , " || " ) ) , nil
}
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
func hostSNIRule ( hostnames [ ] gatev1 . Hostname ) ( string , error ) {
rules := make ( [ ] string , 0 , len ( hostnames ) )
uniqHostnames := map [ gatev1 . Hostname ] struct { } { }
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
for _ , hostname := range hostnames {
if len ( hostname ) == 0 {
continue
2020-12-15 15:40:05 +00:00
}
2024-05-01 04:38:03 +00:00
if _ , exists := uniqHostnames [ hostname ] ; exists {
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
host := string ( hostname )
uniqHostnames [ hostname ] = struct { } { }
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
wildcard := strings . Count ( host , "*" )
if wildcard == 0 {
rules = append ( rules , fmt . Sprintf ( "HostSNI(`%s`)" , host ) )
continue
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
if ! strings . HasPrefix ( host , "*." ) || wildcard > 1 {
return "" , fmt . Errorf ( "invalid rule: %q" , host )
}
2020-12-15 15:40:05 +00:00
2024-05-01 04:38:03 +00:00
host = strings . Replace ( regexp . QuoteMeta ( host ) , ` \*\. ` , ` [a-zA-Z0-9-]+\. ` , 1 )
rules = append ( rules , fmt . Sprintf ( "HostSNIRegexp(`^%s$`)" , host ) )
2024-03-25 13:38:04 +00:00
}
2024-05-01 04:38:03 +00:00
if len ( hostnames ) == 0 || len ( rules ) == 0 {
return "HostSNI(`*`)" , nil
2024-03-25 13:38:04 +00:00
}
2024-05-01 04:38:03 +00:00
return strings . Join ( rules , " || " ) , nil
2024-03-25 13:38:04 +00:00
}
2024-05-01 04:38:03 +00:00
func extractRule ( routeRule gatev1 . HTTPRouteRule , hostRule string ) ( string , error ) {
var rule string
var matchesRules [ ] string
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
for _ , match := range routeRule . Matches {
if ( match . Path == nil || match . Path . Type == nil ) && match . Headers == nil {
2021-11-09 10:34:06 +00:00
continue
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
var matchRules [ ] string
if match . Path != nil && match . Path . Type != nil && match . Path . Value != nil {
switch * match . Path . Type {
case gatev1 . PathMatchExact :
matchRules = append ( matchRules , fmt . Sprintf ( "Path(`%s`)" , * match . Path . Value ) )
case gatev1 . PathMatchPathPrefix :
matchRules = append ( matchRules , buildPathMatchPathPrefixRule ( * match . Path . Value ) )
default :
return "" , fmt . Errorf ( "unsupported path match type %s" , * match . Path . Type )
}
2021-11-09 10:34:06 +00:00
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
headerRules , err := extractHeaderRules ( match . Headers )
if err != nil {
return "" , err
}
matchRules = append ( matchRules , headerRules ... )
matchesRules = append ( matchesRules , strings . Join ( matchRules , " && " ) )
}
// If no matches are specified, the default is a prefix
// path match on "/", which has the effect of matching every
// HTTP request.
if len ( routeRule . Matches ) == 0 {
matchesRules = append ( matchesRules , "PathPrefix(`/`)" )
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
if hostRule != "" {
if len ( matchesRules ) == 0 {
return hostRule , nil
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
rule += hostRule + " && "
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
if len ( matchesRules ) == 1 {
return rule + matchesRules [ 0 ] , nil
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
if len ( rule ) == 0 {
return strings . Join ( matchesRules , " || " ) , nil
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
return rule + "(" + strings . Join ( matchesRules , " || " ) + ")" , nil
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
func extractHeaderRules ( headers [ ] gatev1 . HTTPHeaderMatch ) ( [ ] string , error ) {
var headerRules [ ] string
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
// TODO handle other headers types
for _ , header := range headers {
if header . Type == nil {
// Should never happen due to kubernetes validation.
2021-05-20 09:50:12 +00:00
continue
}
2024-05-01 04:38:03 +00:00
switch * header . Type {
case gatev1 . HeaderMatchExact :
headerRules = append ( headerRules , fmt . Sprintf ( "Header(`%s`,`%s`)" , header . Name , header . Value ) )
default :
return nil , fmt . Errorf ( "unsupported header match type %s" , * header . Type )
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
return headerRules , nil
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
func buildPathMatchPathPrefixRule ( path string ) string {
if path == "/" {
return "PathPrefix(`/`)"
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
path = strings . TrimSuffix ( path , "/" )
return fmt . Sprintf ( "(Path(`%[1]s`) || PathPrefix(`%[1]s/`))" , path )
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
func makeRouterKey ( rule , name string ) ( string , error ) {
h := sha256 . New ( )
if _ , err := h . Write ( [ ] byte ( rule ) ) ; err != nil {
return "" , err
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
key := fmt . Sprintf ( "%s-%.10x" , name , h . Sum ( nil ) )
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
return key , nil
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
func makeID ( namespace , name string ) string {
if namespace == "" {
return name
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
return namespace + "-" + name
}
2021-05-20 09:50:12 +00:00
2024-05-01 04:38:03 +00:00
func getTLS ( k8sClient Client , secretName gatev1 . ObjectName , namespace string ) ( * tls . CertAndStores , error ) {
secret , exists , err := k8sClient . GetSecret ( namespace , string ( secretName ) )
if err != nil {
return nil , fmt . Errorf ( "failed to fetch secret %s/%s: %w" , namespace , secretName , err )
}
if ! exists {
return nil , fmt . Errorf ( "secret %s/%s does not exist" , namespace , secretName )
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
cert , key , err := getCertificateBlocks ( secret , namespace , string ( secretName ) )
if err != nil {
return nil , err
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
return & tls . CertAndStores {
Certificate : tls . Certificate {
CertFile : types . FileOrContent ( cert ) ,
KeyFile : types . FileOrContent ( key ) ,
} ,
} , nil
2021-05-20 09:50:12 +00:00
}
2024-05-01 04:38:03 +00:00
func getTLSConfig ( tlsConfigs map [ string ] * tls . CertAndStores ) [ ] * tls . CertAndStores {
var secretNames [ ] string
for secretName := range tlsConfigs {
secretNames = append ( secretNames , secretName )
2022-12-22 14:02:05 +00:00
}
2024-05-01 04:38:03 +00:00
sort . Strings ( secretNames )
2022-12-22 14:02:05 +00:00
2024-05-01 04:38:03 +00:00
var configs [ ] * tls . CertAndStores
for _ , secretName := range secretNames {
configs = append ( configs , tlsConfigs [ secretName ] )
}
2024-03-25 13:38:04 +00:00
2024-05-01 04:38:03 +00:00
return configs
}
2024-03-25 13:38:04 +00:00
2024-05-01 04:38:03 +00:00
func getCertificateBlocks ( secret * corev1 . Secret , namespace , secretName string ) ( string , string , error ) {
var missingEntries [ ] string
2024-04-05 15:18:03 +00:00
2024-05-01 04:38:03 +00:00
tlsCrtData , tlsCrtExists := secret . Data [ "tls.crt" ]
if ! tlsCrtExists {
missingEntries = append ( missingEntries , "tls.crt" )
}
2024-04-05 15:18:03 +00:00
2024-05-01 04:38:03 +00:00
tlsKeyData , tlsKeyExists := secret . Data [ "tls.key" ]
if ! tlsKeyExists {
missingEntries = append ( missingEntries , "tls.key" )
2022-12-22 14:02:05 +00:00
}
2024-05-01 04:38:03 +00:00
if len ( missingEntries ) > 0 {
return "" , "" , fmt . Errorf ( "secret %s/%s is missing the following TLS data entries: %s" ,
namespace , secretName , strings . Join ( missingEntries , ", " ) )
}
2022-12-22 14:02:05 +00:00
2024-05-01 04:38:03 +00:00
cert := string ( tlsCrtData )
if cert == "" {
missingEntries = append ( missingEntries , "tls.crt" )
2024-03-25 13:38:04 +00:00
}
2024-05-01 04:38:03 +00:00
key := string ( tlsKeyData )
if key == "" {
missingEntries = append ( missingEntries , "tls.key" )
2024-03-25 13:38:04 +00:00
}
2024-05-01 04:38:03 +00:00
if len ( missingEntries ) > 0 {
return "" , "" , fmt . Errorf ( "secret %s/%s contains the following empty TLS data entries: %s" ,
namespace , secretName , strings . Join ( missingEntries , ", " ) )
2024-03-25 13:38:04 +00:00
}
2024-05-01 04:38:03 +00:00
return cert , key , nil
2024-03-25 13:38:04 +00:00
}
2024-04-05 15:18:03 +00:00
// createRequestHeaderModifier does not enforce/check the configuration,
// as the spec indicates that either the webhook or CEL (since v1.0 GA Release) should enforce that.
func createRequestHeaderModifier ( filter * gatev1 . HTTPHeaderFilter ) * dynamic . Middleware {
sets := map [ string ] string { }
for _ , header := range filter . Set {
sets [ string ( header . Name ) ] = header . Value
}
adds := map [ string ] string { }
for _ , header := range filter . Add {
adds [ string ( header . Name ) ] = header . Value
}
return & dynamic . Middleware {
RequestHeaderModifier : & dynamic . RequestHeaderModifier {
Set : sets ,
Add : adds ,
Remove : filter . Remove ,
} ,
}
}
2024-01-09 09:28:05 +00:00
func createRedirectRegexMiddleware ( scheme string , filter * gatev1 . HTTPRequestRedirectFilter ) ( * dynamic . Middleware , error ) {
2022-12-22 14:02:05 +00:00
// Use the HTTPRequestRedirectFilter scheme if defined.
filterScheme := scheme
if filter . Scheme != nil {
filterScheme = * filter . Scheme
}
if filterScheme != "http" && filterScheme != "https" {
return nil , fmt . Errorf ( "invalid scheme %s" , filterScheme )
}
statusCode := http . StatusFound
if filter . StatusCode != nil {
statusCode = * filter . StatusCode
}
if statusCode != http . StatusMovedPermanently && statusCode != http . StatusFound {
return nil , fmt . Errorf ( "invalid status code %d" , statusCode )
}
port := "${port}"
if filter . Port != nil {
port = fmt . Sprintf ( ":%d" , * filter . Port )
}
hostname := "${hostname}"
if filter . Hostname != nil && * filter . Hostname != "" {
hostname = string ( * filter . Hostname )
}
return & dynamic . Middleware {
RedirectRegex : & dynamic . RedirectRegex {
Regex : ` ^[a-z]+:\/\/(?P<userInfo>.+@)?(?P<hostname>\[[\w:\.]+\]|[\w\._-]+)(?P<port>:\d+)?\/(?P<path>.*) ` ,
Replacement : fmt . Sprintf ( "%s://${userinfo}%s%s/${path}" , filterScheme , hostname , port ) ,
Permanent : statusCode == http . StatusMovedPermanently ,
} ,
} , nil
}
2021-05-20 09:50:12 +00:00
func getProtocol ( portSpec corev1 . ServicePort ) string {
2020-12-15 15:40:05 +00:00
protocol := "http"
2021-05-20 09:50:12 +00:00
if portSpec . Port == 443 || strings . HasPrefix ( portSpec . Name , "https" ) {
2020-12-15 15:40:05 +00:00
protocol = "https"
}
return protocol
}
func throttleEvents ( ctx context . Context , throttleDuration time . Duration , pool * safe . Pool , eventsChan <- chan interface { } ) chan interface { } {
if throttleDuration == 0 {
return nil
}
// Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling)
eventsChanBuffered := make ( chan interface { } , 1 )
// Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent.
// This guarantees that writing to eventChan will never block,
// and that pendingEvent will have something in it if there's been an event since we read from that channel.
pool . GoCtx ( func ( ctxPool context . Context ) {
for {
select {
case <- ctxPool . Done ( ) :
return
case nextEvent := <- eventsChan :
select {
case eventsChanBuffered <- nextEvent :
default :
// We already have an event in eventsChanBuffered, so we'll do a refresh as soon as our throttle allows us to.
// It's fine to drop the event and keep whatever's in the buffer -- we don't do different things for different events
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msgf ( "Dropping event kind %T due to throttling" , nextEvent )
2020-12-15 15:40:05 +00:00
}
}
}
} )
return eventsChanBuffered
}
2021-02-02 18:36:04 +00:00
2024-01-09 09:28:05 +00:00
func isTraefikService ( ref gatev1 . BackendRef ) bool {
2021-11-09 10:34:06 +00:00
if ref . Kind == nil || ref . Group == nil {
return false
}
2023-03-22 15:40:06 +00:00
return * ref . Group == traefikv1alpha1 . GroupName && * ref . Kind == kindTraefikService
2023-03-20 14:38:08 +00:00
}
2024-01-09 09:28:05 +00:00
func isInternalService ( ref gatev1 . BackendRef ) bool {
2023-03-20 14:38:08 +00:00
return isTraefikService ( ref ) && strings . HasSuffix ( string ( ref . Name ) , "@internal" )
2021-02-02 18:36:04 +00:00
}
2022-06-23 09:58:09 +00:00
// makeListenerKey joins protocol, hostname, and port of a listener into a string key.
2024-01-09 09:28:05 +00:00
func makeListenerKey ( l gatev1 . Listener ) string {
var hostname gatev1 . Hostname
2022-06-23 09:58:09 +00:00
if l . Hostname != nil {
hostname = * l . Hostname
}
return fmt . Sprintf ( "%s|%s|%d" , l . Protocol , hostname , l . Port )
}
2024-01-30 15:44:05 +00:00
func filterReferenceGrantsFrom ( referenceGrants [ ] * gatev1beta1 . ReferenceGrant , group , kind , namespace string ) [ ] * gatev1beta1 . ReferenceGrant {
var matchingReferenceGrants [ ] * gatev1beta1 . ReferenceGrant
for _ , referenceGrant := range referenceGrants {
if referenceGrantMatchesFrom ( referenceGrant , group , kind , namespace ) {
matchingReferenceGrants = append ( matchingReferenceGrants , referenceGrant )
}
}
return matchingReferenceGrants
}
func referenceGrantMatchesFrom ( referenceGrant * gatev1beta1 . ReferenceGrant , group , kind , namespace string ) bool {
for _ , from := range referenceGrant . Spec . From {
sanitizedGroup := string ( from . Group )
if sanitizedGroup == "" {
sanitizedGroup = groupCore
}
if string ( from . Namespace ) != namespace || string ( from . Kind ) != kind || sanitizedGroup != group {
continue
}
return true
}
return false
}
func filterReferenceGrantsTo ( referenceGrants [ ] * gatev1beta1 . ReferenceGrant , group , kind , name string ) [ ] * gatev1beta1 . ReferenceGrant {
var matchingReferenceGrants [ ] * gatev1beta1 . ReferenceGrant
for _ , referenceGrant := range referenceGrants {
if referenceGrantMatchesTo ( referenceGrant , group , kind , name ) {
matchingReferenceGrants = append ( matchingReferenceGrants , referenceGrant )
}
}
return matchingReferenceGrants
}
func referenceGrantMatchesTo ( referenceGrant * gatev1beta1 . ReferenceGrant , group , kind , name string ) bool {
for _ , to := range referenceGrant . Spec . To {
sanitizedGroup := string ( to . Group )
if sanitizedGroup == "" {
sanitizedGroup = groupCore
}
if string ( to . Kind ) != kind || sanitizedGroup != group || ( to . Name != nil && string ( * to . Name ) != name ) {
continue
}
return true
}
return false
}
func groupToString ( p * gatev1 . Group ) string {
if p == nil {
return "<nil>"
}
return string ( * p )
}
func kindToString ( p * gatev1 . Kind ) string {
if p == nil {
return "<nil>"
}
return string ( * p )
}
2024-05-01 04:38:03 +00:00
func makeHTTPRouteStatuses ( gwNs string , routeParentStatuses map [ ktypes . NamespacedName ] [ ] gatev1 . RouteParentStatus ) map [ ktypes . NamespacedName ] gatev1 . HTTPRouteStatus {
res := map [ ktypes . NamespacedName ] gatev1 . HTTPRouteStatus { }
for nsName , parentStatuses := range routeParentStatuses {
var httpRouteStatus gatev1 . HTTPRouteStatus
for _ , parentStatus := range parentStatuses {
exists := slices . ContainsFunc ( httpRouteStatus . Parents , func ( status gatev1 . RouteParentStatus ) bool {
return parentRefEquals ( gwNs , parentStatus . ParentRef , status . ParentRef )
} )
if ! exists {
httpRouteStatus . Parents = append ( httpRouteStatus . Parents , parentStatus )
}
}
res [ nsName ] = httpRouteStatus
}
return res
}
func parentRefEquals ( gwNs string , p1 , p2 gatev1 . ParentReference ) bool {
if ! pointerEquals ( p1 . Group , p2 . Group ) {
return false
}
if ! pointerEquals ( p1 . Kind , p2 . Kind ) {
return false
}
if ! pointerEquals ( p1 . SectionName , p2 . SectionName ) {
return false
}
if p1 . Name != p2 . Name {
return false
}
p1Ns := gwNs
if p1 . Namespace != nil {
p1Ns = string ( * p1 . Namespace )
}
p2Ns := gwNs
if p2 . Namespace != nil {
p2Ns = string ( * p2 . Namespace )
}
return p1Ns == p2Ns
}
func pointerEquals [ T comparable ] ( p1 , p2 * T ) bool {
if p1 == nil && p2 == nil {
return true
}
var val1 T
if p1 != nil {
val1 = * p1
}
var val2 T
if p2 != nil {
val2 = * p2
}
return val1 == val2
}
2024-05-22 15:20:04 +00:00
func appendCondition ( conditions [ ] metav1 . Condition , condition metav1 . Condition ) [ ] metav1 . Condition {
res := [ ] metav1 . Condition { condition }
for _ , c := range conditions {
if c . Type != condition . Type {
res = append ( res , c )
}
}
return res
}