DEV Community

Cover image for Using DNS01 challenge and Let's Encrypt to secure your AKS kubernetes cluster
Sébastien Pertus
Sébastien Pertus

Posted on

Using DNS01 challenge and Let's Encrypt to secure your AKS kubernetes cluster

TL;DR;

This blog post is all about "how to get a certificate using the DNS01 challenge" to secure your Kubernetes cluster running inside AKS.

You will find the repository code source on github : https://github.com/Mimetis/AKS_DNS01Solver

To be able to continue, you need to :

  • Own a domain name.
  • Be able to create an NS record from within your domain provider to be able to redirect all that sub domain queries to Azure DNS Zone.
  • Have an Azure subscription to create your own AKS cluster.

Schema

Intoduction

The most common way to secure an AKS entry endpoint cluster (basically your ingress) is to allow only https queries through your ingress (like nginx or traefik)

A usefull pattern is to use the combo Let's Encrypt and Cert manager to get a valid certificate from Let's encrypt, that will be renewed over time, automatically.

Let's Encrypt uses the ACME protocol to verify that you control a given domain name and then to issue a valid certificate.

Solving challenges

In order for the ACME CA server to verify that a client owns the domain, a certificate is being requested for, the client must complete challenges.

We have mainly two types of challenges available:

  • HTTP01 challenge is completed by presented a computed key on a regular HTTP url endpoint.
  • DNS01 challenge is completed by presented a computed key that is present in a DNS TXT record.

HTTP01 problem

In some circumstances, you just want your cluster to be available using only a secure connection over https.

The main issue with the HTTP01 solving challenge is that you have to let an url endpoint opened on port 80.

IF you are considering this as a security issue, you may want to resolve your challenge, using the DNS01 challenge.

Using DNS01 challenge

We're going to explain how to implement a DNS01 challenge in an Azure environnement, using:

  • AKS as a kubernetes cluster.
  • NGINX as ingress.
  • Cert Manager as a certificate management controller.
  • Azure DNS Zone as a dns resolution engine.

Obviously we will use some useful (and almost mandatory is you are working with kubernetes) tools like kubectl and helm.

The sample application used is the Azure voting application sample that will be slightly modified to add an nginx controller as an ingress.

DNS zone

In this example, I'm working with my own domain called dotmim.com (I used to have a blog on this domain.. long time ago !)

I will create a sub domain that will redirect all the trafic for this sub domain to my DNS Zone.

First of all, you may want to create your Azure DNS zone.

Keep in mind that your dns zone should be a sub domain of your domain.

In the screenshot below, we will manage a subdomain called vote.dotmim.com:

DNS Zone creation

Once created, just copy the Name server 1 property (in my case ns1-02.azure-dns.com.)

From my domain provider, i'm registering a new NS entry that will redirect everything to my DNS Zone:

NS Redirection

You can check that the redirection is working by doing a quick ns lookup:

nslookup -type=SOA dotmim.com
Non-authoritative answer:
dotmim.com
        origin = ns1.amen.fr


nslookup -type=SOA vote.dotmim.com
Non-authoritative answer:
vote.dotmim.com
        origin = ns1-02.azure-dns.com

(Some informations have been removed for clarity)

As you can see the root domain is handled by my domain provider and the vote.* sub domain is now handled by my new created Azure DNS Zone.

nginx ingress controller

The application we want to deploy is quite simple and based on the Azure voting application sample.

You will find the deployment script here : https://github.com/Mimetis/AKS_DNS01Solver/vote.yaml

The only modification is that we will use an nginx ingress as entry point instead of exposing the vote application directly.

Adding nginx is quite straightforward using the official helm chart:

# Create a namespace for your ingress resources
kubectl create namespace ingress-basic

# Use Helm to deploy an NGINX ingress controller
helm install nginx stable/nginx-ingress \
    --namespace ingress-basic \
    --set controller.replicaCount=2 \
    --set controller.nodeSelector."beta\.kubernetes\.io/os"=linux \
    --set defaultBackend.nodeSelector."beta\.kubernetes\.io/os"=linux

cert-manager

to be able to manage everything, we are installing as well cert-manager on the cluster:

# Deploy custom resources definition
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager.crds.yaml

# create namespace for cert
kubectl create namespace cert-manager  

# Add repo
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Label the ingress-basic namespace to disable resource validation
kubectl label namespace ingress-basic cert-manager.io/disable-validation=true

# Install cert manager
helm install cert-manager jetstack/cert-manager \
     --namespace cert-manager \
     --version v0.14.1 \
     --set ingressShim.defaultIssuerName=letsencrypt-staging \
     --set ingressShim.defaultIssuerKind=ClusterIssuer

Service principal

Once it's done, we need a service principal to be able to reach the DNS Zone from within our cluster.

For the sake of simplicity, I will create a simple service principal without any role assignement, but you may want to look at the cert-manager official documentation to ensure you have the least privileges assigned to your spn : Azure DNS configuration

Once created, we will save it as a secret in the AKS cluster.

# Create a service principal for DNS validation
$ az ad sp create-for-rbac --name spcertmanageridentity

Creating a role assignment under the scope of "/subscriptions/subid000-eeee-ffff-gggg-hhhhhhhhhhhh"
{
  "appId": "appid000-aaaa-bbbb-cccc-dddddddddddd",
  "displayName": "spcertmanageridentity",
  "name": "http://spcertmanageridentity",
  "password": "password-aaaa-bbbb-cccc-dddddddddddd",
  "tenant": "tenant00-aaaa-bbbb-cccc-dddddddddddd"
}

kubectl create secret generic azuredns-config --from-literal=client-secret=password-aaaa-bbbb-cccc-dddddddddddd -n cert-manager

DNS Issuer

This is where everything is happening.

Your DNS issuer is the configuration that will be used by cert manager to be able to create the TXT record requested by the ACME CA (Let's Encrypt)

Here is a DNS issuer used with the Let's Encrypt staging endpoint:

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: yourname@youremail.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt
    # ACME DNS-01 provider configurations
    solvers:
    - dns01:
        azuredns:
          clientID: appid000-aaaa-bbbb-cccc-dddddddddddd
          clientSecretSecretRef:
            name: azuredns-config
            key: client-secret
          subscriptionID: subid000-eeee-ffff-gggg-hhhhhhhhhhhh
          tenantID: tenant00-aaaa-bbbb-cccc-dddddddddddd
          resourceGroupName: rgdns
          hostedZoneName: vote.dotmim.com
          environment: AzurePublicCloud

Do not hesitate to create all the cluster issuer you need: DNS with Let's Encrypt staging / DNS with Let's Encrypt production and so on...

You will use the one you need from the ingress configuration object.

kubectl apply -f dnsissuer.yaml

ingress

Now that we've deployed everything we need, just create an ingress with the correct values:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    ingress.kubernetes.io/ssl-redirect: "false"
    ingress.kubernetes.io/force-ssl-redirect: "false" 
    #nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-staging
  name: vote-ingress
spec:
  tls:
    - hosts:
        - vote.dotmim.com
      secretName: tls-secret
  rules:
    - host: vote.dotmim.com
      http:
        paths:
          - backend:
              serviceName: azure-vote-front
              servicePort: 80

Debug

Check that your certificate has been correctly issued, using the describe command on objects Certificate, CertificateRequest and Order :

(Once again, removing unrelevant part, for clarity)


kubectl describe cert tls-secret
Name:         tls-secret
Kind:         Certificate
Metadata:
  Owner References:
    Kind:                  Ingress
    Name:                  vote-ingress
Spec:
  Dns Names:
    vote.dotmim.com
  Issuer Ref:
    Group:      cert-manager.io
    Kind:       ClusterIssuer
    Name:       letsencrypt-staging
  Secret Name:  tls-secret
Events:
  Type    Reason     Age   From          Message
  ----    ------     ----  ----          -------
  Normal  Requested  8s    cert-manager  Created new CertificateRequest resource "tls-secret-1150149038"

The Certificate request:

kubectl describe  CertificateRequest tls-secret-1150149038
Name:         tls-secret-1150149038
Kind:         CertificateRequest
Metadata:
  Owner References:
    Kind:                  Certificate
    Name:                  tls-secret
Spec:
  Issuer Ref:
    Group:  cert-manager.io
    Kind:   ClusterIssuer
    Name:   letsencrypt-staging
Events:
  Type    Reason        Age   From          Message
  ----    ------        ----  ----          -------
  Normal  OrderCreated  28s   cert-manager  Created Order resource default/tls-secret-1150149038-248511818
  Normal  OrderPending  28s   cert-manager  Waiting on certificate issuance from order default/tls-secret-1150149038-248511818: ""

And eventually the Order :

kubectl describe order tls-secret-1150149038-248511818
Name:         tls-secret-1150149038-248511818
Kind:         Order
Metadata:
  Owner References:
    Kind:                  CertificateRequest
    Name:                  tls-secret-1150149038
Spec:
  Dns Names:
    vote.dotmim.com
  Issuer Ref:
    Group:  cert-manager.io
    Kind:   ClusterIssuer
    Name:   letsencrypt-staging
Status:
  Certificate:   LS0tLS1CRUdJTiBDRVJUSU....
Events:
  Type    Reason    Age   From          Message
  ----    ------    ----  ----          -------
  Normal  Created   113s  cert-manager  Created Challenge resource "tls-secret-1150149038-248511818-2518637675" for domain "vote.dotmim.com"
  Normal  Complete  49s   cert-manager  Order completed successfully

Once you've reached that point, your certificate has been issued correctly. You can check once again the certificate and confirms it's correcty configured and downloaded in your cluster:

(base) spertus@MSI2019:~/cert$ kubectl describe cert tls-secret
Name:         tls-secret
Kind:         Certificate
Metadata:
Spec:
Status:
  Conditions:
    Last Transition Time:  2020-06-12T09:57:10Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
  Not After:               2020-09-10T08:57:10Z
Events:
  Type    Reason     Age                 From          Message
  ----    ------     ----                ----          -------
  Normal  Requested  10m                 cert-manager  Created new CertificateRequest resource "tls-secret-1150149038"
  Normal  Issued     9m25s (x2 over 8d)  cert-manager  Certificate issued successfully

Last step: Redirection

Now that you're able to reach your https entry point, don't forget to redirect all the trafic to your AKS cluster:

Redirect to AKS

As you can see, I'm able to reach my vote application, using an HTTPS connection and a staging Let's Encrypt certificate !

Web site using FAKE LE certificate

Top comments (0)