cert-manager quickstart
This article shows how to install and configure cert-manager to obtain locally issued X.509 certificates by using the CA issuer of cert-manager.
1. About cert-manager
cert-manager is an X.509 certificate manager. It runs as a Kubernetes controller that accepts certificate requests in the form of Kubernetes custom resources, and produces Kubernetes Secrets holding the private key, the signed X.509 certificate, and the CA certificates that signed the certificate.
You request a certificate from cert-manager by creating a Kubernetes custom resource that contains details such as the subject names, key usages, key algorithm, key size, certificate validity duration, a reference to an issuer, truststore format, output secret’s name, etc. cert-manager reads this resource, generates a key pair, creates a certificate signing request (CSR), submits the CSR to the issuer, reads the certificate issued by the issuer and creates a Kubernetes Secret holding the private key, the issued certificate and the CA certificates that signed the certificate. Applications mount this Kubernetes Secret as a volume and use the key and certificates for purposes such as TLS.
When cert-manager receives a request for a certificate, it creates a certificate signing request (CSR) and submits it to an issuer. The issuer communicates with the CA to obtain a signed X.509 certificate. cert-manager has built-in issuers for CA services such as HashiCorp Vault, AWS Private CA, Venafi, ACME CA services (e.g. Let’s Encrypt), etc. There also exist external issuers, contributed by the community, for CA services such as Google Cloud CA Service, Cloudflare Origin CA, etc. You can even write your own external issuer for integration with your custom CA service. A single installation of cert-manager can support multiple issuers simultaneously.
cert-manager was created by Jetstack.
Jetstack is now a part of Venafi.
cert-manager was donated to CNCF in the year 2020.
It is currently at Incubating
maturity level in CNCF.
cert-manager is an open source project.
Jetstack provides paid customer support.
Jetstack also offers an enterprise version of cert-manager that offers additional features on top of what is available in the open source version.
2. Prerequisites
-
OpenSSL CLI.
-
A working Kubernetes cluster. See Kubernetes on Windows Subsystem for Linux (WSL) 2 using kind and Docker Engine.
-
Helm CLI. See Installing Helm.
I was using the following at the time of writing this article:
|
3. Install cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install \
cert-manager jetstack/cert-manager \
--version v1.10.0 \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true \(1)
--set featureGates=LiteralCertificateSubject=true \(2)
--set "webhook.extraArgs={--feature-gates=LiteralCertificateSubject=true}" \(3)
--set "extraArgs={--enable-certificate-owner-ref=true}"(4)
1 | CRD resources will be installed as part of the Helm chart. When cert-manager is uninstalled, CRD resources will be deleted causing all installed custom resources to be deleted. |
2 | Enable LiteralCertificateSubject feature gate on cert-manager controller. Read Literal Certificate Subjects. |
3 | Enable LiteralCertificateSubject feature gate on cert-manager webhook. Read Literal Certificate Subjects. |
4 | Enable deletion of the Kubernetes Secret generated by cert-manager when the corresponding Certificate resource is deleted. Read Cleaning up Secrets when Certificates are deleted. |
4. Generate keys and certificates for the issuing CA
We will be using the following certificate chain:
-
End entity certificates will be signed by the issuing CA.
-
The certificate of the issuing CA will be signed by an intermediate CA.
-
The certificate of the intermediate CA will be signed by a self-signed root CA.
end entity: user of PKI certificates and/or end user system that is the subject of a certificate
. . .
End entity certificates are issued to subjects that are not authorized to issue certificates.
4.1. Generate key and certificate for the root CA
Generate a key pair.
openssl genpkey \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:4096 \
-outform PEM \
-out root-ca-key.pem
Generate a self-signed certificate.
openssl req \
-new \
-x509 \
-key root-ca-key.pem \
-keyform PEM \
-sha256 \
-subj "/CN=root-ca/OU=Foo/O=Bar" \
-days 3650 \
-addext "keyUsage = keyCertSign,cRLSign" \
-utf8 \
-batch \
-outform PEM \
-out root-ca-crt.pem
4.2. Generate key and certificate for the intermediate CA
Generate a key pair.
openssl genpkey \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:4096 \
-outform PEM \
-out intermediate-ca-key.pem
Generate a certificate signing request (CSR).
openssl req \
-new \
-key intermediate-ca-key.pem \
-keyform PEM \
-subj "/CN=intermediate-ca/OU=Foo/O=Bar" \
-sha256 \
-utf8 \
-batch \
-outform PEM \
-out intermediate-ca.csr
Get the certificate signed by the root CA.
cat <<EOF > intermediate-ca.cnf
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_ca
[ req_distinguished_name ]
[ v3_ca ]
subjectKeyIdentifier=hash
basicConstraints = critical,CA:TRUE
keyUsage = critical,keyCertSign,cRLSign
authorityKeyIdentifier = keyid
EOF
openssl x509 \
-req \
-in intermediate-ca.csr \
-inform PEM \
-extfile intermediate-ca.cnf \
-extensions v3_ca \
-CA root-ca-crt.pem \
-CAform PEM \
-CAkey root-ca-key.pem \
-CAkeyform PEM \
-CAcreateserial \
-sha256 \
-days 1825 \
-outform PEM \
-out intermediate-ca-crt.pem
Cleanup.
rm \
root-ca-key.pem \
root-ca-crt.srl \
intermediate-ca.cnf \
intermediate-ca.csr
4.3. Generate key and certificate for the issuing CA
Generate a key pair.
openssl genpkey \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:4096 \
-outform PEM \
-out issuer-ca-key.pem
Generate a certificate signing request (CSR).
openssl req \
-new \
-key issuer-ca-key.pem \
-keyform PEM \
-subj "/CN=issuer-ca/OU=Foo/O=Bar" \
-sha256 \
-utf8 \
-batch \
-out issuer-ca.csr \
-outform PEM
Get the certificate signed by the intermediate CA.
cat <<EOF > issuer-ca.cnf
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_ca
[ req_distinguished_name ]
[ v3_ca ]
subjectKeyIdentifier=hash
basicConstraints = critical,CA:TRUE
keyUsage = critical,keyCertSign,cRLSign
authorityKeyIdentifier = keyid
EOF
openssl x509 \
-req \
-in issuer-ca.csr \
-inform PEM \
-extfile issuer-ca.cnf \
-extensions v3_ca \
-CA intermediate-ca-crt.pem \
-CAform PEM \
-CAkey intermediate-ca-key.pem \
-CAkeyform PEM \
-CAcreateserial \
-sha256 \
-days 730 \
-outform PEM \
-out issuer-ca-crt.pem
Cleanup.
rm \
intermediate-ca-key.pem \
intermediate-ca-crt.srl \
issuer-ca.cnf \
issuer-ca.csr
5. Deploy the issuing CA
We will use the CA issuer of cert-manager as the issuing CA.
The CA issuer represents a Certificate Authority whose certificate and private key are stored inside the cluster as a Kubernetes Secret.
|
The CA issuer of cert-manager signs certificates locally using its private key.
Create a Kubernetes namespace for the issuer.
kubectl create namespace ns1
Create a Kubernetes secret holding the private key of the issuing CA and the certificates of the issuing CA, intermediate CA and root CA.
cat issuer-ca-crt.pem > tls.crt
cat intermediate-ca-crt.pem >> tls.crt
cat root-ca-crt.pem >> tls.crt
kubectl create secret generic \
ca-key-pair \
--namespace=ns1 \
--from-file=tls.crt=./tls.crt \
--from-file=tls.key=./issuer-ca-key.pem
Create a CA issuer of cert-manager to be used as the issuing CA.
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: ca-issuer
namespace: ns1
spec:
ca:
secretName: ca-key-pair
EOF
Cleanup.
rm \
issuer-ca-key.pem \
issuer-ca-crt.pem \
intermediate-ca-crt.pem \
root-ca-crt.pem \
tls.crt
6. Request a certificate
6.1. Submit a certificate request
Request cert-manager to issue an end entity certificate.
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-service-cert-request
namespace: ns1
spec:
isCA: false
usages:
- server auth
- client auth
privateKey:
algorithm: RSA
encoding: PKCS8
size: 4096
literalSubject: "CN=ExampleService,O=ExampleOrg,OU=ExampleOrgUnit,STREET=1st street,L=Madhapur,ST=Hyderabad,C=IN,SERIALNUMBER=1234567890"
dnsNames:
- ExampleService.ns1.svc.cluster.local
- ExampleService.ns1
emailAddresses:
- ExampleService@ExampleOrg.com
duration: 240h # 10d
renewBefore: 120h # 5d
issuerRef:
kind: Issuer
group: cert-manager.io
name: ca-issuer
secretName: example-service-key-cert
EOF
6.2. Verify the status of certificate request
kubectl -n ns1 \
get certificate example-service-cert-request \
-o jsonpath="{.status}" | jq .
{
"conditions": [
{
"lastTransitionTime": "2022-10-23T07:25:01Z",
"message": "Certificate is up to date and has not expired",
"observedGeneration": 1,
"reason": "Ready",
"status": "True",(1)
"type": "Ready"(1)
}
],
"notAfter": "2022-11-02T07:25:01Z",
"notBefore": "2022-10-23T07:25:01Z",
"renewalTime": "2022-10-28T07:25:01Z",
"revision": 1
}
1 | The certificate has been issued and is ready to be used. |
7. Inspect the issued certificate
Inspect the Kubernetes Secret generated by cert-manager.
kubectl get secret \
example-service-key-cert \
-n ns1 \
-o yaml
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: example-service-key-cert
namespace: ns1
annotations:
cert-manager.io/alt-names: ExampleService.ns1.svc.cluster.local,ExampleService.ns1
cert-manager.io/certificate-name: example-service-cert-request
cert-manager.io/common-name: ExampleService
cert-manager.io/ip-sans: ""
cert-manager.io/issuer-group: cert-manager.io
cert-manager.io/issuer-kind: Issuer
cert-manager.io/issuer-name: ca-issuer
cert-manager.io/uri-sans: ""
ownerReferences: (1)
- apiVersion: cert-manager.io/v1
blockOwnerDeletion: true
controller: true
kind: Certificate
name: example-service-cert-request
uid: 8652a900-65b1-4f95-b30b-8d85f8424d67
resourceVersion: "58088"
uid: 8c25906d-f8f6-4150-9d57-fafc50a117e1
creationTimestamp: "2022-10-23T09:41:57Z"
data:
ca.crt: [REDACTED] (2)
tls.crt: [REDACTED] (3)
tls.key: [REDACTED] (4)
1 | Because we passed the argument --enable-certificate-owner-ref=true to cert-manager, the Certificate resource owns this Secret. Therefore, Kubernetes will delete this Secret when the Certificate resource is deleted. |
2 | ca.crt holds the certificate of the root CA. |
3 | tls.crt holds the certificates of the end entity, the issuing CA and the intermediate CA. |
4 | tls.key holds the private key of the end entity. |
Inspect tls.crt
in the generated secret.
kubectl -n ns1 \
get secret example-service-key-cert \
-o "jsonpath={.data['tls\.crt']}" \
| base64 -d \
| openssl crl2pkcs7 -nocrl -certfile /dev/stdin \
| openssl pkcs7 -print_certs -text -noout \
| egrep "(Certificate:|Subject:|Issuer:)"
Certificate: (1)
Issuer: CN=issuer-ca, OU=Foo, O=Bar
Subject: serialNumber=1234567890, C=IN, ST=Hyderabad, L=Madhapur/street=1st street, OU=ExampleOrgUnit, O=ExampleOrg, CN=ExampleService
Certificate: (2)
Issuer: CN=intermediate-ca, OU=Foo, O=Bar
Subject: CN=issuer-ca, OU=Foo, O=Bar
Certificate: (3)
Issuer: CN=root-ca, OU=Foo, O=Bar
Subject: CN=intermediate-ca, OU=Foo, O=Bar
1 | End entity certificate signed by the issuing CA. |
2 | Issuing CA certificate signed by the intermediate CA. |
3 | Intermediate CA certificate signed by the root CA. |
Inspect ca.crt
in the generated secret.
kubectl -n ns1 \
get secret example-service-key-cert \
-o "jsonpath={.data['ca\.crt']}" \
| base64 -d \
| openssl crl2pkcs7 -nocrl -certfile /dev/stdin \
| openssl pkcs7 -print_certs -text -noout \
| egrep "(Certificate:|Subject:|Issuer:)"
Certificate: (1)
Issuer: CN=root-ca, OU=Foo, O=Bar
Subject: CN=root-ca, OU=Foo, O=Bar
1 | Self-signed certificate of the root CA. |
Inspect tls.key
in the generated secret.
kubectl -n ns1 \
get secret example-service-key-cert \
-o "jsonpath={.data['tls\.key']}" \
| base64 -d \
| openssl pkey -text -noout
RSA Private-Key: (4096 bit, 2 primes)
modulus:
[REDACTED]
publicExponent: [REDACTED]
privateExponent:
[REDACTED]
prime1:
[REDACTED]
prime2:
[REDACTED]
exponent1:
[REDACTED]
exponent2:
[REDACTED]
coefficient:
[REDACTED]
8. Cleanup
8.1. Delete the certificate request
kubectl -n ns1 \
delete certificate example-service-cert-request
Notice that deletion of the Certificate
resource also causes the deletion of the corresponding Kubernetes Secret that was generated by cert-manager.
This is because we passed the argument --enable-certificate-owner-ref=true
to cert-manager when cert-manager was installed.
$ kubectl -n ns1 \
get secret example-service-key-cert
Error from server (NotFound): secrets "example-service-key-cert" not found (1)
1 | The Secret was not found because it was already deleted by Kubernetes in response to the deletion of the Certificate resource. |
9. References
9.1. cert-manager
Appendix A: Acronyms
Acronym | Meaning |
---|---|
CA |
|
CRD |
Kubernetes CustomResourceDefinitions |
CSR |
|
PEM |
|
PKCS |
|
RSA |
|
TLS |
|
WSL |