PowerDNS-AuthAdmin

# PowerDNS-AuthAdmin > A modern, self-hosted DNS administration UI for PowerDNS Authoritative - first-class RBAC, > audit log with diffs, SSO with group-driven role mapping, optimistic concurrency in the editor, > and a UI built for teams that actually run multi-backend infrastructure. [![CI](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/ci.yml?query=branch%3Amain) [![Release](https://img.shields.io/github/v/release/PowerDNS-AuthAdmin/powerdns-authadmin)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/releases/latest) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/PowerDNS-AuthAdmin/powerdns-authadmin/badge)](https://scorecard.dev/viewer/?uri=github.com/PowerDNS-AuthAdmin/powerdns-authadmin) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12974/badge)](https://www.bestpractices.dev/projects/12974) [![Container: GHCR](https://img.shields.io/badge/ghcr.io-powerdns--authadmin-2496ED?logo=docker&logoColor=white)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/pkgs/container/powerdns-authadmin) [![GHCR version](https://ghcr-badge.ngn.au/powerdns-authadmin/powerdns-authadmin/latest_tag?ignore=latest,edge,%2Asha%2A,%5B0-9%5D,%5B0-9%5D.%5B0-9%5D,%5B0-9%5D.%5B0-9%5D%5B0-9%5D&label=ghcr.io&color=%232ea44f)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/pkgs/container/powerdns-authadmin) [![GHCR pulls](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fghcr-badge.elias.eu.org%2Fapi%2Fpowerdns-authadmin%2Fpowerdns-authadmin&query=%24.downloadCount&label=ghcr%20pulls&logo=docker&logoColor=white&color=2496ED)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/pkgs/container/powerdns-authadmin) [![GHCR image size](https://ghcr-badge.ngn.au/powerdns-authadmin/powerdns-authadmin/size?tag=latest&label=image%20size&color=%232ea44f)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/pkgs/container/powerdns-authadmin) [![Node.js 24](https://img.shields.io/badge/Node.js-24-339933?logo=node.js&logoColor=white)](.nvmrc) [![Next.js 16](https://img.shields.io/badge/Next.js-16-000000?logo=next.js)](https://nextjs.org) [![TypeScript strict](https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white)](tsconfig.json) **[Website](https://powerdns-authadmin.org/)** **[Docs](https://powerdns-authadmin.org/docs/)** **[Live demo](https://demo.powerdns-authadmin.org/)** (sign in with `admin@example.com` / `change-me-now`) PowerDNS-AuthAdmin manages one or many PowerDNS Authoritative backends from a single web app. It ships with a permissive RBAC engine, OIDC single sign-on with group→role mapping, transactional zone editing, NOTIFY-aware sync probes for cluster + primary/secondary topologies, an append-only audit log, scoped API tokens, and a YAML-driven first-boot provisioning system that brings up a ready-to-use install without a single click. **PowerDNS Auth version compatibility tests:** [![PowerDNS 4.6](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-46.yml/badge.svg)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-46.yml) [![PowerDNS 4.7](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-47.yml/badge.svg)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-47.yml) [![PowerDNS 4.8](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-48.yml/badge.svg)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-48.yml) [![PowerDNS 4.9](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-49.yml/badge.svg)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-49.yml) [![PowerDNS 5.0](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-50.yml/badge.svg)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-50.yml) [![PowerDNS 5.1](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-51.yml/badge.svg)](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/actions/workflows/pdns-compat-51.yml) ## At a glance - **Multi-backend.** One install fronts standalone primaries, primary + secondaries groups, and multi-primary clusters. Backends are visible side-by-side; zones merge into one amalgamated list. Per-cluster peer-selection strategies (round-robin / random / lowest-latency / least-load) route reads and writes to a member of the cluster. Any backend can be marked **read-only** so writes skip it - the escape hatch for public nameservers fed by database replication, which PowerDNS itself reports as ordinary writable primaries. - **Real RBAC.** Five system roles plus org-defined custom roles. Permissions span 54 actions across zones, records, DNSSEC, TSIG, metadata, autoprimaries, templates, users, teams, servers, API tokens, audit, and auth providers. Assignments scope to global / team / zone / server; a role scoped to a zone (e.g. from an IdP group) grants that zone and nothing else. A zone's authority is held apart from its records - the SOA, the apex NS and the Zone settings tab each answer to their own permission, so a hosting customer can manage their records without ever being shown the knobs that break the zone. - **Auth.** Local accounts (Argon2id), generic OIDC with PKCE, SAML 2.0 and LDAP direct-bind providers - each with per-provider group→role mapping - TOTP or passkey / security-key MFA (WebAuthn, also usable as passwordless sign-in; greyed out for SSO-only users - the IdP is the trust root), `pda_pat_` API tokens with per-token permission scopes. - **RP-initiated logout.** OIDC sessions sign you out at the IdP, not just locally; the `end_session_endpoint` + `id_token_hint` flow lands you on the IdP's signed-out screen. - **Reverse DNS.** Optional auto-PTR: saving an A/AAAA record keeps its PTR in a hosted reverse zone in sync (global, per-zone or per-save; never creates zones, never clobbers a PTR that points elsewhere), plus a fleet-wide Reverse DNS page that diffs every address against its PTR and rectifies the rows you pick. **Opt-in:** auto-PTR is the `auto_ptr` setting; the Reverse DNS page and PTR chips need `REVERSE_DNS_ENABLED=true`. - **Zone monitoring.** Every hosted zone checked from the outside on a schedule: delegation at the parent (walked from the root servers, not through a resolver), nameserver reachability, authority, serials and NS sets, open AXFR, DS versus DNSKEY, what public or internal resolvers answer, SOA sanity and reverse-DNS drift. Stable finding codes with a what-to-do, horizon-aware, bounded load, findings in the bell and a count in the sidebar. **Opt-in:** set `ZONE_MONITOR_ENABLED=true` to turn it on (it sends DNS queries outside your network). - **Zones + records.** Per-RRset editor with diff-before-apply, zone cloning, zone templates (NS + SOA timers + prelude records + zone-object settings + per-kind metadata), per-type record validators, optimistic concurrency at the RRset level. - **DNSSEC.** Cryptokey create / update / delete with per-key activity surfaced from the audit log. Zone metadata management with diff history. - **TSIG + autoprimaries.** Manage TSIG keys (read vs reveal split into separate permissions), configure autoprimary registrations. - **Cluster + sync.** A "cluster" is N writable PDNS peers behind a replicated store. Sync probe for primary+secondaries: compare every secondary's serial against the primary, record-for-record diff on demand. Sync probe for clusters: same UI shape, all peers compared against the highest-serial peer as anchor. - **Audit.** Append-only log of every write. Before/after JSONB snapshots redacted for known secret fields. Per-zone history feed with chip-coloured action types. Operator-driven export. - **Provisioning.** `provisioning.yaml` applied on first boot: settings, custom roles, teams, zone templates, PDNS clusters + servers, OIDC / SAML / LDAP providers (with group mappings), demo zones. See [`provisioning.example.yaml`](./provisioning.example.yaml) for an exhaustive reference. - **Observability.** Pino structured logs (secret-redacted), bearer-gated Prometheus `/metrics`, `/healthz` liveness, `/readyz` readiness (200 when the database is reachable, 503 otherwise). - **Natural-language DNS (MCP).** A built-in [Model Context Protocol](./docs/14-MCP.md) server lets Claude Code, Codex, Cursor and any MCP client manage zones and records from a prompt, with every call scoped to a personal access token and written to the audit log. - **Self-contained.** One Docker image, no CDN, no telemetry phone-home. Migrations run inside the app entrypoint; on Postgres they're serialized by an advisory lock so multi-replica boots are safe. The full feature catalog with module-level docs is in [`docs/FEATURES.md`](./docs/FEATURES.md). ## Screenshots **Dashboard** - live PowerDNS stats, active sessions, and operator-attention surfaces. Dashboard
**Multi-backend** - clusters, primary + secondaries groups, and standalone primaries side by side. Live sync state, drift advisories, and the dashboard PowerDNS-metrics tab are opt-in via [`PDNS_BACKGROUND_POLLING=true`](./docs/03-CONFIGURATION.md#pdns_background_polling) - recommended for replication topologies, off by default for standalone installs. PowerDNS servers
**Amalgamated zones** - every backend's zones in one searchable list with serial + per-row sync state. Zones
**Per-RRset editor** - per-type structured editors with inline validation. Edit record dialog
**Diff-before-apply** - every change previewed as a BIND-style before / after diff before it's written. Review changes diff
**Backend health** - bell-driven advisories for unreachable hosts, replication drift, missing TSIG keys, daemon-config drift. Backend health alerts
**Append-only audit log** - redacted before/after snapshots, per-row PDNS HTTP trail, CSV export. Audit log ### Mobile-first Every page is responsive down to a phone viewport - the off-canvas hamburger drawer, the bell + theme + avatar cluster, and the record table all reflow cleanly. Screenshots are rendered inside an iPhone 16 Pro bezel by [`scripts/screenshots.mjs`](./scripts/screenshots.mjs).

Dashboard on mobile   Zone detail on mobile   Audit log on mobile

Full gallery - every page, four variants: [**screenshots/README.md**](./screenshots/README.md). ## Talk to your DNS PowerDNS-AuthAdmin ships an **MCP server** at `/api/mcp`, so an AI assistant can manage your zones the way you would describe it to a colleague: > "Point `www` on example.com at 192.0.2.10 and add an AAAA for 2001:db8::10." > "Which zones still have the old mail server? Replace their MX with mail.example.net." > "Create staging.example.com from the template and give it the same NS set as example.com." Three steps: 1. **Profile → API tokens → Create**, pick the scopes the assistant may use (for example `zone.read` + `record.*`, or a single per-zone grant). 2. Connect your client to `https:///api/mcp` with `Authorization: Bearer pda_pat_…`: ```sh claude mcp add --transport http powerdns https://dns.example.com/api/mcp \ --header "Authorization: Bearer pda_pat_xxxxxxxx" ``` Codex, Cursor and others take the same URL and header - see [docs/14-MCP.md](./docs/14-MCP.md) for each client. 3. Ask. The assistant gets nine task-shaped tools (`list_zones`, `list_records`, `set_records`, `add_record`, `delete_records`, `create_zone`, …), never more power than the token's owner, and every change lands in the audit log under that owner's name with the client's user agent. Read-only tokens see a read-only server. Destructive tools are annotated so clients can ask before running them. ## Run it The app ships as a single image - **[`ghcr.io/powerdns-authadmin/powerdns-authadmin`](https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin/pkgs/container/powerdns-authadmin)**. It runs on **SQLite** (single instance: homelab, eval, small teams) or **Postgres** (multi-instance, write-concurrent). Migrations and the system-role seed run automatically on boot. > New here? The [Quickstart](./docs/01-QUICKSTART.md) gets you clicking around in ~2 minutes; the > [Installation guide](./docs/02-INSTALLATION.md) covers a real production deploy. ### Try it instantly - the minimal-demo stack A throwaway SQLite stack with a bundled PowerDNS and 10 pre-seeded demo zones: ```sh git clone https://github.com/PowerDNS-AuthAdmin/powerdns-authadmin.git cd powerdns-authadmin docker compose up -d # → http://localhost:3000 (login: admin@example.com / change-me-now) ``` > ⚠️ Demo only: it reads `.env.example` directly, which ships public throwaway secrets. Don't expose it. ### Production For a real deployment - SQLite or Postgres, TLS, backups, and the boot sequence - follow the **[Installation guide](./docs/02-INSTALLATION.md)**. It's four copy-paste steps and the canonical source of truth (the demo above is evaluation-only). > Store `APP_SECRET_KEY` / `APP_ENCRYPTION_KEY` once in a persistent **`.env`** next to > your compose file - **never** shell `export`s. Exports vanish when the shell closes, and > a regenerated `APP_ENCRYPTION_KEY` makes every stored PowerDNS API key, OIDC secret, and > MFA secret undecryptable. Generate once, back the `.env` up, never change them. ### High availability (replicas > 1) To run more than one app replica, use **Postgres + Redis** and put a load balancer in front. Sessions are already shared (they live in Postgres); setting `REDIS_URL` makes the three remaining per-process pieces - auth rate limiting, one-time reveal tokens, and the realtime SSE event bus - coordinate across replicas. Without Redis those degrade to per-replica behaviour (looser rate limits, reveal tokens that only work on their origin replica, live updates that don't cross replicas), so Redis is **required** past one replica. See [ADR-0016](./docs/adr/0016-redis-horizontal-scale.md). > SQLite is single-instance only - a file-backed DB isn't shared storage. HA means > Postgres. The boot log says so if it detects the combination. ```yaml # docker-compose.ha.yml - Postgres + Redis, app fronted by your load balancer. services: app: image: ghcr.io/powerdns-authadmin/powerdns-authadmin:latest restart: unless-stopped # No host port - your load balancer (nginx/Traefik/cloud LB) fronts the replicas. expose: ["3000"] depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } environment: APP_URL: https://dns.example.com DATABASE_URL: postgres://pdns:${POSTGRES_PASSWORD}@postgres:5432/powerdns_authadmin REDIS_URL: redis://redis:6379 # ← enables cross-replica coordination APP_SECRET_KEY: ${APP_SECRET_KEY} APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY} BOOTSTRAP_ADMIN_EMAIL: admin@example.com BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD} # Plain compose: `docker compose -f docker-compose.ha.yml up -d --scale app=3`. # Swarm / k8s: set replicas in your orchestrator instead. deploy: replicas: 3 postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: pdns POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: powerdns_authadmin volumes: ["pg-data:/var/lib/postgresql/data"] healthcheck: test: ["CMD-SHELL", "pg_isready -U pdns -d powerdns_authadmin"] interval: 5s timeout: 5s retries: 10 redis: image: redis:7-alpine restart: unless-stopped command: ["redis-server", "--save", "", "--appendonly", "no"] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 volumes: pg-data: ``` > Migrations are serialized across replica boots by a Postgres advisory lock > (ADR-0011), so starting N replicas at once is safe - only one applies migrations. > Redis here is a coordination cache, not a datastore: persistence is off > (`--save "" --appendonly no`) because nothing it holds needs to survive a restart. ### Configuration Every variable is documented in [`.env.example`](./.env.example). The essentials: | Variable | Required | What | | ---------------------------------------------------- | ----------- | ---------------------------------------------------------------------- | | `APP_URL` | ✅ | Public URL the app is served from, no trailing slash. | | `APP_SECRET_KEY` | ✅ | Session / CSRF / token HMAC secret. `openssl rand -base64 32`. | | `APP_ENCRYPTION_KEY` | ✅ | AES-256 key (base64, decodes to ≥32 bytes). `openssl rand -base64 32`. | | `DATABASE_URL` | ✅ | `file:/data/powerdns_authadmin.db` (SQLite) or `postgres://…`. | | `REDIS_URL` | optional | Enables cross-replica coordination - **required** for replicas > 1. | | `BOOTSTRAP_ADMIN_EMAIL` / `_PASSWORD` | ⭐ | First SuperAdmin (password ≥12 chars). | | `OIDC_*` | optional | SSO - or add providers in the UI instead. | | `SMTP_*` | optional | Transactional email (verify-email, password reset). | | `APP_PDNS_ALLOW_PRIVATE_NETWORKS` / `_INSECURE_HTTP` | situational | Allow internal-network / `http://` PDNS backends. | | `APP_OIDC_ALLOW_PRIVATE_NETWORKS` / `_INSECURE_HTTP` | situational | Allow internal-network / `http://` OIDC issuers (SSRF guard). | Full reference with every variable: **[Configuration](./docs/03-CONFIGURATION.md)**. For SSO setup (env vs provisioning vs UI), see **[OIDC single sign-on](./docs/05-OIDC.md)**. ### Advanced topologies To see a primary + secondaries group or a multi-primary cluster wired up end-to-end (Postgres-backed, all official PowerDNS images), use the topology compose files: [`docker-compose-combined.yml`](./docker-compose-combined.yml), [`docker-compose-primary-secondaries.yml`](./docker-compose-primary-secondaries.yml), [`docker-compose-multi-primary.yml`](./docker-compose-multi-primary.yml). ### Local development ```sh nvm use && npm ci # Node 24 from .nvmrc cp .env.example .env.local # set APP_SECRET_KEY + APP_ENCRYPTION_KEY docker compose up pdns -d # a local PowerDNS to talk to npm run dev # http://localhost:3000 npm run validate # lint + typecheck + format + test ``` Full workflow + troubleshooting in [`docs/dev-setup.md`](./docs/dev-setup.md). ## Documentation Full guides live in **[`docs/`](./docs/)** - start at the [documentation index](./docs/README.md). | Guide | Purpose | | ----------------------------------------------------- | ---------------------------------------------------------------- | | [Quickstart](./docs/01-QUICKSTART.md) | Run the demo stack end-to-end in ~2 minutes. | | [Installation](./docs/02-INSTALLATION.md) | Production install - SQLite or Postgres, TLS, backups, upgrades. | | [Configuration](./docs/03-CONFIGURATION.md) | Every environment variable, grouped and explained. | | [Connecting PowerDNS backends](./docs/04-BACKENDS.md) | Primaries, secondaries, and multi-primary clusters. | | [OIDC single sign-on](./docs/05-OIDC.md) | SSO with group → role mapping (env vs provisioning vs UI). | | [First-boot provisioning](./docs/06-PROVISIONING.md) | Bring up a configured install from one YAML file. | | [Roles & permissions (RBAC)](./docs/07-RBAC.md) | Roles, the permission vocabulary, and scopes. | | [Hardening & best practices](./docs/08-HARDENING.md) | Lock down a production deployment. | | [Upgrading](./docs/09-UPGRADING.md) | Move to a new version safely. | | [Troubleshooting](./docs/10-TROUBLESHOOTING.md) | Fix startup errors and backend connectivity. | | [Passkeys & security keys](./docs/11-PASSKEYS.md) | WebAuthn as passwordless sign-in or second factor. | | [LDAP sign-in](./docs/12-LDAP.md) | Authenticate against Active Directory / OpenLDAP. | | [SAML 2.0 single sign-on](./docs/13-SAML.md) | SAML SSO with AD FS, Authentik, Keycloak. | ### Reference | Doc | Purpose | | ---------------------------------------------------------- | -------------------------------------------------------------------- | | [`docs/FEATURES.md`](./docs/FEATURES.md) | The full feature catalog with module pointers. | | [`docs/dev-setup.md`](./docs/dev-setup.md) | Local development workflow. | | [`docs/adr/`](./docs/adr/) | Architecture Decision Records - why the codebase is shaped this way. | | [`provisioning.example.yaml`](./provisioning.example.yaml) | Exhaustive provisioning reference. | | [`.env.example`](./.env.example) | Documented environment variables. | | [`CONTRIBUTING.md`](./CONTRIBUTING.md) | Code standards, testing, security, perf budgets. | | [`SECURITY.md`](./SECURITY.md) | Vulnerability reporting policy. | | [`CLAUDE.md`](./CLAUDE.md) | Guidance for AI coding agents working on this repo. | ## Status **Production-ready.** Deploy on SQLite or Postgres with the published image; see [Run it](#run-it). Released versions and changes are in [`CHANGELOG.md`](./CHANGELOG.md); the roadmap is tracked in GitHub issues - contributions welcome, read [`CONTRIBUTING.md`](./CONTRIBUTING.md) first. ## License [![License: MIT](https://img.shields.io/github/license/PowerDNS-AuthAdmin/powerdns-authadmin)](./LICENSE)