diff --git a/controllers/authpolicy_envoysecuritypolicy_controller.go b/controllers/authpolicy_envoysecuritypolicy_controller.go index 3f91daa69..ebad0af6b 100644 --- a/controllers/authpolicy_envoysecuritypolicy_controller.go +++ b/controllers/authpolicy_envoysecuritypolicy_controller.go @@ -7,6 +7,7 @@ import ( egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/go-logr/logr" + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" "github.com/kuadrant/kuadrant-operator/pkg/kuadranttools" @@ -15,7 +16,9 @@ import ( "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/library/utils" + "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" @@ -33,55 +36,35 @@ type AuthPolicyEnvoySecurityPolicyReconciler struct { //+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=securitypolicies,verbs=get;list;watch;create;update;patch;delete func (r *AuthPolicyEnvoySecurityPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("Gateway", req.NamespacedName) + logger := r.Logger().WithValues("Kuadrant", req.NamespacedName) logger.Info("Reconciling auth SecurityPolicy") ctx := logr.NewContext(eventCtx, logger) - gw := &gatewayapiv1.Gateway{} - if err := r.Client().Get(ctx, req.NamespacedName, gw); err != nil { + kObj := &kuadrantv1beta1.Kuadrant{} + if err := r.Client().Get(ctx, req.NamespacedName, kObj); err != nil { if apierrors.IsNotFound(err) { - logger.Info("no gateway found") + logger.Info("no kuadrant object found") return ctrl.Result{}, nil } - logger.Error(err, "failed to get gateway") + logger.Error(err, "failed to get kuadrant object") return ctrl.Result{}, err } if logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(gw, "", " ") + jsonData, err := json.MarshalIndent(kObj, "", " ") if err != nil { return ctrl.Result{}, err } logger.V(1).Info(string(jsonData)) } - if !kuadrant.IsKuadrantManaged(gw) { - return ctrl.Result{}, nil - } - - topology, err := kuadranttools.TopologyFromGateway(ctx, r.Client(), gw, &kuadrantv1beta2.AuthPolicy{}) + topology, err := kuadranttools.TopologyForPolicies(ctx, r.Client(), &kuadrantv1beta2.AuthPolicy{}) if err != nil { return ctrl.Result{}, err } - kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(gw) - if err != nil { - logger.Error(err, "failed to get kuadrant namespace") - return ctrl.Result{}, err - } - - // reconcile security policies for gateways - for _, gwNode := range topology.Gateways() { - node := gwNode - err := r.reconcileSecurityPolicy(ctx, &node, kuadrantNamespace) - if err != nil { - return ctrl.Result{}, err - } - } - // reconcile security policies for routes - for _, routeNode := range topology.Routes() { - node := routeNode - err := r.reconcileSecurityPolicy(ctx, &node, kuadrantNamespace) + for _, policy := range topology.Policies() { + err := r.reconcileSecurityPolicy(ctx, policy, kObj.Namespace) if err != nil { return ctrl.Result{}, err } @@ -90,19 +73,29 @@ func (r *AuthPolicyEnvoySecurityPolicyReconciler) Reconcile(eventCtx context.Con return ctrl.Result{}, nil } -func (r *AuthPolicyEnvoySecurityPolicyReconciler) reconcileSecurityPolicy(ctx context.Context, targetable kuadrantgatewayapi.PolicyTargetNode, kuadrantNamespace string) error { +func (r *AuthPolicyEnvoySecurityPolicyReconciler) reconcileSecurityPolicy(ctx context.Context, policy kuadrantgatewayapi.PolicyNode, kuadrantNamespace string) error { logger, _ := logr.FromContext(ctx) logger = logger.WithName("reconcileSecurityPolicy") - esp := envoySecurityPolicy(targetable.GetObject(), kuadrantNamespace) - if len(targetable.AttachedPolicies()) == 0 { - utils.TagObjectToDelete(esp) + targetRef := policy.TargetRef() + if policy.TargetRef() == nil { + return nil } - if err := r.SetOwnerReference(targetable.GetObject(), esp); err != nil { + esp := envoySecurityPolicy(targetRef.GetObject(), kuadrantNamespace) + if err := r.SetOwnerReference(policy.Policy, esp); err != nil { return err } + // if gateway target and not programmed, or route target which is not accepted by any parent + // tag for deletion + if (targetRef.GetGatewayNode() != nil && meta.IsStatusConditionFalse(targetRef.GetGatewayNode().Status.Conditions, string(gatewayapiv1.GatewayConditionProgrammed))) || + (targetRef.GetRouteNode() != nil && !lo.ContainsBy(targetRef.GetRouteNode().Status.Parents, func(p gatewayapiv1.RouteParentStatus) bool { + return meta.IsStatusConditionTrue(p.Conditions, string(gatewayapiv1.RouteConditionAccepted)) + })) { + utils.TagObjectToDelete(esp) + } + if err := r.ReconcileResource(ctx, &egv1alpha1.SecurityPolicy{}, esp, kuadrantenvoygateway.EnvoySecurityPolicyMutator); err != nil && !apierrors.IsAlreadyExists(err) { logger.Error(err, "failed to reconcile envoy SecurityPolicy resource") return err @@ -177,24 +170,39 @@ func (r *AuthPolicyEnvoySecurityPolicyReconciler) SetupWithManager(mgr ctrl.Mana return nil } - httpRouteToParentGatewaysEventMapper := mappers.NewHTTPRouteToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("httpRouteToParentGatewaysEventMapper")), + securityPolicyToKuadrantEventMapper := mappers.NewSecurityPolicyToKuadrantEventMapper( + mappers.WithLogger(r.Logger().WithName("securityPolicyToKuadrantEventMapper")), + mappers.WithClient(r.Client()), ) - apToParentGatewaysEventMapper := mappers.NewPolicyToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("authpolicyToParentGatewaysEventMapper")), + policyToKuadrantEventMapper := mappers.NewPolicyToKuadrantEventMapper( + mappers.WithLogger(r.Logger().WithName("policyToKuadrantEventMapper")), + mappers.WithClient(r.Client()), + ) + gatewayToKuadrantEventMapper := mappers.NewGatewayToKuadrantEventMapper( + mappers.WithLogger(r.Logger().WithName("gatewayToKuadrantEventMapper")), + mappers.WithClient(r.Client()), + ) + httpRouteToKuadrantEventMapper := mappers.NewHTTPRouteToKuadrantEventMapper( + mappers.WithLogger(r.Logger().WithName("httpRouteToKuadrantEventMapper")), mappers.WithClient(r.Client()), ) - return ctrl.NewControllerManagedBy(mgr). - For(&gatewayapiv1.Gateway{}). - Owns(&egv1alpha1.SecurityPolicy{}). + For(&kuadrantv1beta1.Kuadrant{}). Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(httpRouteToParentGatewaysEventMapper.Map), + &egv1alpha1.SecurityPolicy{}, + handler.EnqueueRequestsFromMapFunc(securityPolicyToKuadrantEventMapper.Map), ). Watches( &kuadrantv1beta2.AuthPolicy{}, - handler.EnqueueRequestsFromMapFunc(apToParentGatewaysEventMapper.Map), + handler.EnqueueRequestsFromMapFunc(policyToKuadrantEventMapper.Map), + ). + Watches( + &gatewayapiv1.Gateway{}, + handler.EnqueueRequestsFromMapFunc(gatewayToKuadrantEventMapper.Map), + ). + Watches( + &gatewayapiv1.HTTPRoute{}, + handler.EnqueueRequestsFromMapFunc(httpRouteToKuadrantEventMapper.Map), ). Complete(r) } diff --git a/pkg/kuadranttools/topology_tools.go b/pkg/kuadranttools/topology_tools.go index 0d67c2cef..e19c6325c 100644 --- a/pkg/kuadranttools/topology_tools.go +++ b/pkg/kuadranttools/topology_tools.go @@ -27,7 +27,7 @@ func TopologyFromGateway(ctx context.Context, cl client.Client, gw *gatewayapiv1 client.MatchingFields{ fieldindexers.HTTPRouteGatewayParentField: client.ObjectKeyFromObject(gw).String(), }) - logger.V(1).Info("topologyIndexesFromGateway: list httproutes from gateway", + logger.V(1).Info("TopologyFromGateway: list httproutes from gateway", "gateway", client.ObjectKeyFromObject(gw), "#HTTPRoutes", len(routeList.Items), "err", err) @@ -37,7 +37,7 @@ func TopologyFromGateway(ctx context.Context, cl client.Client, gw *gatewayapiv1 // Get all the policyKind policies policies := policyKind.List(ctx, cl, "") - logger.V(1).Info("topologyIndexesFromGateway: list policies", + logger.V(1).Info("TopologyFromGateway: list policies", "#policies", len(policies), "err", err) @@ -48,3 +48,44 @@ func TopologyFromGateway(ctx context.Context, cl client.Client, gw *gatewayapiv1 kuadrantgatewayapi.WithLogger(logger), ) } + +func TopologyForPolicies(ctx context.Context, cl client.Client, policyKind kuadrantgatewayapi.Policy) (*kuadrantgatewayapi.Topology, error) { + logger, err := logr.FromContext(ctx) + if err != nil { + return nil, err + } + + gatewayList := &gatewayapiv1.GatewayList{} + err = cl.List( + ctx, + gatewayList) + logger.V(1).Info("TopologyForPolicies: list all gateways", + "#Gateways", len(gatewayList.Items), + "err", err) + if err != nil { + return nil, err + } + + routeList := &gatewayapiv1.HTTPRouteList{} + err = cl.List( + ctx, + routeList) + logger.V(1).Info("TopologyForPolicies: list all httproutes", + "#HTTPRoutes", len(routeList.Items), + "err", err) + if err != nil { + return nil, err + } + + policies := policyKind.List(ctx, cl, "") + logger.V(1).Info("TopologyForPolicies: list policies", + "#policies", len(policies), + "err", err) + + return kuadrantgatewayapi.NewTopology( + kuadrantgatewayapi.WithGateways(utils.Map(gatewayList.Items, ptr.To[gatewayapiv1.Gateway])), + kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), + kuadrantgatewayapi.WithPolicies(policies), + kuadrantgatewayapi.WithLogger(logger), + ) +} diff --git a/pkg/library/mappers/gateway_to_kuadrant.go b/pkg/library/mappers/gateway_to_kuadrant.go new file mode 100644 index 000000000..e6af16193 --- /dev/null +++ b/pkg/library/mappers/gateway_to_kuadrant.go @@ -0,0 +1,53 @@ +package mappers + +import ( + "context" + "fmt" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +type GatewayToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewGatewayToKuadrantEventMapper(o ...MapperOption) *GatewayToKuadrantEventMapper { + return &GatewayToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *GatewayToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + gw, ok := obj.(*gatewayapiv1.Gateway) + if !ok { + logger.Error(fmt.Errorf("%T is not a *gatweayapiv1.Gateway", obj), "cannot map") + return []reconcile.Request{} + } + + if !kuadrant.IsKuadrantManaged(gw) { + logger.V(1).Info("gateway is not kuadrant managed", "gateway", gw) + return []reconcile.Request{} + } + + kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(gw) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + return []reconcile.Request{} + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} +} diff --git a/pkg/library/mappers/httproute_to_kuadrant.go b/pkg/library/mappers/httproute_to_kuadrant.go new file mode 100644 index 000000000..336601ce8 --- /dev/null +++ b/pkg/library/mappers/httproute_to_kuadrant.go @@ -0,0 +1,65 @@ +package mappers + +import ( + "context" + "fmt" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +type HTTPRouteToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewHTTPRouteToKuadrantEventMapper(o ...MapperOption) *HTTPRouteToKuadrantEventMapper { + return &HTTPRouteToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *HTTPRouteToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + httpRoute, ok := obj.(*gatewayapiv1.HTTPRoute) + if !ok { + logger.Error(fmt.Errorf("%T is not a *gatweayapiv1.HTTPRoute", obj), "cannot map") + return []reconcile.Request{} + } + + gatewayKeys := kuadrantgatewayapi.GetRouteAcceptedGatewayParentKeys(httpRoute) + + for _, gatewayKey := range gatewayKeys { + gateway := &gatewayapiv1.Gateway{} + err := m.opts.Client.Get(ctx, gatewayKey, gateway) + if err != nil { + logger.Info("cannot get gateway", "error", err) + continue + } + + if !kuadrant.IsKuadrantManaged(gateway) { + logger.V(1).Info("gateway is not kuadrant managed", "gateway", gateway) + continue + } + kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(gateway) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + continue + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} + } + logger.V(1).Info("no matching kuadrant instance found") + return []reconcile.Request{} +} diff --git a/pkg/library/mappers/policy_to_kuadrant.go b/pkg/library/mappers/policy_to_kuadrant.go new file mode 100644 index 000000000..9fdcd5ffa --- /dev/null +++ b/pkg/library/mappers/policy_to_kuadrant.go @@ -0,0 +1,47 @@ +package mappers + +import ( + "context" + "fmt" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type PolicyToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewPolicyToKuadrantEventMapper(o ...MapperOption) *PolicyToKuadrantEventMapper { + return &PolicyToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *PolicyToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + policy, ok := obj.(kuadrant.Policy) + if !ok { + logger.Error(fmt.Errorf("%T is not a kuadrant.Policy", obj), "cannot map") + return []reconcile.Request{} + } + + kuadrantNamespace, err := kuadrant.GetKuadrantNamespaceFromPolicyTargetRef(ctx, m.opts.Client, policy) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + return []reconcile.Request{} + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} +}