Files
fancy-dns/deploy/k8s/console/console.yaml
T
matsandClaude Sonnet 5.5 1a536e7d0e Split the console from the resolver: fancy-dns-console writes the cluster
fancy-dns-console is a second binary holding the web UI, authentication and
every edit. It writes ClientGroup, BlockList and DNSZone objects through the new
kubeedit package and forwards all other /api routes to a running resolver
with its own key, dropping the browser's credentials, so it can run in the
cluster or on a workstation with a kubeconfig and nothing about the resolver's
placement matters. The resolver never imports kubeedit and the console is not on
the DNS path.

kubeedit validates every spec with the runtime's own converters
(kubesource.CheckGroup/CheckList/CheckZone) before writing, so the console
cannot store an object the resolver would reject. Zone edits keep the
file-based writer's behaviour: duplicate and below-a-delegation records are
refused, a delegation in a new zone also gives the zone its servers, and a
zone with no apex NS is never stored. Each object records the editing user in
an annotation; updates retry on conflict.

The resolver reports groups from ClientGroup objects as source "kubernetes" and
counter-only rows (the "(default)" bucket, or a group removed since) as
"history", and lists DNSZone zones as the editable ones. The console uses that
to leave the YAML's groups alone and to drop history rows, which the UI would
otherwise send back on save. Its GET /api/clients overlays the cluster's
current objects on the resolver's list, because the resolver only sees an edit
after its watch fires and a save made just after another would delete the
first.

An end-to-end test runs both halves against one fake apiserver: a group saved
through the console refuses queries, a zone built record by record answers
authoritatively, and a request with the resolver's own key is rejected by the
console.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 08:43:41 +02:00

114 lines
3.7 KiB
YAML

# The console: web UI plus the API behind it, as its own Deployment. It writes
# ClientGroup / BlockList / DNSZone objects and forwards everything else to the
# resolver's admin API. It is not on the DNS path; stopping it changes no answer.
#
# Secrets it needs (create them out of band):
# fancy-dns-console-runtime key FANCYDNS_RUNTIME_KEY : an API key the resolver accepts
# fancy-dns-console-apikey key FANCYDNS_API_KEY : the console's own machine key
apiVersion: v1
kind: ConfigMap
metadata:
name: fancy-dns-console
namespace: dns
data:
console.yaml: |
# The console has no blocklists or listeners of its own.
blocklist: {enabled: false}
api:
enabled: true
listen: ["0.0.0.0:8080"]
base_path: /api
ui: true
auth:
mode: oidc
api_keys: ["${FANCYDNS_API_KEY}"]
oidc_issuer: https://users.k6n.net
oidc_audience: https://dns.k6n.net
oidc_redirect_uri: https://dns.k6n.net/auth/oidc/callback
oidc_client_id: fancy-dns
oidc_write_roles: [admin]
trust_loopback: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fancy-dns-console
namespace: dns
---
# The only component allowed to write the objects.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: fancy-dns-console
namespace: dns
rules:
- apiGroups: [fancydns.k6n.net]
resources: [dnszones, clientgroups, blocklists]
verbs: [get, list, create, update, delete]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: fancy-dns-console
namespace: dns
subjects:
- {kind: ServiceAccount, name: fancy-dns-console, namespace: dns}
roleRef: {apiGroup: rbac.authorization.k8s.io, kind: Role, name: fancy-dns-console}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fancy-dns-console
namespace: dns
labels: {app.kubernetes.io/name: fancy-dns-console}
spec:
# One replica: OIDC login state and sessions are in memory, as in the
# resolver's console. More than one needs sticky routing.
replicas: 1
selector:
matchLabels: {app.kubernetes.io/name: fancy-dns-console}
template:
metadata:
labels: {app.kubernetes.io/name: fancy-dns-console}
spec:
serviceAccountName: fancy-dns-console
securityContext:
runAsNonRoot: true
runAsUser: 65532
seccompProfile: {type: RuntimeDefault}
containers:
- name: console
image: fancydns/fancy-dns:latest
command: ["/usr/local/bin/fancy-dns-console"]
args:
- -config=/etc/fancy-dns/console.yaml
# A Service in front of the resolver pods. With several replicas the
# statistics and query log come from whichever pod answers.
- -runtime-url=http://fancy-dns-admin.dns.svc:8080
env:
- name: FANCYDNS_RUNTIME_KEY
valueFrom: {secretKeyRef: {name: fancy-dns-console-runtime, key: FANCYDNS_RUNTIME_KEY}}
- name: FANCYDNS_API_KEY
valueFrom: {secretKeyRef: {name: fancy-dns-console-apikey, key: FANCYDNS_API_KEY}}
ports: [{name: http, containerPort: 8080}]
readinessProbe:
httpGet: {path: /api/healthz, port: http}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: {drop: [ALL]}
volumeMounts:
- {name: config, mountPath: /etc/fancy-dns, readOnly: true}
volumes:
- name: config
configMap: {name: fancy-dns-console}
---
apiVersion: v1
kind: Service
metadata:
name: fancy-dns-console
namespace: dns
spec:
selector: {app.kubernetes.io/name: fancy-dns-console}
ports: [{name: http, port: 80, targetPort: http}]