The cert-manager manages TLS certificates in Kubernetes clusters using custom resources.
In a multi-cluster environment like Gardener, using existing open source projects for certificate management like cert-manager becomes cumbersome. With this project the separation of concerns between multiple clusters is realized more easily. The cert-controller-manager runs in a secured cluster where the issuer secrets are stored. At the same time it watches an untrusted source cluster and can provide certificates for it. The cert-controller-manager relies on DNS challenges (ACME only) for validating the domain names of the certificates. For this purpose it creates DNSEntry custom resources (in a possible separate dns cluster) to be handled by the companion dns-controller-manager from external-dns-management.
Currently, the cert-controller-manager supports certificate authorities via:
- Automatic Certificate Management Environment (ACME) protocol like Let's Encrypt.
- Certificate Authority (CA): an existing certificate and a private key provided as a TLS Secret.
Index
- Quick start using certificates in a Gardener shoot cluster
- Setting up Issuers
- Requesting a Certificate
- Requesting a Certificate for Ingress
- Requesting a Certificate for Service
- Requesting a Certificate for Gateways
- Demo quick start
- Using the cert-controller-manager
- Renewal of Certificates
- Revoking Certificates
- Metrics
- Using DNSRecords
- Troubleshooting
- Development
- Why not use the community
cert-managersolution?
This component is typically deployed by the Gardener Extension for certificate services to simplify requesting certificates for Gardener shoot clusters.
For a quick start please see Request X.509 Certificates
Before you can obtain certificates from a certificate authority (CA), you need to set up an issuer.
The issuer is specified in the default cluster, while the certificates are specified in the source cluster.
The issuer custom resource contains the configuration and registration data for your account at the CA.
Three modes are supported:
- auto registration
- using an existing account
- using an existing account with external account binding
Auto registration is mainly used for development and test environments. You only need to provide the server URL and an email address. The registration process is done automatically for you by creating a private key and performing the registration at the CA. Optionally you can provide the target secret with the privateKeySecretRef section.
For example see examples/20-issuer-staging.yaml:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Issuer
metadata:
name: issuer-staging
namespace: default
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: [email protected]
autoRegistration: true
# with 'autoRegistration: true' a new account will be created if the secretRef is not existing
privateKeySecretRef:
name: issuer-staging-secret
namespace: defaultIf you already have an existing account at the certificate authority, you need to specify email address and reference the private key from a secret. This is usually the case if you want to use an existing production account from a public CA like Let's Encrypt.
apiVersion: v1
kind: Secret
metadata:
name: my-issuer-secret
namespace: default
type: Opaque
data:
privateKey: LS0tLS1...apiVersion: cert.gardener.cloud/v1alpha1
kind: Issuer
metadata:
name: my-issuer
namespace: default
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: my-issuer-secret
namespace: defaultIn both cases, the state of an issuer resource can be checked on the default cluster with
▶ kubectl get issuer
NAME SERVER EMAIL STATUS TYPE AGE
issuer-staging https://acme-staging-v02.api.letsencrypt.org/directory [email protected] Ready acme 8sACME with External Account Binding (EAB) is needed when a Certificate Authority (CA) requires an additional layer of authentication before allowing certificate issuance through the ACME protocol.
Note
What Is External Account Binding (EAB)? EAB is a mechanism in the ACME protocol (RFC 8555) that allows a CA to bind a certificate request to a pre-authorized account. It ensures that only clients with valid credentials (provided out-of-band) can register and request certificates.
To use an existing account with EAB, you need to provide the EAB credentials, consisting of the Key Identifier (KID) and HMAC key.
The Key Identifier (KID) must be provided in the .spec.acme.externalAccountBinding.keyID field.
The HMAC key must be stored in a Kubernetes secret referenced by keySecretRef with the data key hmacKey.
apiVersion: v1
kind: Secret
metadata:
name: issuer-external-account-secret
namespace: default
type: Opaque
data:
hmacKey: ...apiVersion: cert.gardener.cloud/v1alpha1
kind: Issuer
metadata:
name: my-issuer-with-external-account
namespace: default
spec:
acme:
server: https://some-enterprise-or-commercial-ca.com/directory
email: [email protected]
autoRegistration: true
externalAccountBinding:
keyID: mykey
keySecretRef:
# the secret must contain the data key 'hmacKey'
name: issuer-external-account-secret
namespace: default
# For some special setups, the DNS challenges are only performed pro forma. In this case the
# DNS Entry creation and DNS propagation check can be disabled with 'skipDNSChallengeValidation: true'
# skipDNSChallengeValidation: truePlease see the documentation of your CA on how to create the EAB credentials. For example, DigiCert provides some information here: ACME external account binding (EAB)
This issuer is meant to be used where a central Certificate Authority is already in place. The operator must request/provide by its own means a CA or an intermediate CA. This is mainly used for on-premises and air-gapped environments.
To create a self-signed certificate a dedicated issuer of type selfSigned should be used.
It is also possible to manually create a self-signed certificate using the CA issuer
Manual steps
Create a Self-signed Certificate Authority
▶ openssl genrsa -out CA-key.pem 4096
▶ export CONFIG="
[req]
distinguished_name=dn
[ dn ]
[ ext ]
basicConstraints=CA:TRUE,pathlen:0
"
▶ openssl req \
-new -nodes -x509 -config <(echo "$CONFIG") -key CA-key.pem \
-subj "/CN=Hello" -extensions ext -days 1000 -out CA-cert.pemCreate a TLS secret from the certificate CA-cert.pem and the private key CA-key.pem
▶ kubectl -n default create secret tls issuer-ca-secret \
--cert=CA-cert.pem --key=CA-key.pem -oyaml \
--dry-run=client > secret.yamlThe content of the secret.yaml should look like the following, for a full example see examples/20-issuer-ca.yaml
apiVersion: v1
data:
tls.crt: {base64 certificate}
tls.key: {base64 private key}
kind: Secret
metadata:
name: issuer-ca-secret
type: kubernetes.io/tlsApply the secrets in the cluster and create the issuer, for example see examples/20-issuer-ca.yaml
---
apiVersion: cert.gardener.cloud/v1alpha1
kind: Issuer
metadata:
name: issuer-ca
namespace: default
spec:
ca:
privateKeySecretRef:
name: issuer-ca-secret
namespace: defaultThe state of the issuer resource can be checked on the default cluster with
▶ kubectl get issuer
NAME SERVER EMAIL STATUS TYPE AGE
issuer-ca Ready ca 6sSome details about the CA can be found in the status of the issuer.
▶ kubectl get issuer issuer-ca -ojsonpath='{.status}' | jq '.'
{
"ca": {
"NotAfter": "2023-05-31T14:55:55Z",
"NotBefore": "2020-09-03T14:55:55Z",
"Subject": {
"CommonName": "my-domain.com",
"Country": [
"DE"
],
"Locality": [
"Walldorf"
],
"Organization": [
"Gardener"
],
"OrganizationalUnit": [
"Gardener"
],
"PostalCode": null,
"Province": [
"BW"
],
"SerialNumber": "1E04A2C8F057AC890F45FEC5446AE4DDA73EA1D5",
"StreetAddress": null
}
},
"observedGeneration": 1,
"requestsPerDayQuota": 10000,
"state": "Ready",
"type": "ca"
}This issuer is meant to be used when you want to create a fully managed self-signed certificate.
Configure your shoot to allow custom issuers in the shoot cluster. By default, issuers are created in the control plane of your cluster.
kind: Shoot
...
spec:
extensions:
- type: shoot-cert-service
providerConfig:
apiVersion: service.cert.extensions.gardener.cloud/v1alpha1
kind: CertConfig
shootIssuers:
enabled: true # if true, allows to specify issuers in the shoot cluster
...Create and deploy a self-signed issuer in your shoot cluster (examples/20-issuer-selfsigned.yaml)
apiVersion: cert.gardener.cloud/v1alpha1
kind: Issuer
metadata:
name: issuer-selfsigned
namespace: default
spec:
selfSigned: {}
Create a certificate (examples/30-cert-selfsigned.yaml).
Please note that spec.isCA must be set to true to create a self-signed certificate. The duration (life-time) of the certificate
as well as the private key algorithm and key size may be specified. Duration value must be in units accepted by Go time.ParseDuration
(see here), and it must be at least 1440h (60 days) with the default renewal window of 30 days.
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-selfsigned
namespace: default
spec:
commonName: cert1.mydomain.com
isCA: true
# optional: default is 90 days (2160h). Must be at least 60 days (1440h) with the default renewal window of 30 days.
# duration: 1440h
# optional defaults to RSA 2048
#privateKey:
# algorithm: ECDSA
# size: 384
issuerRef:
name: issuer-selfsigned
namespace: default # must be specified when issuer runs in shoot!
# optional: secret where the certificate should be stored
#secretRef:
# name: cert-selfsigned-foo
# namespace: defaultTo obtain a certificate for a domain, you specify a certificate custom resource on the source cluster.
You can specify the issuer explicitly by reference. If there is no issuer reference, the default issuer is
used (provided as command line option). You must either specify the commonName and further optional dnsNames or
you can also start with a certificate signing request (CSR).
For domain validation, the cert-controller-manager only supports DNS challenges. For this purpose it relies
on the dns-controller-manager from the external-dns-management
project.
If any domain name (commonName or any item from dnsNames) needs to be validated, it creates a custom resource
DNSEntry in the dns cluster.
When the certificate authority sees the temporary DNS record, the certificate is stored in a secret finally.
The name of the secret can be specified explicitly with secretRef.name and its namespace with secretRef.namespace.
By default, it will be stored in the same namespace as the certificate on the source cluster
The certificate is checked for renewal periodically. The renewal is performed automatically and the secret is updated. Default values for periodical check is daily, the certificate is renewed if its validity expires within 60 days.
For example see examples/30-cert-simple.yaml:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-simple
namespace: default
spec:
commonName: cert1.mydomain.com
dnsNames:
- cert1-foo.mydomain.com
- cert1-bar.mydomain.com
# if issuer is not specified, the default issuer is used
issuerRef:
name: issuer-stagingThis option is useful if a delegated domain for DNS01 challenge should be used.
If you don't have permissions for the DNS hosted zone to write the DNS record for the challenge, you can
ask the domain owner to provide a CNAME record to domain name in a writable hosted zone.
Example:
Assume you want to request a certificate for my-service.example-domain.com, but you only
have write permissions for the hosted zone sandbox.other-domain.com.
-
The owner of
example-domain.comadds thisCNAMEDNS record_acme-challenge.my-service.example-domain.com->_acme-challenge.my-service.sandbox.other-domain.com -
Set
followCNAME: truein the certificate specapiVersion: cert.gardener.cloud/v1alpha1 kind: Certificate metadata: name: cert-follow namespace: default spec: commonName: my-service.example-domain.com followCNAME: true
In this case, the cert-management controller will see the CNAME record and write the TXT record for the
DNS challenge to the target, i.e. _acme-challenge.my-service.sandbox.other-domain.com.
If you are using an annotated ingress or service resource, the option is set by the annotation cert.gardener.cloud/follow-cname=true.
If the CA offers multiple certificate chains, you can select the chain with the optional preferredChain field.
The value is the Subject Common Name of the issuer. If no match, the default offered chain will be used.
Please consult the documentation of the ACME server about offered certificate chains.
Example:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-follow
namespace: default
spec:
commonName: my-service.example-domain.com
preferredChain: "ISRG Root X1"The secretLabels section allows to specify labels to be set for the certificate secret.
Example:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-secret-labels
namespace: default
spec:
commonName: my-service.example-domain.com
secretRef:
name: my-secret
secretLabels:
key1: value1
key2: value2In this case the secret my-secret will contains the labels.
The private key algorithm and size used by default are deployment specific. To override these defaults, you may override them in the certificate itself. Please note, that changing these values will lead to an immediate renewal of the certificate. In case the default values have changed in the deployment, and you have not overwritten it, the new default values will only apply to new certificates or when a certificate is renewed.
Note: The default algorithm and sizes can be overwritten by command line arguments --default-private-key-algorithm,
--default-rsa-private-key-size, --default-ecdsa-private-key-size
Add the privateKey section to specify private key algorithm and/or size.
Example:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-ecdsa
namespace: default
spec:
commonName: my-service.example-domain.com
secretRef:
name: my-secret
privateKey:
algorithm: ECDSA
size: 384Allowed values for spec.privateKey.algorithm are RSA and ECDSA.
For RSA, the allowed key sizes are 2048, 3072, and 4096. If the size field is not specified,
a deployment specific default value will be used.
For ECDSA, the allowed key sizes are 256 and 384. If the size field is not specified,
a deployment specific default value will be used.
You can provide a complete CSR in PEM format (and encoded as Base64).
For example see examples/30-cert-csr.yaml:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-csr
namespace: default
spec:
csr: LS0tLS1CRUd...
issuerRef:
name: issuer-stagingBy default, the certificate secret contains the TLS certificate using the standard
data entries tls.key, tls.crt and ca.crt.
With the keystores section in the certificate spec, bundles in the form of JKS or PKCS#12 keystores
can be requested to be stored in the secret additionally.
For the JKS (Java keystore) format, the additional data entries are keystore.jks and truststore.jks.
For PKCS#12 format, the data entries are named keystore.p12 and truststore.p12.
In both cases, the keystore file contains all the data, but the truststore file only the CA.
The keystores are secured by the password as provided with the secret/key pair passwordSecretRef.
For example see examples/30-cert-simple-with-keystores.yaml:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-simple-with-keystores
namespace: default
spec:
commonName: ...
# enable keystore creation for both JKS and PKCS#12
# This will create additional data entries in the certificate secret named `keystore.jks`, `truststore.jks` for JKS
# and `keystore.p12`, `truststore.p12` for PKCS#12
keystores:
jks:
create: true
passwordSecretRef:
secretName: keystore-secret
key: password
pkcs12:
create: true
passwordSecretRef:
secretName: keystore-secret
key: password Note
ACME providers such as Let's Encrypt typically only support DNS names as SANs.
Avoid specifying email addresses, IP addresses, or URIs as SANs when using an ACME Issuer.
You can use all SANs with a self-signed Issuer or CA Issuer.
You can specify subject alternative names (SANs) in the Certificate spec or by using a Certificate Signing Request (CSR).
The supported SANs are:
- DNS names –
.spec.dnsNames - Email addresses –
.spec.emailAddresses - IP addresses –
.spec.ipAddresses - URIs –
.spec.uris
An example of a Certificate with SANs is shown below:
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cert-with-sans
namespace: default
spec:
commonName: foo.example.com
dnsNames:
- bar.example.com
emailAddresses:
- [email protected]
ipAddresses:
- 1.1.1.1
uris:
- spiffe://example.com/foo Add the annotation cert.gardener.cloud/purpose: managed to the Ingress resource.
The cert-controller-manager will then automatically request a certificate for all domains given by the hosts in the
tls section of the Ingress spec.
For compatibility with the Gardener Cert-Broker, you can
alternatively use the deprecated label garden.sapcloud.io/purpose: managed-cert for the same outcome.
See also examples/40-ingress-echoheaders.yaml:
-
Create the Ingress Resource (optional)
In order to request a certificate for a domain managed by
cert-controller-manageran Ingress is required. In case you don’t already have one, take the following as an example:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: vuejs-ingress spec: tls: # Gardener managed default domain. # The first host is used as common name if it does not exceed 64 characters - hosts: - test.ingress.<GARDENER-CLUSTER>.<GARDENER-PROJECT>.shoot.example.com # Certificate and private key reside in this secret. secretName: testsecret-tls rules: - host: test.ingress.<GARDENER-CLUSTER>.<GARDENER-PROJECT>.shoot.example.com http: paths: - backend: service: name: vuejs-svc port: number: 8080 path: / pathType: Prefix
-
Annotate the Ingress Resource
The annotation
cert.gardener.cloud/purpose: managedinstructscert-controller-managerto handle certificate issuance for the domains found in labeled Ingress.apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: tls-example-ingress annotations: # Let Gardener manage certificates for this Ingress. cert.gardener.cloud/purpose: managed #dns.gardener.cloud/class: garden # needed on Gardener shoot clusters for managed DNS record creation (if not covered by `*.ingress.<GARDENER-CLUSTER>.<GARDENER-PROJECT>.shoot.example.com) #cert.gardener.cloud/commonname: "*.demo.mydomain.com" # optional, if not specified the first name from spec.tls[].hosts is used as common name #cert.gardener.cloud/dnsnames: "" # optional, if not specified the names from spec.tls[].hosts are used #cert.gardener.cloud/follow-cname: "true" # optional, to activate CNAME following for the DNS challenge #cert.gardener.cloud/secret-labels: "key1=value1,key2=value2" # optional labels for the certificate secret #cert.gardener.cloud/issuer: issuer-name # optional to specify custom issuer (use namespace/name for shoot issuers) #cert.gardener.cloud/preferred-chain: "chain name" # optional to specify preferred-chain (value is the Subject Common Name of the root issuer) #cert.gardener.cloud/private-key-algorithm: ECDSA # optional to specify algorithm for private key, allowed values are 'RSA' or 'ECDSA' #cert.gardener.cloud/private-key-size: "384" # optional to specify size of private key, allowed values for RSA are "2048", "3072", "4096" and for ECDSA "256" and "384" # annotations needed when using DNSRecords #cert.gardener.cloud/dnsrecord-provider-type: aws-route53 #cert.gardener.cloud/dnsrecord-secret-ref: myns/mysecret spec: tls: - hosts: - echoheaders.demo.mydomain.com secretName: cert-echoheaders rules: - host: echoheaders.demo.mydomain.com http: paths: - backend: service: name: echoheaders port: number: 80 path: / pathType: Prefix
The annotation
cert.gardener.cloud/commonnamecan be set to explicitly specify the common name. If no set, the first name ofspec.tls.hostsis used as common name. The annotationcert.gardener.cloud/dnsnamescan be used to explicitly specify the alternative DNS names. If no set, the names ofspec.tls.hostsare used.
-
-
Check status
A
certificatecustom resource is created in the same namespace of thesourcecluster. You can either check the status of this certificate resource withkubectl get certor you can check the events for the ingress withkubectl get eventsThe certificate is stored in the secret as specified in the Ingress resource.
If you have a service of type LoadBalancer, you can use the annotation cert.gardener.cloud/secretname together
with the annotation dns.gardener.cloud/dnsnames from the dns-controller-manager to trigger automatic creation of
a certificate. If you want to share a certificate between multiple services and ingresses, using the annotations
cert.gardener.cloud/commonname and cert.gardener.cloud/dnsnames may be helpful.
apiVersion: v1
kind: Service
metadata:
annotations:
cert.gardener.cloud/secretname: test-service-secret
dns.gardener.cloud/dnsnames: test-service.demo.mydomain.com
#dns.gardener.cloud/class: garden # needed on Gardener shoot clusters for managed DNS record creation
#cert.gardener.cloud/commonname: "*.demo.mydomain.com" # optional, if not specified the first name from dns.gardener.cloud/dnsnames is used as common name
#cert.gardener.cloud/dnsnames: "" # optional, if specified overrides dns.gardener.cloud/dnsnames annotation for certificate names
#cert.gardener.cloud/follow-cname: "true" # optional, to activate CNAME following for the DNS challenge
#cert.gardener.cloud/secret-labels: "key1=value1,key2=value2" # optional labels for the certificate secret
#cert.gardener.cloud/issuer: issuer-name # optional to specify custom issuer (use namespace/name for shoot issuers)
#cert.gardener.cloud/preferred-chain: "chain name" # optional to specify preferred-chain (value is the Subject Common Name of the root issuer)
#cert.gardener.cloud/private-key-algorithm: ECDSA # optional to specify algorithm for private key, allowed values are 'RSA' or 'ECDSA'
#cert.gardener.cloud/private-key-size: "384" # optional to specify size of private key, allowed values for RSA are "2048", "3072", "4096" and for ECDSA "256" and "384"
#cert.gardener.cloud/secret-namespace: "my-namespace" # optional to specify the namespace where the certificate secret should be created
# annotations needed when using DNSRecords
#cert.gardener.cloud/dnsrecord-provider-type: aws-route53
#cert.gardener.cloud/dnsrecord-secret-ref: myns/mysecret
dns.gardener.cloud/ttl: "600"
name: test-service
namespace: default
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
type: LoadBalancerThe annotation cert.gardener.cloud/commonname is optional. If not specified, the first name of the annotation
dns.gardener.cloud/dnsnames is used as common name if it does not exceed 64 characters. It is useful to specify it explicitly, if no DNSEntry
should be created for the common name by the dns-controller-manager.
A typical use case is if the common name (limited to 64 characters) is set only to
deal with real domain names specified with dns.gardener.cloud/dnsnames which are longer than 64 characters.
The annotation cert.gardener.cloud/dnsnames can be used to explicitly specify the alternative DNS names.
If set, it overrides the values from the annotation dns.gardener.cloud/dnsnames for the certificate (but not for
creating DNS records by the dns-controller-manager).
If you want to share a certificate between multiple services and ingresses, using the annotations cert.gardener.cloud/commonname and
cert.gardener.cloud/dnsnames may be helpful. For example, to share a wildcard certificate, you should add these two annotations
cert.gardener.cloud/commonname: "*.demo.mydomain.com"
cert.gardener.cloud/dnsnames: ""This will create or reuse a certificate for *.demo.mydomain.com. An existing certificate is automatically reused,
if it has exactly the same common name and DNS names.
The annotation cert.gardener.cloud/secret-namespace can be used to change the namespace, the TLS secret is created in.
By default, it is created in the same namespace as the service.
There are source controllers for Gateways from Istio or the new
Kubernetes Gateway API.
By annotating the Gateway resource with the cert.gardener.cloud/purpose=managed annotation,
certificates are managed automatically for the hosts.
For Istio, gateways for API versions networking.istio.io/v1, networking.istio.io/v1beta1, and networking.istio.io/v1alpha3 are supported.
To enable automatic management of Certificate resources, annotate the Istio Gateway resource with cert.gardener.cloud/purpose=managed.
The domain names are extracted from the spec.servers.hosts field and from the field spec.hosts of related VirtualService resources.
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
annotations:
cert.gardener.cloud/purpose: managed
#cert.gardener.cloud/commonname: "*.demo.mydomain.com" # optional, if not specified the first name from spec.tls[].hosts is used as common name
#cert.gardener.cloud/dnsnames: "" # optional, if not specified the names from spec.tls[].hosts are used
#cert.gardener.cloud/follow-cname: "true" # optional, to activate CNAME following for the DNS challenge
#cert.gardener.cloud/secret-labels: "key1=value1,key2=value2" # optional labels for the certificate secret
#cert.gardener.cloud/issuer: issuer-name # optional to specify custom issuer (use namespace/name for shoot issuers)
#cert.gardener.cloud/preferred-chain: "chain name" # optional to specify preferred-chain (value is the Subject Common Name of the root issuer)
#cert.gardener.cloud/private-key-algorithm: ECDSA # optional to specify algorithm for private key, allowed values are 'RSA' or 'ECDSA'
#cert.gardener.cloud/private-key-size: "384" # optional to specify size of private key, allowed values for RSA are "2048", "3072", "4096" and for ECDSA "256" and "384"
#cert.gardener.cloud/secret-namespace: "istio-system" # optional to specify the namespace where the certificate secret should be created
# annotations needed when using DNSRecords
#cert.gardener.cloud/dnsrecord-provider-type: aws-route53
#cert.gardener.cloud/dnsrecord-secret-ref: myns/mysecret
name: my-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- uk.example.com
- eu.example.com
port:
name: https-443
number: 443
protocol: HTTPS
tls: # this server is ignored by the cert-controller-manager, as `tls.credentialName` is not set
mode: SIMPLE
privateKey: /etc/certs/privatekey.pem
serverCertificate: /etc/certs/servercert.pem
- hosts:
- bookinfo-namespace/*.example2.com
port:
name: https-9443
number: 9443
protocol: HTTPS
tls:
credentialName: my-secret # only servers with credentialName will be considered
mode: SIMPLEIn this case, only a Certificate resource would be created with domain name *.example2.com, as the first server item
specifies no tls.credentialName field.
The annotation cert.gardener.cloud/secret-namespace can be used to change the namespace, the TLS secret is created in.
By default, it is created in the same namespace as the gateway object.
See the Istio tutorial for a more detailed example.
The Gateway API versions gateway.networking.k8s.io/v1 and gateway.networking.k8s.io/v1beta1 are supported.
To enable automatic management of Certificate resources, annotate the Gateway API Gateway resource with cert.gardener.cloud/purpose=managed.
The domain names are extracted from the spec.listeners.hostnames field and from the field spec.hostnames of related HTTPRoute resources.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
annotations:
cert.gardener.cloud/purpose: managed
#cert.gardener.cloud/commonname: "*.demo.mydomain.com" # optional, if not specified the first name from spec.tls[].hosts is used as common name
#cert.gardener.cloud/dnsnames: "" # optional, if not specified the names from spec.tls[].hosts are used
#cert.gardener.cloud/follow-cname: "true" # optional, to activate CNAME following for the DNS challenge
#cert.gardener.cloud/secret-labels: "key1=value1,key2=value2" # optional labels for the certificate secret
#cert.gardener.cloud/issuer: issuer-name # optional to specify custom issuer (use namespace/name for shoot issuers)
#cert.gardener.cloud/preferred-chain: "chain name" # optional to specify preferred-chain (value is the Subject Common Name of the root issuer)
#cert.gardener.cloud/private-key-algorithm: ECDSA # optional to specify algorithm for private key, allowed values are 'RSA' or 'ECDSA'
#cert.gardener.cloud/private-key-size: "384" # optional to specify size of private key, allowed values for RSA are "2048", "3072", "4096" and for ECDSA "256" and "384"
# annotations needed when using DNSRecords
#cert.gardener.cloud/dnsrecord-provider-type: aws-route53
#cert.gardener.cloud/dnsrecord-secret-ref: myns/mysecret
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners:
- allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
hostname: foo.example.com
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- name: my-tls-secret # note: listeners are only considered if they have exactly one certificateRefs itemIn this case, a single Certificate resource with domain name foo.example.com would be created.
See the Gateway API tutorial for a more detailed example.
-
Run dns-controller-manager with:
./dns-controller-manager --controllers=azure-dns --identifier=myOwnerId --disable-namespace-restriction
-
Ensure provider and its secret, e.g.
kubectl apply -f azure-secret.yaml kubectl apply -f azure-provider.yaml
-
check with
▶ kubectl get dnspr NAME TYPE STATUS AGE azure-playground azure-dns Ready 28m
-
-
Create test namespace
kubectl create ns test -
Run cert-controller-manager
./cert-controller-manager
-
Register user
[email protected]at Let's Encryptkubectl apply -f examples/20-issuer-staging.yaml
-
check with
▶ kubectl get issuer NAME SERVER EMAIL STATUS TYPE AGE issuer-staging https://acme-staging-v02.api.letsencrypt.org/directory [email protected] Ready acme 8s
-
-
Request a certificate for
cert1.my-domain.comkubectl apply -f examples/30-cert-simple.yaml
If this certificate has been already registered for the same issuer before, it will be returned immediately from the ACME server. Otherwise, a DNS challenge is started using a temporary DNSEntry to be set by
dns-controller-manager-
check with
▶ kubectl get cert -o wide NAME COMMON NAME ISSUER STATUS EXPIRATION_DATE DNS_NAMES AGE cert-simple cert1.mydomain.com issuer-staging Ready 2019-11-10T09:48:17Z [cert1.my-domain.com] 34s
-
The cert-controller-manager communicates with up to four different clusters:
- default
used for managing issuers and lease management.
The path to the kubeconfig is specified with command line option
--kubeconfig. - source
used for watching resources ingresses, services and certificates
The path to the kubeconfig is specified with command line option
--source. If option is omitted, the default cluster is used for source. - dns
used to write temporary DNSEntries for DNS challenges
The path to the kubeconfig is specified with command line option
--dns. If option is omitted, the default cluster is used for dns. - target
used for storing generated certificates (and issuers if
--allow-target-issuersoption is set) The path to the kubeconfig is specified with command line option--target. If option is omitted, the source cluster is also used for target.
The complete list of options is:
Usage:
cert-controller-manager [flags]
Flags:
--accepted-maintainers string accepted maintainer key(s) for crds
--acme-deactivate-authorizations if true authorizations are always deactivated after each ACME certificate request
--allow-target-issuers If true, issuers are also watched on the target cluster
--bind-address-http string HTTP server bind address
--cascade-delete If true, certificate secrets are deleted if dependent resources (certificate, ingress) are deleted
--cert-class string Identifier used to differentiate responsible controllers for entries
--cert-target-class string Identifier used to differentiate responsible dns controllers for target entries
--config string config file
-c, --controllers string comma separated list of controllers to start (<name>,<group>,all)
--cpuprofile string set file for cpu profiling
--default-ecdsa-private-key-size int Default certificate private key size for 'ecdsa' algorithm.
--default-issuer string name of default issuer (from default cluster)
--default-issuer-domain-ranges string domain range restrictions when using default issuer separated by comma
--default-private-key-algorithm string default algorithm for certificate private keys
--default-requests-per-day-quota int Default value for requestsPerDayQuota if not set explicitly in the issuer spec.
--default-rsa-private-key-size int Default certificate private key size for 'rsa' algorithm.
--default.pool.resync-period duration Period for resynchronization for pool default
--default.pool.size int Worker pool size for pool default
--disable-namespace-restriction disable access restriction for namespace local access only
--dns string cluster for writing challenge DNSEntries or DNSRecords
--dns-class string class for creating challenge DNSEntries (in DNS cluster)
--dns-namespace string namespace for creating challenge DNSEntries or DNSRecords (in DNS cluster)
--dns-owner-id string ownerId for creating challenge DNSEntries
--dns.disable-deploy-crds disable deployment of required crds for cluster dns
--dns.id string id for cluster dns
--dns.migration-ids string migration id for cluster dns
--force-crd-update enforce update of crds even they are unmanaged
--grace-period duration inactivity grace period for detecting end of cleanup for shutdown
-h, --help help for cert-controller-manager
--httproutes.pool.size int Worker pool size for pool httproutes
--ingress-cert.cert-class string Identifier used to differentiate responsible controllers for entries of controller ingress-cert
--ingress-cert.cert-target-class string Identifier used to differentiate responsible dns controllers for target entries of controller ingress-cert
--ingress-cert.default.pool.resync-period duration Period for resynchronization for pool default of controller ingress-cert
--ingress-cert.default.pool.size int Worker pool size for pool default of controller ingress-cert
--ingress-cert.pool.resync-period duration Period for resynchronization of controller ingress-cert
--ingress-cert.pool.size int Worker pool size of controller ingress-cert
--ingress-cert.target-name-prefix string name prefix in target namespace for cross cluster generation of controller ingress-cert
--ingress-cert.target-namespace string target namespace for cross cluster generation of controller ingress-cert
--ingress-cert.targets.pool.size int Worker pool size for pool targets of controller ingress-cert
--issuer-namespace string namespace to lookup issuers on default cluster
--issuer.acme-deactivate-authorizations if true authorizations are always deactivated after each ACME certificate request of controller issuer
--issuer.allow-target-issuers If true, issuers are also watched on the target cluster of controller issuer
--issuer.cascade-delete If true, certificate secrets are deleted if dependent resources (certificate, ingress) are deleted of controller issuer
--issuer.cert-class string Identifier used to differentiate responsible controllers for entries of controller issuer
--issuer.default-ecdsa-private-key-size int Default certificate private key size for 'ecdsa' algorithm. of controller issuer
--issuer.default-issuer string name of default issuer (from default cluster) of controller issuer
--issuer.default-issuer-domain-ranges string domain range restrictions when using default issuer separated by comma of controller issuer
--issuer.default-private-key-algorithm string default algorithm for certificate private keys of controller issuer
--issuer.default-requests-per-day-quota int Default value for requestsPerDayQuota if not set explicitly in the issuer spec. of controller issuer
--issuer.default-rsa-private-key-size int Default certificate private key size for 'rsa' algorithm. of controller issuer
--issuer.default.pool.resync-period duration Period for resynchronization for pool default of controller issuer
--issuer.default.pool.size int Worker pool size for pool default of controller issuer
--issuer.dns-class string class for creating challenge DNSEntries (in DNS cluster) of controller issuer
--issuer.dns-namespace string namespace for creating challenge DNSEntries or DNSRecords (in DNS cluster) of controller issuer
--issuer.dns-owner-id string ownerId for creating challenge DNSEntries of controller issuer
--issuer.issuer-namespace string namespace to lookup issuers on default cluster of controller issuer
--issuer.issuers.pool.size int Worker pool size for pool issuers of controller issuer
--issuer.pool.resync-period duration Period for resynchronization of controller issuer
--issuer.pool.size int Worker pool size of controller issuer
--issuer.precheck-additional-wait duration additional wait time after DNS propagation check of controller issuer
--issuer.precheck-nameservers string Default DNS nameservers used for checking DNS propagation. If explicity set empty, it is tried to read them from /etc/resolv.conf of controller issuer
--issuer.propagation-timeout duration propagation timeout for DNS challenge of controller issuer
--issuer.renewal-overdue-window duration certificate is counted as 'renewal overdue' if its validity period is shorter (metrics cert_management_overdue_renewal_certificates) of controller issuer
--issuer.renewal-window duration certificate is renewed if its validity period is shorter of controller issuer
--issuer.revocations.pool.size int Worker pool size for pool revocations of controller issuer
--issuer.secrets.pool.size int Worker pool size for pool secrets of controller issuer
--issuer.use-dnsrecords if true, DNSRecords (using Gardener Provider extensions) are created instead of DNSEntries of controller issuer
--issuers.pool.size int Worker pool size for pool issuers
--istio-gateways-dns.cert-class string Identifier used to differentiate responsible controllers for entries of controller istio-gateways-dns
--istio-gateways-dns.cert-target-class string Identifier used to differentiate responsible dns controllers for target entries of controller istio-gateways-dns
--istio-gateways-dns.default.pool.resync-period duration Period for resynchronization for pool default of controller istio-gateways-dns
--istio-gateways-dns.default.pool.size int Worker pool size for pool default of controller istio-gateways-dns
--istio-gateways-dns.pool.resync-period duration Period for resynchronization of controller istio-gateways-dns
--istio-gateways-dns.pool.size int Worker pool size of controller istio-gateways-dns
--istio-gateways-dns.target-name-prefix string name prefix in target namespace for cross cluster generation of controller istio-gateways-dns
--istio-gateways-dns.target-namespace string target namespace for cross cluster generation of controller istio-gateways-dns
--istio-gateways-dns.targets.pool.size int Worker pool size for pool targets of controller istio-gateways-dns
--istio-gateways-dns.targetsources.pool.size int Worker pool size for pool targetsources of controller istio-gateways-dns
--istio-gateways-dns.virtualservices.pool.size int Worker pool size for pool virtualservices of controller istio-gateways-dns
--k8s-gateways-dns.cert-class string Identifier used to differentiate responsible controllers for entries of controller k8s-gateways-dns
--k8s-gateways-dns.cert-target-class string Identifier used to differentiate responsible dns controllers for target entries of controller k8s-gateways-dns
--k8s-gateways-dns.default.pool.resync-period duration Period for resynchronization for pool default of controller k8s-gateways-dns
--k8s-gateways-dns.default.pool.size int Worker pool size for pool default of controller k8s-gateways-dns
--k8s-gateways-dns.httproutes.pool.size int Worker pool size for pool httproutes of controller k8s-gateways-dns
--k8s-gateways-dns.pool.resync-period duration Period for resynchronization of controller k8s-gateways-dns
--k8s-gateways-dns.pool.size int Worker pool size of controller k8s-gateways-dns
--k8s-gateways-dns.target-name-prefix string name prefix in target namespace for cross cluster generation of controller k8s-gateways-dns
--k8s-gateways-dns.target-namespace string target namespace for cross cluster generation of controller k8s-gateways-dns
--k8s-gateways-dns.targets.pool.size int Worker pool size for pool targets of controller k8s-gateways-dns
--kubeconfig string default cluster access
--kubeconfig.disable-deploy-crds disable deployment of required crds for cluster default
--kubeconfig.id string id for cluster default
--kubeconfig.migration-ids string migration id for cluster default
--lease-duration duration lease duration
--lease-name string name for lease object
--lease-renew-deadline duration lease renew deadline
--lease-resource-lock string determines which resource lock to use for leader election, defaults to 'leases'
--lease-retry-period duration lease retry period
-D, --log-level string logrus log level
--maintainer string maintainer key for crds (default "cert-controller-manager")
--name string name used for controller manager (default "cert-controller-manager")
--namespace string namespace for lease (default "kube-system")
-n, --namespace-local-access-only enable access restriction for namespace local access only (deprecated)
--omit-lease omit lease for development
--plugin-file string directory containing go plugins
--pool.resync-period duration Period for resynchronization
--pool.size int Worker pool size
--precheck-additional-wait duration additional wait time after DNS propagation check
--precheck-nameservers string Default DNS nameservers used for checking DNS propagation. If explicity set empty, it is tried to read them from /etc/resolv.conf
--propagation-timeout duration propagation timeout for DNS challenge
--renewal-overdue-window duration certificate is counted as 'renewal overdue' if its validity period is shorter (metrics cert_management_overdue_renewal_certificates)
--renewal-window duration certificate is renewed if its validity period is shorter
--revocations.pool.size int Worker pool size for pool revocations
--secrets.pool.size int Worker pool size for pool secrets
--server-port-http int HTTP server port (serving /healthz, /metrics, ...)
--service-cert.cert-class string Identifier used to differentiate responsible controllers for entries of controller service-cert
--service-cert.cert-target-class string Identifier used to differentiate responsible dns controllers for target entries of controller service-cert
--service-cert.default.pool.resync-period duration Period for resynchronization for pool default of controller service-cert
--service-cert.default.pool.size int Worker pool size for pool default of controller service-cert
--service-cert.pool.resync-period duration Period for resynchronization of controller service-cert
--service-cert.pool.size int Worker pool size of controller service-cert
--service-cert.target-name-prefix string name prefix in target namespace for cross cluster generation of controller service-cert
--service-cert.target-namespace string target namespace for cross cluster generation of controller service-cert
--service-cert.targets.pool.size int Worker pool size for pool targets of controller service-cert
--source string source cluster to watch for ingresses and services
--source.disable-deploy-crds disable deployment of required crds for cluster source
--source.id string id for cluster source
--source.migration-ids string migration id for cluster source
--target string target cluster for certificates
--target-name-prefix string name prefix in target namespace for cross cluster generation
--target-namespace string target namespace for cross cluster generation
--target.disable-deploy-crds disable deployment of required crds for cluster target
--target.id string id for cluster target
--target.migration-ids string migration id for cluster target
--targets.pool.size int Worker pool size for pool targets
--targetsources.pool.size int Worker pool size for pool targetsources
--use-dnsrecords if true, DNSRecords (using Gardener Provider extensions) are created instead of DNSEntries
-v, --version version for cert-controller-manager
--virtualservices.pool.size int Worker pool size for pool virtualservices
--watch-gateways-crds.default.pool.size int Worker pool size for pool default of controller watch-gateways-crds
--watch-gateways-crds.pool.size int Worker pool size of controller watch-gateways-crds
Certificates are automatically renewed. With the standard configuration,
the certificate is renewed 30 days before its validity expires.
For example, if Let's Encrypt is used as certificate authority of an ACME issuer, a certificate
is always valid for 90 days and will be rolled 30 days before it expires by updating the referenced Secret
in the Certificate object.
The configuration can be changed with the command line parameter --issuer.renewal-window.
You can trigger a manual renewal of a Certificate by setting .spec.renew to true.
The controller will then renew the certificate with the next reconciliation and remove the field.
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: renew-sample
namespace: default
spec:
commonName: cert1.mydomain.com
renew: true # trigger a renewal with the next reconciliation, the field will be removed
ensureRenewedAfter: null # mandatory if a manual renewal was already triggered If the field .spec.ensureRenewedAfter is set and you want to trigger the renewal again, make sure to remove it (e.g. by setting the value explicitly to null).
You can control the renewal window of a Certificate by using .spec.RenewBefore.
The value is a duration string parseable by Go's time.ParseDuration.
It must be at least 5 minutes and not greater than the issued certificate's duration minus 5 minutes.
The controller subtracts the given duration from the issued certificate's expiration date to determine the renewal date.
You can inspect the planned renewal date in the status field .status.renewalDate (also set when no renewBefore is given).
Let's assume a certificate is valid for 90 days, and instead of renewing it 30 days before expiration, you want to renew it 60 days before expiration.
apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: renew-before-sample
namespace: default
spec:
commonName: cert1.mydomain.com
duration: 2160h # 90 days
renewBefore: 1440h # 60 daysIf the issued certificate's expiration date is 2025-12-31T00:00:00Z the status would show:
status:
renewalDate: 2025-11-01T00:00:00ZThis is the planned renewal date of the Certificate.
Note
The expiration date of the issued certificate is used for the calculation of the renewal date. This is relevant in case the issuer ignores the requested duration and issues a certificate with a different validity period.
Certificates created with an ACME issuer can also be revoked if private key of the certificate
is no longer safe. This page about Revoking certificates on Let's Encrypt
list various reasons:
For instance, you might accidentally share the private key on a public website; hackers might copy the private key off of your servers; or hackers might take temporary control over your servers or your DNS configuration, and use that to validate and issue a certificate for which they hold the private key.
Revoking a certificate is quite simple. You create a CertificateRevocation object on the source cluster with a reference
to the Certificate object to be revoked.
apiVersion: cert.gardener.cloud/v1alpha1
kind: CertificateRevocation
metadata:
name: revoke-sample
namespace: default
spec:
certificateRef:
name: mycert
namespace: default
# Uncomment the following line if certificate should be renewed before revoking the old one(s)
#renew: true
# Optionally specify a qualifying date. All certificates requested before this date will be revoked.
# If not specified, the current time is used by default.
#qualifyingDate: "2020-12-22T17:00:35Z"The cert-controller-manager will then perform several steps.
-
Using the certificate secret it looks for other
Certificateobjects using the same certificate. The "same" certificate means same issuer, Common Name, and DNS Names. All found objects will be reconciled too. -
It will look for other valid certificate secrets older than the qualifying date. Concretely this will deal with unused certificates, which are still valid. As a certificate is renewed 30 days before the end of validity, the old certificate is still valid, but not used anymore.
-
All found certificate secrets are revoked and marked with an annotation
cert.gardener.cloud/revoked: "true" -
The state of all found
Certificateobjects is set toRevoked -
The state of the
CertificateRevocationobject is set toApplied. Additionally, the status of theCertificateRevocationobject contains more details about revoked objects and secrets:apiVersion: cert.gardener.cloud/v1alpha1 kind: CertificateRevocation metadata: name: revoke-sample namespace: default spec: certificateRef: name: mycert namespace: default qualifyingDate: "2020-12-22T17:00:35Z" status: message: certificate(s) revoked objects: revoked: - name: mycert namespace: default revocationApplied: "2020-12-22T17:09:32Z" secrets: revoked: - name: cert-backup-default-issuer-8a7e93f7-sks7p namespace: kube-system serialNumber: fa:3f:9a:5e:ac:47:ee:d1:91:a6:31:a7:43:6f:8a:7e:93:f7 state: Applied
The secrets listed in the status are only the internal backups maintained by the
cert-controller-manager. The actual secrets used by theCertificateobjects are not listed, but nonetheless marked as revoked.
With this variant the certificate is renewed, before the old one(s) are revoked. This means the
certificate secrets of the Certificate objects will contain newly requested certificates and
the old certificate(s) will be revoked afterward.
For this purpose, set renew: true in the spec of the CertificateRevocation object:
apiVersion: cert.gardener.cloud/v1alpha1
kind: CertificateRevocation
metadata:
name: revoke-sample
namespace: default
spec:
certificateRef:
name: mycert
namespace: default
renew: trueIn this case, the status will list the renewed Certificate objects:
apiVersion: cert.gardener.cloud/v1alpha1
kind: CertificateRevocation
metadata:
name: revoke-sample
namespace: default
spec:
certificateRef:
name: mycert
namespace: default
renew: true
qualifyingDate: "2020-12-22T17:00:35Z"
status:
message: certificate renewed and old certificate(s) revoked
objects:
renewed:
- name: mycert
namespace: default
revocationApplied: "2020-12-22T17:09:32Z"
secrets:
revoked:
- name: cert-backup-default-issuer-8a7e93f7-sks7p
namespace: kube-system
serialNumber: fa:3f:9a:5e:ac:47:ee:d1:91:a6:31:a7:43:6f:8a:7e:93:f7
state: AppliedTo verify the OCSP revocation of the X509 certificate of a Certificate object,
you can use the tool hack/check-cert-secret.sh in this repository.
Usage:
hack/check-cert-secret.sh check-revoke mynamespace mycertnameHere mynamespace and mycertname are the namespace and the name of the certificate object.
Metrics are exposed for Prometheus if the command line option --server-port-http <port> is specified.
The endpoint URL is http://<pod-ip>:<port>/metrics.
Besides the default Go metrics, the following cert-management specific metrics are provided:
| Name | Labels | Description |
|---|---|---|
| cert_management_acme_account_registrations | uri, email, issuer | ACME account registrations |
| cert_management_acme_orders | issuer, success, dns_challenges, renew | Number of ACME orders |
| cert_management_cert_entries | issuer, issuertype | Total number of certificate objects per issuer |
| cert_management_cert_object_expire | namespace, name | Expire date as Unix time (the number of seconds elapsed since January 1, 1970 UTC) |
| cert_management_acme_active_dns_challenges | issuer | Currently active number of ACME DNS challenges |
| cert_management_overdue_renewal_certificates | - | Number of certificate objects with certificate's renewal overdue |
| cert_management_revoked_certificates | - | Number of certificate objects with revoked certificate |
| cert_management_secrets | classification | Number of certificate secrets per classification (only updated on startup and every 24h on GC of secrets). Currently there are three classifications: total = total number of certificate secrets on the source cluster, revoked = number of revoked certificate secrets, backup= number of backups of certificate secrets (every certificate has a backup secret in the kube-system namespace to allow revocation even if it is not used anymore) |
With the command line option --use-dnsrecords, the cert-controller-manager creates DNSRecords resources instead of
DNSEntries. In this case, the Certificate or source objects need two additional annotations:
cert.gardener.cloud/dnsrecord-provider-typeto fill the.spec.typefield ofDNSRecordscert.gardener.cloud/dnsrecord-secret-refto fill the.spec.secretRef. The value is either the name of the secret in the same namespace as the certificate or in the format<namespace>/<name>.
Requesting certificates from an ACME provider (like Let's Encrypt) is always performed using a DNS01 challenge.
For this purpose, the cert-controller-manager creates an DNSEntry for the dns-controller-manager
(see project external-dns-management).
Your dns-controller-manager needs a suitable DNSProvider responsible for the domain(s) of the common name and further
DNS names of the certificate. It will create a DNS TXT record in the corresponding zone.
This DNS TXT record must be visible to the ACME issuer. In case of Let's encrypt this means the DNS record must be
available to the public internet. If the certificate fails with an event obtaining certificate failed: error: one or more domains had a problem,
there are a lot of possible reasons.
Here are the two most frequent ones.
-
You see a
Warningevent withFailed check: DNS entry getting readylikeLAST SEEN TYPE REASON OBJECT MESSAGE 20m Warning reconcile certificate/mycert obtaining certificate failed: error: one or more domains had a problem: [mycert.<mydomain>] time limit exceeded . Details: DNS TXT record '_acme-challenge.mycert.<mydomain>' is not visible on public (or precheck) name servers. Failed check: DNS entry getting ready
This means there is a problem with the
DNSEntrywhich is not getting ready. Either there was no suitableDNSProviderfor this domain, or the provider is not ready itself (e.g. invalid credentials) Please note that thisDNSEntryis deleted automatically if a try to request the certificate request is finished. -
You see a
Warningevent withFailed check: DNS record propagationlikeLAST SEEN TYPE REASON OBJECT MESSAGE 10m Warning reconcile certificate/mycert obtaining certificate failed: error: one or more domains had a problem: [mycert.<mydomain>] time limit exceeded . Details: DNS TXT record '_acme-challenge.mycert.<mydomain>' is not visible on public (or precheck) name servers. Failed check: DNS record propagation
This means the DNS TXT record could not be looked up by the configured "precheck" nameservers. With the default configuration, these are some public DNS servers. In this case, check if the configured
DNSProvideruses a private hosted zone or if the "precheck" nameservers need to be adjusted to your use case. There may also some configuration of the hosted zone itself (i.e. generic CNAME forwarding) which may cause problems.
For development please see Development documentation
Why not use the community cert-manager solution?
Some of the reasons for developing our own solution relate to Gardener's highly dynamic requirements.
The following list differentiates reasons based on the effort that would be required to adapt cert-manager to our needs.
⛔️ is used to indicate that overcoming the difference would require a significant effort.
-
⚠️ CertificateSigningRequests (CSR) are supported via a custom resource in thecert-managerproject (ref). Gardener'scert-managementsupports CSRs via thecsrspec field of theCertificateresource. -
⚠️ TheFollowCNAMEoptions is only supported on theIssuerresource in thecert-managerproject (ref). Gardener'scert-managementallows to configure thefollowCNAMEoption on theCertificateresource. -
⚠️ CertificateRevocations are not supported in thecert-managerproject. -
⚠️ Only KubernetesGatewayresources can be annotated in thecert-managerproject (ref). Gardener'scert-managementsupports both KubernetesGatewayand IstioGatewayresources. -
⚠️ It is not possible to annotateServiceresources of typeLoadBalancerin thecert-managerproject (ref). -
⛔️ There is no reuse of existing certificates in the
cert-managerproject. This means that if a certificate is requested multiple times, it will be issued multiple times. Gardener'scert-managementreuses existing certificates if the common name and DNS names match. -
⛔️ The ACME DNS-01 challenge is represented through the custom resources
DNSEntryandDNSRecordin thecert-managerproject. Gardener'scert-managementuses the companiondns-controller-managerfrom external-dns-management to solve DNS-01 challenges (possibly in a separate DNS cluster). This would require developing a webhookIssuerforcert-managerto integrate with thedns-controller-manager(ref). -
⛔️ Private keys for the ACME
Issuerhave to be stored in the same cluster as thecert-managercontroller is running in (ref). This is an essential requirement for a hosted control-plane solution like Gardener, where issuers and especially their secrets should remain opaque to end-users. Gardener'scert-managementallows to separateIssuerandCertificateresources in different clusters. -
⛔️ There's no dynamic watch support for Istio
Gatewayresources in thecert-managerproject since it only supports KubernetesGateways. In Gardener'scert-managementthecert-controller-managerautomatically restarts itself when the IstioGatewayCRD is installed to start watching the resources.
The cert-management authors are open to align the projects more closely in the future if the gaps can be overcome.