Cert-manager: automate certificate management on kubernetes
Tags: kubernetes, addon, tutorial
Published: 2022-09-19 05:39:25

Secure HTTP communication is popularly achieved through TLS certificates. These certificates can be to validate the server (https://your-bank.com) or both parties involved in the communication (https://amount-sender.com and https://amount-receiver.com) which is commonly known as Mutual TLS (mTLS). mTLS is not common in web browsers but is used in service-to-service communication models.
To keep things secure, these certificates have a life-time and needs renewal upon expiry

In kubernetes for example `kublet` authenticates with `kube-api-server` through mTLS. The certificates come from within kubernetes control plane itself and a new one is issued for every new node that joins. For it's internal purposes there is a [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) object provided, but it is not flexible enough to be used by workloads themselves.

#### Cert-manager
Cert-manager is a general purpose x509 certificate management tool for Kubernetes. 
- It can be used as a standard Certificate Authority (CA).
- It can also work with other well known CAs like **Hashicorp Vault** and **LetsEncrypt**. 
- It can generate self-signed certificates
- It can inject certificates into workloads automatically through annotations
- It can handle certificate rotation

##### Installation
```bash
❯ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories
❯ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
Update Complete. ⎈Happy Helming!⎈
❯ helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.9.1 \
  --set installCRDs=true
NAME: cert-manager
LAST DEPLOYED: Mon Sep 19 08:45:10 2022
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.9.1 has been deployed successfully!
...

❯ kubectl get all -n cert-manager
NAME                                           READY   STATUS    RESTARTS   AGE
pod/cert-manager-85f68f95f-k9f6q               1/1     Running   0          2m19s
pod/cert-manager-cainjector-7d9668978d-lrtgn   1/1     Running   0          2m19s
pod/cert-manager-webhook-66c8f6c75-hq7lj       1/1     Running   0          2m19s

NAME                           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/cert-manager           ClusterIP   10.96.27.186   <none>        9402/TCP   2m19s
service/cert-manager-webhook   ClusterIP   10.96.27.27    <none>        443/TCP    2m19s

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/cert-manager              1/1     1            1           2m19s
deployment.apps/cert-manager-cainjector   1/1     1            1           2m19s
deployment.apps/cert-manager-webhook      1/1     1            1           2m19s

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/cert-manager-85f68f95f               1         1         1       2m19s
replicaset.apps/cert-manager-cainjector-7d9668978d   1         1         1       2m19s
replicaset.apps/cert-manager-webhook-66c8f6c75       1         1         1       2m19s
```

##### Concepts
- **Issuer**
	Objects representing CAs that generate certificates as requested through Certificate Signing Requests (CSRs). It is a namespace scoped resource - can issue certifcates valid in the operating namespace scope
- **ClusterIssuer**
	Issuer with cluster scope - can issue certificate valid throughout the cluster
- **Certificate**
	an object defining a X.509 certificate. This certificate is issued, kept up-to-date through renewal with cert-manager. A certificate issued through `Issuer` is namespace scoped, while the one issued through `ClusterIssuer` is valid through the cluster
- **CertificateRequest**
	a namespaced resource used to request certificate from `Issuer`. It contains a base-64 encoded CSR sent to `Issuer`. Upon success, it will return a valid, signed certificate based on the CSR
[more info](https://cert-manager.io/docs/concepts/certificaterequest/)

cert-manager supports requesting certificates from ACME servers, including from [Let's Encrypt](https://letsencrypt.org/), with use of the [ACME Issuer](https://cert-manager.io/docs/configuration/acme//). These certificates are typically trusted on the public Internet by most computers. To successfully request a certificate, cert-manager must solve ACME Challenges which are completed in order to prove that the client owns the DNS addresses that are being requested.

- **Order**
	are resources used by the ACME issuer to manage the lifecycle of an ACME 'order' for a signed TLS certificate.	It represents a single CSR.
- **Challenge**
	are resources are used by the ACME issuer to manage the lifecycle of an ACME 'challenge' that must be completed in order to complete an 'authorization' for a single DNS name/identifier. It is created and managed by the `challenge-controller`
[more info](https://cert-manager.io/docs/concepts/acme-orders-challenges/)
- **cainjector**
	helps fetch CA certificates for webhooks and other API services. It populates the `caBundle` field of four API types: `ValidatingWebhookConfiguration`, `MutatingWebhookConfiguration` `CustomResourceDefinition` and `APIService`. It copies CA data from one of three _sources_: a Kubernetes `Secret`, a cert-manager `Certificate`, or from the Kubernetes API server CA certificate.
Note: If the _source_ is a Kubernetes `Secret`, that resource MUST also have an `cert-manager.io/allow-direct-injection: "true"` annotation.

##### Example with a self-signed certificate
```bash
# assumption: cert-manager is installed and configured as described above

# Prepare a namespace
❯ kubectl create ns demo-ns
namespace/demo-ns created

# Prepare an issuer
❯ cat issuer.yaml
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
  namespace: demo-ns
spec:
  selfSigned: {}

# Create
❯ kubectl create -f issuer.yaml
issuer.cert-manager.io/selfsigned-issuer created

# Verify
❯ kubectl get issuers.cert-manager.io -n demo-ns
NAME                READY   AGE
selfsigned-issuer   True    17s

# Create a Certificate object
❯ cat certificate.yaml
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: sample-certificate
  namespace: demo-ns
spec:
  secretName: sample-certificate
  dnsNames:
  - sample.demo-ns
  issuerRef:
    name: selfsigned-issuer

# Request for certificate
❯ kubectl create -f certificate.yaml
certificate.cert-manager.io/sample-certificate created

# Verify 
❯ kubectl get certificate -n demo-ns
NAME                 READY   SECRET               AGE
sample-certificate   True    sample-certificate   10s

# Describe the secret
❯ kubectl get secrets/sample-certificate -n demo-ns -o yaml | cut -c -80
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lRWjFCcU
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lRWjFCc
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBNEdQT
kind: Secret
metadata:
  annotations:
    cert-manager.io/alt-names: sample.demo-ns
    cert-manager.io/certificate-name: sample-certificate
    cert-manager.io/common-name: ""
    cert-manager.io/ip-sans: ""
    cert-manager.io/issuer-group: ""
    cert-manager.io/issuer-kind: Issuer
    cert-manager.io/issuer-name: selfsigned-issuer
    cert-manager.io/uri-sans: ""
  name: sample-certificate
  namespace: demo-ns
type: kubernetes.io/tls

# Inject secret to a validation webhook
❯ cat val-webhook.yaml
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: test-webhook
  annotations:
    cert-manager.io/inject-ca-from-secret: demo-ns/sample-certificate
webhooks:
- name: test-webhook.example.com
  admissionReviewVersions:
  - v1
  clientConfig:
    service:
      name: test-webhook
      namespace: demo-ns
      path: /validate
      port: 443
  sideEffects: None

# Instantiate assuming test-webhook is a valid service in demo-ns pointing to port 443 to validate on /validate route
❯ kubectl create -f val-webhook.yaml
validatingwebhookconfiguration.admissionregistration.k8s.io/test-webhook created

# Verify 
❯ kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io
NAME                   WEBHOOKS   AGE
cert-manager-webhook   1          137m
test-webhook           1          6s

```