diff --git a/controllers/tlspolicy_tasks.go b/controllers/tlspolicy_tasks.go index d9f7fb050..e0f26d16f 100644 --- a/controllers/tlspolicy_tasks.go +++ b/controllers/tlspolicy_tasks.go @@ -236,7 +236,7 @@ func (t *TLSPolicyStatusTask) isIssuerReady(ctx context.Context, tlsPolicy *kuad return o.GroupVersionKind().GroupKind() == CertManagerIssuerKind && o.GetNamespace() == tlsPolicy.GetNamespace() && o.GetName() == tlsPolicy.Spec.IssuerRef.Name }) if !ok { - err := errors.New("unable to find issuer for TLSPolicy") + err := fmt.Errorf("%s \"%s\" not found", tlsPolicy.Spec.IssuerRef.Kind, tlsPolicy.Spec.IssuerRef.Name) logger.Error(err, "error finding object in topology") return err } @@ -250,7 +250,7 @@ func (t *TLSPolicyStatusTask) isIssuerReady(ctx context.Context, tlsPolicy *kuad return o.GroupVersionKind().GroupKind() == CertManagerClusterIssuerKind && o.GetName() == tlsPolicy.Spec.IssuerRef.Name }) if !ok { - err := errors.New("unable to find cluster issuer for TLSPolicy") + err := fmt.Errorf("%s \"%s\" not found", tlsPolicy.Spec.IssuerRef.Kind, tlsPolicy.Spec.IssuerRef.Name) logger.Error(err, "error finding object in topology") return err } @@ -266,7 +266,7 @@ func (t *TLSPolicyStatusTask) isIssuerReady(ctx context.Context, tlsPolicy *kuad }) if !meta.IsStatusConditionTrue(transformedCond, string(certmanagerv1.IssuerConditionReady)) { - return errors.New("issuer not ready") + return fmt.Errorf("%s not ready", tlsPolicy.Spec.IssuerRef.Kind) } return nil @@ -281,7 +281,6 @@ func (t *TLSPolicyStatusTask) isCertificatesReady(ctx context.Context, p machine // Get all gateways that contains this policy gws := lo.FilterMap(topology.Targetables().Items(), func(item machinery.Targetable, index int) (*machinery.Gateway, bool) { gw, ok := item.(*machinery.Gateway) - return gw, ok && lo.Contains(gw.Policies(), p) }) diff --git a/controllers/tlspolicy_tasks_test.go b/controllers/tlspolicy_tasks_test.go index 58a0da459..40527f25c 100644 --- a/controllers/tlspolicy_tasks_test.go +++ b/controllers/tlspolicy_tasks_test.go @@ -3,9 +3,24 @@ package controllers import ( + "context" + "fmt" + "reflect" "testing" + certmanv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/utils/ptr" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" ) func TestTLSPolicyValidKey(t *testing.T) { @@ -33,3 +48,448 @@ func TestTLSPolicyValidKey(t *testing.T) { }) } } + +func TestTLSPolicyStatusTask_enforcedCondition(t *testing.T) { + const ( + ns = "default" + tlsPolicyName = "kuadrant-tls-policy" + issuerName = "kuadrant-issuer" + certificateName = "kuadrant-certifcate" + gwName = "kuadrant-gateway" + ) + + policyFactory := func(mutateFn ...func(policy *kuadrantv1alpha1.TLSPolicy)) *kuadrantv1alpha1.TLSPolicy { + p := &kuadrantv1alpha1.TLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: tlsPolicyName, + UID: types.UID(rand.String(9)), + }, + TypeMeta: metav1.TypeMeta{ + Kind: "TLSPolicy", + APIVersion: kuadrantv1alpha1.GroupVersion.String(), + }, + Spec: kuadrantv1alpha1.TLSPolicySpec{ + CertificateSpec: kuadrantv1alpha1.CertificateSpec{ + IssuerRef: certmanmetav1.ObjectReference{ + Name: issuerName, + Kind: certmanv1.IssuerKind, + }, + }, + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Name: gwName, + Kind: "Gateway", + Group: gatewayapiv1alpha2.GroupName, + }, + }, + } + for _, mutate := range mutateFn { + mutate(p) + } + + return p + } + + withClusterIssuerMutater := func(p *kuadrantv1alpha1.TLSPolicy) { + p.Spec.CertificateSpec.IssuerRef.Kind = certmanv1.ClusterIssuerKind + } + + issuerFactory := func(mutateFn ...func(issuer *certmanv1.Issuer)) *certmanv1.Issuer { + issuer := &certmanv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: issuerName, + Namespace: ns, + UID: types.UID(rand.String(9)), + }, + TypeMeta: metav1.TypeMeta{ + Kind: certmanv1.IssuerKind, + APIVersion: certmanv1.SchemeGroupVersion.String(), + }, + Status: certmanv1.IssuerStatus{ + Conditions: []certmanv1.IssuerCondition{ + { + Type: certmanv1.IssuerConditionReady, + Status: certmanmetav1.ConditionTrue, + }, + }, + }, + } + + for _, mutate := range mutateFn { + mutate(issuer) + } + + return issuer + } + + issuerNotReadyMutater := func(issuer *certmanv1.Issuer) { + issuer.Status = certmanv1.IssuerStatus{ + Conditions: []certmanv1.IssuerCondition{ + { + Type: certmanv1.IssuerConditionReady, + Status: certmanmetav1.ConditionFalse, + }, + }, + } + } + + clusterIssuerFactory := func(mutateFn ...func(issuer *certmanv1.ClusterIssuer)) *certmanv1.ClusterIssuer { + issuer := &certmanv1.ClusterIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: issuerName, Namespace: ns}, + TypeMeta: metav1.TypeMeta{ + Kind: certmanv1.ClusterIssuerKind, + APIVersion: certmanv1.SchemeGroupVersion.String(), + }, + Status: certmanv1.IssuerStatus{ + Conditions: []certmanv1.IssuerCondition{ + { + Type: certmanv1.IssuerConditionReady, + Status: certmanmetav1.ConditionTrue, + }, + }, + }, + } + + for _, mutate := range mutateFn { + mutate(issuer) + } + + return issuer + } + + clusterIssuerNotReadyMutater := func(issuer *certmanv1.ClusterIssuer) { + issuer.Status = certmanv1.IssuerStatus{ + Conditions: []certmanv1.IssuerCondition{ + { + Type: certmanv1.IssuerConditionReady, + Status: certmanmetav1.ConditionFalse, + }, + }, + } + } + + certificateFactory := func(mutateFn ...func(certificate *certmanv1.Certificate)) *certmanv1.Certificate { + c := &certmanv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: certificateName, + Namespace: ns, + UID: types.UID(rand.String(9)), + }, + TypeMeta: metav1.TypeMeta{ + Kind: certmanv1.CertificateKind, + APIVersion: certmanv1.SchemeGroupVersion.String(), + }, + Status: certmanv1.CertificateStatus{ + Conditions: []certmanv1.CertificateCondition{ + { + Type: certmanv1.CertificateConditionReady, + Status: certmanmetav1.ConditionTrue, + }, + }, + }, + } + + for _, mutate := range mutateFn { + mutate(c) + } + + return c + } + + certificateNotReadyMutater := func(certificate *certmanv1.Certificate) { + certificate.Status = certmanv1.CertificateStatus{ + Conditions: []certmanv1.CertificateCondition{ + { + Type: certmanv1.CertificateConditionReady, + Status: certmanmetav1.ConditionFalse, + }, + }, + } + } + + gwFactory := func() *gatewayapiv1.Gateway { + return &gatewayapiv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gwName, + Namespace: ns, + UID: types.UID(rand.String(9)), + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: gatewayapiv1.GroupVersion.String(), + }, + Spec: gatewayapiv1.GatewaySpec{ + Listeners: []gatewayapiv1.Listener{ + { + Name: "http", + Hostname: ptr.To[gatewayapiv1.Hostname]("localhost"), + TLS: &gatewayapiv1.GatewayTLSConfig{ + CertificateRefs: []gatewayapiv1.SecretObjectReference{{ + Group: ptr.To[gatewayapiv1.Group]("core"), + Kind: ptr.To[gatewayapiv1.Kind]("Secret"), + Name: certificateName, + Namespace: ptr.To[gatewayapiv1.Namespace]("default"), + }}, + Mode: ptr.To(gatewayapiv1.TLSModeTerminate), + }, + }, + }, + }, + } + } + + topologyOpts := func(policy *kuadrantv1alpha1.TLSPolicy, additionalOps ...machinery.GatewayAPITopologyOptionsFunc) []machinery.GatewayAPITopologyOptionsFunc { + store := make(controller.Store) + gw := gwFactory() + store[string(gw.UID)] = gw + store[string(policy.UID)] = policy + + opts := []machinery.GatewayAPITopologyOptionsFunc{ + machinery.WithGateways(gw), + machinery.WithGatewayAPITopologyPolicies(policy), + machinery.WithGatewayAPITopologyLinks( + LinkGatewayToCertificateFunc(store), + LinkGatewayToIssuerFunc(store), + LinkGatewayToClusterIssuerFunc(store), + ), + } + opts = append(opts, additionalOps...) + + return opts + } + + type args struct { + tlsPolicy *kuadrantv1alpha1.TLSPolicy + topology func(*kuadrantv1alpha1.TLSPolicy) *machinery.Topology + } + tests := []struct { + name string + args args + want *metav1.Condition + }{ + { + name: "unable to get issuer", + args: args{ + tlsPolicy: policyFactory(), + topology: func(p *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + topology, _ := machinery.NewGatewayAPITopology( + topologyOpts(p)..., + ) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: fmt.Sprintf("TLSPolicy has encountered some issues: Issuer \"%s\" not found", issuerName), + }, + }, + { + name: "unable to get cluster issuer", + args: args{ + tlsPolicy: policyFactory(withClusterIssuerMutater), + topology: func(p *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + topology, _ := machinery.NewGatewayAPITopology( + topologyOpts(p)..., + ) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: fmt.Sprintf("TLSPolicy has encountered some issues: ClusterIssuer \"%s\" not found", issuerName), + }, + }, + { + name: "issuer not ready", + args: args{ + tlsPolicy: policyFactory(), + topology: func(p *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(p, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory(issuerNotReadyMutater)}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: "TLSPolicy has encountered some issues: Issuer not ready", + }, + }, + { + name: "issuer has no ready condition", + args: args{ + tlsPolicy: policyFactory(), + topology: func(p *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(p, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory(func(issuer *certmanv1.Issuer) { + issuer.Status.Conditions = []certmanv1.IssuerCondition{} + })}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: "TLSPolicy has encountered some issues: Issuer not ready", + }, + }, + { + name: "cluster issuer not ready", + args: args{ + tlsPolicy: policyFactory(withClusterIssuerMutater), + topology: func(p *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(p, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: clusterIssuerFactory(clusterIssuerNotReadyMutater)}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: "TLSPolicy has encountered some issues: ClusterIssuer not ready", + }, + }, + { + name: "cluster issuer has no ready condition", + args: args{ + tlsPolicy: policyFactory(withClusterIssuerMutater), + topology: func(p *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(p, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: clusterIssuerFactory(func(issuer *certmanv1.ClusterIssuer) { + issuer.Status.Conditions = []certmanv1.IssuerCondition{} + })}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: "TLSPolicy has encountered some issues: ClusterIssuer not ready", + }, + }, + { + name: "no valid gateways found", + args: args{ + tlsPolicy: policyFactory(), + topology: func(_ *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(policyFactory(), machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory()}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: "TLSPolicy has encountered some issues: no valid gateways found", + }, + }, + { + name: "unable to get certificate", + args: args{ + tlsPolicy: policyFactory(), + topology: func(policy *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(policy, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory()}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: "TLSPolicy has encountered some issues: certificate not found", + }, + }, + { + name: "certificate is not ready", + args: args{ + tlsPolicy: policyFactory(), + topology: func(policy *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(policy, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory()}, + &controller.RuntimeObject{Object: certificateFactory(certificateNotReadyMutater)}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: fmt.Sprintf("TLSPolicy has encountered some issues: certificate %s not ready", certificateName), + }, + }, + { + name: "certificate has no ready condition", + args: args{ + tlsPolicy: policyFactory(), + topology: func(policy *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(policy, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory()}, + &controller.RuntimeObject{Object: certificateFactory(func(certificate *certmanv1.Certificate) { + certificate.Status.Conditions = []certmanv1.CertificateCondition{} + })}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionFalse, + Reason: string(kuadrant.PolicyReasonUnknown), + Message: fmt.Sprintf("TLSPolicy has encountered some issues: certificate %s not ready", certificateName), + }, + }, + { + name: "is enforced", + args: args{ + tlsPolicy: policyFactory(), + topology: func(policy *kuadrantv1alpha1.TLSPolicy) *machinery.Topology { + opts := topologyOpts(policy, machinery.WithGatewayAPITopologyObjects( + &controller.RuntimeObject{Object: issuerFactory()}, + &controller.RuntimeObject{Object: certificateFactory()}, + )) + topology, _ := machinery.NewGatewayAPITopology(opts...) + return topology + }, + }, + want: &metav1.Condition{ + Type: string(kuadrant.PolicyConditionEnforced), + Status: metav1.ConditionTrue, + Reason: string(kuadrant.PolicyConditionEnforced), + Message: "TLSPolicy has been successfully enforced", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t1 *testing.T) { + t := &TLSPolicyStatusTask{} + if got := t.enforcedCondition(context.Background(), tt.args.tlsPolicy, tt.args.topology(tt.args.tlsPolicy)); !reflect.DeepEqual(got, tt.want) { + t1.Errorf("enforcedCondition() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod index 01eeeca67..ac6e6a45b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/kuadrant/authorino-operator v0.11.1 github.com/kuadrant/dns-operator v0.0.0-20240926100317-2e2497411ab3 github.com/kuadrant/limitador-operator v0.9.0 - github.com/kuadrant/policy-machinery v0.2.1-0.20240930131002-fdcc3c3eeb54 + github.com/kuadrant/policy-machinery v0.3.0 github.com/martinlindhe/base36 v1.1.1 github.com/onsi/ginkgo/v2 v2.17.2 github.com/onsi/gomega v1.33.1 diff --git a/go.sum b/go.sum index 15a363f00..1eea55197 100644 --- a/go.sum +++ b/go.sum @@ -259,10 +259,8 @@ github.com/kuadrant/dns-operator v0.0.0-20240926100317-2e2497411ab3 h1:r5Ed62Aet github.com/kuadrant/dns-operator v0.0.0-20240926100317-2e2497411ab3/go.mod h1:IHAt2o/VH1c0GIZTprggUDZuxoH0I304R9DUErBNIhk= github.com/kuadrant/limitador-operator v0.9.0 h1:hTQ6CFPayf/sL7cIzwWjCoU8uTn6fzWdsJgKbDlnFts= github.com/kuadrant/limitador-operator v0.9.0/go.mod h1:DQOlg9qFOcnWPrwO529JRCMLLOEXJQxkmOes952S/Hw= -github.com/kuadrant/policy-machinery v0.2.0 h1:6kACb+bdEwHXz2tvTs6dlLgvxFgFrowvGTZKMI9p0Qo= -github.com/kuadrant/policy-machinery v0.2.0/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= -github.com/kuadrant/policy-machinery v0.2.1-0.20240930131002-fdcc3c3eeb54 h1:KArygmCTpHCoPlMfPBMMrqtVTi1mFpbErLXjMOzWqBI= -github.com/kuadrant/policy-machinery v0.2.1-0.20240930131002-fdcc3c3eeb54/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= +github.com/kuadrant/policy-machinery v0.3.0 h1:8cRI5YdTD2vwuHSbdY9bqTZmi8PL+zCtylFQqKGDQ3Q= +github.com/kuadrant/policy-machinery v0.3.0/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=