Description
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:
- [BUG] chart version 3.0.0 throws "Your DNS resolver at 10.43.0.10 isn't doing DNSSEC validation; Please use another resolver or enable unbound." #144
- Add dnsConfig support for admin #326
- https://github.com/Mailu/Mailu/blob/2024.06.10/optional/unbound/unbound.conf
- helm/k8s deployments seem to have problems with DNS resolution; Mailu expects validated dnssec records Mailu#2239
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.