Skip to content

Approach to fix "Your DNS resolver isn't doing DNSSEC validation" #405

Open
1 of 1 issue completed
Open
@pgmillon

Description

@pgmillon

Hi,
This is just some knowledge sharing to help people seeking some guidance when having a DNSSEC issue with Mailu:

Your DNS resolver at 10.32.0.10 isn't doing DNSSEC validation; Please use another resolver or enable unbound

I'm running Mailu using this helm chart on Kubernetes by Scaleway and as of today and as far as I understand it, the cluster resolver is not validating DNSSEC.
sources:

root@debug:/# dig www.example.org. A +adflag

; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> www.example.org. A +adflag
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7369
;; flags: qr rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 72266556e2462318 (echoed)
;; QUESTION SECTION:
;www.example.org.               IN      A

;; ANSWER SECTION:
www.example.org.        30      IN      CNAME   www.example.org-v2.edgesuite.net.
www.example.org-v2.edgesuite.net. 30 IN CNAME   a1519.dscr.akamai.net.
a1519.dscr.akamai.net.  30      IN      A       92.123.239.35
a1519.dscr.akamai.net.  30      IN      A       92.123.239.75

;; Query time: 38 msec
;; SERVER: 10.32.0.10#53(10.32.0.10) (UDP)
;; WHEN: Sun May 04 20:57:11 UTC 2025
;; MSG SIZE  rcvd: 258

I opened a ticket to check with my provider if we could make DNSSEC validation work on the upstream resolver for my cluster but in the meantime I needed to fix my broken admin pod.

Since I run a combination of Terraform and Kustomize with Helm, here's what I did.

First, get the coredns IP and all the pod subnets within my cluster and use them to template the unbound config file:

data "kubernetes_nodes" "nodes" {}
data "kubernetes_service" "coredns" {
  metadata {
    name      = "coredns"
    namespace = "kube-system"
  }
}

resource "kubernetes_config_map" "unbound" {
    metadata {
        name      = "unbound-config"
        namespace = "mail-system"
    }

    data = {
        "unbound.conf" = templatefile("${path.module}/files/unbound.conf.tftpl", {
            subnets = tolist(data.kubernetes_nodes.nodes.nodes[*].spec[0].pod_cidr)
            dns_ip  = data.kubernetes_service.coredns.spec[0].cluster_ip
        })
    }
}
server:
  verbosity: 1
  interface: 0.0.0.0
  logfile: ""
  do-ip4: yes
  do-ip6: no
  do-udp: yes
  do-tcp: yes
  do-daemonize: no
%{ for subnet in subnets ~}
  access-control: ${subnet} allow
%{ endfor ~}
  directory: "/etc/unbound"
  username: unbound
  auto-trust-anchor-file: trusted-key.key
  root-hints: "/etc/unbound/root.hints"
  hide-identity: yes
  hide-version: yes
  cache-min-ttl: 300

  domain-insecure: "cluster.local"
  private-domain: "cluster.local"
  local-zone: ".172.in-addr.arpa." nodefault
  stub-zone:
    name: "cluster.local"
    stub-addr: ${dns_ip}
  forward-zone:
    name: "."
    forward-addr: ${dns_ip}

source: #144 (comment)

Then, add a resolver service using Mailu's image:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mailu-resolver
  namespace: mail-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/component: resolver
      app.kubernetes.io/instance: main
      app.kubernetes.io/name: mailu
  template:
    metadata:
      labels:
        app.kubernetes.io/component: resolver
        app.kubernetes.io/instance: main
        app.kubernetes.io/name: mailu
    spec:
      containers:
        - name: resolver
          image: ghcr.io/mailu/unbound:2024.06.10
          command:
            - /usr/sbin/unbound
          volumeMounts:
            - mountPath: /etc/unbound/unbound.conf
              name: unbound-config
              subPath: unbound.conf
          ports:
            - name: dns-udp
              containerPort: 53
              protocol: UDP
            - name: dns-tcp
              containerPort: 53
              protocol: TCP
          readinessProbe:
            tcpSocket:
              port: 53
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            tcpSocket:
              port: 53
            initialDelaySeconds: 15
            periodSeconds: 20
      volumes:
        - name: unbound-config
          configMap:
            name: unbound-config
---

apiVersion: v1
kind: Service
metadata:
  name: mailu-resolver
  namespace: mail-system
  labels:
      app.kubernetes.io/component: resolver
      app.kubernetes.io/instance: main
      app.kubernetes.io/name: mailu
spec:
    type: ClusterIP
    ports:
      - name: dns-udp
        port: 53
        protocol: UDP
      - name: dns-tcp
        port: 53
        protocol: TCP
    selector:
        app.kubernetes.io/component: resolver
        app.kubernetes.io/instance: main
        app.kubernetes.io/name: mailu

source: https://github.com/Mailu/Mailu/blob/2024.06.10/setup/flavors/compose/docker-compose.yml#L56-L67

And finally, where Kustomize became very useful to tweak the admin deployment and add an initContainer:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: mail-system

helmCharts:
  - name: mailu
    includeCRDs: false
    valuesFile: values.yml
    releaseName: main
    namespace: mail-system
    version: 2.1.2
    repo: https://mailu.github.io/helm-charts/

resources:
  - resolver.yml

patches:
  - path: patch-admin.yml

patch-admin.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mailu-admin
  namespace: mail-system
spec:
  template:
    spec:
      initContainers:
        - name: configure-dns
          image: ubuntu
          command:
            - sh
            - -c
            - |
              RESOLVER_IP=$(getent hosts mailu-resolver | awk '{ print $1 }')
              sed "s/^nameserver .*/nameserver $RESOLVER_IP/" /etc/resolv.conf > /tmp/resolv.conf
              cat /tmp/resolv.conf > /etc/resolv.conf
              cat /etc/resolv.conf

It's definitively not ideal and I agree the upstream resolver should be fixed to properly support DNSSEC validation. But for now, this does the job.

I saw some comments recommending to run our own recursive resolver for safety reasons.
Maybe adding unbound to this chart as an option could be a nice idea.

I hope this will be useful to anyone.

Sub-issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions