DEBOPS.PDNS(5)

NAME
debops.pdns - Manage PowerDNS authoritative server

SYNOPSIS

debops run [<args>] [--] service/pdns [ansible_args]

DESCRIPTION

The debops.pdns role can be used to configure PowerDNS Authoritative Server <https://www.powerdns.com/auth.html>, whose service is called 'pdns' in Debian. pdns supports multiple backends like major relational databases, LDAP servers and plain text files. Backends that support native replication can be used in place of traditional zone transfers. Furthermore, pdns can be used for geographical load balancing and has excellent DNSSEC support, currently supporting the vast majority of DNSSEC-enabled domains in Europe. pdns is extensible using a wide variety of scripting languages.

GETTING STARTED

The default configuration assumes that the target host has been configured with debops.postgresql_server.

Without any additional configuration, the service/pdns playbook will configure a PostgreSQL role on the target host, create a database and initialize it with the pdns PostgreSQL schema, install PowerDNS Authoritative Server, and set it up using the gpgsql backend. The only mode of operation available will be native replication (i.e. pdns relies on the backend doing the replication of zone data to other pdns servers).

  Modes of operation

pdns has four modes of operation: native replication, primary operation, secondary operation, and autosecondary operation. A brief description of each mode follows. For full details, refer to the DNS Modes of Operation documentation <https://doc.powerdns.com/authoritative/modes-of-operation.html>.

  Native replication

Native replication does not need to be enabled. It is always available, provided that your backend supports it. With native replication, pdns just lets the backend figure out how to get the zone data to the backends of your other pdns servers.

  Primary operation

Primary operation can be enabled by setting pdns__primary to True. Doing so will instruct pdns to send (optionally TSIG-signed) notifications of changes to secondaries, which can then initiate zone transfers. Notifications are only sent for domains with type MASTER in your backend.

  Secondary operation

Secondary operation can be enabled by setting pdns__secondary to True. Doing so will instruct pdns to periodically check for zone changes at the primary nameservers, and update the local zones accordingly. These checks happen every 'refresh' seconds (as specified by the SOA record) and are only performed for domains with type SLAVE in your backend. Additionally, if the primary nameserver sends notifications for such domains, pdns will initiate a zone transfer immediately.

  Autosecondary operation

Autosecondary operation can be enabled by setting pdns__autosecondary to True. Doing so will instruct pdns to automatically provision domains that it receives notifications for, if the notifications come from an IP address listed in the 'supermasters' table in your backend database. pdns will then act as a secondary for those domains.

  Dynamic DNS Update (RFC 2136)

Dynamic DNS Update (RFC 2136 <https://datatracker.ietf.org/doc/html/rfc2136.html>) allows for changing authoritative zone data in a standardized way. It works by having a client send a DNS UPDATE message to the primary nameserver, which, after optional IP allowlist and/or transaction signature (TSIG) checking, updates the zone in whatever way the DNS UPDATE message tells it to. One service that supports sending these messages is the ISC DHCP Server, represented in the debops.dhcpd role.

The debops.pdns role enables support for the DNS UPDATE mechanism by default, but also denies all DNS updates unless an IP allowlist or TSIG key has been specified in the domain metadata. If you want to allow all DNS updates from a list of IP ranges, see pdns__allow_dnsupdate_from. For per-domain metadata related to DNS updates, see https://doc.powerdns.com/authoritative/dnsupdate.html#per-zone-settings

Note that not all pdns backends support DNS updates.

  DNSSEC

When operating in primary or native replication mode, pdns can perform online signing of zone data, i.e. signed responses are generated on-the-fly. These responses are cached internally. In much the same fashion, pdns can operate as a "bump-in-the-wire" front-signing server between a legacy (non-DNSSEC-capable) authoritative server and its clients.

A secondary pdns server can perform a DNSSEC-capable zone transfer, i.e. it stores and serves pre-signed zone data which it received from the primary.

There is also BIND-mode operation, which takes a traditional BIND-style zone file and signs it using DNSSEC keys stored in another backend.

The default setup when signing a zone with # pdnsutil secure-zone is a single ECDSAP256SHA256 key that is used as a Combined-Signing Key (CSK), with NSEC as the negative answer strategy. NSEC3 is also supported. If you use pdns, a split-key setup most likely makes little sense, but you can do it if you really want to.

Important caveats regarding DNSSEC support in pdns:

•   When operating in online signing mode, the default pdns configuration will not increase the SOA serial when signatures are being rolled. This is not a problem in the default native replication mode. For primary operation however, you need to pick a SOA-EDIT value to ensure signature freshness on secondaries. Please refer to the SOA-EDIT documentation <https://doc.powerdns.com/authoritative/dnssec/operational.html#soa-edit-ensure-signature-freshness-on-slaves> for this.

•   In online-signing mode, the ALIAS record type is not supported.

    Anyone using pdns to serve DNSSEC-signed zone data is encouraged to read the DNSSEC guide: https://doc.powerdns.com/authoritative/dnssec/index.html

  Example inventory

To run the pdns playbook against a host, it needs to be added to the [debops_service_pdns] inventory group:

[debops_service_pdns]
hostname

If you want to use the nginx reverse proxy, useful for remotely accessing the pdns API or metrics endpoint over TLS, you can add the host to the [debops_service_pdns_nginx] inventory group instead:

[debops_service_pdns_nginx]
hostname

Doing so will install the nginx webserver, configure it for use with pdns, and automatically enable the pdns API and metrics endpoint.

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.pdns role:

---
- name: Manage PowerDNS authoritative server
  hosts: [ 'debops_service_pdns' ]
  become: True
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: etc_services
      tags: [ 'role::etc_services', 'skip::etc_services' ]
      etc_services__dependent_list:
        - '{{ pdns__etc_services__dependent_list }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules:
        - '{{ pdns__ferm__dependent_rules }}'
    - role: postgresql
      tags: [ 'role::postgresql', 'skip::postgresql' ]
      postgresql__dependent_roles:
        - '{{ pdns__postgresql__dependent_roles }}'
    - role: pdns
      tags: [ 'role::pdns', 'skip::pdns' ]

There is a separate playbook for pdns with nginx as a reverse proxy:

---
- name: Manage PowerDNS authoritative server with Nginx
  hosts: [ 'debops_service_pdns_nginx' ]
  become: True
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: keyring
      tags: [ 'role::keyring', 'skip::keyring' ]
      keyring__dependent_apt_keys:
        - '{{ nginx__keyring__dependent_apt_keys }}'
    - role: apt_preferences
      tags: [ 'role::apt_preferences', 'skip::apt_preferences' ]
      apt_preferences__dependent_list:
        - '{{ nginx__apt_preferences__dependent_list }}'
    - role: etc_services
      tags: [ 'role::etc_services', 'skip::etc_services' ]
      etc_services__dependent_list:
        - '{{ pdns__etc_services__dependent_list }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules:
        - '{{ nginx__ferm__dependent_rules }}'
        - '{{ pdns__ferm__dependent_rules }}'
    - role: postgresql
      tags: [ 'role::postgresql', 'skip::postgresql' ]
      postgresql__dependent_roles:
        - '{{ pdns__postgresql__dependent_roles }}'
    - role: python
      tags: [ 'role::python', 'skip::python' ]
      python__dependent_packages3:
        - '{{ nginx__python__dependent_packages3 }}'
      python__dependent_packages2:
        - '{{ nginx__python__dependent_packages2 }}'
    - role: nginx
      tags: [ 'role::nginx', 'skip::nginx' ]
      nginx__dependent_servers:
        - '{{ pdns__nginx__dependent_servers }}'
    - role: pdns
      tags: [ 'role::pdns', 'skip::pdns' ]

DEFAULT VARIABLES: CONFIGURATION

Some of the debops.pdns default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  pdns__*_configuration

The pdns__configuration, pdns__group_configuration and pdns__host_configuration variables allow you to override /etc/powerdns/pdns.conf settings on a global, group or host basis. The variables are lists of dicts that get merged using the principles of Universal Configuration.

name
    Required. Name of the setting you want to change.

comment
    Optional. Comment added in the configuration file.

value
    Required. The value to configure as a string or YAML text block.

state
    Optional. The state of the setting in /etc/powerdns/pdns.conf, either "present" or "absent". Defaults to "present".

    Example:

    pdns__configuration:
      - name: 'also-notify'
        comment: |-
          Our secondary DNS provider uses unicast hosts to collect zone transfer
          data, and then distributes it internally to all their anycast servers.
        value: '2001:db8:a::1, 2001:db8:b::1, 2001:db8:c::1'
        state: '{{ "present"
                   if ansible_local.machine.deployment
                       | d("production") == "production"
                   else "absent" }}'

AUTHOR
Imre Jonk

COPYRIGHT
2014-2024, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others