diff --git a/api/v1alpha1/dnspolicy_types.go b/api/v1alpha1/dnspolicy_types.go index 8d9eae62f..8f9e368e9 100644 --- a/api/v1alpha1/dnspolicy_types.go +++ b/api/v1alpha1/dnspolicy_types.go @@ -145,7 +145,8 @@ var _ kuadrant.Referrer = &DNSPolicy{} // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=direct" -// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason`,description="DNSPolicy Status",priority=2 +// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`,description="DNSPolicy Accepted",priority=2 +// +kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.status.conditions[?(@.type=="Enforced")].status`,description="DNSPolicy Enforced",priority=2 // +kubebuilder:printcolumn:name="TargetRefKind",type="string",JSONPath=".spec.targetRef.kind",description="Type of the referenced Gateway API resource",priority=2 // +kubebuilder:printcolumn:name="TargetRefName",type="string",JSONPath=".spec.targetRef.name",description="Name of the referenced Gateway API resource",priority=2 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/api/v1alpha1/tlspolicy_types.go b/api/v1alpha1/tlspolicy_types.go index fa9808b3c..a54d59b7b 100644 --- a/api/v1alpha1/tlspolicy_types.go +++ b/api/v1alpha1/tlspolicy_types.go @@ -129,7 +129,8 @@ var _ kuadrant.Referrer = &TLSPolicy{} // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=direct" -// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason`,description="TLSPolicy Status",priority=2 +// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`,description="TLSPolicy Accepted",priority=2 +// +kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.status.conditions[?(@.type=="Enforced")].status`,description="TLSPolicy Enforced",priority=2 // +kubebuilder:printcolumn:name="TargetRefKind",type="string",JSONPath=".spec.targetRef.kind",description="Type of the referenced Gateway API resource",priority=2 // +kubebuilder:printcolumn:name="TargetRefName",type="string",JSONPath=".spec.targetRef.name",description="Name of the referenced Gateway API resource",priority=2 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/bundle/manifests/kuadrant.io_dnspolicies.yaml b/bundle/manifests/kuadrant.io_dnspolicies.yaml index 5c6f2542b..114cab32b 100644 --- a/bundle/manifests/kuadrant.io_dnspolicies.yaml +++ b/bundle/manifests/kuadrant.io_dnspolicies.yaml @@ -18,9 +18,14 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - description: DNSPolicy Status - jsonPath: .status.conditions[0].reason - name: Status + - description: DNSPolicy Accepted + jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + priority: 2 + type: string + - description: DNSPolicy Enforced + jsonPath: .status.conditions[?(@.type=="Enforced")].status + name: Enforced priority: 2 type: string - description: Type of the referenced Gateway API resource diff --git a/bundle/manifests/kuadrant.io_tlspolicies.yaml b/bundle/manifests/kuadrant.io_tlspolicies.yaml index a6ee4e5c6..6121cf16c 100644 --- a/bundle/manifests/kuadrant.io_tlspolicies.yaml +++ b/bundle/manifests/kuadrant.io_tlspolicies.yaml @@ -18,9 +18,14 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - description: TLSPolicy Status - jsonPath: .status.conditions[0].reason - name: Status + - description: TLSPolicy Accepted + jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + priority: 2 + type: string + - description: TLSPolicy Enforced + jsonPath: .status.conditions[?(@.type=="Enforced")].status + name: Enforced priority: 2 type: string - description: Type of the referenced Gateway API resource diff --git a/config/crd/bases/kuadrant.io_dnspolicies.yaml b/config/crd/bases/kuadrant.io_dnspolicies.yaml index 9dea3031b..5d1c8743a 100644 --- a/config/crd/bases/kuadrant.io_dnspolicies.yaml +++ b/config/crd/bases/kuadrant.io_dnspolicies.yaml @@ -17,9 +17,14 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - description: DNSPolicy Status - jsonPath: .status.conditions[0].reason - name: Status + - description: DNSPolicy Accepted + jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + priority: 2 + type: string + - description: DNSPolicy Enforced + jsonPath: .status.conditions[?(@.type=="Enforced")].status + name: Enforced priority: 2 type: string - description: Type of the referenced Gateway API resource diff --git a/config/crd/bases/kuadrant.io_tlspolicies.yaml b/config/crd/bases/kuadrant.io_tlspolicies.yaml index 164a9ff6f..f49c41b8e 100644 --- a/config/crd/bases/kuadrant.io_tlspolicies.yaml +++ b/config/crd/bases/kuadrant.io_tlspolicies.yaml @@ -17,9 +17,14 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - description: TLSPolicy Status - jsonPath: .status.conditions[0].reason - name: Status + - description: TLSPolicy Accepted + jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + priority: 2 + type: string + - description: TLSPolicy Enforced + jsonPath: .status.conditions[?(@.type=="Enforced")].status + name: Enforced priority: 2 type: string - description: Type of the referenced Gateway API resource diff --git a/controllers/tlspolicy_controller.go b/controllers/tlspolicy_controller.go index 6f9156642..58a0e5106 100644 --- a/controllers/tlspolicy_controller.go +++ b/controllers/tlspolicy_controller.go @@ -80,7 +80,7 @@ func (r *TLSPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( if delResErr == nil { delResErr = err } - return r.reconcileStatus(ctx, tlsPolicy, kuadrant.NewErrTargetNotFound(tlsPolicy.Kind(), tlsPolicy.GetTargetRef(), delResErr)) + return r.reconcileStatus(ctx, tlsPolicy, targetReferenceObject, kuadrant.NewErrTargetNotFound(tlsPolicy.Kind(), tlsPolicy.GetTargetRef(), delResErr)) } return ctrl.Result{}, err } @@ -109,7 +109,7 @@ func (r *TLSPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( specErr := r.reconcileResources(ctx, tlsPolicy, targetReferenceObject) - statusResult, statusErr := r.reconcileStatus(ctx, tlsPolicy, specErr) + statusResult, statusErr := r.reconcileStatus(ctx, tlsPolicy, targetReferenceObject, specErr) if specErr != nil { return ctrl.Result{}, specErr diff --git a/controllers/tlspolicy_controller_test.go b/controllers/tlspolicy_controller_test.go index 2814d467b..bcc384521 100644 --- a/controllers/tlspolicy_controller_test.go +++ b/controllers/tlspolicy_controller_test.go @@ -22,6 +22,7 @@ import ( gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "github.com/kuadrant/kuadrant-operator/api/v1alpha1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" ) var _ = Describe("TLSPolicy controller", func() { @@ -85,6 +86,11 @@ var _ = Describe("TLSPolicy controller", func() { "Message": Equal("TLSPolicy target test-gateway was not found"), })), ) + g.Expect(tlsPolicy.Status.Conditions).ToNot( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + })), + ) }, TestTimeoutMedium, time.Second).Should(Succeed()) }) @@ -100,6 +106,11 @@ var _ = Describe("TLSPolicy controller", func() { "Message": Equal("TLSPolicy target test-gateway was not found"), })), ) + g.Expect(tlsPolicy.Status.Conditions).ToNot( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + })), + ) }, TestTimeoutMedium, time.Second).Should(Succeed()) By("creating a valid Gateway") @@ -118,6 +129,14 @@ var _ = Describe("TLSPolicy controller", func() { "Message": Equal("TLSPolicy has been accepted"), })), ) + g.Expect(tlsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(kuadrant.PolicyConditionEnforced)), + "Message": Equal("TLSPolicy has been successfully enforced"), + })), + ) }, TestTimeoutMedium, time.Second).Should(Succeed()) }) @@ -146,6 +165,14 @@ var _ = Describe("TLSPolicy controller", func() { "Message": Equal("TLSPolicy has been accepted"), })), ) + g.Expect(tlsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(kuadrant.PolicyConditionEnforced)), + "Message": Equal("TLSPolicy has been successfully enforced"), + })), + ) }, TestTimeoutMedium, time.Second).Should(Succeed()) }) @@ -186,7 +213,7 @@ var _ = Describe("TLSPolicy controller", func() { Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) }) - It("should have accepted condition with status true", func() { + It("should have accepted and enforced condition with status true", func() { Eventually(func(g Gomega) { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(tlsPolicy), tlsPolicy) g.Expect(err).NotTo(HaveOccurred()) @@ -198,6 +225,14 @@ var _ = Describe("TLSPolicy controller", func() { "Message": Equal("TLSPolicy has been accepted"), })), ) + g.Expect(tlsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(kuadrant.PolicyConditionEnforced)), + "Message": Equal("TLSPolicy has been successfully enforced"), + })), + ) }, TestTimeoutMedium, time.Second).Should(Succeed()) }) }) diff --git a/controllers/tlspolicy_status.go b/controllers/tlspolicy_status.go index 43384cf5d..25d10548d 100644 --- a/controllers/tlspolicy_status.go +++ b/controllers/tlspolicy_status.go @@ -18,20 +18,28 @@ package controllers import ( "context" + "errors" + "fmt" "slices" + certmanv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "github.com/kuadrant/kuadrant-operator/api/v1alpha1" "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" + "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" + "github.com/kuadrant/kuadrant-operator/pkg/library/utils" ) -func (r *TLSPolicyReconciler) reconcileStatus(ctx context.Context, tlsPolicy *v1alpha1.TLSPolicy, specErr error) (ctrl.Result, error) { - newStatus := r.calculateStatus(tlsPolicy, specErr) +func (r *TLSPolicyReconciler) reconcileStatus(ctx context.Context, tlsPolicy *v1alpha1.TLSPolicy, targetNetworkObject client.Object, specErr error) (ctrl.Result, error) { + newStatus := r.calculateStatus(ctx, tlsPolicy, targetNetworkObject, specErr) equalStatus := equality.Semantic.DeepEqual(newStatus, tlsPolicy.Status) if equalStatus && tlsPolicy.Generation == tlsPolicy.Status.ObservedGeneration { @@ -57,7 +65,7 @@ func (r *TLSPolicyReconciler) reconcileStatus(ctx context.Context, tlsPolicy *v1 return ctrl.Result{}, nil } -func (r *TLSPolicyReconciler) calculateStatus(tlsPolicy *v1alpha1.TLSPolicy, specErr error) *v1alpha1.TLSPolicyStatus { +func (r *TLSPolicyReconciler) calculateStatus(ctx context.Context, tlsPolicy *v1alpha1.TLSPolicy, targetNetworkObject client.Object, specErr error) *v1alpha1.TLSPolicyStatus { newStatus := &v1alpha1.TLSPolicyStatus{ // Copy initial conditions. Otherwise, status will always be updated Conditions: slices.Clone(tlsPolicy.Status.Conditions), @@ -67,5 +75,84 @@ func (r *TLSPolicyReconciler) calculateStatus(tlsPolicy *v1alpha1.TLSPolicy, spe acceptedCond := kuadrant.AcceptedCondition(tlsPolicy, specErr) meta.SetStatusCondition(&newStatus.Conditions, *acceptedCond) + // Do not set enforced condition if Accepted condition is false + if meta.IsStatusConditionFalse(newStatus.Conditions, string(gatewayapiv1alpha2.PolicyReasonAccepted)) { + meta.RemoveStatusCondition(&newStatus.Conditions, string(kuadrant.PolicyConditionEnforced)) + return newStatus + } + + enforcedCond := r.enforcedCondition(ctx, tlsPolicy, targetNetworkObject) + meta.SetStatusCondition(&newStatus.Conditions, *enforcedCond) + return newStatus } + +func (r *TLSPolicyReconciler) enforcedCondition(ctx context.Context, tlsPolicy *v1alpha1.TLSPolicy, targetNetworkObject client.Object) *metav1.Condition { + if err := r.isIssuerReady(ctx, tlsPolicy); err != nil { + return kuadrant.EnforcedCondition(tlsPolicy, kuadrant.NewErrUnknown(tlsPolicy.Kind(), err), false) + } + + if err := r.isCertificatesReady(ctx, tlsPolicy, targetNetworkObject); err != nil { + return kuadrant.EnforcedCondition(tlsPolicy, kuadrant.NewErrUnknown(tlsPolicy.Kind(), err), false) + } + + return kuadrant.EnforcedCondition(tlsPolicy, nil, true) +} + +func (r *TLSPolicyReconciler) isIssuerReady(ctx context.Context, tlsPolicy *v1alpha1.TLSPolicy) error { + var conditions []certmanv1.IssuerCondition + + switch tlsPolicy.Spec.IssuerRef.Kind { + case "", certmanv1.IssuerKind: + issuer := &certmanv1.Issuer{} + if err := r.Client().Get(ctx, client.ObjectKey{Name: tlsPolicy.Spec.IssuerRef.Name, Namespace: tlsPolicy.Namespace}, issuer); err != nil { + return err + } + conditions = issuer.Status.Conditions + case certmanv1.ClusterIssuerKind: + issuer := &certmanv1.ClusterIssuer{} + if err := r.Client().Get(ctx, client.ObjectKey{Name: tlsPolicy.Spec.IssuerRef.Name}, issuer); err != nil { + return err + } + conditions = issuer.Status.Conditions + default: + return fmt.Errorf(`invalid value %q for issuerRef.kind. Must be empty, %q or %q`, tlsPolicy.Spec.IssuerRef.Kind, certmanv1.IssuerKind, certmanv1.ClusterIssuerKind) + } + + transformedCond := utils.Map(conditions, func(c certmanv1.IssuerCondition) metav1.Condition { + return metav1.Condition{Reason: c.Reason, Status: metav1.ConditionStatus(c.Status), Type: string(c.Type), Message: c.Message} + }) + + if meta.IsStatusConditionFalse(transformedCond, "Ready") { + return errors.New("issuer not ready") + } + + return nil +} + +func (r *TLSPolicyReconciler) isCertificatesReady(ctx context.Context, tlsPolicy *v1alpha1.TLSPolicy, targetNetworkObject client.Object) error { + gwDiffObj, err := reconcilers.ComputeGatewayDiffs(ctx, r.Client(), tlsPolicy, targetNetworkObject) + if err != nil { + return err + } + + for _, gw := range gwDiffObj.GatewaysWithValidPolicyRef { + expectedCertificates := r.expectedCertificatesForGateway(ctx, gw.Gateway, tlsPolicy) + + for _, cert := range expectedCertificates { + c := &certmanv1.Certificate{} + if err := r.Client().Get(ctx, client.ObjectKeyFromObject(cert), c); err != nil { + return err + } + conditions := utils.Map(c.Status.Conditions, func(c certmanv1.CertificateCondition) metav1.Condition { + return metav1.Condition{Reason: c.Reason, Status: metav1.ConditionStatus(c.Status), Type: string(c.Type), Message: c.Message} + }) + + if meta.IsStatusConditionFalse(conditions, "Ready") { + return fmt.Errorf("certificate %s not ready", cert.Name) + } + } + } + + return nil +}