From d8e92787614485de3c54ba66780a41fb2b6e435d Mon Sep 17 00:00:00 2001 From: Simon Wachter Date: Thu, 12 Oct 2023 17:03:28 +0200 Subject: [PATCH] Implement issuance of temporary certificate when using VirtualServer cert-manager integration --- .../crds/k8s.nginx.org_virtualservers.yaml | 2 ++ ...server-and-virtualserverroute-resources.md | 1 + internal/certmanager/helper.go | 9 ++++++++ internal/certmanager/helper_test.go | 19 +++++++++++++++++ pkg/apis/configuration/v1/types.go | 1 + .../configuration/v1/zz_generated.deepcopy.go | 21 +++++++++++++++++++ pkg/client/clientset/versioned/doc.go | 4 ---- .../informers/externalversions/factory.go | 4 ++-- 8 files changed, 55 insertions(+), 6 deletions(-) delete mode 100644 pkg/client/clientset/versioned/doc.go diff --git a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml index 189cce4f6e..4ec56b8e98 100644 --- a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml +++ b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml @@ -535,6 +535,8 @@ spec: type: string duration: type: string + issue-temp-cert: + type: boolean issuer: type: string issuer-group: diff --git a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md index 93b7624050..2b675ada2d 100644 --- a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md +++ b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md @@ -127,6 +127,7 @@ cert-manager: |``duration`` | This field allows you to configure spec.duration field for the Certificate to be generated. Must be specified using a [Go time.Duration](https://pkg.go.dev/time#ParseDuration) string format, which does not allow the d (days) suffix. You must specify these values using s, m, and h suffixes instead. | ``string`` | No | |``renew-before`` | this annotation allows you to configure spec.renewBefore field for the Certificate to be generated. Must be specified using a [Go time.Duration](https://pkg.go.dev/time#ParseDuration) string format, which does not allow the d (days) suffix. You must specify these values using s, m, and h suffixes instead. | ``string`` | No | |``usages`` | This field allows you to configure spec.usages field for the Certificate to be generated. Pass a string with comma-separated values i.e. ``key agreement,digital signature, server auth``. An exhaustive list of supported key usages can be found in the [the cert-manager api documentation](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.KeyUsage). | ``string`` | No | +|``issue-temp-cert`` | When ``true``, ask cert-manager for a [temporary self-signed certificate](https://cert-manager.io/docs/usage/certificate/#temporary-certificates-while-issuing) pending the issuance of the Certificate. This allows HTTPS-only servers to use ACME HTTP01 challenges when the TLS secret does not exist yet. | ``boolean`` | No | {{% /table %}} ### VirtualServer.Listener diff --git a/internal/certmanager/helper.go b/internal/certmanager/helper.go index 03c60d05b1..d6a8e39103 100644 --- a/internal/certmanager/helper.go +++ b/internal/certmanager/helper.go @@ -41,6 +41,7 @@ var ( issuerKindCmField = "tls.cert-manager.issuer-kind" renewBeforeCmField = "tls.cert-manager.renew-before" usagesCmField = "tls.cert-manager.usages" + certMgrTempCertAnnotation = "cert-manager.io/issue-temporary-certificate" ) // translateVsSpec updates the Certificate spec using the VS TLS Cert-Manager @@ -115,6 +116,14 @@ func translateVsSpec(crt *cmapi.Certificate, vsCmSpec *vsapi.CertManager) error } crt.Spec.Usages = newUsages } + + if vsCmSpec.IssueTempCert { + if crt.ObjectMeta.Annotations == nil { + crt.ObjectMeta.Annotations = make(map[string]string) + } + crt.ObjectMeta.Annotations[certMgrTempCertAnnotation] = "true" + } + if len(errs) > 0 { return errors.New(strings.Join(errs, ", ")) } diff --git a/internal/certmanager/helper_test.go b/internal/certmanager/helper_test.go index da384a2f0b..2de36747cd 100644 --- a/internal/certmanager/helper_test.go +++ b/internal/certmanager/helper_test.go @@ -44,6 +44,14 @@ func Test_translateVsSpec(t *testing.T) { Usages: "server auth,signing", } + validSpecWithTempCert := vsapi.CertManager{ + CommonName: "www.example.com", + Duration: "168h", // 1 week + RenewBefore: "24h", + Usages: "server auth,signing", + IssueTempCert: true, + } + invalidDuration := vsapi.CertManager{ Duration: "un-parsable duration", } @@ -71,6 +79,17 @@ func Test_translateVsSpec(t *testing.T) { a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) }, }, + "success with temp cert": { + crt: gen.Certificate("example-cert"), + cmspec: &validSpecWithTempCert, + check: func(a *assert.Assertions, crt *cmapi.Certificate) { + a.Equal("www.example.com", crt.Spec.CommonName) + a.Equal(&metav1.Duration{Duration: time.Hour * 24 * 7}, crt.Spec.Duration) + a.Equal(&metav1.Duration{Duration: time.Hour * 24}, crt.Spec.RenewBefore) + a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) + a.Equal("true", crt.ObjectMeta.Annotations[certMgrTempCertAnnotation]) + }, + }, "nil cm spec": { crt: gen.Certificate("example-cert"), cmspec: nil, diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 5c88b915bb..c28b13e984 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -300,6 +300,7 @@ type CertManager struct { Duration string `json:"duration"` RenewBefore string `json:"renew-before"` Usages string `json:"usages"` + IssueTempCert bool `json:"issue-temp-cert"` } // VirtualServerStatus defines the status for the VirtualServer resource. diff --git a/pkg/apis/configuration/v1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1/zz_generated.deepcopy.go index aa65af4686..98e086d6c7 100644 --- a/pkg/apis/configuration/v1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1/zz_generated.deepcopy.go @@ -413,6 +413,22 @@ func (in *JWTAuth) DeepCopy() *JWTAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Listener) DeepCopyInto(out *Listener) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Listener. +func (in *Listener) DeepCopy() *Listener { + if in == nil { + return nil + } + out := new(Listener) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Match) DeepCopyInto(out *Match) { *out = *in @@ -1195,6 +1211,11 @@ func (in *VirtualServerRouteStatus) DeepCopy() *VirtualServerRouteStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualServerSpec) DeepCopyInto(out *VirtualServerSpec) { *out = *in + if in.Listener != nil { + in, out := &in.Listener, &out.Listener + *out = new(Listener) + **out = **in + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLS) diff --git a/pkg/client/clientset/versioned/doc.go b/pkg/client/clientset/versioned/doc.go deleted file mode 100644 index 0e0c2a8900..0000000000 --- a/pkg/client/clientset/versioned/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated clientset. -package versioned diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index f3a1605aeb..1176548bcc 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -152,7 +152,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return res } -// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// InformerFor returns the SharedIndexInformer for obj using an internal // client. func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { f.lock.Lock() @@ -225,7 +225,7 @@ type SharedInformerFactory interface { // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) - // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // InformerFor returns the SharedIndexInformer for obj using an internal // client. InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer