diff --git a/cmd/controller/app/controller.go b/cmd/controller/app/controller.go index 8a7d02707..d25426e29 100644 --- a/cmd/controller/app/controller.go +++ b/cmd/controller/app/controller.go @@ -307,9 +307,12 @@ func buildControllerContext(ctx context.Context, opts *options.ControllerOptions KubeSharedInformerFactory: kubeSharedInformerFactory, SharedInformerFactory: sharedInformerFactory, GWShared: gwSharedInformerFactory, - Namespace: opts.Namespace, - Clock: clock.RealClock{}, - Metrics: metrics.New(log, clock.RealClock{}), + // TODO (@jakexks) / code reviewer: should this be automatically enabled or disabled based on discovering the gateway + // api or a flag? + GatewaySolverEnabled: true, + Namespace: opts.Namespace, + Clock: clock.RealClock{}, + Metrics: metrics.New(log, clock.RealClock{}), ACMEOptions: controller.ACMEOptions{ HTTP01SolverImage: opts.ACMEHTTP01SolverImage, HTTP01SolverResourceRequestCPU: HTTP01SolverResourceRequestCPU, diff --git a/deploy/crds/crd-challenges.yaml b/deploy/crds/crd-challenges.yaml index 873858e24..c43f94307 100644 --- a/deploy/crds/crd-challenges.yaml +++ b/deploy/crds/crd-challenges.yaml @@ -383,9 +383,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -1852,12 +1854,14 @@ spec: type: object properties: gateway: - description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -3328,9 +3332,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -4801,9 +4807,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object diff --git a/deploy/crds/crd-clusterissuers.yaml b/deploy/crds/crd-clusterissuers.yaml index 7a42931d7..db655797a 100644 --- a/deploy/crds/crd-clusterissuers.yaml +++ b/deploy/crds/crd-clusterissuers.yaml @@ -417,9 +417,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -2098,12 +2100,14 @@ spec: type: object properties: gateway: - description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -3787,9 +3791,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -5473,9 +5479,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object diff --git a/deploy/crds/crd-issuers.yaml b/deploy/crds/crd-issuers.yaml index 04165e363..ec3083866 100644 --- a/deploy/crds/crd-issuers.yaml +++ b/deploy/crds/crd-issuers.yaml @@ -417,9 +417,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -2098,12 +2100,14 @@ spec: type: object properties: gateway: - description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -3787,9 +3791,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object @@ -5473,9 +5479,11 @@ spec: description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create or modify HTTPRoutes for a particular gateway class. type: object properties: - class: - description: The Gateway class to use when creating HTTPRoute resources to solve ACME challenges that use this challenge solver. - type: string + labels: + description: The labels to set on HTTPRoute resources to solve ACME challenges that use this challenge solver. + type: object + additionalProperties: + type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges type: object diff --git a/pkg/apis/acme/v1/types_issuer.go b/pkg/apis/acme/v1/types_issuer.go index 7e781e69d..2cf1586ce 100644 --- a/pkg/apis/acme/v1/types_issuer.go +++ b/pkg/apis/acme/v1/types_issuer.go @@ -242,15 +242,17 @@ type ACMEChallengeSolverHTTP01Ingress struct { IngressTemplate *ACMEChallengeSolverHTTP01IngressTemplate `json:"ingressTemplate,omitempty"` } +// The ACMEChallengeSolverHTTP01Gateway solver will create HTTPRoute objects for a Gateway class +// routing to an ACME challenge solver pod. type ACMEChallengeSolverHTTP01Gateway struct { // Optional service type for Kubernetes solver service // +optional ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - // The Gateway class to use when creating HTTPRoute resources to solve ACME + // The labels to set on HTTPRoute resources to solve ACME // challenges that use this challenge solver. // +optional - Class *string `json:"class,omitempty"` + Labels map[string]string `json:"labels,omitempty"` // Optional pod template used to configure the ACME challenge solver pods // used for HTTP01 challenges diff --git a/pkg/apis/acme/v1/zz_generated.deepcopy.go b/pkg/apis/acme/v1/zz_generated.deepcopy.go index ebd6cb4d7..6e128b17c 100644 --- a/pkg/apis/acme/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1/zz_generated.deepcopy.go @@ -191,10 +191,12 @@ func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Gateway) DeepCopyInto(out *ACMEChallengeSolverHTTP01Gateway) { *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.PodTemplate != nil { in, out := &in.PodTemplate, &out.PodTemplate diff --git a/pkg/apis/acme/v1alpha2/types_issuer.go b/pkg/apis/acme/v1alpha2/types_issuer.go index fab94e614..bbfcc4cfe 100644 --- a/pkg/apis/acme/v1alpha2/types_issuer.go +++ b/pkg/apis/acme/v1alpha2/types_issuer.go @@ -246,10 +246,10 @@ type ACMEChallengeSolverHTTP01Gateway struct { // +optional ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - // The Gateway class to use when creating HTTPRoute resources to solve ACME + // The labels to set on HTTPRoute resources to solve ACME // challenges that use this challenge solver. // +optional - Class *string `json:"class,omitempty"` + Labels map[string]string `json:"labels,omitempty"` // Optional pod template used to configure the ACME challenge solver pods // used for HTTP01 challenges diff --git a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go index bf3deaac4..7a3a99c8a 100644 --- a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go @@ -191,10 +191,12 @@ func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Gateway) DeepCopyInto(out *ACMEChallengeSolverHTTP01Gateway) { *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.PodTemplate != nil { in, out := &in.PodTemplate, &out.PodTemplate diff --git a/pkg/apis/acme/v1alpha3/types_issuer.go b/pkg/apis/acme/v1alpha3/types_issuer.go index d15d7148f..7a4e13926 100644 --- a/pkg/apis/acme/v1alpha3/types_issuer.go +++ b/pkg/apis/acme/v1alpha3/types_issuer.go @@ -207,7 +207,7 @@ type ACMEChallengeSolverHTTP01 struct { // The Gateway API is a sig-network community API that models service networking // in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will - // create or modify HTTPRoutes for a particular gateway class. + // create or modify HTTPRoutes. Gateway *ACMEChallengeSolverHTTP01Gateway `json:"gateway,omitempty"` } @@ -246,10 +246,10 @@ type ACMEChallengeSolverHTTP01Gateway struct { // +optional ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - // The Gateway class to use when creating HTTPRoute resources to solve ACME + // The labels to set on HTTPRoute resources to solve ACME // challenges that use this challenge solver. // +optional - Class *string `json:"class,omitempty"` + Labels map[string]string `json:"labels,omitempty"` // Optional pod template used to configure the ACME challenge solver pods // used for HTTP01 challenges diff --git a/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go index 6ad2790ce..6e00517c6 100644 --- a/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go @@ -191,10 +191,12 @@ func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Gateway) DeepCopyInto(out *ACMEChallengeSolverHTTP01Gateway) { *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.PodTemplate != nil { in, out := &in.PodTemplate, &out.PodTemplate diff --git a/pkg/apis/acme/v1beta1/types_issuer.go b/pkg/apis/acme/v1beta1/types_issuer.go index 7cb15767c..1e1e376de 100644 --- a/pkg/apis/acme/v1beta1/types_issuer.go +++ b/pkg/apis/acme/v1beta1/types_issuer.go @@ -246,10 +246,10 @@ type ACMEChallengeSolverHTTP01Gateway struct { // +optional ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - // The Gateway class to use when creating HTTPRoute resources to solve ACME + // The labels to set on HTTPRoute resources to solve ACME // challenges that use this challenge solver. // +optional - Class *string `json:"class,omitempty"` + Labels map[string]string `json:"labels,omitempty"` // Optional pod template used to configure the ACME challenge solver pods // used for HTTP01 challenges diff --git a/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go b/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go index f5bd52be2..1783f1433 100644 --- a/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go @@ -191,10 +191,12 @@ func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Gateway) DeepCopyInto(out *ACMEChallengeSolverHTTP01Gateway) { *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.PodTemplate != nil { in, out := &in.PodTemplate, &out.PodTemplate diff --git a/pkg/controller/acmechallenges/controller.go b/pkg/controller/acmechallenges/controller.go index a62bc6cf0..5112d6108 100644 --- a/pkg/controller/acmechallenges/controller.go +++ b/pkg/controller/acmechallenges/controller.go @@ -21,14 +21,6 @@ import ( "time" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/workqueue" - "github.com/jetstack/cert-manager/pkg/acme/accounts" cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned" cmacmelisters "github.com/jetstack/cert-manager/pkg/client/listers/acme/v1" @@ -40,6 +32,13 @@ import ( "github.com/jetstack/cert-manager/pkg/issuer/acme/dns" "github.com/jetstack/cert-manager/pkg/issuer/acme/http" logf "github.com/jetstack/cert-manager/pkg/logs" + corev1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" ) type controller struct { @@ -114,6 +113,11 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin ingressInformer.HasSynced, } + if ctx.GatewaySolverEnabled { + gwAPIHTTPRouteInformer := ctx.GWShared.Networking().V1alpha1().HTTPRoutes() + mustSync = append(mustSync, gwAPIHTTPRouteInformer.Informer().HasSynced) + } + // set all the references to the listers for used by the Sync function c.challengeLister = challengeInformer.Lister() c.issuerLister = issuerInformer.Lister() diff --git a/pkg/controller/context.go b/pkg/controller/context.go index 712731576..0319b2476 100644 --- a/pkg/controller/context.go +++ b/pkg/controller/context.go @@ -70,7 +70,8 @@ type Context struct { // The Gateway API is an external CRD, which means its shared informers are // not available in controllerpkg.Context. - GWShared gwinformers.SharedInformerFactory + GWShared gwinformers.SharedInformerFactory + GatewaySolverEnabled bool // Namespace is the namespace to operate within. // If unset, operates on all namespaces diff --git a/pkg/internal/apis/acme/types_issuer.go b/pkg/internal/apis/acme/types_issuer.go index c30534c60..d0e53f5b2 100644 --- a/pkg/internal/apis/acme/types_issuer.go +++ b/pkg/internal/apis/acme/types_issuer.go @@ -223,11 +223,10 @@ type ACMEChallengeSolverHTTP01Gateway struct { // +optional ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - // The ingress class to use when creating Ingress resources to solve ACME + // The labels to set on HTTPRoute resources to solve ACME // challenges that use this challenge solver. - // Only one of 'class' or 'name' may be specified. // +optional - Class *string `json:"class,omitempty"` + Labels map[string]string `json:"labels,omitempty"` // Optional pod template used to configure the ACME challenge solver pods // used for HTTP01 challenges diff --git a/pkg/internal/apis/acme/v1/zz_generated.conversion.go b/pkg/internal/apis/acme/v1/zz_generated.conversion.go index d8df98bbc..124cf3ea1 100644 --- a/pkg/internal/apis/acme/v1/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1/zz_generated.conversion.go @@ -657,7 +657,7 @@ func Convert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(in * func autoConvert_v1_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolverHTTP01Gateway(in *v1.ACMEChallengeSolverHTTP01Gateway, out *acme.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = corev1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } @@ -669,7 +669,7 @@ func Convert_v1_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolverHTTP func autoConvert_acme_ACMEChallengeSolverHTTP01Gateway_To_v1_ACMEChallengeSolverHTTP01Gateway(in *acme.ACMEChallengeSolverHTTP01Gateway, out *v1.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = corev1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*v1.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } diff --git a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go index b612cbe22..d0ff33d88 100644 --- a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go @@ -657,7 +657,7 @@ func Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha2_ACMEChallengeSolverHTTP0 func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolverHTTP01Gateway(in *v1alpha2.ACMEChallengeSolverHTTP01Gateway, out *acme.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } @@ -669,7 +669,7 @@ func Convert_v1alpha2_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolv func autoConvert_acme_ACMEChallengeSolverHTTP01Gateway_To_v1alpha2_ACMEChallengeSolverHTTP01Gateway(in *acme.ACMEChallengeSolverHTTP01Gateway, out *v1alpha2.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*v1alpha2.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } diff --git a/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go index de986dc90..126b44390 100644 --- a/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go @@ -657,7 +657,7 @@ func Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha3_ACMEChallengeSolverHTTP0 func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolverHTTP01Gateway(in *v1alpha3.ACMEChallengeSolverHTTP01Gateway, out *acme.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } @@ -669,7 +669,7 @@ func Convert_v1alpha3_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolv func autoConvert_acme_ACMEChallengeSolverHTTP01Gateway_To_v1alpha3_ACMEChallengeSolverHTTP01Gateway(in *acme.ACMEChallengeSolverHTTP01Gateway, out *v1alpha3.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*v1alpha3.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } diff --git a/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go b/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go index 7bc15b297..d55fa445e 100644 --- a/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go @@ -657,7 +657,7 @@ func Convert_acme_ACMEChallengeSolverHTTP01_To_v1beta1_ACMEChallengeSolverHTTP01 func autoConvert_v1beta1_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolverHTTP01Gateway(in *v1beta1.ACMEChallengeSolverHTTP01Gateway, out *acme.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } @@ -669,7 +669,7 @@ func Convert_v1beta1_ACMEChallengeSolverHTTP01Gateway_To_acme_ACMEChallengeSolve func autoConvert_acme_ACMEChallengeSolverHTTP01Gateway_To_v1beta1_ACMEChallengeSolverHTTP01Gateway(in *acme.ACMEChallengeSolverHTTP01Gateway, out *v1beta1.ACMEChallengeSolverHTTP01Gateway, s conversion.Scope) error { out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) out.PodTemplate = (*v1beta1.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } diff --git a/pkg/internal/apis/acme/zz_generated.deepcopy.go b/pkg/internal/apis/acme/zz_generated.deepcopy.go index 1b614f318..42d1902a1 100644 --- a/pkg/internal/apis/acme/zz_generated.deepcopy.go +++ b/pkg/internal/apis/acme/zz_generated.deepcopy.go @@ -191,10 +191,12 @@ func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Gateway) DeepCopyInto(out *ACMEChallengeSolverHTTP01Gateway) { *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.PodTemplate != nil { in, out := &in.PodTemplate, &out.PodTemplate diff --git a/pkg/issuer/acme/http/BUILD.bazel b/pkg/issuer/acme/http/BUILD.bazel index 9c1a45bfa..4b9442599 100644 --- a/pkg/issuer/acme/http/BUILD.bazel +++ b/pkg/issuer/acme/http/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "http.go", + "httproute.go", "ingress.go", "pod.go", "service.go", @@ -27,6 +28,9 @@ go_library( "@io_k8s_apimachinery//pkg/util/errors:go_default_library", "@io_k8s_apimachinery//pkg/util/intstr:go_default_library", "@io_k8s_client_go//listers/core/v1:go_default_library", + "@io_k8s_client_go//util/retry:go_default_library", + "@io_k8s_sigs_gateway_api//apis/v1alpha1:go_default_library", + "@io_k8s_sigs_gateway_api//pkg/client/listers/apis/v1alpha1:go_default_library", "@io_k8s_utils//net:go_default_library", "@io_k8s_utils//pointer:go_default_library", ], diff --git a/pkg/issuer/acme/http/http.go b/pkg/issuer/acme/http/http.go index 2914d5c2d..28f16ccde 100644 --- a/pkg/issuer/acme/http/http.go +++ b/pkg/issuer/acme/http/http.go @@ -27,10 +27,11 @@ import ( "strings" "time" - k8snet "k8s.io/utils/net" - + corev1 "k8s.io/api/core/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" corev1listers "k8s.io/client-go/listers/core/v1" + k8snet "k8s.io/utils/net" + gwapilisters "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1alpha1" cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1" v1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" @@ -61,6 +62,7 @@ type Solver struct { serviceLister corev1listers.ServiceLister ingressLister ingress.InternalIngressLister ingressCreateUpdater ingress.InternalIngressCreateUpdater + httpRouteLister gwapilisters.HTTPRouteLister testReachability reachabilityTest requiredPasses int @@ -84,6 +86,7 @@ func NewSolver(ctx *controller.Context) (*Solver, error) { serviceLister: ctx.KubeSharedInformerFactory.Core().V1().Services().Lister(), ingressLister: ingressLister, ingressCreateUpdater: ingressCreateUpdater, + httpRouteLister: ctx.GWShared.Networking().V1alpha1().HTTPRoutes().Lister(), testReachability: testReachability, requiredPasses: 5, }, nil @@ -101,6 +104,16 @@ func httpDomainCfgForChallenge(ch *cmacme.Challenge) (*cmacme.ACMEChallengeSolve return ch.Spec.Solver.HTTP01.Ingress, nil } +func getServiceType(ch *cmacme.Challenge) (corev1.ServiceType, error) { + if ch.Spec.Solver.HTTP01 != nil && ch.Spec.Solver.HTTP01.Ingress != nil { + return ch.Spec.Solver.HTTP01.Ingress.ServiceType, nil + } + if ch.Spec.Solver.HTTP01 != nil && ch.Spec.Solver.HTTP01.Gateway != nil { + return ch.Spec.Solver.HTTP01.Gateway.ServiceType, nil + } + return "", fmt.Errorf("neither HTTP01 Ingress nor Gateway solvers were found") +} + // Present will realise the resources required to solve the given HTTP01 // challenge validation in the apiserver. If those resources already exist, it // will return nil (i.e. this function is idempotent). @@ -112,8 +125,26 @@ func (s *Solver) Present(ctx context.Context, issuer v1.GenericIssuer, ch *cmacm if svcErr != nil { return utilerrors.NewAggregate([]error{podErr, svcErr}) } - _, ingressErr := s.ensureIngress(ctx, ch, svc.Name) - return utilerrors.NewAggregate([]error{podErr, svcErr, ingressErr}) + var ingressErr, gatewayErr error + if ch.Spec.Solver.HTTP01 != nil { + if ch.Spec.Solver.HTTP01.Ingress != nil { + _, ingressErr = s.ensureIngress(ctx, ch, svc.Name) + return utilerrors.NewAggregate([]error{podErr, svcErr, ingressErr}) + } + if ch.Spec.Solver.HTTP01.Gateway != nil { + _, gatewayErr = s.ensureGatewayHTTPRoute(ctx, ch, svc.Name) + return utilerrors.NewAggregate([]error{podErr, svcErr, gatewayErr}) + } + } + return utilerrors.NewAggregate( + []error{ + podErr, + svcErr, + ingressErr, + gatewayErr, + fmt.Errorf("couldn't Present challenge %s/%s: no Ingress nor Gateway HTTP01 solvers were specified", ch.Namespace, ch.Name), + }, + ) } func (s *Solver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { diff --git a/pkg/issuer/acme/http/httproute.go b/pkg/issuer/acme/http/httproute.go new file mode 100644 index 000000000..e8c8da049 --- /dev/null +++ b/pkg/issuer/acme/http/httproute.go @@ -0,0 +1,180 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package http + +import ( + "context" + "fmt" + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/util/retry" + "k8s.io/utils/pointer" + gwapi "sigs.k8s.io/gateway-api/apis/v1alpha1" + + cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1" + logf "github.com/jetstack/cert-manager/pkg/logs" +) + +// ensureGatewayHTTPRoute ensures that the HTTPRoutes needed to solve a challenge exist. +func (s *Solver) ensureGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge, svcName string) (*gwapi.HTTPRoute, error) { + if ch == nil { + return nil, fmt.Errorf("ensureGatewayHTTPRoute received nil *acme.Challenge") + } + log := logf.FromContext(ctx).WithName("ensureGatewayHTTPRoute") + + httpRoute, err := s.getGatewayHTTPRoute(ctx, ch) + if err != nil { + return nil, err + } + + if httpRoute == nil { + log.Info("creating HTTPRoute for challenge", "name", ch.Name, "namespace", ch.Namespace) + httpRoute, err = s.createGatewayHTTPRoute(ctx, ch, svcName) + if err != nil { + return nil, err + } + return httpRoute, nil + } + + log.Info("Found existing HTTPRoute for challenge", "name", ch.Name, "namespace", ch.Namespace) + + httpRoute, err = s.checkAndUpdateGatewayHTTPRoute(ctx, ch, svcName, httpRoute) + if err != nil { + return nil, err + } + + return nil, fmt.Errorf("not implemented") +} + +func (s *Solver) getGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge) (*gwapi.HTTPRoute, error) { + log := logf.FromContext(ctx).WithName("getGatewayHTTPRoute") + log.Info("getting httpRoutes for challenge", "name", ch.Name, "namespace", ch.Namespace) + httpRoutes, err := s.httpRouteLister.HTTPRoutes(ch.Namespace).List(labels.Set(podLabels(ch)).AsSelector()) + if err != nil { + return nil, err + } + switch len(httpRoutes) { + case 0: + return nil, nil + case 1: + return httpRoutes[0], nil + default: + for _, httpRoute := range httpRoutes[1:] { + log.Info("Deleting extra HTTPRoute", "name", httpRoute.Name, "namespace", httpRoute.Namespace) + err := s.GWClient.NetworkingV1alpha1().HTTPRoutes(httpRoute.Namespace).Delete(ctx, httpRoute.Name, metav1.DeleteOptions{}) + if err != nil { + return nil, err + } + } + return nil, fmt.Errorf("multiple HTTPRoutes found") + } +} + +func (s *Solver) createGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge, svcName string) (*gwapi.HTTPRoute, error) { + labels := podLabels(ch) + if ch.Spec.Solver.HTTP01.Gateway.Labels != nil { + for k, v := range ch.Spec.Solver.HTTP01.Gateway.Labels { + labels[k] = v + } + } + httpRoute := &gwapi.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cm-acme-http-solver", + Namespace: ch.Namespace, + Labels: labels, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(ch, challengeGvk)}, + }, + Spec: generateHTTPRouteSpec(ch, svcName), + } + newHTTPRoute, err := s.GWClient.NetworkingV1alpha1().HTTPRoutes(ch.Namespace).Create(ctx, httpRoute, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + return newHTTPRoute, nil +} + +func (s *Solver) checkAndUpdateGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge, svcName string, httpRoute *gwapi.HTTPRoute) (*gwapi.HTTPRoute, error) { + log := logf.FromContext(ctx, "checkAndUpdateGatewayHTTPRoute") + expectedSpec := generateHTTPRouteSpec(ch, svcName) + actualSpec := httpRoute.Spec + expectedLabels := podLabels(ch) + if ch.Spec.Solver.HTTP01.Gateway.Labels != nil { + for k, v := range ch.Spec.Solver.HTTP01.Gateway.Labels { + expectedLabels[k] = v + } + } + actualLabels := ch.Labels + if reflect.DeepEqual(expectedSpec, actualSpec) && reflect.DeepEqual(expectedLabels, actualLabels) { + return httpRoute, nil + } + log.Info("HTTPRoute is out of date, updating", "name", httpRoute.Name, "namespace", httpRoute.Namespace) + var ret *gwapi.HTTPRoute + var err error + if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { + oldHTTPRoute, err := s.GWClient.NetworkingV1alpha1().HTTPRoutes(httpRoute.Namespace).Get(ctx, httpRoute.Name, metav1.GetOptions{}) + if err != nil { + return err + } + newHTTPRoute := oldHTTPRoute.DeepCopy() + newHTTPRoute.Spec = expectedSpec + newHTTPRoute.Labels = expectedLabels + ret, err = s.GWClient.NetworkingV1alpha1().HTTPRoutes(newHTTPRoute.Namespace).Update(ctx, newHTTPRoute, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + return ret, nil +} + +func generateHTTPRouteSpec(ch *cmacme.Challenge, svcName string) gwapi.HTTPRouteSpec { + return gwapi.HTTPRouteSpec{ + Gateways: &gwapi.RouteGateways{ + Allow: func() *gwapi.GatewayAllowType { a := gwapi.GatewayAllowAll; return &a }(), + }, + Hostnames: []gwapi.Hostname{ + gwapi.Hostname(ch.Spec.DNSName), + }, + Rules: []gwapi.HTTPRouteRule{ + { + Matches: []gwapi.HTTPRouteMatch{ + { + Path: &gwapi.HTTPPathMatch{ + Type: func() *gwapi.PathMatchType { p := gwapi.PathMatchExact; return &p }(), + Value: pointer.String(fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Spec.Token)), + }, + }, + }, + ForwardTo: []gwapi.HTTPRouteForwardTo{ + { + ServiceName: pointer.String(svcName), + Port: func() *gwapi.PortNumber { p := gwapi.PortNumber(acmeSolverListenPort); return &p }(), + }, + }, + }, + }, + } +} + +func (s *Solver) cleanupGatewayHTTPRoutes(_ context.Context, _ *cmacme.Challenge) error { + // Unlike Ingress, we don't modify existing HTTPRoutes so there is nothing to do here. + return nil +} diff --git a/pkg/issuer/acme/http/ingress.go b/pkg/issuer/acme/http/ingress.go index 942b44168..c34715baa 100644 --- a/pkg/issuer/acme/http/ingress.go +++ b/pkg/issuer/acme/http/ingress.go @@ -264,7 +264,11 @@ func (s *Solver) addChallengePathToIngress(ctx context.Context, ch *cmacme.Chall // ingress, or delete the ingress if an existing ingress name is not specified // on the certificate. func (s *Solver) cleanupIngresses(ctx context.Context, ch *cmacme.Challenge) error { - log := logf.FromContext(ctx, "cleanupPods") + log := logf.FromContext(ctx, "cleanupIngresses") + + if ch.Spec.Solver.HTTP01.Ingress == nil { + return nil + } httpDomainCfg, err := httpDomainCfgForChallenge(ch) if err != nil { diff --git a/pkg/issuer/acme/http/pod.go b/pkg/issuer/acme/http/pod.go index 49805729b..ba3cfccd9 100644 --- a/pkg/issuer/acme/http/pod.go +++ b/pkg/issuer/acme/http/pod.go @@ -145,10 +145,14 @@ func (s *Solver) buildPod(ch *cmacme.Challenge) *corev1.Pod { pod := s.buildDefaultPod(ch) // Override defaults if they have changed in the pod template. - if ch.Spec.Solver.HTTP01 != nil && - ch.Spec.Solver.HTTP01.Ingress != nil { - pod = s.mergePodObjectMetaWithPodTemplate(pod, - ch.Spec.Solver.HTTP01.Ingress.PodTemplate) + if ch.Spec.Solver.HTTP01 != nil { + if ch.Spec.Solver.HTTP01.Ingress != nil { + pod = s.mergePodObjectMetaWithPodTemplate(pod, + ch.Spec.Solver.HTTP01.Ingress.PodTemplate) + } else if ch.Spec.Solver.HTTP01.Gateway != nil { + pod = s.mergePodObjectMetaWithPodTemplate(pod, + ch.Spec.Solver.HTTP01.Gateway.PodTemplate) + } } return pod diff --git a/pkg/issuer/acme/http/service.go b/pkg/issuer/acme/http/service.go index 3adf7a450..518f6909f 100644 --- a/pkg/issuer/acme/http/service.go +++ b/pkg/issuer/acme/http/service.go @@ -59,7 +59,7 @@ func (s *Solver) ensureService(ctx context.Context, ch *cmacme.Challenge) (*core // getServicesForChallenge returns a list of services that were created to solve // http challenges for the given domain func (s *Solver) getServicesForChallenge(ctx context.Context, ch *cmacme.Challenge) ([]*corev1.Service, error) { - log := logf.FromContext(ctx) + log := logf.FromContext(ctx).WithName("getServicesForChallenge") podLabels := podLabels(ch) selector := labels.NewSelector() @@ -125,13 +125,11 @@ func buildService(ch *cmacme.Challenge) (*corev1.Service, error) { } // checking for presence of http01 config and if set serviceType is set, override our default (NodePort) - httpDomainCfg, err := httpDomainCfgForChallenge(ch) + serviceType, err := getServiceType(ch) if err != nil { return nil, err } - if httpDomainCfg.ServiceType != "" { - service.Spec.Type = httpDomainCfg.ServiceType - } + service.Spec.Type = serviceType return service, nil }