Setting up Wildcard Certificates Using Traefik, Let’s Encrypt and Acme-Dns
  1. Anasayfa
  2. DevOps

Setting up Wildcard Certificates Using Traefik, Let’s Encrypt and Acme-Dns

0

In this post, we are going to create a setup of Traefik on Kubernetes with CRDs and Let’s Encrypt with wildcard certificates, while also enabling Traefik to be highly available. This setup also allows you to continue using your existing DNS provider, even if it doesn’t have an API for usage with cert-manager. The only requirement is that it supports wildcard, CNAME and NS entries. To create this setup, we will be using cert-manager and acme-dns.

Prerequisites

– Kubernetes cluster

– Traefik with CRD configuration installed on Kubernetes

– Helm v3

– DNS provider with wildcard, CNAME and NS capability, either self-hosted or provided

– separate IP for acme-dns

Installing cert-mgr

First, we are going to install cert-manager (https://cert-manager.io). This tool is responsible for automatically requesting new LetsEncrypt certificates and keeping them up to date.

 

We will use the namespace cert-manager to install it.

kubectl create namespace cert-manager

Add the Jetstack Helm repo (https://github.com/jetstack/cert-manager), afterwards install cert-manager.

helm install \

cert-manager jetstack/cert-manager \

--namespace cert-manager \

--version v1.1.0 \

--set installCRDs=true

The installCRDs=true allows you to automatically manage all custom resources cert-manager requires with Helm instead of having to manually apply a manifest yaml.

Installing acme-dns

Next, we are going to install acme-dns (https://github.com/joohoi/acme-dns), which will serve the necessary TXT records LetsEncrypt requires for wildcard certificates. For this purpose, we will create a simple Helm chart using the command “helm create acme-dns”.

 

Open the values.yaml file Helm created. Change the image repository to “joohoi/acme-dns” and change the nodeSelector to match the node acme-dns will run on, e.g. “kubernetes.io/hostname: “auth.example.org””

 

Create a new config.yaml file under the templates folder, which will contain our ConfigMap for acme-dns.

Copy the configuration from https://github.com/joohoi/acme-dns#configuration into the ConfigMap and replace domain, nsname, nsadmin and records with your chosen domain and IP(s).

 

apiVersion: v1

kind: ConfigMap

metadata:

 name: acme-dns-config

data:

config.cfg: |

<content of cfg>

 

Next, open the deployment.yaml and change the kind to a DaemonSet, remove “:{{ .Chart.AppVersion }}” from the container image, add the DNS port and the ConfigMap we just created as well as a volume for the data acme-dns will store. The final file should look something like this:

 

apiVersion: apps/v1

kind: DaemonSet

metadata:

  name: {{ include "test.fullname" . }}

  labels:

    {{- include "test.labels" . | nindent 4 }}

spec:

  replicas: {{ .Values.replicaCount }}

  selector:

    matchLabels:

      {{- include "test.selectorLabels" . | nindent 6 }}

  template:

    metadata:

      labels:

        {{- include "test.selectorLabels" . | nindent 8 }}

    spec:

    {{- with .Values.imagePullSecrets }}

      imagePullSecrets:

        {{- toYaml . | nindent 8 }}

    {{- end }}

      serviceAccountName: {{ include "test.serviceAccountName" . }}

      securityContext:

        {{- toYaml .Values.podSecurityContext | nindent 8 }}

      containers:

        - name: {{ .Chart.Name }}

          securityContext:

            {{- toYaml .Values.securityContext | nindent 12 }}

          image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"

          imagePullPolicy: {{ .Values.image.pullPolicy }}

          ports:

            - name: dns-tcp

              containerPort: 53

              hostPort: 53

              protocol: TCP

            - name: dns-udp

              containerPort: 53

              hostPort: 53

              protocol: UDP

            - name: http

              containerPort: 80

              protocol: TCP

          livenessProbe:

            httpGet:

              path: /health

              port: http

          readinessProbe:

            httpGet:

              path: /health

              port: http

          volumeMounts:

            - mountPath: /etc/acme-dns

              name: config

            - mountPath: /var/lib/acme-dns

              name: data

          resources:

            {{- toYaml .Values.resources | nindent 12 }}

      {{- with .Values.nodeSelector }}

      nodeSelector:

        {{- toYaml . | nindent 8 }}

      {{- end }}

    {{- with .Values.affinity }}

      affinity:

        {{- toYaml . | nindent 8 }}

    {{- end }}

    {{- with .Values.tolerations }}

      tolerations:

        {{- toYaml . | nindent 8 }}

    {{- end }}

      volumes:

        - name: config

          configMap:

            name: acme-dns-config

        - name: data

          hostPath:

            path: /opt/acme-dns/data

            type: DirectoryOrCreate

 

Note that I also changed the livenessProbe and readinessProbe path to “/health”, which acme-dns provides out of the box.

 

For the sake of simplicity, I used hostPath and hostPort for persistent storage and access to the DNS port. In case you are using a cloud provider like AWS, you may want to change hostPort and hostPath to use a LoadBalancer type Service and Persistent Volumes.

 

Finally, create a namespace for acme-dns with “kubectl create namespace acme-dns” and run “helm install acme-dns <path to chart folder> –namespace acme-dns”

 

Registering an account with acme-dns

 

In order to use acme-dns, you first need to register with your instance. From within your cluster, execute

 

curl -X POST http://acme-dns.acme-dns.svc.cluster.local/register

 

Adjust the service URL to your cluster if, for example, you use a different hostname suffix.

You will receive a JSON response like this:

 

{

"username":"eabcdb41-d89f-4580-826f-3e62e9755ef2",

"password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0",

"fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org",

"subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf",

"allowfrom":[]

}

 

Adding DNS entries

 

We can now add all necessary DNS entries. The domain acme-dns will use needs the following two entries:

 

auth.example.org NS auth.example.org

auth.example.org A 1.2.3.4

 

This tells the DNS servers that acme-dns is responsible for “*.auth.example.org”, and that it is reachable under 1.2.3.4. If you also have an IPv6 address pointing to acme-dns, simply create an additional AAAA entry.

 

All domains that you want to authenticate need a DNS entry like the following one for “*.example.org”:

 

_acme-challenge.example.org CNAME d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org

 

Be aware that if you want to serve additional domains or you need to generate certificates that your applications need to use, **every** domain you want to authenticate needs such an entry. For example, a mailserver under “mail.example.org” needs an entry like

_acme-challenge.mail.example.org CNAME d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org

 

Creating a ClusterIssuer

 

Next, we are going to create a ClusterIssuer. This will instruct cert-manager to use acme-dns for generating new certificates.

 

Create a new json file acme-dns-creds.json, which should look something like this for “*.example.org”:

 

{

  "example.org": {

    "username":"eabcdb41-d89f-4580-826f-3e62e9755ef2",

    "password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0",

    "fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org",

    "subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf",

    "allowfrom":[]

            }

}

 

As with the DNS entries, **every** domain you want to authenticate needs a new entry. An additional mailserver under “mail.example.org” requires the file to look more like this:

 

{

  "example.org": {

    "username":"eabcdb41-d89f-4580-826f-3e62e9755ef2",

    "password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0",

    "fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org",

    "subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf",

    "allowfrom":[]

            },

            "mail.example.org": {

    "username":"eabcdb41-d89f-4580-826f-3e62e9755ef2",

    "password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0",

    "fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org",

    "subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf",

    "allowfrom":[]

            }

}

 

Note that you can re-use the already existing credentials you created for “*.example.org”.

Afterwards, create a secret from the json file:

kubectl create secret generic acme-dns --from-file acme-dns-creds.json

Create and apply a ClusterIssuer manifest with the following content:

 

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

  name: letsencrypt-issuer

spec:

  acme:

    email: admin@example.org

    server: https://acme-v02.api.letsencrypt.org/directory

    privateKeySecretRef:

      name: letsencrypt-issuer-account-key

    solvers:

    - dns01:

        cnameStrategy: Follow

        acmeDNS:

          host: http://acme-dns.acme-dns.svc.cluster.local:80

          accountSecretRef:

            name: acme-dns

            key: acme-dns-creds.json

 

Adjust the host if, for example, your cluster uses a different host suffix.

 

Adding Certificates

 

The next step is to create Certificate objects. Each represents one certificate cert-manager should request and keep up to date. Their manifest should look like this:

 

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

  name: example-org

  namespace: traefik

spec:

  secretName: example-org-tls

  issuerRef:

    name: letsencrypt-issuer

    kind: ClusterIssuer

  dnsNames:

  - '*.example.org'

  - example.org

 

 

This guide assumes Traefik runs in the “traefik” namespace. Adjust that to your case if necessary.

 

Create and apply one Certificate manifest for each wildcard certificate you want to have. You can check the state of the certificate by using “kubectl describe”.

 

Equipping traefik with the generated certificates

 

apiVersion: v1

kind: ConfigMap

metadata:

  name: traefik-tls-config

  namespace: traefik

data:

  traefik-certs.toml: |

    [tls]




      [[tls.certificates]]

        certFile = "/certs/example-org/tls.crt"

        keyFile = "/certs/example-org/tls.key"

 

As with the Certificate object, adjust the namespace if necessary.

 

Add the following two volumes and volume mounts to your traefik manifest:

 

volumes:

            - name: traefik-tls-config

                        configMap:

                                    name: traefik-tls-config

            - name: example-org-tls

                        secret:

                                    secretName: example-org-tls

  [...]

volumeMounts:

            - mountPath: /config

                        name: traefik-tls-config

            - mountPath: /certs/example-org

                        name: example-org-tls

  [...]

 

Make sure that the secretName matches the secretName you specified in the Certificate.

Now add the configuration file to Traefik, for example using an additional CLI argument:

 

args:

  [..]

--providers.file.filename=/config/traefik-certs.toml

 

Finally, apply the new Traefik manifest and watch your sites get served using your shiny new wildcard certificate!

TAGs: Traefik,Let’s Encrypt,Acme-Dns,wildcard certificate

Bu İçeriğe Tepkin Ne Oldu?
  • 4
    harika_
    Harika!!
  • 0
    be_enmedim
    Beğenmedim
  • 0
    _ok_iyi
    Çok iyi
  • 0
    sevdim_
    Sevdim!
  • 0
    bilemedim_
    Bilemedim!
  • 0
    olmad_
    Olmadı!
  • 0
    k_zd_m_
    Kızdım!

Having completed his undergraduate degree at Rosenheim University of Applied Sciences, Christopher Zentgraf now works as a software developer in the transportation industry in Germany.

Yazarın Profili
İlginizi Çekebilir

Bültenimize Katılın

Tıklayın, üyemiz olun ve yeni güncellemelerden haberdar olan ilk kişi siz olun.

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir