Add support for Gateway API BackendTLSPolicies

This commit is contained in:
Romain 2024-09-17 10:50:04 +02:00 committed by GitHub
parent 9750bbc353
commit 1ebd12ff82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 657 additions and 66 deletions

View file

@ -13,11 +13,14 @@ import (
"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"
"k8s.io/utils/ptr"
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3"
)
func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
@ -158,7 +161,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
default:
var serviceCondition *metav1.Condition
router.Service, serviceCondition = p.loadWRRService(conf, routerName, routeRule, route)
router.Service, serviceCondition = p.loadWRRService(ctx, listener, conf, routerName, routeRule, route)
if serviceCondition != nil {
condition = *serviceCondition
}
@ -173,7 +176,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
return conf, condition
}
func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) {
func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) {
name := routeKey + "-wrr"
if _, ok := conf.HTTP.Services[name]; ok {
return name, nil
@ -182,7 +185,7 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string,
var wrr dynamic.WeightedRoundRobin
var condition *metav1.Condition
for _, backendRef := range routeRule.BackendRefs {
svcName, svc, errCondition := p.loadService(route, backendRef)
svcName, errCondition := p.loadService(ctx, listener, conf, route, backendRef)
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
if errCondition != nil {
condition = errCondition
@ -194,10 +197,6 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string,
continue
}
if svc != nil {
conf.HTTP.Services[svcName] = svc
}
wrr.Services = append(wrr.Services, dynamic.WRRService{
Name: svcName,
Weight: weight,
@ -210,7 +209,7 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string,
// loadService 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) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
func (p *Provider) loadService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *metav1.Condition) {
kind := ptr.Deref(backendRef.Kind, kindService)
group := groupCore
@ -226,7 +225,7 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
if err := p.isReferenceGranted(kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
return serviceName, nil, &metav1.Condition{
return serviceName, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
@ -239,7 +238,7 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
if group != groupCore || kind != kindService {
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
if err != nil {
return serviceName, nil, &metav1.Condition{
return serviceName, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
@ -249,12 +248,16 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
}
}
return name, service, nil
if service != nil {
conf.HTTP.Services[name] = service
}
return name, nil
}
port := ptr.Deref(backendRef.Port, gatev1.PortNumber(0))
if port == 0 {
return serviceName, nil, &metav1.Condition{
return serviceName, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
@ -267,12 +270,97 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
portStr := strconv.FormatInt(int64(port), 10)
serviceName = provider.Normalize(serviceName + "-" + portStr)
lb, errCondition := p.loadHTTPServers(namespace, route, backendRef)
lb, svcPort, errCondition := p.loadHTTPServers(namespace, route, backendRef)
if errCondition != nil {
return serviceName, nil, errCondition
return serviceName, errCondition
}
return serviceName, &dynamic.Service{LoadBalancer: lb}, nil
if !p.ExperimentalChannel {
conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb}
return serviceName, nil
}
servicePolicies, err := p.client.ListBackendTLSPoliciesForService(namespace, string(backendRef.Name))
if err != nil {
return serviceName, &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),
}
}
var matchedPolicy *gatev1alpha3.BackendTLSPolicy
for _, policy := range servicePolicies {
matched := false
for _, targetRef := range policy.Spec.TargetRefs {
if targetRef.SectionName == nil || svcPort.Name == string(*targetRef.SectionName) {
matchedPolicy = policy
matched = true
break
}
}
// If the policy targets the service, but doesn't match any port.
if !matched {
// update policy status
status := gatev1alpha2.PolicyStatus{
Ancestors: []gatev1alpha2.PolicyAncestorStatus{{
AncestorRef: gatev1alpha2.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,
Conditions: []metav1.Condition{{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.RouteReasonBackendNotFound),
Message: fmt.Sprintf("BackendTLSPolicy has no valid TargetRef for Service %s/%s", namespace, string(backendRef.Name)),
}},
}},
}
if err := p.client.UpdateBackendTLSPolicyStatus(ctx, ktypes.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, status); err != nil {
logger := log.Ctx(ctx).With().
Str("http_route", route.Name).
Str("namespace", route.Namespace).Logger()
logger.Warn().
Err(err).
Msg("Unable to update TLSRoute status")
}
}
}
if matchedPolicy != nil {
st, err := p.loadServersTransport(namespace, *matchedPolicy)
if err != nil {
return serviceName, &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), err),
}
}
if st != nil {
lb.ServersTransport = serviceName
conf.HTTP.ServersTransports[serviceName] = st
}
}
conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb}
return serviceName, nil
}
func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, error) {
@ -365,10 +453,10 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
return filterFunc(string(extensionRef.Name), namespace)
}
func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, *metav1.Condition) {
func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, corev1.ServicePort, *metav1.Condition) {
backendAddresses, svcPort, err := p.getBackendAddresses(namespace, backendRef.BackendRef)
if err != nil {
return nil, &metav1.Condition{
return nil, corev1.ServicePort{}, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
@ -380,7 +468,7 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba
protocol, err := getProtocol(svcPort)
if err != nil {
return nil, &metav1.Condition{
return nil, corev1.ServicePort{}, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
@ -398,7 +486,40 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ba.Address, strconv.Itoa(int(ba.Port)))),
})
}
return lb, nil
return lb, svcPort, nil
}
func (p *Provider) loadServersTransport(namespace string, policy gatev1alpha3.BackendTLSPolicy) (*dynamic.ServersTransport, error) {
st := &dynamic.ServersTransport{
ServerName: string(policy.Spec.Validation.Hostname),
}
if policy.Spec.Validation.WellKnownCACertificates != nil {
return st, nil
}
for _, caCertRef := range policy.Spec.Validation.CACertificateRefs {
if caCertRef.Group != groupCore || caCertRef.Kind != "ConfigMap" {
continue
}
configMap, exists, err := p.client.GetConfigMap(namespace, string(caCertRef.Name))
if err != nil {
return nil, fmt.Errorf("getting configmap: %w", err)
}
if !exists {
return nil, fmt.Errorf("configmap %s/%s not found", namespace, string(caCertRef.Name))
}
caCRT, ok := configMap.Data["ca.crt"]
if !ok {
return nil, fmt.Errorf("configmap %s/%s does not have ca.crt", namespace, string(caCertRef.Name))
}
st.RootCAs = append(st.RootCAs, types.FileOrContent(caCRT))
}
return st, nil
}
func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
@ -715,4 +836,11 @@ func mergeHTTPConfiguration(from, to *dynamic.Configuration) {
for serviceName, service := range from.HTTP.Services {
to.HTTP.Services[serviceName] = service
}
if to.HTTP.ServersTransports == nil {
to.HTTP.ServersTransports = map[string]*dynamic.ServersTransport{}
}
for name, serversTransport := range from.HTTP.ServersTransports {
to.HTTP.ServersTransports[name] = serversTransport
}
}