Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backendTLSPolicy controller #6712

Merged
merged 5 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ linters-settings:
forbidigo:
exclude-godoc-examples: false
forbid:
- 'gatewayv1alpha2|gatewayv1beta1|gatewayv1(# use internal/gatewayapi aliases instead)?'
- 'gatewayv1alpha2|gatewayv1alpha3|gatewayv1beta1|gatewayv1(# use internal/gatewayapi aliases instead)?'
- 'CoreV1\(\)\.Endpoints(# use DiscoveryV1 EndpointSlices API instead)?'
- 'corev1\.Endpoint(# use DiscoveryV1 EndpointSlices API instead)?'
- '(gokong|kong)\.NewClient(# use adminapi.NewKongAPIClient instead )?'
Expand Down Expand Up @@ -216,12 +216,12 @@ issues:
# gatewayv1beta1 and gatewayv1 as their values are different between versions, and we can't alias them in internal/gatewayapi/aliases.go.
- linters:
- forbidigo
text: "use of `(gatewayv1alpha2|gatewayv1beta1|gatewayv1)\\.(SchemeGroupVersion|GroupVersion|GroupName|AddToScheme|Install)"
text: "use of `(gatewayv1alpha2|gatewayv1alpha3|gatewayv1beta1|gatewayv1)\\.(SchemeGroupVersion|GroupVersion|GroupName|AddToScheme|Install)"
# Allow gatewayv1alpha2, gatewayv1beta1 and gatewayv1 types references in internal/gatewayapi/aliases.go as that
# should be the only place where we use them.
- linters:
- forbidigo
text: "use of `(gatewayv1alpha2|gatewayv1beta1|gatewayv1)"
text: "use of `(gatewayv1alpha2|gatewayv1alpha3|gatewayv1beta1|gatewayv1)"
path: (internal/gatewayapi/aliases.go|pkg/apis/.*/.*\.go)
# Allow explicit zero value declarations for integer types
- linters:
Expand Down
11 changes: 11 additions & 0 deletions config/rbac/gateway/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@ rules:
- ""
resources:
- namespaces
- services
verbs:
- get
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- backendtlspolicies
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
Expand Down
4 changes: 4 additions & 0 deletions hack/generators/cache-stores/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ var supportedTypes = []cacheStoreSupportedType{
Type: "Gateway",
Package: "gatewayapi",
},
{
Type: "BackendTLSPolicy",
Package: "gatewayapi",
},
// Kong types
{
Type: "KongPlugin",
Expand Down
297 changes: 297 additions & 0 deletions internal/controllers/gateway/backendtlspolicy_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package gateway

import (
"context"
"fmt"
"reflect"
"time"

"github.com/go-logr/logr"
"github.com/samber/lo"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
k8stypes "k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status"
)

// -----------------------------------------------------------------------------
// BackendTLSPolicy Controller - BackendTLSPolicyReconciler
// -----------------------------------------------------------------------------

// BackendTLSPolicyReconciler reconciles a BackendTLSPolicy object.
type BackendTLSPolicyReconciler struct {
client.Client

Log logr.Logger
Scheme *runtime.Scheme
DataplaneClient controllers.DataPlane
CacheSyncTimeout time.Duration
StatusQueue *status.Queue
}

// SetupWithManager sets up the controller with the Manager.
func (r *BackendTLSPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := setupBackendTLSPolicyIndices(mgr); err != nil {
return fmt.Errorf("failed to setup indexers: %w", err)
}
return ctrl.NewControllerManagedBy(mgr).
Named("backendtlspolicy-controller").
WithOptions(controller.Options{
LogConstructor: func(_ *reconcile.Request) logr.Logger {
return r.Log
},
CacheSyncTimeout: r.CacheSyncTimeout,
}).
For(&gatewayapi.BackendTLSPolicy{}).
Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(r.listBackendTLSPoliciesForServices)).
Watches(&gatewayapi.HTTPRoute{}, handler.EnqueueRequestsFromMapFunc(r.listBackendTLSPoliciesForHTTPRoutes)).
Watches(&gatewayapi.Gateway{}, handler.EnqueueRequestsFromMapFunc(r.listBackendTLSPoliciesForGateways)).
Complete(r)
}

// -----------------------------------------------------------------------------
// BackendTLSPolicy Controller - Indexers
// -----------------------------------------------------------------------------

const (
// backendTLSPolicyTargetRefIndexKey is the index key for BackendTLSPolicy objects by their target service reference.
// The value is the name of the service.
backendTLSPolicyTargetRefIndexKey = "backendtlspolicy-targetref"
// httpRouteParentRefIndexKey is the index key for HTTPRoute objects by their parent Gateway reference.
// The value is the namespace and name of the Gateway.
httpRouteParentRefIndexKey = "httproute-parentref"
// httpRouteBackendRefIndexKey is the index key for HTTPRoute objects by their backend service reference.
// The value is the namespace and name of the Service.
httpRouteBackendRefIndexKey = "httproute-backendref"
)

// indexBackendTLSPolicyOnTargetRef indexes BackendTLSPolicy objects by their target service reference.
func indexBackendTLSPolicyOnTargetRef(obj client.Object) []string {
policy, ok := obj.(*gatewayapi.BackendTLSPolicy)
if !ok {
return []string{}
}

services := []string{}
for _, targetRef := range policy.Spec.TargetRefs {
if (targetRef.Group == "" || targetRef.Group == "core") && targetRef.Kind == "Service" {
services = append(services, string(targetRef.Name))
}
}
return services
}

// indexHTTPRouteOnParentRef indexes HTTPRoute objects by their parent Gateway references.
func indexHTTPRouteOnParentRef(obj client.Object) []string {
httpRoute, ok := obj.(*gatewayapi.HTTPRoute)
if !ok {
return []string{}
}

gateways := []string{}
for _, parentRef := range httpRoute.Spec.ParentRefs {
// no need to check group and kind nilness, as they have a default value in case not specified
if *parentRef.Group == gatewayapi.V1Group && *parentRef.Kind == "Gateway" {
namespace := httpRoute.Namespace
if parentRef.Namespace != nil {
namespace = string(*parentRef.Namespace)
}
gateways = append(gateways, namespace+"/"+string(parentRef.Name))
}
}
return gateways
}

// indexHTTPRouteOnBackendRef indexes HTTPRoute objects by their backend service references.
func indexHTTPRouteOnBackendRef(obj client.Object) []string {
httpRoute, ok := obj.(*gatewayapi.HTTPRoute)
if !ok {
return []string{}
}

services := []string{}
for _, rule := range httpRoute.Spec.Rules {
for _, backendRef := range rule.BackendRefs {
// no need to check group and kind nilness, as they have a default value in case not specified
if (*backendRef.Group != "core" && *backendRef.Group != "") || *backendRef.Kind != "Service" {
continue
}
namespace := httpRoute.Namespace
if backendRef.Namespace != nil {
namespace = string(*backendRef.Namespace)
}
services = append(services, namespace+"/"+string(backendRef.Name))
}
}
return services
}

// setupIndexers sets up the indexers for the BackendTLSPolicy controller.
func setupBackendTLSPolicyIndices(mgr ctrl.Manager) error {
if err := mgr.GetCache().IndexField(
context.Background(),
&gatewayapi.BackendTLSPolicy{},
backendTLSPolicyTargetRefIndexKey,
indexBackendTLSPolicyOnTargetRef,
); err != nil {
return fmt.Errorf("failed to index backendTLSPolicies on service reference: %w", err)
}

if err := mgr.GetCache().IndexField(
context.Background(),
&gatewayapi.HTTPRoute{},
httpRouteParentRefIndexKey,
indexHTTPRouteOnParentRef,
); err != nil {
return fmt.Errorf("failed to index httpRoute on ParentRef reference: %w", err)
}

if err := mgr.GetCache().IndexField(
context.Background(),
&gatewayapi.HTTPRoute{},
httpRouteBackendRefIndexKey,
indexHTTPRouteOnBackendRef,
); err != nil {
return fmt.Errorf("failed to index httpRoute on Backend reference: %w", err)
}

return nil
}

// -----------------------------------------------------------------------------
// BackendTLSPolicy Controller - Event Handlers
// -----------------------------------------------------------------------------

// listBackendTLSPoliciesForServices returns the list of BackendTLSPolicies that targets the given Service.
func (r *BackendTLSPolicyReconciler) listBackendTLSPoliciesForServices(ctx context.Context, obj client.Object) []reconcile.Request {
service, ok := obj.(*corev1.Service)
if !ok {
r.Log.Error(fmt.Errorf("invalid type"), "Found invalid type in event handlers", "expected", "Service", "found", reflect.TypeOf(obj))
return nil
}
policies := &gatewayapi.BackendTLSPolicyList{}
if err := r.List(ctx, policies,
client.InNamespace(service.Namespace),
client.MatchingFields{backendTLSPolicyTargetRefIndexKey: service.Name},
); err != nil {
r.Log.Error(err, "Failed to list BackendTLSPolicies for Service", "service", service)
return nil
}
requests := make([]reconcile.Request, 0, len(policies.Items))
for _, policy := range policies.Items {
requests = append(requests, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(&policy),
})
}
return requests
}

// listBackendTLSPoliciesForHTTPRoutes returns the list of BackendTLSPolicies that targets a service which is used as a backend by
// the given HTTPRoute.
func (r *BackendTLSPolicyReconciler) listBackendTLSPoliciesForHTTPRoutes(ctx context.Context, obj client.Object) []reconcile.Request {
httpRoute, ok := obj.(*gatewayapi.HTTPRoute)
if !ok {
r.Log.Error(fmt.Errorf("invalid type"), "Found invalid type in event handlers", "expected", "HTTPRoute", "found", reflect.TypeOf(obj))
return nil
}
policiesNN, err := r.getBackendTLSPoliciesByHTTPRoute(ctx, *httpRoute)
if err != nil {
r.Log.Error(err, "Failed to list BackendTLSPolicies for HTTPRoute", "httpRoute", httpRoute)
return nil
}
return lo.Map(policiesNN, func(policy gatewayapi.BackendTLSPolicy, _ int) reconcile.Request {
return reconcile.Request{
NamespacedName: k8stypes.NamespacedName{
Namespace: policy.Namespace,
Name: policy.Name,
},
}
})
}

// listBackendTLSPoliciesForGateways returns the list of BackendTLSPolicies that targets a service which is used as a backend by
// HTTPRoutes connected to the given Gateway.
func (r *BackendTLSPolicyReconciler) listBackendTLSPoliciesForGateways(ctx context.Context, obj client.Object) []reconcile.Request {
gateway, ok := obj.(*gatewayapi.Gateway)
if !ok {
r.Log.Error(fmt.Errorf("invalid type"), "Found invalid type in event handlers", "expected", "Gateway", "found", reflect.TypeOf(obj))
return nil
}
httpRoutes := &gatewayapi.HTTPRouteList{}
if err := r.List(ctx, httpRoutes,
client.MatchingFields{httpRouteParentRefIndexKey: gateway.Namespace + "/" + gateway.Name},
); err != nil {
r.Log.Error(err, "Failed to list HTTPRoutes for Gateway", "gateway", gateway)
return nil
}
policies := []reconcile.Request{}
for _, httpRoute := range httpRoutes.Items {
policiesUsedByHTTPRoute, err := r.getBackendTLSPoliciesByHTTPRoute(ctx, httpRoute)
if err != nil {
r.Log.Error(err, "Failed to list BackendTLSPolicies for HTTPRoute", "httpRoute", httpRoute)
return nil
}
policies = append(policies, lo.Map(policiesUsedByHTTPRoute, func(policy gatewayapi.BackendTLSPolicy, _ int) reconcile.Request {
return reconcile.Request{
NamespacedName: k8stypes.NamespacedName{
Namespace: policy.Namespace,
Name: policy.Name,
},
}
})...)
}
return policies
}

// -----------------------------------------------------------------------------
// BackendTLSPolicy Controller - Reconciliation
// -----------------------------------------------------------------------------

// +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes;gateways;gatewayclasses,verbs=get;list;watch
// +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=backendtlspolicies,verbs=get;list;watch;patch;update
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch

// Reconcile processes the watched objects.
func (r *BackendTLSPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("GatewayV1alpha3BackendTLSPolicy", req.NamespacedName)

backendTLSPolicy := new(gatewayapi.BackendTLSPolicy)
if err := r.Get(ctx, req.NamespacedName, backendTLSPolicy); err != nil {
if apierrors.IsNotFound(err) {
backendTLSPolicy.Namespace = req.Namespace
backendTLSPolicy.Name = req.Name

return ctrl.Result{}, r.DataplaneClient.DeleteObject(backendTLSPolicy)
}
return ctrl.Result{}, err
}

debug(log, backendTLSPolicy, "Processing backendTLSPolicy")

ancestors, err := r.getBackendTLSPolicyAncestors(ctx, *backendTLSPolicy)
if err != nil {
return ctrl.Result{}, err
}

// If there are valid ancestors for the given policy, push the policy to the dataplane cache.
if len(ancestors) > 0 {
if err := r.DataplaneClient.UpdateObject(backendTLSPolicy); err != nil {
return ctrl.Result{}, err
}

if err := r.setPolicyStatus(ctx, *backendTLSPolicy, ancestors); err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}
Loading