In this tutorial, we will expose a kubernetes application via HTTPS with a valid Let’s Encrypt certificate. A certificate manager will help us to automatically receive and provision a trusted TLS certificate. It is trusted since Let’s Encrypt has signed the certificate for us:

Lock Icon in a Browser

However, before we install the needed ingredients, you may first want to review the cert-manager architecture:

Architecture

In this blog post we have installed cert-manager, which has the following architecture:

cert-manager Architecture for Creation and Delivery of a Certificate

The goal is to automatically provision a trusted certificate to the ingress controller, which in turn will use the certificate to terminate encrypted HTTPS connections from the Internet. This is shown in Magenta on the right part of the figure.

However, several steps have to be performed to get such a certificate:

  1. the cert-manager issuer registers with the Let’s Encrypt service.
  2. Upon successful registration, the cert-manager certificate object will send a list of domains. Let’s encrypt will send a URL path and a key to the cert-manager certificate object
  3. Immediately thereafter, the certification object creates three temporary objects for the purpose of ACME validation:
    • an ACME POD listening on port 8089
    • an ACME  Service accessing the POD
    • a backend entry in the ingress controller that points to port 8090
  4. Once, the three temporary objects are fully functional, the Let’s Encrypt ACME service can access the POD on the domain and URL and will receive the key from the POD. With that, Let’s Encrypt has validated that the person, who has requested a certificate signature has full control of the domain listed in the certificate.
  5. Thus, the domain valid and the ACME service will send a signed certificate to the certificate object.
  6. After receipt of the signed certificate, the cert-manager certificate object will encapsulate the certificate and private key into a TLS secret.
  7. The ingress controller now can access the certificate and key and use it to terminate HTTPS sessions from the Internet.

For housekeeping purposes, the certificate object will delete the POD, the Service and the backend entry in the ingress controller, once it has received the signed certificate.

Now, let us start to install the components needed for such a process.

Prerequisites

  • You will need a kubernetes installation, whether it be a single node cluster or a multi-node cluster:
    • You can create a single node kubernetes cluster via minikube. See e.g. this blog post for details.
    • For a multi-node cluster, we recommend following the instruction in the blog post with the title Kubernetes Cluster with Kubeadm.

Step 1: Installing cert-manager

Step 1.1: Installing Helm

Helm is a package manager for Kubernetes. We will use helm to install the cert-manager on the kubernetes master. For that, we are following the installation instructions on the Helm Docs:

On the kubernetes master (assuming Linux with 64 bits), the following command can be used:

curl -s -o helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-v2.12.1-linux-amd64.tar.gz \
  && tar -zxvf helm.tar.gz \
  && cp -p -f linux-amd64/helm /usr/local/bin/ \
  && helm init

This will yield the output:

linux-amd64/
linux-amd64/tiller
linux-amd64/helm
linux-amd64/LICENSE
linux-amd64/README.md
Creating /root/.helm
Creating /root/.helm/repository
Creating /root/.helm/repository/cache
Creating /root/.helm/repository/local
Creating /root/.helm/plugins
Creating /root/.helm/starters
Creating /root/.helm/cache/archive
Creating /root/.helm/repository/repositories.yaml
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
Adding local repo with URL: http://127.0.0.1:8879/charts
$HELM_HOME has been configured at /root/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!

For this hello world application, we will allow also unauthenticated users to use helm. However, if this is not only a test installation and/or the kubernetes cluster is shared with other users, we recommend securing your helm installation.

Step 1.2: Create a Cluster Admin Account for Tiller

In case RBAC is enabled on your kubernetes cluster, we need to allow tiller to create resources for us. In our case, I have chosen to allow tiller to create any resource on any namespace by creating a cluster admin service account:

cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
EOF

The output of the command should be:

serviceaccount/tiller created
clusterrolebinding.rbac.authorization.k8s.io/tiller created

If you do not want to give cluster admin rights to tiller, check out other options here on Helm Docs.

With the service account created, we need to re-run the init command and specifying the service account:

helm init --service-account tiller --upgrade

The output should look as follows:

$HELM_HOME has been configured at /root/.helm.

Tiller (the Helm server-side component) has been upgraded to the current version.
Happy Helming!

We had to add the upgrade option, even though we have not changed the tiller version. If the upgrade option is omitted, you get the error message that tiller is already installed and the installation of the cert-manager will fail.

$HELM_HOME has been configured at /root/.helm.
Warning: Tiller is already installed in the cluster.
(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)
Happy Helming!

Step 1.3: Installing cert-manager

Installing the cert-manager is as simple as cutting&pasting the following command to your helm installation.

helm install \
  --name cert-manager \
  --namespace kube-system \
  stable/cert-manager

The output of the above command should look similar to:

NAME:   cert-manager
LAST DEPLOYED: Thu Jan  3 19:21:44 2019
NAMESPACE: kube-system
STATUS: DEPLOYED

RESOURCES:
==> v1/ServiceAccount
NAME          SECRETS  AGE
cert-manager  1        0s

==> v1beta1/ClusterRole
NAME          AGE
cert-manager  0s

==> v1beta1/ClusterRoleBinding
NAME          AGE
cert-manager  0s

==> v1beta1/Deployment
NAME          DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
cert-manager  1        1        1           0          0s

==> v1/Pod(related)
NAME                          READY  STATUS             RESTARTS  AGE
cert-manager-d86d844f7-5xcll  0/1    ContainerCreating  0         0s


NOTES:
cert-manager has been deployed successfully!

In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).

More information on the different types of issuers and how to configure them
can be found in our documentation:

https://cert-manager.readthedocs.io/en/latest/reference/issuers.html

For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:

https://cert-manager.readthedocs.io/en/latest/reference/ingress-shim.html

Note: If RBAC ins not installed on your kubernetes cluster, you need to add the option --set rbac.create=false, so helm does not try to create RBAC resources for you.

Be sure to perform step 2 in case RBAC is enabled. If you fail to do so, you will get an error message as follows:

Error: namespaces "kube-system" is forbidden: User "system:serviceaccount:kube-system:default" cannot get resource "namespaces" in API group "" in the namespace "kube-system"

Step 2 (optional): Review Custom Resource Definitions added by cert-manager

The cert-manager has created three custom resource definitions to your kubernetes installation:

  • certificates: can create a kubernetes secret from a certificate
  • cluster issuer: can issue certificates for an entire cluster
  • issuer: can issue certificates for a single namespace
# kubectl get crd
NAME                                CREATED AT
certificates.certmanager.k8s.io     2019-01-03T18:21:45Z
clusterissuers.certmanager.k8s.io   2019-01-03T18:21:45Z
issuers.certmanager.k8s.io          2019-01-03T18:21:45Z

Step 3: Create the Issuer

cat <<EOF > issuer-staging.yaml
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: oliver.veits+letsencrypt-test@vocon-it.com
    privateKeySecretRef:
      name: letsencrypt-staging
    http01: {}
EOF

kubectl apply -f issuer-staging.yaml
# output: issuer.certmanager.k8s.io/letsencrypt-staging created

Now the issuer should have the following status:

# kubectl describe issuer letsencrypt-staging
Name:         letsencrypt-staging
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"Issuer","metadata":{"annotations":{},"name":"letsencrypt-staging","namespace":"default...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         Issuer
Metadata:
  Creation Timestamp:  2019-01-03T18:54:53Z
  Generation:          1
  Resource Version:    4828189
  Self Link:           /apis/certmanager.k8s.io/v1alpha1/namespaces/default/issuers/letsencrypt-staging
  UID:                 0e636493-0f89-11e9-a13e-9600001441cb
Spec:
  Acme:
    Email:  oliver.veits+letsencrypt-test@vocon-it.com
    Http 01:
    Private Key Secret Ref:
      Key:
      Name:  letsencrypt-staging
    Server:  https://acme-staging-v02.api.letsencrypt.org/directory
Status:
  Acme:
    Uri:  https://acme-staging-v02.api.letsencrypt.org/acme/acct/7753182
  Conditions:
    Last Transition Time:  2019-01-03T19:16:34Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

First, I tried with the v1 version of the ACME server https://acme-staging.api.letsencrypt.org/directory, but I got the following status:

Status:
  Conditions:
    Last Transition Time:  2019-01-03T19:08:46Z
    Message:               Your ACME server URL is set to a v1 endpoint (https://acme-staging.api.letsencrypt.org/directory). You should update the spec.acme.server field to "https://acme-staging-v02.api.letsencrypt.org/directory"
    Reason:                InvalidConfig
    Status:                False
    Type:                  Ready
Events:                    <none>

However, after using the specified link, the ACME account was registered successfully

 

If you have registration problems, it might be helpful to view the log of the certmanager POD like follows:

CERT_MANAGER_POD=$(kubectl -n kube-system get pods | grep '^cert-manager' | awk '{print $1}'); echo CERT_MANAGER_POD=$CERT_MANAGER_POD
kubectl logs -n kube-system $CERT_MANAGER_POD

Step 4: Create Application and Ingress Entry

We now need to create the application, if not already done.

Step 4.1: Create Deployment & Expose Service

We create an application named nginx-letsencrypt-demo and expose it to port 80 of the Cluster IP:

kubectl create deployment nginx-letsencrypt-demo --image=nginxdemos/hello
# output: 'deployment.apps/nginx-letsencrypt-demo created'

kubectl expose deployment nginx-letsencrypt-demo --port=80
# output: 'service/nginx-letsencrypt-demo exposed'

Step 4.2: Create Ingress Entry

With the next command, we create an entry in the ingress controller:

cat <<EOF | kubectl apply -f -
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-letsencrypt-demo
spec:
  rules:
    - host: nginx-letsencrypt-demo.159.69.221.89.nip.io
      http:
        paths:
          - backend:
              serviceName: nginx-letsencrypt-demo
              servicePort: 80
            path: /
EOF

# output: 'ingress.extensions/nginx-letsencrypt-demo created'

Note: the path specification might mandatory in order to discriminate this backend from a backend cert-manager creates automatically for the authenticate the ACME request.

Step 5: Create a Certificate

We are connected and verified by Let’s Encrypt now, but we have not yet created a certificate. Let us do so now.

cat <<EOF > certificate-staging.yaml
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: test-letsencrypt-staging
spec:
  secretName: test-letsencrypt-staging-tls
  issuerRef:
    name: letsencrypt-staging
  commonName: nginx-letsencrypt-demo.159.69.221.89.nip.io
  acme:
    config:
      - http01:
          ingress: nginx-letsencrypt-demo
        domains:
          - nginx-letsencrypt-demo.159.69.221.89.nip.io
EOF

kubectl apply -f certificate-staging.yaml
# output: certificate.certmanager.k8s.io/test-letsencrypt-staging created

After a few seconds or minutes, the issuer changes the ingress configuration automatically:

# kubectl get ingress.extensions/nginx-letsencrypt-demo -o yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"nginx-letsencrypt-demo","namespace":"default"},"spec":{"rules":[{"host":"nginx-letsencrypt-demo.159.69.221.89.nip.io","http":{"paths":[{"backend":{"serviceName":"nginx-letsencrypt-demo","servicePort":80},"path":"/"}]}}]}}
  creationTimestamp: 2019-01-04T10:41:27Z
  generation: 33
  name: nginx-letsencrypt-demo
  namespace: default
  resourceVersion: "4908697"
  selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/nginx-letsencrypt-demo
  uid: 4a206ab1-100d-11e9-a13e-9600001441cb
spec:
  rules:
  - host: nginx-letsencrypt-demo.159.69.221.89.nip.io
    http:
      paths:
      - backend:
          serviceName: nginx-letsencrypt-demo
          servicePort: 80
        path: /
      - backend:
          serviceName: cm-acme-http-solver-ddtzh
          servicePort: 8089
        path: /.well-known/acme-challenge/SBoTejTEqCr3suAxB_XHPiZLwGBU1X303meQ0hBhhb0
status:
  loadBalancer: {}

The cert-manager has created a second backend to the ingress controller: this backend is used to authenticate the ACME request. Let us check the corresponding URL manually:

# RESOURCE=$(kubectl get ingress nginx-letsencrypt-demo -o yaml | grep acme-challenge | awk '{print $2}'); echo $RESOURCE; curl nginx-letsencrypt-demo.159.69.221.89.nip.io$RESOURCE; echo
/.well-known/acme-challenge/Myq6wRERD8S2hJAPFjMjsDBwr6ImGZPMP36PZiKJxG4
Myq6wRERD8S2hJAPFjMjsDBwr6ImGZPMP36PZiKJxG4.he6wm92_EarVMVZ2pRzXy-hbTrPNaQvcLPEv7k76hg0

Step 6: Validate that the Certificate has created a TLS Secret

We now can watch the events of the certificate in order to check, whether the certificate object has successfully created a TLS secret:

watch 'kubectl describe certificate test-letsencrypt-staging | grep Events -A 40'

First, I did not observe any positive event, but after I have taken a break of an hour or so, I have checked again and I have obtained a message that a certificate is issued:

# kubectl describe certificate
Name:         test-letsencrypt-staging
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"Certificate","metadata":{"annotations":{},"name":"test-letsencrypt-staging","namespace...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2019-01-04T10:52:52Z
  Generation:          1
  Resource Version:    4915051
  Self Link:           /apis/certmanager.k8s.io/v1alpha1/namespaces/default/certificates/test-letsencrypt-staging
  UID:                 e244c65e-100e-11e9-a13e-9600001441cb
Spec:
  Acme:
    Config:
      Domains:
        nginx-letsencrypt-demo.159.69.221.89.nip.io
      Http 01:
        Ingress:  nginx-letsencrypt-demo
  Common Name:    nginx-letsencrypt-demo.159.69.221.89.nip.io
  Issuer Ref:
    Name:       letsencrypt-staging
  Secret Name:  test-letsencrypt-staging-tls
Status:
  Acme:
    Order:
      URL:  https://acme-staging-v02.api.letsencrypt.org/acme/order/7753182/18501635
  Conditions:
    Last Transition Time:  2019-01-04T12:31:44Z
    Message:               Certificate issued successfully
    Reason:                CertIssued
    Status:                True
    Type:                  Ready
    Last Transition Time:  2019-01-04T12:31:42Z
    Message:               Order validated
    Reason:                OrderValidated
    Status:                False
    Type:                  ValidateFailed
Events:
  Type    Reason       Age                  From          Message
  ----    ------       ----                 ----          -------
  Normal  CreateOrder  13m (x44 over 111m)  cert-manager  Created new ACME order, attempting validation...

According to the second message above, the validation has failed two seconds before it was validated successfully, but let us disregard this for now. In any case, the cert-manager should have created a secret by now:

# kubectl get secret test-letsencrypt-staging-tls
NAME                           TYPE                DATA   AGE
test-letsencrypt-staging-tls   kubernetes.io/tls   2      12m

Correct. Let us add the certificate secret to the ingress controller’s configuration now.

Step 7: Use the Certificate in the Ingress Controller

Let us make use of it now in the ingress controller: we add a tls section before the rules section as follows:

kubectl edit ingress nginx-letsencrypt-demo
...
  tls:
    - hosts:
      - nginx-letsencrypt-demo.159.69.221.89.nip.io
      secretName: test-letsencrypt-staging-tls
  rules:
...

If we do not want to wait for the POD to be updated, we can delete it, so the effect is immediate:

# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nginx-letsencrypt-demo-7d54866c4c-5ls7l   1/1     Running   0          145m
[root@centos-2gb-nbg1-1 ~]# kubectl delete pod nginx-letsencrypt-demo-7d54866c4c-5ls7l
pod "nginx-letsencrypt-demo-7d54866c4c-5ls7l" deleted
[root@centos-2gb-nbg1-1 ~]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nginx-letsencrypt-demo-7d54866c4c-psktl   1/1     Running   0          7s

Step 8: Access Demo App via HTTPS

Accessing the HTTPS URL leading to a security warning

Accessing the HTTPS URL after acknowledging the security warning

Step 9: Move to Production

In order to move to production, we just need to remove the ‘-staging’ statements on the certificate and ingress.

Step 9.1: Create the Production Issuer

cp -f issuer-staging.yaml issuer.yaml
sed -i 's/-staging//g' issuer.yaml
kubectl apply -f issuer.yaml

# output: 'issuer.certmanager.k8s.io/letsencrypt created'

The following command should indicate that the ACME account is registered:

# kubectl describe issuer letsencrypt
Name:         letsencrypt
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"Issuer","metadata":{"annotations":{},"name":"letsencrypt","namespace":"default"},"spec...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         Issuer
Metadata:
  Creation Timestamp:  2019-01-04T15:52:35Z
  Generation:          1
  Resource Version:    4931557
  Self Link:           /apis/certmanager.k8s.io/v1alpha1/namespaces/default/issuers/letsencrypt
  UID:                 c136f5bf-1038-11e9-a13e-9600001441cb
Spec:
  Acme:
    Email:  oliver.veits+letsencrypt-test@vocon-it.com
    Http 01:
    Private Key Secret Ref:
      Key:
      Name:  letsencrypt
    Server:  https://acme-v02.api.letsencrypt.org/directory
Status:
  Acme:
    Uri:  https://acme-v02.api.letsencrypt.org/acme/acct/48920128
  Conditions:
    Last Transition Time:  2019-01-04T15:52:41Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

This has not worked! I got a 301 Moved permanently, which will prevent the ACME to succesfully verify that we have full control over the web site.

# RESOURCE=$(kubectl get ingress nginx-letsencrypt-demo -o yaml | grep acme-challenge | awk '{print $2}'); echo $RESOURCE; 
# curl -s -D - nginx-letsencrypt-demo.159.69.221.89.nip.io$RESOURCE 
/.well-known/acme-challenge/Sqb42aOWpj8uUbITuPgEBf76e1egLPHc157zGz8fCaA
HTTP/1.1 301 Moved Permanently
Server: nginx/1.15.7
Date: Sat, 05 Jan 2019 07:40:00 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://nginx-letsencrypt-demo.159.69.221.89.nip.io:443/.well-known/acme-challenge/Sqb42aOWpj8uUbITuPgEBf76e1egLPHc157zGz8fCaA

<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.15.7</center>
</body>
</html>

Therefore, I had to edit the ingress, remove the tls part. After that, the certificate object has created the corresponding secret with no problems. In order to avoid those problems, I recommend to edit the ingress and remove the tls part altogether and move it back after the certificate was created successfully.

Step 9.2: Create the Production Certificate

cp certificate-staging.yaml certificate.yaml
sed -i 's/-staging//g' certificate.yaml
kubectl apply -f certificate.yaml

# output: 'certificate.certmanager.k8s.io/test-letsencrypt created'

Now, the ingress should show a new backend for the acme challenge again:

# kubectl get ingress.extensions/nginx-letsencrypt-demo -o yaml
...
spec:
  rules:
  - host: nginx-letsencrypt-demo.159.69.221.89.nip.io
    http:
      paths:
      - backend:
          serviceName: nginx-letsencrypt-demo
          servicePort: 80
        path: /
      - backend:
          serviceName: cm-acme-http-solver-l2tm2
          servicePort: 8089
        path: /.well-known/acme-challenge/Sqb42aOWpj8uUbITuPgEBf76e1egLPHc157zGz8fCaA
...


Now the non-staging secret should be available:

# kubectl get secret
NAME                           TYPE                                  DATA   AGE
test-letsencrypt-staging-tls   kubernetes.io/tls                     2      19h
test-letsencrypt-tls           kubernetes.io/tls                     2      15m

Step 10: Configure Secret in Ingress Entry

kubectl edit ingress

kubectl edit ingress nginx-letsencrypt-demo
...
spec:
  tls:
  - hosts:
    - nginx-letsencrypt-demo.159.69.221.89.nip.io
    secretName: test-letsencrypt-tls
...

Step 11: Access the Application via Browser

Now we access the application via a browser:

HTTPS access still showing "unsecure"

The “not secure” icon before the URL seems to be an artifact. The certificate itself is valid:

However, the certificate is valid

The certificate is valid. We enter the browser’s incognito modus, in order to avoid that the browser is confused by any self-signed certificate it had seen earlier. There, the browser shows the lock sign to the left side of the HTTPS URL:

HTTPS access showing secure certificate in a private tab

After a restart of the browser application (all windows of the browser), the broser shows the secure lock sign on the left of the URL also in normal mode:

HTTPS access showing a secure connection

Note that the kubernetes ingress will redirect the corresponding HTTP connection to HTTPS:

Showing the 301 Moved Permanently message in the debugger

You will receive a ‘301 Moved Permanently’ message with the HTTPS location header. The browser then will access the HTTPS site and will receive a ‘200 OK’ message:

Showing the 200 OK message in the debugger

We have created our first kubernetes application with a valid Let’s Encrypt certificate.

Summary

In this blog post we have installed cert-manager, which has the following architecture:

cert-manager Architecture for Creation and Delivery of a Certificate

The cert-manager issuer registers with the Let’s Encrypt service. Upon successful registration, the cert-manager certificate object will obtain an URL path and a key. It will send a list of domains. At the same time, the certification object creates three temporary objects for the purpose of ACME validation:

  • a POD listening on port 8089
  • a Service accessing the POD
  • a backend entry in the ingress controller

With that, the Let’s Encrypt ACME service can access the POD on the Domain and URL and will receive the key from the POD. With that, the Domain is validated and the ACME service will offer to sign a certificate with its private key. The cert-manager certificate object will encapsulate the signed certificate and private key into a TLS secret.

The ingress controller now can access the certificate and key and use it to terminate HTTPS sessions from the Internet.

For housekeeping purposes, the certificate object will delete the POD, the Service and the backend entry in the ingress controller, once it has received the signed certificate.

WORK in PROGRESS: Appendix A: Wildcard Certificate via DNS Auth

WORK in PROGRESS

The HTTP01 authentication method described above does not support wildcard certificates (e.g. *.example.com). However, Let’s Encrypt supports a DNS authentication that supports wildcard certificates as well as normal certificates. We will follow the instructions on the official Cert-Manager docs in order to test this feature. We will use

A.1 Move your Domain Name Server to CloudFlare

WORK in PROGRESS

In this test, we will use the free version of CloudFlare DNS in order to perform our task. For that, we have imported the Domain configuration to CloudFlare like follows:

  • On CloudFlare, create a free account
  • Import all Name Server Records from our domain
  • Check, that all entries are covered. In our case, some entries were missing, so we had to add those manually
  • Switch off the CDN functionality; i.e. the cloud must be grey and the arrow goes around. Example:CloudFlare DNS Record for main Domain
  • Change the name server on your domain host to the one offered by CloudFlare
  • Wait long enought for the Internet to learn about your new Domain Name Server (typically 1 hr, but it depends on your current name server records; best wait 24 hours to be sure)

Step A.1 Create a Key File

WORK in PROGRESS

In our case, we will use a file that holds the CloudFlare secret key and other relevant information:

CREDS=$HOME/.cloudflare/credentials.sh
touch $CREDS; chmod 400 $CREDS
echo "EMAIL=your_cloudflare_email_address@example.com" > $CREDS
echo "ZONE=your_cloudflare_zone >> $CREDS
echo "GLOBAL_API_KEY=your_cloudflare_global_api_key" >> $CREDS

Step A.2 Create Secret from Key File

WORK in PROGRESS

export NAMESPACE=default

source $CREDS
touch cloudflare-api-key.txt
chmod 600 cloudflare-api-key.txt
echo -n $GLOBAL_API_KEY > cloudflare-api-key.txt

kubectl delete secret cloudflare-api-key --namespace=$NAMESPACE 2>/dev/null
kubectl create secret generic cloudflare-api-key --from-file=cloudflare-api-key.txt --namespace=$NAMESPACE

# output:
# secret/cloudflare-api-key created

rm cloudflare-api-key.txt

Step A.3 Create an Issuer

WORK in PROGRESS

In Step 3, we have created an issuer for the HTTP01 authentication method. This time, we will create an issuer for the DNS01 authentication method with CloudFlare as the provider:


[ "$1" == "-d" ] && CMD=delete || CMD=apply

cat <<EOF | kubectl $CMD -f -
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-staging-dns
  namespace: ${NAMESPACE}
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: ${EMAIL}
    privateKeySecretRef:
      name: letsencrypt-staging-dns
    dns01:
      providers:
        - name: cloudflare
          cloudflare:
            email: ${EMAIL}
            zone: ${ZONE}
            apiKeySecretRef:
              name: cloudflare-api-key
              key: cloudflare-api-key.txt
EOF

# output:
# secret "cloudflare-api-key" deleted  # only, if it had existed
# secret/cloudflare-api-key created
# issuer.certmanager.k8s.io/letsencrypt-staging-dns created

Immediately after the issuer is created, we can check the status of the issuer:

kubectl describe issuer.certmanager.k8s.io/letsencrypt-staging-dns | grep "Message\|Reason"

# output:
#    Message:               The ACME account was registered with the ACME server
#    Reason:                ACMEAccountRegistered

Step A.4 Create Certificate

cat <<EOF | kubectl $CMD -f -
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: vocon-it-com-staging
  namespace: ${NAMESPACE}
spec:
  secretName: vocon-it-com-staging-tls
  issuerRef:
    name: letsencrypt-staging-dns
  commonName: '*.vocon-it.com'
  dnsNames:
  - vocon-it.com
  acme:
    config:
    - dns01:
        provider: cloudflare
      domains:
      - '*.vocon-it.com'
      - vocon-it.com
EOF

# output: 
# certificate.certmanager.k8s.io/vocon-it-com-staging created

Step A.5 Check Status

kubectl describe certificate vocon-it-com-staging | grep 'Message\|Reason' | grep -v 'Type'

# output:
# 
#    Message:               Order validated
#    Reason:                OrderValidated
#    Message:               Certificate issued successfully
#    Reason:                CertIssued

You might need to be a little bit patient here. I have seen “slef-check failed” in the beginning, but the problem has disappeared after 5 to 10 minutes.

Step A.6 Access Secret

kubectl describe secret letsencrypt-staging-dns

This will yield the output as follows:

Name:         letsencrypt-staging-dns
Namespace:    staging
Labels:       
Annotations:  

Type:  Opaque

Data
====
tls.key:  1675 bytes

Step A.7 Use Secret in Ingress Controller

 

References

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.