mirror of
https://github.com/traefik/traefik.git
synced 2026-06-17 19:09:29 +03:00
Support BackendTLSPolicy for TLSRoute
This commit is contained in:
@@ -64,6 +64,12 @@ spec:
|
||||
- group: core
|
||||
kind: ConfigMap
|
||||
name: ca-file-2
|
||||
- group: ""
|
||||
kind: Secret
|
||||
name: ca-file
|
||||
- group: core
|
||||
kind: Secret
|
||||
name: ca-file-2
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
@@ -82,3 +88,21 @@ metadata:
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: "CA2"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ca-file
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: Q0ExLXNlY3JldA==
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ca-file-2
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: Q0EyLXNlY3JldA==
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t
|
||||
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9001
|
||||
hostname: foo.com
|
||||
tls:
|
||||
mode: Terminate # Default mode
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: supersecret
|
||||
group: ""
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: TLSRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
kind: TLSRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tls-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
||||
---
|
||||
kind: BackendTLSPolicy
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: policy-1
|
||||
namespace: default
|
||||
spec:
|
||||
targetRefs:
|
||||
- group: ""
|
||||
kind: Service
|
||||
name: whoami
|
||||
validation:
|
||||
hostname: whoami
|
||||
caCertificateRefs:
|
||||
- group: ""
|
||||
kind: ConfigMap
|
||||
name: ca-file
|
||||
- group: core
|
||||
kind: ConfigMap
|
||||
name: ca-file-2
|
||||
- group: ""
|
||||
kind: Secret
|
||||
name: ca-file
|
||||
- group: core
|
||||
kind: Secret
|
||||
name: ca-file-2
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ca-file
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: "CA1"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ca-file-2
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: "CA2"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ca-file
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: Q0ExLXNlY3JldA==
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ca-file-2
|
||||
namespace: default
|
||||
data:
|
||||
ca.crt: Q0EyLXNlY3JldA==
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t
|
||||
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9001
|
||||
hostname: foo.com
|
||||
tls:
|
||||
mode: Terminate # Default mode
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: supersecret
|
||||
group: ""
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: TLSRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
kind: TLSRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tls-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
|
||||
---
|
||||
kind: BackendTLSPolicy
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: policy-1
|
||||
namespace: default
|
||||
spec:
|
||||
targetRefs:
|
||||
- group: core
|
||||
kind: Service
|
||||
name: whoami
|
||||
validation:
|
||||
hostname: whoami
|
||||
wellKnownCACertificates: System
|
||||
@@ -606,7 +606,7 @@ func (p *Provider) loadServersTransport(namespace string, policy *gatev1.Backend
|
||||
}
|
||||
|
||||
for _, caCertRef := range policy.Spec.Validation.CACertificateRefs {
|
||||
if (caCertRef.Group != "" && caCertRef.Group != groupCore) || (caCertRef.Kind != "ConfigMap" && caCertRef.Kind != "Secret") {
|
||||
if (caCertRef.Group != "" && caCertRef.Group != groupCore) || (caCertRef.Kind != kindConfigMap && caCertRef.Kind != kindSecret) {
|
||||
return nil, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
@@ -619,7 +619,7 @@ func (p *Provider) loadServersTransport(namespace string, policy *gatev1.Backend
|
||||
|
||||
var caCRT string
|
||||
switch caCertRef.Kind {
|
||||
case "ConfigMap":
|
||||
case kindConfigMap:
|
||||
configmap, err := p.client.GetConfigMap(namespace, string(caCertRef.Name))
|
||||
if err != nil {
|
||||
return nil, metav1.Condition{
|
||||
@@ -632,7 +632,7 @@ func (p *Provider) loadServersTransport(namespace string, policy *gatev1.Backend
|
||||
}
|
||||
}
|
||||
caCRT = configmap.Data["ca.crt"]
|
||||
case "Secret":
|
||||
case kindSecret:
|
||||
secret, err := p.client.GetSecret(namespace, string(caCertRef.Name))
|
||||
if err != nil {
|
||||
return nil, metav1.Condition{
|
||||
|
||||
@@ -49,6 +49,8 @@ const (
|
||||
kindTCPRoute = "TCPRoute"
|
||||
kindTLSRoute = "TLSRoute"
|
||||
kindService = "Service"
|
||||
kindConfigMap = "ConfigMap"
|
||||
kindSecret = "Secret"
|
||||
|
||||
appProtocolHTTP = "http"
|
||||
appProtocolHTTPS = "https"
|
||||
@@ -591,7 +593,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, gateway *gatev1.Gat
|
||||
var errCertConditions []metav1.Condition
|
||||
listenerTLSCerts := make(map[string]*tls.CertAndStores)
|
||||
for _, certificateRef := range listener.TLS.CertificateRefs {
|
||||
if certificateRef.Kind == nil || *certificateRef.Kind != "Secret" || certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != groupCore) {
|
||||
if certificateRef.Kind == nil || *certificateRef.Kind != kindSecret || certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != groupCore) {
|
||||
errCertConditions = append(errCertConditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
@@ -604,7 +606,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, gateway *gatev1.Gat
|
||||
}
|
||||
|
||||
certificateNamespace := string(ptr.Deref(certificateRef.Namespace, gatev1.Namespace(gateway.Namespace)))
|
||||
if err := p.isReferenceGranted(kindGateway, gateway.Namespace, groupCore, "Secret", string(certificateRef.Name), certificateNamespace); err != nil {
|
||||
if err := p.isReferenceGranted(kindGateway, gateway.Namespace, groupCore, kindSecret, string(certificateRef.Name), certificateNamespace); err != nil {
|
||||
errCertConditions = append(errCertConditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
||||
@@ -3579,6 +3579,8 @@ func TestLoadGRPCRoutes(t *testing.T) {
|
||||
RootCAs: []types.FileOrContent{
|
||||
"CA1",
|
||||
"CA2",
|
||||
"CA1-secret",
|
||||
"CA2-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -6156,6 +6158,178 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Simple TLSRoute and BackendTLSPolicy with CA certificate",
|
||||
paths: []string{"services.yml", "tlsroute/with_backend_tls_policy.yml"},
|
||||
entryPoints: map[string]Entrypoint{"tls": {
|
||||
Address: ":9001",
|
||||
}},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"deny-unknown-host": {
|
||||
Rule: "HostSNI(`*`) && !ALPN(`h2`) && !ALPN(`http/1.1`)",
|
||||
Priority: 1,
|
||||
Service: "deny-unknown-host",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"tlsroute-default-tls-app-1-gw-default-my-gateway-ep-tls-0-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls"},
|
||||
Service: "tlsroute-default-tls-app-1-gw-default-my-gateway-ep-tls-0-e3b0c44298fc1c149afb-wrr",
|
||||
Rule: `HostSNI("foo.com")`,
|
||||
Priority: 7,
|
||||
RuleSyntax: "default",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"deny-unknown-host": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||
},
|
||||
"tlsroute-default-tls-app-1-gw-default-my-gateway-ep-tls-0-e3b0c44298fc1c149afb-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "default-whoami-80",
|
||||
Weight: ptr.To(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
ServersTransport: "default-whoami-80",
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{
|
||||
"default-whoami-80": {
|
||||
TLS: &dynamic.TLSClientConfig{
|
||||
ServerName: "whoami",
|
||||
RootCAs: []types.FileOrContent{
|
||||
"CA1",
|
||||
"CA2",
|
||||
"CA1-secret",
|
||||
"CA2-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Certificates: []*tls.CertAndStores{
|
||||
{
|
||||
Certificate: tls.Certificate{
|
||||
CertFile: types.FileOrContent(listenerCert),
|
||||
KeyFile: types.FileOrContent(listenerKey),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Simple TLSRoute and BackendTLSPolicy with System CA",
|
||||
paths: []string{"services.yml", "tlsroute/with_backend_tls_policy_system.yml"},
|
||||
entryPoints: map[string]Entrypoint{"tls": {
|
||||
Address: ":9001",
|
||||
}},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"deny-unknown-host": {
|
||||
Rule: "HostSNI(`*`) && !ALPN(`h2`) && !ALPN(`http/1.1`)",
|
||||
Priority: 1,
|
||||
Service: "deny-unknown-host",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"tlsroute-default-tls-app-1-gw-default-my-gateway-ep-tls-0-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls"},
|
||||
Service: "tlsroute-default-tls-app-1-gw-default-my-gateway-ep-tls-0-e3b0c44298fc1c149afb-wrr",
|
||||
Rule: `HostSNI("foo.com")`,
|
||||
Priority: 7,
|
||||
RuleSyntax: "default",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"deny-unknown-host": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||
},
|
||||
"tlsroute-default-tls-app-1-gw-default-my-gateway-ep-tls-0-e3b0c44298fc1c149afb-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "default-whoami-80",
|
||||
Weight: ptr.To(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
ServersTransport: "default-whoami-80",
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{
|
||||
"default-whoami-80": {
|
||||
TLS: &dynamic.TLSClientConfig{
|
||||
ServerName: "whoami",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Certificates: []*tls.CertAndStores{
|
||||
{
|
||||
Certificate: tls.Certificate{
|
||||
CertFile: types.FileOrContent(listenerCert),
|
||||
KeyFile: types.FileOrContent(listenerKey),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@@ -347,4 +347,9 @@ func mergeTCPConfiguration(from, to *dynamic.Configuration) {
|
||||
to.TCP.Services = map[string]*dynamic.TCPService{}
|
||||
}
|
||||
maps.Copy(to.TCP.Services, from.TCP.Services)
|
||||
|
||||
if to.TCP.ServersTransports == nil {
|
||||
to.TCP.ServersTransports = map[string]*dynamic.TCPServersTransport{}
|
||||
}
|
||||
maps.Copy(to.TCP.ServersTransports, from.TCP.ServersTransports)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ktypes "k8s.io/apimachinery/pkg/types"
|
||||
@@ -73,7 +75,7 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gateway
|
||||
}
|
||||
}
|
||||
|
||||
routeConf, resolveRefCondition := p.loadTLSRoute(listener, route, hostnames)
|
||||
routeConf, resolveRefCondition := p.loadTLSRoute(ctx, listener, route, hostnames)
|
||||
if accepted && listener.Attached {
|
||||
mergeTCPConfiguration(routeConf, conf)
|
||||
}
|
||||
@@ -110,7 +112,7 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gateway
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1.TLSRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||
func (p *Provider) loadTLSRoute(ctx context.Context, listener gatewayListener, route *gatev1.TLSRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
@@ -171,7 +173,7 @@ func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1.TLSRoute
|
||||
}
|
||||
|
||||
var serviceCondition *metav1.Condition
|
||||
router.Service, serviceCondition = p.loadTLSWRRService(conf, routerName, routeRule.BackendRefs, route)
|
||||
router.Service, serviceCondition = p.loadTLSWRRService(ctx, listener, conf, routerName, routeRule.BackendRefs, route)
|
||||
if serviceCondition != nil {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
@@ -183,7 +185,7 @@ func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1.TLSRoute
|
||||
}
|
||||
|
||||
// loadTLSWRRService is generating a WRR service, even when there is only one target.
|
||||
func (p *Provider) loadTLSWRRService(conf *dynamic.Configuration, routeKey string, backendRefs []gatev1.BackendRef, route *gatev1.TLSRoute) (string, *metav1.Condition) {
|
||||
func (p *Provider) loadTLSWRRService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, routeKey string, backendRefs []gatev1.BackendRef, route *gatev1.TLSRoute) (string, *metav1.Condition) {
|
||||
name := routeKey + "-wrr"
|
||||
if _, ok := conf.TCP.Services[name]; ok {
|
||||
return name, nil
|
||||
@@ -192,7 +194,7 @@ func (p *Provider) loadTLSWRRService(conf *dynamic.Configuration, routeKey strin
|
||||
var wrr dynamic.TCPWeightedRoundRobin
|
||||
var condition *metav1.Condition
|
||||
for _, backendRef := range backendRefs {
|
||||
svcName, svc, errCondition := p.loadTLSService(route, backendRef)
|
||||
svcName, svc, errCondition := p.loadTLSService(ctx, listener, conf, route, backendRef)
|
||||
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||
|
||||
if errCondition != nil {
|
||||
@@ -226,7 +228,7 @@ func (p *Provider) loadTLSWRRService(conf *dynamic.Configuration, routeKey strin
|
||||
return name, condition
|
||||
}
|
||||
|
||||
func (p *Provider) loadTLSService(route *gatev1.TLSRoute, backendRef gatev1.BackendRef) (string, *dynamic.TCPService, *metav1.Condition) {
|
||||
func (p *Provider) loadTLSService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, route *gatev1.TLSRoute, backendRef gatev1.BackendRef) (string, *dynamic.TCPService, *metav1.Condition) {
|
||||
kind := ptr.Deref(backendRef.Kind, kindService)
|
||||
|
||||
group := groupCore
|
||||
@@ -283,18 +285,23 @@ func (p *Provider) loadTLSService(route *gatev1.TLSRoute, backendRef gatev1.Back
|
||||
portStr := strconv.FormatInt(int64(port), 10)
|
||||
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||
|
||||
lb, errCondition := p.loadTLSServers(namespace, route, backendRef)
|
||||
lb, st, errCondition := p.loadTLSServers(ctx, namespace, route, backendRef, listener)
|
||||
if errCondition != nil {
|
||||
return serviceName, nil, errCondition
|
||||
}
|
||||
|
||||
if st != nil {
|
||||
lb.ServersTransport = serviceName
|
||||
conf.TCP.ServersTransports[serviceName] = st
|
||||
}
|
||||
|
||||
return serviceName, &dynamic.TCPService{LoadBalancer: lb}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) loadTLSServers(namespace string, route *gatev1.TLSRoute, backendRef gatev1.BackendRef) (*dynamic.TCPServersLoadBalancer, *metav1.Condition) {
|
||||
func (p *Provider) loadTLSServers(ctx context.Context, namespace string, route *gatev1.TLSRoute, backendRef gatev1.BackendRef, listener gatewayListener) (*dynamic.TCPServersLoadBalancer, *dynamic.TCPServersTransport, *metav1.Condition) {
|
||||
backendAddresses, svcPort, err := p.getBackendAddresses(namespace, backendRef)
|
||||
if err != nil {
|
||||
return nil, &metav1.Condition{
|
||||
return nil, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.GetGeneration(),
|
||||
@@ -304,8 +311,126 @@ func (p *Provider) loadTLSServers(namespace string, route *gatev1.TLSRoute, back
|
||||
}
|
||||
}
|
||||
|
||||
backendTLSPolicies, err := p.client.ListBackendTLSPoliciesForService(namespace, string(backendRef.Name))
|
||||
if err != nil {
|
||||
return nil, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||
Message: fmt.Sprintf("Cannot list BackendTLSPolicies for Service %s/%s: %s", namespace, string(backendRef.Name), err),
|
||||
}
|
||||
}
|
||||
|
||||
// Sort BackendTLSPolicies by creation timestamp, then by name to match the BackendTLSPolicy requirements.
|
||||
slices.SortStableFunc(backendTLSPolicies, func(a, b *gatev1.BackendTLSPolicy) int {
|
||||
cmpTime := a.CreationTimestamp.Time.Compare(b.CreationTimestamp.Time)
|
||||
if cmpTime == 0 {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
}
|
||||
return cmpTime
|
||||
})
|
||||
|
||||
var serversTransport *dynamic.TCPServersTransport
|
||||
for _, policy := range backendTLSPolicies {
|
||||
for _, targetRef := range policy.Spec.TargetRefs {
|
||||
// Skip targetRefs that doesn't match the backendRef,
|
||||
// since a BackendTLSPolicy can select multiple services.
|
||||
if targetRef.Name != backendRef.Name {
|
||||
continue
|
||||
}
|
||||
// Skip the targetRef if the sectionName doesn't match the backendRef port.
|
||||
if targetRef.SectionName != nil && svcPort.Name != string(*targetRef.SectionName) {
|
||||
continue
|
||||
}
|
||||
|
||||
policyAncestorStatus := gatev1.PolicyAncestorStatus{
|
||||
AncestorRef: gatev1.ParentReference{
|
||||
Group: ptr.To(gatev1.Group(groupGateway)),
|
||||
Kind: ptr.To(gatev1.Kind(kindGateway)),
|
||||
Namespace: ptr.To(gatev1.Namespace(namespace)),
|
||||
Name: gatev1.ObjectName(listener.GWName),
|
||||
SectionName: ptr.To(gatev1.SectionName(listener.Name)),
|
||||
},
|
||||
ControllerName: controllerName,
|
||||
}
|
||||
|
||||
// Multiple BackendTLSPolicies can match the same service port, meaning that there is a conflict.
|
||||
if serversTransport != nil {
|
||||
policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions,
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonResolvedRefs),
|
||||
},
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.PolicyConditionAccepted),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.PolicyReasonConflicted),
|
||||
},
|
||||
)
|
||||
|
||||
status := gatev1.PolicyStatus{
|
||||
Ancestors: []gatev1.PolicyAncestorStatus{policyAncestorStatus},
|
||||
}
|
||||
if err := p.client.UpdateBackendTLSPolicyStatus(ctx, ktypes.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, status); err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msg("Unable to update conflicting BackendTLSPolicy status")
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var resolvedRefCondition metav1.Condition
|
||||
serversTransport, resolvedRefCondition = p.loadTCPServersTransport(namespace, policy)
|
||||
|
||||
policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, resolvedRefCondition)
|
||||
if resolvedRefCondition.Status == metav1.ConditionFalse {
|
||||
policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, metav1.Condition{
|
||||
Type: string(gatev1.PolicyConditionAccepted),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonNoValidCACertificate),
|
||||
})
|
||||
} else {
|
||||
policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, metav1.Condition{
|
||||
Type: string(gatev1.PolicyConditionAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.PolicyReasonAccepted),
|
||||
})
|
||||
}
|
||||
|
||||
status := gatev1.PolicyStatus{
|
||||
Ancestors: []gatev1.PolicyAncestorStatus{policyAncestorStatus},
|
||||
}
|
||||
if err := p.client.UpdateBackendTLSPolicyStatus(ctx, ktypes.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, status); err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msg("Unable to update BackendTLSPolicy status")
|
||||
}
|
||||
|
||||
// When something went wrong during the loading of a ServersTransport,
|
||||
// we stop here and return a route condition error.
|
||||
if resolvedRefCondition.Status == metav1.ConditionFalse {
|
||||
return nil, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||
Message: fmt.Sprintf("Cannot apply BackendTLSPolicy for Service %s/%s: %s", namespace, string(backendRef.Name), resolvedRefCondition.Message),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if svcPort.Protocol != corev1.ProtocolTCP {
|
||||
return nil, &metav1.Condition{
|
||||
return nil, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.GetGeneration(),
|
||||
@@ -323,7 +448,89 @@ func (p *Provider) loadTLSServers(namespace string, route *gatev1.TLSRoute, back
|
||||
Address: net.JoinHostPort(ba.IP, strconv.Itoa(int(ba.Port))),
|
||||
})
|
||||
}
|
||||
return lb, nil
|
||||
return lb, serversTransport, nil
|
||||
}
|
||||
|
||||
func (p *Provider) loadTCPServersTransport(namespace string, policy *gatev1.BackendTLSPolicy) (*dynamic.TCPServersTransport, metav1.Condition) {
|
||||
st := &dynamic.TCPServersTransport{
|
||||
TLS: &dynamic.TLSClientConfig{
|
||||
ServerName: string(policy.Spec.Validation.Hostname),
|
||||
},
|
||||
}
|
||||
|
||||
if policy.Spec.Validation.WellKnownCACertificates != nil {
|
||||
return st, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonResolvedRefs),
|
||||
}
|
||||
}
|
||||
|
||||
for _, caCertRef := range policy.Spec.Validation.CACertificateRefs {
|
||||
if (caCertRef.Group != "" && caCertRef.Group != groupCore) || (caCertRef.Kind != kindConfigMap && caCertRef.Kind != kindSecret) {
|
||||
return nil, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonInvalidKind),
|
||||
Message: "Only ConfigMaps and Secrets are supported",
|
||||
}
|
||||
}
|
||||
|
||||
var caCRT string
|
||||
switch caCertRef.Kind {
|
||||
case kindConfigMap:
|
||||
configmap, err := p.client.GetConfigMap(namespace, string(caCertRef.Name))
|
||||
if err != nil {
|
||||
return nil, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef),
|
||||
Message: fmt.Sprintf("getting configmap %s/%s: %s", namespace, string(caCertRef.Name), err),
|
||||
}
|
||||
}
|
||||
caCRT = configmap.Data["ca.crt"]
|
||||
case kindSecret:
|
||||
secret, err := p.client.GetSecret(namespace, string(caCertRef.Name))
|
||||
if err != nil {
|
||||
return nil, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef),
|
||||
Message: fmt.Sprintf("getting secret %s/%s: %s", namespace, string(caCertRef.Name), err),
|
||||
}
|
||||
}
|
||||
caCRT = string(secret.Data["ca.crt"])
|
||||
}
|
||||
|
||||
if caCRT == "" {
|
||||
return nil, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef),
|
||||
Message: fmt.Sprintf("%s %s/%s does not have a ca.crt", caCertRef.Kind, namespace, string(caCertRef.Name)),
|
||||
}
|
||||
}
|
||||
|
||||
st.TLS.RootCAs = append(st.TLS.RootCAs, types.FileOrContent(caCRT))
|
||||
}
|
||||
|
||||
return st, metav1.Condition{
|
||||
Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: policy.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.BackendTLSPolicyReasonResolvedRefs),
|
||||
}
|
||||
}
|
||||
|
||||
func hostSNIRule(hostnames []gatev1.Hostname) (string, int) {
|
||||
|
||||
Reference in New Issue
Block a user