Renew Certificates automatically in OpenShift 4
How generate certificates for our apps and sign them with a free and trusted Certificate Authority? And the most important, how I can automate this process for generate and renew this certificates?
Let’s start!
First of all we will use for this purpose, Let’s Encrypt certificates for this purpose. Let’s Encrypt is a automated, and open Certificate Authority and that can be used for sign our certificates without expend a dime (or euro in my case :D).
But for sign the certificate with the let’s encrypt CA some steps must be executed. That’s were the magic of the cert-manager operator can be very helpful.
Installation of the cert-manager
- Create a namespace to run cert-manager in:
# oc create namespace cert-manager
namespace/cert-manager created
- Disable resource validation on the cert-manager namespace
# oc label namespace cert-manager certmanager.k8s.io/disable-validation=true
namespace/cert-manager labeled
NOTE: The –validate=false flag is added to the oc apply command above else you will receive a validation error relating to the caBundle field of the ValidatingWebhookConfiguration resource.
- Install the cert-manager components (CRDs, cert-manager and webhook component):
# oc apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.8.1/cert-manager-openshift.yaml customresourcedefinition.apiextensions.k8s.io/certificates.certmanager.k8s.io created customresourcedefinition.apiextensions.k8s.io/challenges.certmanager.k8s.io created customresourcedefinition.apiextensions.k8s.io/clusterissuers.certmanager.k8s.io created customresourcedefinition.apiextensions.k8s.io/issuers.certmanager.k8s.io created customresourcedefinition.apiextensions.k8s.io/orders.certmanager.k8s.io created
- Check the resources generated by the installation:
# oc get pod -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-68cfd787b6-cz27g 1/1 Running 0 68s
cert-manager-cainjector-5975fd64c5-4wpxs 1/1 Running 0 69s
cert-manager-webhook-5c7f95fd44-tdrpt 1/1 Running 0 68s
# oc get clusterrole | grep cert-manager
cert-manager 3m50s
cert-manager-cainjector 3m50s
cert-manager-edit 3m50s
cert-manager-view 3m50s
cert-manager-webhook:webhook-requester 3m50s
# oc get deployment -n cert-manager
NAME READY UP-TO-DATE AVAILABLE AGE
cert-manager 1/1 1 1 4m10s
cert-manager-cainjector 1/1 1 1 4m11s
cert-manager-webhook 1/1 1 1 4m11s
- Patch the certmanager deployment for allow resolv an external DNS (8.8.8.8)
Due to the cert-manager deployment have the spec of dnsPolicy: ClusterFirst, can not reach an External DNS for perform the DNS Challenge and communicate with Lets Encrypt API (one of the reasons for deploy the cert-manager).
For this reason, a patch for the Deployment of the cert-manager must to be performed:
Original
# oc get deploy cert-manager -n cert-manager -o yaml | grep dnsPolicy
dnsPolicy: ClusterFirst
Patched
# oc get deploy cert-manager -n cert-manager -o yaml
...
terminationMessagePolicy: File
dnsConfig:
nameservers:
- 8.8.8.8
dnsPolicy: None
restartPolicy: Always
...
NOTE: dnsPolicy to None allows Pod to ignore DNS settings from the Kubernetes environment. All DNS settings are supposed to be provided using the dnsConfig field in the Pod Spec.
For more information, check the DNS Pod Service Kubernetes Guide
# oc get pod -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-679fd5459-868cs 0/1 ContainerCreating 0 11s
cert-manager-68cfd787b6-cz27g 1/1 Running 0 61m
cert-manager-cainjector-5975fd64c5-4wpxs 1/1 Running 0 61m
cert-manager-webhook-5c7f95fd44-tdrpt 1/1 Running 0 61m
# oc exec -ti cert-manager-679fd5459-868cs -n cert-manager /bin/sh
~ $ nc www.marca.com -zv 443
www.marca.com (151.101.37.50:443) open
Setting up Issuers
Before you can begin issuing certificates, you must configure at least one Issuer or ClusterIssuer resource in your cluster.
These represent a certificate authority from which signed x509 certificates can be obtained, such as Let’s Encrypt, or your own signing key pair stored in a Kubernetes Secret resource. They are referenced by Certificate resources in order to request certificates from them.
-
Issuer: scoped to a single namespace, and can only fulfill Certificate resources within its own namespace. Useful in a multi-tenant environment where multiple teams or independent parties operate within a single cluster.
-
ClusterIssuer: cluster wide version of an Issuer. It is able to be referenced by Certificate resources in any namespace.
NOTE: cert-manager supports a number of different issuer backends, each with their own different types of configuration. In our case, ACME issuer backend will be used due to Let’s Encrypt certificates will be generated.
ACME issuers in Cert-Manager
Cert-manager can be used to obtain certificates from a CA using the ACME protocol. The ACME protocol supports various challenge mechanisms which are used to prove ownership of a domain so that a valid certificate can be issued for that domain.
One such challenge mechanism is DNS-01. With a DNS-01 challenge, you prove ownership of a domain by proving you control its DNS records. This is done by creating a TXT record with specific content that proves you have control of the domains DNS records.
NOTE: Let’s Encrypt does not support issuing wildcard certificates with HTTP-01 challenges. To issue wildcard certificates, you must use the DNS-01 challenge.
Generate required IAM Policy and User for ACME Cert-Manager Issuer
The ACME Issuer type represents a single Account registered with the ACME server.
When you create a new ACME Issuer, cert-manager will generate a private key which is used to identify you with the ACME server.
To set up a basic ACME issuer, you should create a new Issuer or ClusterIssuer resource.
But first of all, cert-manager requires an IAM Policy that must to be defined.
- First generate an IAM policy with the permissions related below:
# aws iam list-policies | grep acme
"PolicyName": "acme-route53",
"Arn": "arn:aws:iam::920348280276:policy/acme-route53"
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": "route53:ChangeResourceRecordSets",
"Resource": "arn:aws:route53:::hostedzone/*"
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZonesByName",
"Resource": "*"
}
]
}
- Generate an IAM Role User and attach the new brand IAM Policy:
# aws iam list-users | grep cert
"UserName": "cert-manager-iam",
"Arn": "arn:aws:iam::920348280276:user/cert-manager-iam"
# aws iam list-attached-user-policies --user-name cert-manager-iam
{
"AttachedPolicies": [
{
"PolicyName": "acme-route53",
"PolicyArn": "arn:aws:iam::920348280276:policy/acme-route53"
}
]
}
- Generate a secret with the secret-access-key of the new user generated:
# oc create secret generic --from-literal=secret-access-key=xxxxxxxxxx-n cert-manager acme-route53
secret/acme-route53 created
# oc get secret acme-route53 -n cert-manager -o yaml
apiVersion: v1
data:
secret-access-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
kind: Secret
metadata:
creationTimestamp: "2019-08-09T10:17:56Z"
name: acme-route53
namespace: cert-manager
resourceVersion: "1493413"
selfLink: /api/v1/namespaces/cert-manager/secrets/acme-route53
uid: f4dc42b9-ba8e-11e9-9ee8-06221c570820
type: Opaque
Generating ACME Issuers for AWS Route 53
- Generate the ClusterIssuer resource:
apiVersion: v1
items:
- apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: apps-ocp4-dev-opentlc-com
spec:
acme:
dns01:
providers:
- name: dns
route53:
accessKeyID: yyyy
hostedZoneID: zzzz
region: eu-central-1
secretAccessKeySecretRef:
key: secret-access-key
name: acme-route53
email: rcarrata@redhat.com
privateKeySecretRef:
name: cluster-issuer
kind: List
metadata:
resourceVersion: ""
selfLink: ""
- Check that the clusterissuer is generated properly:
# oc get clusterissuer
NAME AGE
apps-ocp4-dev-opentlc-com 2m12s
Issuing Certificates
The Certificate resource type is used to request certificates from different Issuers.
A Certificate resource specifies fields that are used to generated certificate signing requests which are then fulfilled by the issuer type you have referenced.
# oc get certificates -n openshift-ingress -o yaml
apiVersion: v1
items:
- apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: apps-ocp4-dev-xbyorange-com
namespace: openshift-ingress
spec:
acme:
config:
- dns01:
provider: dns
domains:
- '*.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com'
commonName: '*.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com'
dnsNames:
- '*.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com'
issuerRef:
kind: ClusterIssuer
name: apps-ocp4-dev-opentlc-com
secretName: apps-ocp4
duration: 2160h #90d
renewBefore: 360h #15d
kind: List
metadata:
resourceVersion: ""
selfLink: ""
A Certificate resource specifies fields that are used to generated certificate signing requests which are then fulfilled by the issuer type you have referenced.
Certificates specify which issuer will be used to obtain the certificate from by specifying the spec.issuerRef field (in our case the cluster issuer created in the step above).
Furhtermore, the Certificate resource will generate the certificate for the wildcard domain (the route *.apps in our cluster) with an specific duration (90d) and will be renewed automatically 15d before their expiration.
The signed certificate (Let’s Encrypt) for the default ingress controller exposing *.apps routes, will be generated automatically into the namespace of openshift-ingress and stored un a Secret resource named apps-ocp4, once the issuer has successfully issued the requested certificate.
The Certificate will be issued using the issuer named ca-issuer in the openshift-ingress namespace.
# oc logs -f cert-manager-679fd5459-868cs
I0809 12:08:41.656962 1 dns.go:101] Presenting DNS01 challenge for domain
"apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com"
I0809 12:09:14.982797 1 dns.go:112] Checking DNS propagation for
"apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com" using name servers: [8.8.8.8:53]
I0809 12:09:15.959388 1 dns.go:124] Waiting DNS record TTL (60s) to allow propagation of DNS
record for domain "_acme-challenge.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com."
I0809 12:10:15.959599 1 dns.go:126] ACME DNS01 validation record propagated for
"_acme-challenge.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com."
- After wait some minutes for the DNS Challenge between the cert-manager operator and the ACME API, a Certificate is generate and stored into a Secret (check SecretName value into the Certificate CRD before):
# oc get secrets -n openshift-ingress
NAME TYPE DATA AGE
apps-ocp4 kubernetes.io/tls 3 5m51s
# oc get certificates -n openshift-ingress
NAME READY SECRET AGE
apps-ocp4-dev-opentlc-com True apps-ocp4 8m33s
Patching the OpenShift Routers with the new certificates
- Update the Ingress Controller configuration with the newly created secret:
# oc patch ingresscontroller.operator default --type=merg
e -p '{"spec":{"defaultCertificate": {"name": "apps-ocp4"}}}' -n openshift-ingress-operator
ingresscontroller.operator.openshift.io/default patched
# oc get pod -n openshift-ingress
NAME READY STATUS RESTARTS AGE
router-default-79998d9946-lllvj 0/1 ContainerCreating 0 12s
router-default-84ff5bdcb8-ktmk9 1/1 Running 0 3d19h
- After couple of minutes, the default Certificate exposed is updated and now serves the certificate generated by the Cert-Manager:
# oc get ingresscontroller -n openshift-ingress-operator -o yaml | grep -A1 defaultCertificate
defaultCertificate:
name: apps-ocp4
Check the Certificated expose by the OCP routers
If we check the certificated exposed by the OpenShift Routers with openssl, we can realized that the certificated exposed is the Let’s Encrypt generated:
# openssl s_client -showcerts -servername console-openshift-console.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com -connect console-openshift-console.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com:443 </dev/null | grep Issuer
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = *.apps.rcarrata-ipi-aws.8237.sandbox258.opentlc.com
verify return:1
DONE
NOTE: Opinions expressed in this blog are my own and do not necessarily reflect that of the company I work for.
Happy OpenShifting!!!