2019-08-26 08:30:05 +00:00
package crd
import (
"context"
"errors"
"fmt"
2020-12-04 19:56:04 +00:00
"net"
"strconv"
2019-08-26 08:30:05 +00:00
"strings"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
2023-04-17 08:56:36 +00:00
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/tls"
2019-08-26 08:30:05 +00:00
corev1 "k8s.io/api/core/v1"
)
func ( p * Provider ) loadIngressRouteTCPConfiguration ( ctx context . Context , client Client , tlsConfigs map [ string ] * tls . CertAndStores ) * dynamic . TCPConfiguration {
conf := & 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 { } ,
2019-08-26 08:30:05 +00:00
}
for _ , ingressRouteTCP := range client . GetIngressRouteTCPs ( ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx ) . With ( ) . Str ( "ingress" , ingressRouteTCP . Name ) . Str ( "namespace" , ingressRouteTCP . Namespace ) . Logger ( )
2019-08-26 08:30:05 +00:00
if ! shouldProcessIngress ( p . IngressClass , ingressRouteTCP . Annotations [ annotationKubernetesIngressClass ] ) {
continue
}
if ingressRouteTCP . Spec . TLS != nil && ! ingressRouteTCP . Spec . TLS . Passthrough {
err := getTLSTCP ( ctx , ingressRouteTCP , client , tlsConfigs )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error configuring TLS" )
2019-08-26 08:30:05 +00:00
}
}
ingressName := ingressRouteTCP . Name
if len ( ingressName ) == 0 {
ingressName = ingressRouteTCP . GenerateName
}
for _ , route := range ingressRouteTCP . Spec . Routes {
if len ( route . Match ) == 0 {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Msg ( "Empty match rule" )
2019-08-26 08:30:05 +00:00
continue
}
2019-09-13 18:00:06 +00:00
key , err := makeServiceKey ( route . Match , ingressName )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2019-09-13 18:00:06 +00:00
continue
}
2021-06-11 13:30:05 +00:00
mds , err := p . makeMiddlewareTCPKeys ( ctx , ingressRouteTCP . Namespace , route . Middlewares )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Failed to create middleware keys" )
2021-06-11 13:30:05 +00:00
continue
}
2019-09-13 18:00:06 +00:00
serviceName := makeID ( ingressRouteTCP . Namespace , key )
2019-08-26 08:30:05 +00:00
for _ , service := range route . Services {
2020-12-10 13:58:04 +00:00
balancerServerTCP , err := p . createLoadBalancerServerTCP ( client , ingressRouteTCP . Namespace , service )
2019-08-26 08:30:05 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) .
Str ( "serviceName" , service . Name ) .
Stringer ( "servicePort" , & service . Port ) .
Err ( err ) .
Msg ( "Cannot create service" )
2019-08-26 08:30:05 +00:00
continue
}
2019-09-13 18:00:06 +00:00
// If there is only one service defined, we skip the creation of the load balancer of services,
// i.e. the service on top is directly a load balancer of servers.
if len ( route . Services ) == 1 {
conf . Services [ serviceName ] = balancerServerTCP
break
}
2019-08-26 08:30:05 +00:00
2021-01-15 14:54:04 +00:00
serviceKey := fmt . Sprintf ( "%s-%s-%s" , serviceName , service . Name , & service . Port )
2019-09-13 18:00:06 +00:00
conf . Services [ serviceKey ] = balancerServerTCP
srv := dynamic . TCPWRRService { Name : serviceKey }
srv . SetDefaults ( )
if service . Weight != nil {
srv . Weight = service . Weight
}
if conf . Services [ serviceName ] == nil {
conf . Services [ serviceName ] = & dynamic . TCPService { Weighted : & dynamic . TCPWeightedRoundRobin { } }
}
conf . Services [ serviceName ] . Weighted . Services = append ( conf . Services [ serviceName ] . Weighted . Services , srv )
2019-08-26 08:30:05 +00:00
}
2021-09-20 10:54:05 +00:00
r := & dynamic . TCPRouter {
2019-08-26 08:30:05 +00:00
EntryPoints : ingressRouteTCP . Spec . EntryPoints ,
2021-06-11 13:30:05 +00:00
Middlewares : mds ,
2019-08-26 08:30:05 +00:00
Rule : route . Match ,
2022-03-17 17:02:08 +00:00
Priority : route . Priority ,
2024-01-23 10:34:05 +00:00
RuleSyntax : route . Syntax ,
2019-08-26 08:30:05 +00:00
Service : serviceName ,
}
if ingressRouteTCP . Spec . TLS != nil {
2021-09-20 10:54:05 +00:00
r . TLS = & dynamic . RouterTCPTLSConfig {
2019-08-26 08:30:05 +00:00
Passthrough : ingressRouteTCP . Spec . TLS . Passthrough ,
CertResolver : ingressRouteTCP . Spec . TLS . CertResolver ,
2019-09-09 11:52:04 +00:00
Domains : ingressRouteTCP . Spec . TLS . Domains ,
2019-08-26 08:30:05 +00:00
}
2021-09-20 10:54:05 +00:00
if ingressRouteTCP . Spec . TLS . Options != nil && len ( ingressRouteTCP . Spec . TLS . Options . Name ) > 0 {
tlsOptionsName := ingressRouteTCP . Spec . TLS . Options . Name
// Is a Kubernetes CRD reference (i.e. not a cross-provider reference)
ns := ingressRouteTCP . Spec . TLS . Options . Namespace
if ! strings . Contains ( tlsOptionsName , providerNamespaceSeparator ) {
if len ( ns ) == 0 {
ns = ingressRouteTCP . Namespace
}
tlsOptionsName = makeID ( ns , tlsOptionsName )
} else if len ( ns ) > 0 {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) .
Str ( "TLSOption" , ingressRouteTCP . Spec . TLS . Options . Name ) .
Msgf ( "Namespace %q is ignored in cross-provider context" , ns )
2021-09-20 10:54:05 +00:00
}
2019-09-13 18:00:06 +00:00
2021-09-20 10:54:05 +00:00
if ! isNamespaceAllowed ( p . AllowCrossNamespace , ingressRouteTCP . Namespace , ns ) {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Msgf ( "TLSOption %s/%s is not in the IngressRouteTCP namespace %s" ,
2021-09-20 10:54:05 +00:00
ns , ingressRouteTCP . Spec . TLS . Options . Name , ingressRouteTCP . Namespace )
continue
2019-08-26 08:30:05 +00:00
}
2021-09-20 10:54:05 +00:00
r . TLS . Options = tlsOptionsName
}
2019-08-26 08:30:05 +00:00
}
2021-09-20 10:54:05 +00:00
conf . Routers [ serviceName ] = r
2019-08-26 08:30:05 +00:00
}
}
return conf
}
2023-04-17 08:56:36 +00:00
func ( p * Provider ) makeMiddlewareTCPKeys ( ctx context . Context , ingRouteTCPNamespace string , middlewares [ ] traefikv1alpha1 . ObjectReference ) ( [ ] string , error ) {
2021-06-11 13:30:05 +00:00
var mds [ ] string
for _ , mi := range middlewares {
if strings . Contains ( mi . Name , providerNamespaceSeparator ) {
if len ( mi . Namespace ) > 0 {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Warn ( ) .
Str ( logs . MiddlewareName , mi . Name ) .
Msgf ( "Namespace %q is ignored in cross-provider context" , mi . Namespace )
2021-06-11 13:30:05 +00:00
}
mds = append ( mds , mi . Name )
continue
}
ns := ingRouteTCPNamespace
if len ( mi . Namespace ) > 0 {
if ! isNamespaceAllowed ( p . AllowCrossNamespace , ingRouteTCPNamespace , mi . Namespace ) {
return nil , fmt . Errorf ( "middleware %s/%s is not in the IngressRouteTCP namespace %s" , mi . Namespace , mi . Name , ingRouteTCPNamespace )
}
ns = mi . Namespace
}
2021-10-07 13:40:05 +00:00
mds = append ( mds , provider . Normalize ( makeID ( ns , mi . Name ) ) )
2021-06-11 13:30:05 +00:00
}
return mds , nil
}
2023-04-17 08:56:36 +00:00
func ( p * Provider ) createLoadBalancerServerTCP ( client Client , parentNamespace string , service traefikv1alpha1 . ServiceTCP ) ( * dynamic . TCPService , error ) {
2020-12-10 13:58:04 +00:00
ns := parentNamespace
2020-01-14 11:14:05 +00:00
if len ( service . Namespace ) > 0 {
2020-12-10 13:58:04 +00:00
if ! isNamespaceAllowed ( p . AllowCrossNamespace , parentNamespace , service . Namespace ) {
return nil , fmt . Errorf ( "tcp service %s/%s is not in the parent resource namespace %s" , service . Namespace , service . Name , parentNamespace )
}
2020-01-14 11:14:05 +00:00
ns = service . Namespace
}
2021-07-13 10:54:09 +00:00
servers , err := p . loadTCPServers ( client , ns , service )
2019-09-13 18:00:06 +00:00
if err != nil {
return nil , err
}
tcpService := & dynamic . TCPService {
LoadBalancer : & dynamic . TCPServersLoadBalancer {
Servers : servers ,
} ,
}
2020-11-17 12:04:04 +00:00
if service . ProxyProtocol != nil {
tcpService . LoadBalancer . ProxyProtocol = & dynamic . ProxyProtocol { }
tcpService . LoadBalancer . ProxyProtocol . SetDefaults ( )
if service . ProxyProtocol . Version != 0 {
tcpService . LoadBalancer . ProxyProtocol . Version = service . ProxyProtocol . Version
}
}
2024-01-29 16:32:05 +00:00
if service . ServersTransport == "" && service . TerminationDelay != nil {
tcpService . LoadBalancer . TerminationDelay = service . TerminationDelay
}
2022-12-09 08:58:05 +00:00
if service . ServersTransport != "" {
tcpService . LoadBalancer . ServersTransport , err = p . makeTCPServersTransportKey ( parentNamespace , service . ServersTransport )
if err != nil {
return nil , err
}
2019-09-13 18:00:06 +00:00
}
return tcpService , nil
}
2023-04-17 08:56:36 +00:00
func ( p * Provider ) loadTCPServers ( client Client , namespace string , svc traefikv1alpha1 . ServiceTCP ) ( [ ] dynamic . TCPServer , error ) {
2019-08-26 08:30:05 +00:00
service , exists , err := client . GetService ( namespace , svc . Name )
if err != nil {
return nil , err
}
if ! exists {
return nil , errors . New ( "service not found" )
}
2021-07-13 10:54:09 +00:00
if service . Spec . Type == corev1 . ServiceTypeExternalName && ! p . AllowExternalNameServices {
return nil , fmt . Errorf ( "externalName services not allowed: %s/%s" , namespace , svc . Name )
}
2020-03-10 11:46:05 +00:00
svcPort , err := getServicePort ( service , svc . Port )
if err != nil {
return nil , err
2019-08-26 08:30:05 +00:00
}
2023-03-20 15:46:05 +00:00
if svc . NativeLB {
address , err := getNativeServiceAddress ( * service , * svcPort )
if err != nil {
return nil , fmt . Errorf ( "getting native Kubernetes Service address: %w" , err )
}
return [ ] dynamic . TCPServer { { Address : address } } , nil
}
2019-08-26 08:30:05 +00:00
var servers [ ] dynamic . TCPServer
2024-02-27 09:54:04 +00:00
if service . Spec . Type == corev1 . ServiceTypeNodePort && svc . NodePortLB {
nodes , nodesExists , nodesErr := client . GetNodes ( )
if nodesErr != nil {
return nil , nodesErr
}
if ! nodesExists || len ( nodes ) == 0 {
return nil , fmt . Errorf ( "nodes not found for NodePort service %s/%s" , svc . Namespace , svc . Name )
}
for _ , node := range nodes {
for _ , addr := range node . Status . Addresses {
if addr . Type == corev1 . NodeInternalIP {
servers = append ( servers , dynamic . TCPServer {
Address : net . JoinHostPort ( addr . Address , strconv . Itoa ( int ( svcPort . NodePort ) ) ) ,
} )
}
}
}
if len ( servers ) == 0 {
return nil , fmt . Errorf ( "no servers were generated for service %s/%s" , svc . Namespace , svc . Name )
}
return servers , nil
}
2019-08-26 08:30:05 +00:00
if service . Spec . Type == corev1 . ServiceTypeExternalName {
servers = append ( servers , dynamic . TCPServer {
2020-12-04 19:56:04 +00:00
Address : net . JoinHostPort ( service . Spec . ExternalName , strconv . Itoa ( int ( svcPort . Port ) ) ) ,
2019-08-26 08:30:05 +00:00
} )
} else {
endpoints , endpointsExists , endpointsErr := client . GetEndpoints ( namespace , svc . Name )
if endpointsErr != nil {
return nil , endpointsErr
}
if ! endpointsExists {
return nil , errors . New ( "endpoints not found" )
}
2022-03-07 10:08:07 +00:00
if len ( endpoints . Subsets ) == 0 && ! p . AllowEmptyServices {
2019-08-26 08:30:05 +00:00
return nil , errors . New ( "subset not found" )
}
var port int32
for _ , subset := range endpoints . Subsets {
for _ , p := range subset . Ports {
2020-03-10 11:46:05 +00:00
if svcPort . Name == p . Name {
2019-08-26 08:30:05 +00:00
port = p . Port
break
}
}
if port == 0 {
return nil , errors . New ( "cannot define a port" )
}
for _ , addr := range subset . Addresses {
servers = append ( servers , dynamic . TCPServer {
2020-12-04 19:56:04 +00:00
Address : net . JoinHostPort ( addr . IP , strconv . Itoa ( int ( port ) ) ) ,
2019-08-26 08:30:05 +00:00
} )
}
}
}
return servers , nil
}
2022-12-09 08:58:05 +00:00
func ( p * Provider ) makeTCPServersTransportKey ( parentNamespace string , serversTransportName string ) ( string , error ) {
if serversTransportName == "" {
return "" , nil
}
if ! p . AllowCrossNamespace && strings . HasSuffix ( serversTransportName , providerNamespaceSeparator + providerName ) {
// Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd),
// if the provider namespace kubernetescrd is used,
// we don't allow this format to avoid cross namespace references.
return "" , fmt . Errorf ( "invalid reference to serversTransport %s: namespace-name@kubernetescrd format is not allowed when crossnamespace is disallowed" , serversTransportName )
}
if strings . Contains ( serversTransportName , providerNamespaceSeparator ) {
return serversTransportName , nil
}
return provider . Normalize ( makeID ( parentNamespace , serversTransportName ) ) , nil
}
2022-05-19 14:42:09 +00:00
// getTLSTCP mutates tlsConfigs.
2023-04-17 08:56:36 +00:00
func getTLSTCP ( ctx context . Context , ingressRoute * traefikv1alpha1 . IngressRouteTCP , k8sClient Client , tlsConfigs map [ string ] * tls . CertAndStores ) error {
2019-08-26 08:30:05 +00:00
if ingressRoute . Spec . TLS == nil {
return nil
}
if ingressRoute . Spec . TLS . SecretName == "" {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No secret name provided" )
2019-08-26 08:30:05 +00:00
return nil
}
configKey := ingressRoute . Namespace + "/" + ingressRoute . Spec . TLS . SecretName
if _ , tlsExists := tlsConfigs [ configKey ] ; ! tlsExists {
tlsConf , err := getTLS ( k8sClient , ingressRoute . Spec . TLS . SecretName , ingressRoute . Namespace )
if err != nil {
return err
}
tlsConfigs [ configKey ] = tlsConf
}
return nil
}