Add support for Gateway API BackendTLSPolicies
This commit is contained in:
parent
9750bbc353
commit
1ebd12ff82
9 changed files with 657 additions and 66 deletions
|
@ -16,6 +16,7 @@ rules:
|
||||||
resources:
|
resources:
|
||||||
- services
|
- services
|
||||||
- secrets
|
- secrets
|
||||||
|
- configmaps
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
|
@ -34,9 +35,10 @@ rules:
|
||||||
- gateways
|
- gateways
|
||||||
- httproutes
|
- httproutes
|
||||||
- grpcroutes
|
- grpcroutes
|
||||||
- referencegrants
|
|
||||||
- tcproutes
|
- tcproutes
|
||||||
- tlsroutes
|
- tlsroutes
|
||||||
|
- referencegrants
|
||||||
|
- backendtlspolicies
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
|
@ -50,6 +52,8 @@ rules:
|
||||||
- grpcroutes/status
|
- grpcroutes/status
|
||||||
- tcproutes/status
|
- tcproutes/status
|
||||||
- tlsroutes/status
|
- tlsroutes/status
|
||||||
|
- referencegrants/status
|
||||||
|
- backendtlspolicies/status
|
||||||
verbs:
|
verbs:
|
||||||
- update
|
- update
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ rules:
|
||||||
resources:
|
resources:
|
||||||
- services
|
- services
|
||||||
- secrets
|
- secrets
|
||||||
|
- configmaps
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
|
@ -38,6 +39,7 @@ rules:
|
||||||
- tcproutes
|
- tcproutes
|
||||||
- tlsroutes
|
- tlsroutes
|
||||||
- referencegrants
|
- referencegrants
|
||||||
|
- backendtlspolicies
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
|
@ -52,6 +54,7 @@ rules:
|
||||||
- tcproutes/status
|
- tcproutes/status
|
||||||
- tlsroutes/status
|
- tlsroutes/status
|
||||||
- referencegrants/status
|
- referencegrants/status
|
||||||
|
- backendtlspolicies/status
|
||||||
verbs:
|
verbs:
|
||||||
- update
|
- update
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3"
|
||||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||||
gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
|
gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
|
||||||
gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"
|
gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"
|
||||||
|
@ -48,30 +49,6 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
||||||
eventHandlerFunc(reh.ev, obj)
|
eventHandlerFunc(reh.ev, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a client for the Provider master.
|
|
||||||
// WatchAll starts the watch of the Provider resources and updates the stores.
|
|
||||||
// The stores can then be accessed via the Get* functions.
|
|
||||||
type Client interface {
|
|
||||||
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
|
||||||
UpdateGatewayStatus(ctx context.Context, gateway ktypes.NamespacedName, status gatev1.GatewayStatus) error
|
|
||||||
UpdateGatewayClassStatus(ctx context.Context, name string, status gatev1.GatewayClassStatus) error
|
|
||||||
UpdateHTTPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error
|
|
||||||
UpdateGRPCRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1.GRPCRouteStatus) error
|
|
||||||
UpdateTCPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TCPRouteStatus) error
|
|
||||||
UpdateTLSRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TLSRouteStatus) error
|
|
||||||
ListGatewayClasses() ([]*gatev1.GatewayClass, error)
|
|
||||||
ListGateways() []*gatev1.Gateway
|
|
||||||
ListHTTPRoutes() ([]*gatev1.HTTPRoute, error)
|
|
||||||
ListGRPCRoutes() ([]*gatev1.GRPCRoute, error)
|
|
||||||
ListTCPRoutes() ([]*gatev1alpha2.TCPRoute, error)
|
|
||||||
ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error)
|
|
||||||
ListNamespaces(selector labels.Selector) ([]string, error)
|
|
||||||
ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error)
|
|
||||||
ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error)
|
|
||||||
GetService(namespace, name string) (*corev1.Service, bool, error)
|
|
||||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientWrapper struct {
|
type clientWrapper struct {
|
||||||
csGateway gateclientset.Interface
|
csGateway gateclientset.Interface
|
||||||
csKube kclientset.Interface
|
csKube kclientset.Interface
|
||||||
|
@ -198,6 +175,16 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ns := range namespaces {
|
for _, ns := range namespaces {
|
||||||
|
factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns))
|
||||||
|
_, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns))
|
factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns))
|
||||||
_, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler)
|
_, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -225,17 +212,15 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
_, err = factoryGateway.Gateway().V1alpha3().BackendTLSPolicies().Informer().AddEventHandler(eventHandler)
|
||||||
|
|
||||||
factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns))
|
|
||||||
_, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
|
_, err = factoryKube.Core().V1().ConfigMaps().Informer().AddEventHandler(eventHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm))
|
factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm))
|
||||||
_, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler)
|
_, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler)
|
||||||
|
@ -367,8 +352,6 @@ func (c *clientWrapper) ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error) {
|
||||||
|
|
||||||
func (c *clientWrapper) ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
|
func (c *clientWrapper) ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
|
||||||
if !c.isWatchedNamespace(namespace) {
|
if !c.isWatchedNamespace(namespace) {
|
||||||
log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace)
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("failed to get ReferenceGrants: namespace %s is not within watched namespaces", namespace)
|
return nil, fmt.Errorf("failed to get ReferenceGrants: namespace %s is not within watched namespaces", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +407,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(ctx context.Context, name strin
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update GatewayClass %q status: %w", name, err)
|
return fmt.Errorf("failed to update GatewayClass %s status: %w", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -459,7 +442,7 @@ func (c *clientWrapper) UpdateGatewayStatus(ctx context.Context, gateway ktypes.
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update Gateway %q status: %w", gateway.Name, err)
|
return fmt.Errorf("failed to update Gateway %s/%s status: %w", gateway.Namespace, gateway.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -486,7 +469,6 @@ func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, route ktypes.
|
||||||
for _, parentStatus := range currentRoute.Status.Parents {
|
for _, parentStatus := range currentRoute.Status.Parents {
|
||||||
if parentStatus.ControllerName != controllerName {
|
if parentStatus.ControllerName != controllerName {
|
||||||
parentStatuses = append(parentStatuses, parentStatus)
|
parentStatuses = append(parentStatuses, parentStatus)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +493,7 @@ func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, route ktypes.
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update HTTPRoute %q status: %w", route.Name, err)
|
return fmt.Errorf("failed to update HTTPRoute %s/%s status: %w", route.Namespace, route.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -538,7 +520,6 @@ func (c *clientWrapper) UpdateGRPCRouteStatus(ctx context.Context, route ktypes.
|
||||||
for _, parentStatus := range currentRoute.Status.Parents {
|
for _, parentStatus := range currentRoute.Status.Parents {
|
||||||
if parentStatus.ControllerName != controllerName {
|
if parentStatus.ControllerName != controllerName {
|
||||||
parentStatuses = append(parentStatuses, parentStatus)
|
parentStatuses = append(parentStatuses, parentStatus)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,7 +571,6 @@ func (c *clientWrapper) UpdateTCPRouteStatus(ctx context.Context, route ktypes.N
|
||||||
for _, parentStatus := range currentRoute.Status.Parents {
|
for _, parentStatus := range currentRoute.Status.Parents {
|
||||||
if parentStatus.ControllerName != controllerName {
|
if parentStatus.ControllerName != controllerName {
|
||||||
parentStatuses = append(parentStatuses, parentStatus)
|
parentStatuses = append(parentStatuses, parentStatus)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,7 +595,7 @@ func (c *clientWrapper) UpdateTCPRouteStatus(ctx context.Context, route ktypes.N
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update TCPRoute %q status: %w", route.Name, err)
|
return fmt.Errorf("failed to update TCPRoute %s/%s status: %w", route.Namespace, route.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -642,7 +622,6 @@ func (c *clientWrapper) UpdateTLSRouteStatus(ctx context.Context, route ktypes.N
|
||||||
for _, parentStatus := range currentRoute.Status.Parents {
|
for _, parentStatus := range currentRoute.Status.Parents {
|
||||||
if parentStatus.ControllerName != controllerName {
|
if parentStatus.ControllerName != controllerName {
|
||||||
parentStatuses = append(parentStatuses, parentStatus)
|
parentStatuses = append(parentStatuses, parentStatus)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,7 +646,69 @@ func (c *clientWrapper) UpdateTLSRouteStatus(ctx context.Context, route ktypes.N
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update TLSRoute %q status: %w", route.Name, err)
|
return fmt.Errorf("failed to update TLSRoute %s/%s status: %w", route.Namespace, route.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy ktypes.NamespacedName, status gatev1alpha2.PolicyStatus) error {
|
||||||
|
if !c.isWatchedNamespace(policy.Namespace) {
|
||||||
|
return fmt.Errorf("updating BackendTLSPolicy status %s/%s: namespace is not within watched namespaces", policy.Namespace, policy.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||||
|
currentPolicy, err := c.factoriesGateway[c.lookupNamespace(policy.Namespace)].Gateway().V1alpha3().BackendTLSPolicies().Lister().BackendTLSPolicies(policy.Namespace).Get(policy.Name)
|
||||||
|
if err != nil {
|
||||||
|
// We have to return err itself here (not wrapped inside another error)
|
||||||
|
// so that RetryOnConflict can identify it correctly.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestorStatuses := make([]gatev1alpha2.PolicyAncestorStatus, len(status.Ancestors))
|
||||||
|
copy(ancestorStatuses, status.Ancestors)
|
||||||
|
|
||||||
|
// keep statuses added by other gateway controllers,
|
||||||
|
// and statuses for Traefik gateway controller but not for the same Gateway as the one in parameter (AncestorRef).
|
||||||
|
for _, ancestorStatus := range currentPolicy.Status.Ancestors {
|
||||||
|
if ancestorStatus.ControllerName != controllerName {
|
||||||
|
ancestorStatuses = append(ancestorStatuses, ancestorStatus)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.ContainsFunc(status.Ancestors, func(status gatev1alpha2.PolicyAncestorStatus) bool {
|
||||||
|
return reflect.DeepEqual(ancestorStatus.AncestorRef, status.AncestorRef)
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestorStatuses = append(ancestorStatuses, ancestorStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ancestorStatuses) > 16 {
|
||||||
|
return fmt.Errorf("failed to update BackendTLSPolicy %s/%s status: PolicyAncestor statuses count exceeds 16", policy.Namespace, policy.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not update status when nothing has changed.
|
||||||
|
if policyAncestorStatusesEqual(currentPolicy.Status.Ancestors, ancestorStatuses) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPolicy = currentPolicy.DeepCopy()
|
||||||
|
currentPolicy.Status = gatev1alpha2.PolicyStatus{
|
||||||
|
Ancestors: ancestorStatuses,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = c.csGateway.GatewayV1alpha3().BackendTLSPolicies(policy.Namespace).UpdateStatus(ctx, currentPolicy, metav1.UpdateOptions{}); err != nil {
|
||||||
|
// We have to return err itself here (not wrapped inside another error)
|
||||||
|
// so that RetryOnConflict can identify it correctly.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update BackendTLSPolicy %s/%s status: %w", policy.Namespace, policy.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -701,6 +742,32 @@ func (c *clientWrapper) ListEndpointSlicesForService(namespace, serviceName stri
|
||||||
return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector)
|
return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListBackendTLSPoliciesForService returns the BackendTLSPolicy for the given service name in the given namespace.
|
||||||
|
func (c *clientWrapper) ListBackendTLSPoliciesForService(namespace, serviceName string) ([]*gatev1alpha3.BackendTLSPolicy, error) {
|
||||||
|
if !c.isWatchedNamespace(namespace) {
|
||||||
|
return nil, fmt.Errorf("failed to get BackendTLSPolicies for service %s/%s: namespace is not within watched namespaces", namespace, serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
policies, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha3().BackendTLSPolicies().Lister().BackendTLSPolicies(namespace).List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list BackendTLSPolicies in namespace %s", namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
var servicePolicies []*gatev1alpha3.BackendTLSPolicy
|
||||||
|
for _, policy := range policies {
|
||||||
|
for _, ref := range policy.Spec.TargetRefs {
|
||||||
|
// The policy does not target the service.
|
||||||
|
if ref.Group != groupCore || ref.Kind != kindService || string(ref.Name) != serviceName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
servicePolicies = append(servicePolicies, policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servicePolicies, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetSecret returns the named secret from the given namespace.
|
// GetSecret returns the named secret from the given namespace.
|
||||||
func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool, error) {
|
func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool, error) {
|
||||||
if !c.isWatchedNamespace(namespace) {
|
if !c.isWatchedNamespace(namespace) {
|
||||||
|
@ -713,6 +780,18 @@ func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool,
|
||||||
return secret, exist, err
|
return secret, exist, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfigMap returns the named configMap from the given namespace.
|
||||||
|
func (c *clientWrapper) GetConfigMap(namespace, name string) (*corev1.ConfigMap, bool, error) {
|
||||||
|
if !c.isWatchedNamespace(namespace) {
|
||||||
|
return nil, false, fmt.Errorf("failed to get configMap %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
configMap, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().ConfigMaps().Lister().ConfigMaps(namespace).Get(name)
|
||||||
|
exist, err := translateNotFoundError(err)
|
||||||
|
|
||||||
|
return configMap, exist, err
|
||||||
|
}
|
||||||
|
|
||||||
// lookupNamespace returns the lookup namespace key for the given namespace.
|
// lookupNamespace returns the lookup namespace key for the given namespace.
|
||||||
// When listening on all namespaces, it returns the client-go identifier ("")
|
// When listening on all namespaces, it returns the client-go identifier ("")
|
||||||
// for all-namespaces. Otherwise, it returns the given namespace.
|
// for all-namespaces. Otherwise, it returns the given namespace.
|
||||||
|
@ -761,6 +840,36 @@ func gatewayStatusEqual(statusA, statusB gatev1.GatewayStatus) bool {
|
||||||
conditionsEqual(statusA.Conditions, statusB.Conditions)
|
conditionsEqual(statusA.Conditions, statusB.Conditions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func policyAncestorStatusesEqual(policyAncestorStatusesA, policyAncestorStatusesB []gatev1alpha2.PolicyAncestorStatus) bool {
|
||||||
|
if len(policyAncestorStatusesA) != len(policyAncestorStatusesB) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sA := range policyAncestorStatusesA {
|
||||||
|
if !slices.ContainsFunc(policyAncestorStatusesB, func(sB gatev1alpha2.PolicyAncestorStatus) bool {
|
||||||
|
return policyAncestorStatusEqual(sB, sA)
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sB := range policyAncestorStatusesB {
|
||||||
|
if !slices.ContainsFunc(policyAncestorStatusesA, func(sA gatev1alpha2.PolicyAncestorStatus) bool {
|
||||||
|
return policyAncestorStatusEqual(sA, sB)
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func policyAncestorStatusEqual(sA, sB gatev1alpha2.PolicyAncestorStatus) bool {
|
||||||
|
return sA.ControllerName == sB.ControllerName &&
|
||||||
|
reflect.DeepEqual(sA.AncestorRef, sB.AncestorRef) &&
|
||||||
|
conditionsEqual(sA.Conditions, sB.Conditions)
|
||||||
|
}
|
||||||
|
|
||||||
func routeParentStatusesEqual(routeParentStatusesA, routeParentStatusesB []gatev1alpha2.RouteParentStatus) bool {
|
func routeParentStatusesEqual(routeParentStatusesA, routeParentStatusesB []gatev1alpha2.RouteParentStatus) bool {
|
||||||
if len(routeParentStatusesA) != len(routeParentStatusesB) {
|
if len(routeParentStatusesA) != len(routeParentStatusesB) {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
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: http
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
allowedRoutes:
|
||||||
|
kinds:
|
||||||
|
- kind: HTTPRoute
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: HTTPRoute
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: http-app-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
hostnames:
|
||||||
|
- "foo.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: Exact
|
||||||
|
value: /bar
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: BackendTLSPolicy
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1alpha3
|
||||||
|
metadata:
|
||||||
|
name: policy-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
targetRefs:
|
||||||
|
- group: core
|
||||||
|
kind: Service
|
||||||
|
name: whoami
|
||||||
|
validation:
|
||||||
|
hostname: whoami
|
||||||
|
caCertificateRefs:
|
||||||
|
- group: core
|
||||||
|
kind: ConfigMap
|
||||||
|
name: ca-file
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: ca-file
|
||||||
|
namespace: default
|
||||||
|
data:
|
||||||
|
ca.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0="
|
|
@ -0,0 +1,66 @@
|
||||||
|
---
|
||||||
|
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: http
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
allowedRoutes:
|
||||||
|
kinds:
|
||||||
|
- kind: HTTPRoute
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: HTTPRoute
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: http-app-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
hostnames:
|
||||||
|
- "foo.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: Exact
|
||||||
|
value: /bar
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: BackendTLSPolicy
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1alpha3
|
||||||
|
metadata:
|
||||||
|
name: policy-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
targetRefs:
|
||||||
|
- group: core
|
||||||
|
kind: Service
|
||||||
|
name: whoami
|
||||||
|
validation:
|
||||||
|
hostname: whoami
|
||||||
|
wellKnownCACertificates: System
|
|
@ -13,11 +13,14 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider"
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
ktypes "k8s.io/apimachinery/pkg/types"
|
ktypes "k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/utils/ptr"
|
"k8s.io/utils/ptr"
|
||||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
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) {
|
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:
|
default:
|
||||||
var serviceCondition *metav1.Condition
|
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 {
|
if serviceCondition != nil {
|
||||||
condition = *serviceCondition
|
condition = *serviceCondition
|
||||||
}
|
}
|
||||||
|
@ -173,7 +176,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
|
||||||
return conf, condition
|
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"
|
name := routeKey + "-wrr"
|
||||||
if _, ok := conf.HTTP.Services[name]; ok {
|
if _, ok := conf.HTTP.Services[name]; ok {
|
||||||
return name, nil
|
return name, nil
|
||||||
|
@ -182,7 +185,7 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string,
|
||||||
var wrr dynamic.WeightedRoundRobin
|
var wrr dynamic.WeightedRoundRobin
|
||||||
var condition *metav1.Condition
|
var condition *metav1.Condition
|
||||||
for _, backendRef := range routeRule.BackendRefs {
|
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)))
|
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||||
if errCondition != nil {
|
if errCondition != nil {
|
||||||
condition = errCondition
|
condition = errCondition
|
||||||
|
@ -194,10 +197,6 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc != nil {
|
|
||||||
conf.HTTP.Services[svcName] = svc
|
|
||||||
}
|
|
||||||
|
|
||||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||||
Name: svcName,
|
Name: svcName,
|
||||||
Weight: weight,
|
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.
|
// 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).
|
// 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)
|
kind := ptr.Deref(backendRef.Kind, kindService)
|
||||||
|
|
||||||
group := groupCore
|
group := groupCore
|
||||||
|
@ -226,7 +225,7 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
|
||||||
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||||
|
|
||||||
if err := p.isReferenceGranted(kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
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),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -239,7 +238,7 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
|
||||||
if group != groupCore || kind != kindService {
|
if group != groupCore || kind != kindService {
|
||||||
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
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))
|
port := ptr.Deref(backendRef.Port, gatev1.PortNumber(0))
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -267,12 +270,97 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa
|
||||||
portStr := strconv.FormatInt(int64(port), 10)
|
portStr := strconv.FormatInt(int64(port), 10)
|
||||||
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||||
|
|
||||||
lb, errCondition := p.loadHTTPServers(namespace, route, backendRef)
|
lb, svcPort, errCondition := p.loadHTTPServers(namespace, route, backendRef)
|
||||||
if errCondition != nil {
|
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) {
|
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)
|
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)
|
backendAddresses, svcPort, err := p.getBackendAddresses(namespace, backendRef.BackendRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &metav1.Condition{
|
return nil, corev1.ServicePort{}, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -380,7 +468,7 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba
|
||||||
|
|
||||||
protocol, err := getProtocol(svcPort)
|
protocol, err := getProtocol(svcPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &metav1.Condition{
|
return nil, corev1.ServicePort{}, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
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)))),
|
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) {
|
func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
|
||||||
|
@ -715,4 +836,11 @@ func mergeHTTPConfiguration(from, to *dynamic.Configuration) {
|
||||||
for serviceName, service := range from.HTTP.Services {
|
for serviceName, service := range from.HTTP.Services {
|
||||||
to.HTTP.Services[serviceName] = service
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"k8s.io/utils/ptr"
|
"k8s.io/utils/ptr"
|
||||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3"
|
||||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||||
gatefake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
|
gatefake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
|
||||||
)
|
)
|
||||||
|
@ -43,6 +44,9 @@ func init() {
|
||||||
if err := gatev1alpha2.AddToScheme(kscheme.Scheme); err != nil {
|
if err := gatev1alpha2.AddToScheme(kscheme.Scheme); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if err := gatev1alpha3.AddToScheme(kscheme.Scheme); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTTPRoutes(t *testing.T) {
|
func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
|
@ -2132,6 +2136,204 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute and BackendTLSPolicy, experimental channel disabled",
|
||||||
|
paths: []string{"services.yml", "httproute/with_backend_tls_policy.yml"},
|
||||||
|
entryPoints: map[string]Entrypoint{"web": {
|
||||||
|
Address: ":80",
|
||||||
|
}},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
Priority: 100008,
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "default-whoami-80",
|
||||||
|
Weight: ptr.To(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute and BackendTLSPolicy with CA certificate, experimental channel enabled",
|
||||||
|
paths: []string{"services.yml", "httproute/with_backend_tls_policy.yml"},
|
||||||
|
entryPoints: map[string]Entrypoint{"web": {
|
||||||
|
Address: ":80",
|
||||||
|
}},
|
||||||
|
experimentalChannel: true,
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
Priority: 100008,
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "default-whoami-80",
|
||||||
|
Weight: ptr.To(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
ServersTransport: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||||
|
"default-whoami-80": {
|
||||||
|
ServerName: "whoami",
|
||||||
|
RootCAs: []types.FileOrContent{
|
||||||
|
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute and BackendTLSPolicy with System CA, experimental channel enabled",
|
||||||
|
paths: []string{"services.yml", "httproute/with_backend_tls_policy_system.yml"},
|
||||||
|
entryPoints: map[string]Entrypoint{"web": {
|
||||||
|
Address: ":80",
|
||||||
|
}},
|
||||||
|
experimentalChannel: true,
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
Priority: 100008,
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "default-whoami-80",
|
||||||
|
Weight: ptr.To(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
ServersTransport: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||||
|
"default-whoami-80": {
|
||||||
|
ServerName: "whoami",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@ -2148,6 +2350,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||||
|
|
||||||
client := newClientImpl(kubeClient, gwClient)
|
client := newClientImpl(kubeClient, gwClient)
|
||||||
|
client.experimentalChannel = test.experimentalChannel
|
||||||
|
|
||||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
// MustParseYaml parses a YAML to objects.
|
// MustParseYaml parses a YAML to objects.
|
||||||
func MustParseYaml(content []byte) []runtime.Object {
|
func MustParseYaml(content []byte) []runtime.Object {
|
||||||
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|EndpointSlice|Node|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`)
|
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|EndpointSlice|Node|Service|ConfigMap|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant|BackendTLSPolicy)$`)
|
||||||
|
|
||||||
files := strings.Split(string(content), "---\n")
|
files := strings.Split(string(content), "---\n")
|
||||||
retVal := make([]runtime.Object, 0, len(files))
|
retVal := make([]runtime.Object, 0, len(files))
|
||||||
|
|
Loading…
Reference in a new issue