@@ -25,11 +25,15 @@ import (
2525 cmmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
2626 appsv1 "k8s.io/api/apps/v1"
2727 corev1 "k8s.io/api/core/v1"
28+ networkingv1 "k8s.io/api/networking/v1"
2829 "k8s.io/apimachinery/pkg/api/meta"
2930 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3031 "k8s.io/utils/ptr"
3132)
3233
34+ // +kubebuilder:validation:Format=ipv4
35+ type IPAddress string
36+
3337// ImageSpec defines information about the image to expose the CRL.
3438type ImageSpec struct {
3539 // Repository is the container image repository.
@@ -55,6 +59,35 @@ type ImageSpec struct {
5559 PullSecrets []corev1.LocalObjectReference `json:"pullSecrets,omitempty"`
5660}
5761
62+ // IngressSpec defines the ingress configuration for exposing the CRL.
63+ type IngressSpec struct {
64+ // Enabled indicates whether to create an Ingress resource to expose the CRL.
65+ // (default: true)
66+ // +optional
67+ Enabled * bool `json:"enabled"`
68+
69+ // Managed indicates whether the operator should manage the Ingress resource.
70+ // If false, the Ingress resource will not be created or updated by the operator.
71+ // (default: true)
72+ // +optional
73+ Managed * bool `json:"managed"`
74+
75+ // Hostname is the hostname to use for the ingress.
76+ // (One of Hostname or IPAddresses must be specified)
77+ // +kubebuilder:validation:MinLength=1
78+ // +optional
79+ Hostname * string `json:"hostname,omitempty"`
80+
81+ // ClassName is the ingress class name to use for the ingress.
82+ // +optional
83+ ClassName * string `json:"className,omitempty"`
84+
85+ // IPAddresses is a list of IP addresses to use for the ingress.
86+ // (One of Hostname or IPAddresses must be specified)
87+ // +optional
88+ IPAddresses []IPAddress `json:"ipAddresses,omitempty"`
89+ }
90+
5891// CRLExposeSpec defines how the CRL should be exposed.
5992type CRLExposeSpec struct {
6093 // Enabled indicates whether the CRL should be exposed.
@@ -69,6 +102,17 @@ type CRLExposeSpec struct {
69102 // Tolerations to deploy the Virtual IPs manager
70103 // +optional
71104 Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
105+
106+ // Internal indicates whether the CRL should be exposed internally within the cluster.
107+ // (default: true)
108+ // +optional
109+ Internal * bool `json:"internal"`
110+
111+ // Ingress indicates whether the CRL should be exposed externally outside the cluster
112+ // using an Ingress resource.
113+ // (default: Disabled)
114+ // +optional
115+ Ingress * IngressSpec `json:"ingress"`
72116}
73117
74118// RevocationSpec defines a certificate to be revoked.
@@ -112,8 +156,12 @@ type ManagedCRLStatus struct {
112156 // SecretReady indicates whether the CRL is built and available in the Secret.
113157 SecretReady * bool `json:"secretReady,omitempty"`
114158 // PodExposed indicates whether the CRL expose Pod is running.
115- PodExposed * bool `json:"podExposed,omitempty"`
116- Conditions []metav1.Condition `json:"conditions,omitempty"`
159+ PodExposed * bool `json:"podExposed,omitempty"`
160+ // IngressExposed indicates whether the CRL Ingress is available.
161+ IngressExposed * bool `json:"ingressExposed,omitempty"`
162+ // IssuerConfigured indicates whether the Issuer is properly configured.
163+ IssuerConfigured * bool `json:"issuerConfigured,omitempty"`
164+ Conditions []metav1.Condition `json:"conditions,omitempty"`
117165
118166 // CRLValidUntil is the time until which the CRL is valid.
119167 CRLValidUntil metav1.Time `json:"crlValidUntil,omitempty"`
@@ -163,6 +211,21 @@ func (mcrl *ManagedCRL) IsExposed() bool {
163211 return mcrl .Spec .Expose != nil && mcrl .Spec .Expose .Enabled
164212}
165213
214+ // IsIngressEnabled returns true if the CRL is configured to be exposed via Ingress.
215+ func (mcrl * ManagedCRL ) IsIngressEnabled () bool {
216+ return mcrl .IsExposed () && mcrl .Spec .Expose .Ingress != nil && * mcrl .Spec .Expose .Ingress .Enabled
217+ }
218+
219+ // IsIngressManaged returns true if the Ingress is managed by the operator.
220+ func (mcrl * ManagedCRL ) IsIngressManaged () bool {
221+ return mcrl .IsIngressEnabled () && mcrl .Spec .Expose .Ingress .Managed != nil && * mcrl .Spec .Expose .Ingress .Managed
222+ }
223+
224+ // IsInternalEnabled returns true if the CRL is configured to be exposed internally.
225+ func (mcrl * ManagedCRL ) IsInternalEnabled () bool {
226+ return mcrl .IsExposed () && mcrl .Spec .Expose .Internal != nil && * mcrl .Spec .Expose .Internal
227+ }
228+
166229// GetSecret returns the name of the Secret used to store the CRL.
167230func (mcrl * ManagedCRL ) GetSecret () * corev1.Secret {
168231 return & corev1.Secret {
@@ -203,6 +266,16 @@ func (mcrl *ManagedCRL) GetService() *corev1.Service {
203266 }
204267}
205268
269+ // GetIngress returns the name of the Ingress used to expose the CRL.
270+ func (mcrl * ManagedCRL ) GetIngress () * networkingv1.Ingress {
271+ return & networkingv1.Ingress {
272+ ObjectMeta : metav1.ObjectMeta {
273+ Name : fmt .Sprintf ("%s-server" , mcrl .Name ),
274+ Namespace : mcrl .Namespace ,
275+ },
276+ }
277+ }
278+
206279// WithDefaults sets default values on the ManagedCRL resource.
207280func (mcrl * ManagedCRL ) WithDefaults () {
208281 mcrl .Spec .withDefaults ()
@@ -236,6 +309,14 @@ func (ces *CRLExposeSpec) withDefaults() {
236309 ces .Image = & ImageSpec {}
237310 }
238311 ces .Image .withDefaults ()
312+
313+ if ces .Ingress != nil {
314+ ces .Ingress .withDefaults ()
315+ }
316+
317+ if ces .Internal == nil {
318+ ces .Internal = ptr .To (true )
319+ }
239320}
240321
241322func (is * ImageSpec ) withDefaults () {
@@ -247,6 +328,15 @@ func (is *ImageSpec) withDefaults() {
247328 }
248329}
249330
331+ func (is * IngressSpec ) withDefaults () {
332+ if is .Enabled == nil {
333+ is .Enabled = ptr .To (true )
334+ }
335+ if is .Managed == nil {
336+ is .Managed = ptr .To (true )
337+ }
338+ }
339+
250340// Validate validates the ManagedCRL resource.
251341func (mcrl * ManagedCRL ) Validate () error {
252342 err := mcrl .Spec .validate ()
@@ -304,6 +394,14 @@ func (ces *CRLExposeSpec) validate() error {
304394 if err != nil {
305395 return fmt .Errorf ("invalid image configuration: %w" , err )
306396 }
397+
398+ if ces .Ingress != nil {
399+ err := ces .Ingress .validate ()
400+ if err != nil {
401+ return fmt .Errorf ("invalid ingress configuration: %w" , err )
402+ }
403+ }
404+
307405 return nil
308406}
309407
@@ -312,6 +410,18 @@ func (is *ImageSpec) validate() error {
312410 return nil
313411}
314412
413+ func (is * IngressSpec ) validate () error {
414+ if ! * is .Enabled {
415+ return nil
416+ }
417+
418+ if is .Hostname == nil && len (is .IPAddresses ) == 0 {
419+ return fmt .Errorf ("either hostname or ipAddresses must be specified" )
420+ }
421+
422+ return nil
423+ }
424+
315425// ToRevocationListEntry converts a RevocationSpec to an x509.RevocationListEntry.
316426func (rs RevocationSpec ) ToRevocationListEntry () (x509.RevocationListEntry , error ) {
317427 cleanSerial := strings .ReplaceAll (rs .SerialNumber , ":" , "" )
@@ -358,6 +468,33 @@ func (is *ImageSpec) GetImage() string {
358468 return image
359469}
360470
471+ // GetCRLDistributionPoint returns the CRL distribution point URL based on the Ingress configuration.
472+ func (mcrl * ManagedCRL ) GetCRLDistributionPoint () []string {
473+ var urls []string
474+
475+ // Add Ingress URLs if enabled
476+ if mcrl .IsIngressEnabled () {
477+ if mcrl .Spec .Expose .Ingress .Hostname != nil {
478+ urls = append (urls , fmt .Sprintf ("http://%s/crl.der" , * mcrl .Spec .Expose .Ingress .Hostname ))
479+ }
480+ for _ , ip := range mcrl .Spec .Expose .Ingress .IPAddresses {
481+ urls = append (urls , fmt .Sprintf ("http://%s/crl.der" , ip ))
482+ }
483+ }
484+
485+ // Add internal URL if enabled
486+ if mcrl .IsInternalEnabled () {
487+ urls = append (urls , fmt .Sprintf ("http://%s.%s.svc/crl.der" , mcrl .GetName (), mcrl .GetNamespace ()))
488+ }
489+
490+ return urls
491+ }
492+
493+ // NeedsIssuerConfiguration returns true if the Issuer needs to be configured.
494+ func (mcrl * ManagedCRL ) NeedsIssuerConfiguration () bool {
495+ return len (mcrl .GetCRLDistributionPoint ()) > 0
496+ }
497+
361498// SetSecretReady sets the ManagedCRL status to SecretReady.
362499func (mcrl * ManagedCRL ) SetSecretReady () {
363500 condition := metav1.Condition {
@@ -413,3 +550,59 @@ func (mcrl *ManagedCRL) SetPodNotExposed(reason, message string) {
413550 meta .SetStatusCondition (& mcrl .Status .Conditions , condition )
414551 mcrl .Status .PodExposed = ptr .To (false )
415552}
553+
554+ // SetIngressExposed sets the ManagedCRL status to IngressExposed.
555+ func (mcrl * ManagedCRL ) SetIngressExposed () {
556+ condition := metav1.Condition {
557+ Type : "IngressExposed" ,
558+ Status : metav1 .ConditionTrue ,
559+ LastTransitionTime : metav1 .Now (),
560+ Reason : "CRLIngressExposed" ,
561+ Message : "The ingress exposing the CRL is available" ,
562+ ObservedGeneration : mcrl .Generation ,
563+ }
564+ meta .SetStatusCondition (& mcrl .Status .Conditions , condition )
565+ mcrl .Status .IngressExposed = ptr .To (true )
566+ }
567+
568+ // SetIngressNotExposed sets the ManagedCRL status to IngressNotExposed with the given reason and message.
569+ func (mcrl * ManagedCRL ) SetIngressNotExposed (reason , message string ) {
570+ condition := metav1.Condition {
571+ Type : "IngressExposed" ,
572+ Status : metav1 .ConditionFalse ,
573+ LastTransitionTime : metav1 .Now (),
574+ Reason : reason ,
575+ Message : message ,
576+ ObservedGeneration : mcrl .Generation ,
577+ }
578+ meta .SetStatusCondition (& mcrl .Status .Conditions , condition )
579+ mcrl .Status .IngressExposed = ptr .To (false )
580+ }
581+
582+ // SetIssuerConfigured sets the ManagedCRL status to IssuerConfigured.
583+ func (mcrl * ManagedCRL ) SetIssuerConfigured () {
584+ condition := metav1.Condition {
585+ Type : "IssuerConfigured" ,
586+ Status : metav1 .ConditionTrue ,
587+ LastTransitionTime : metav1 .Now (),
588+ Reason : "CRLIssuerConfigured" ,
589+ Message : "The issuer is properly configured" ,
590+ ObservedGeneration : mcrl .Generation ,
591+ }
592+ meta .SetStatusCondition (& mcrl .Status .Conditions , condition )
593+ mcrl .Status .IssuerConfigured = ptr .To (true )
594+ }
595+
596+ // SetIssuerNotConfigured sets the ManagedCRL status to IssuerNotConfigured with the given reason and message.
597+ func (mcrl * ManagedCRL ) SetIssuerNotConfigured (reason , message string ) {
598+ condition := metav1.Condition {
599+ Type : "IssuerConfigured" ,
600+ Status : metav1 .ConditionFalse ,
601+ LastTransitionTime : metav1 .Now (),
602+ Reason : reason ,
603+ Message : message ,
604+ ObservedGeneration : mcrl .Generation ,
605+ }
606+ meta .SetStatusCondition (& mcrl .Status .Conditions , condition )
607+ mcrl .Status .IssuerConfigured = ptr .To (false )
608+ }
0 commit comments