• Stars
    star
    117
  • Rank 301,757 (Top 6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Lightweight API driven Authoritative DNS server

PendingDNS

Lightweight API driven Authoritative DNS server.

Features

  • All records can be edited over REST API
  • All changes are effective immediatelly (or as long as it takes for Redis - eg. the backend for storing data - to distribute changes from master to replica instances)
  • Basic record types (A, AAAA, CNAME, TXT, MX, CAA)
  • ANAME pseudo-record for apex domains
  • URL pseudo-record for HTTP and HTTPS redirects. Valid HTTPS certificates are generated automatically, HTTPS host gets A+ rating from SSLabs.
  • URL record can be turned into a Cloudflare-like proxying by using proxy=true flag. Though, while Cloudflare makes things faster then PendingDNS makes things slightly slower due to not caching anything.
  • Periodic health checks to filter out unhealthy A/AAAA records
  • Lightweight
  • Can be geographically distributed. All writes go to central Redis master, all reads are done from local Redis replica
  • Request certificates over API

Limitations

  • No support for zone files, all records must be managed over API
  • Only the most basic and common record types
  • No support for DNSSEC
  • Only plain old DNS over UDP and TCP, no DoH, no DoT
  • Barely tested on Project Pending. Do not use this for mission critical domains. PendingDNS is only good for leftover domains, ie. for development and testing.

Requirements

  • Node.js, preferrably v12+
  • Redis, any version should do as only basic commands are used

Usage

$ npm install --production
$ npm start

Run as SystemD service

If you want to run PendingDNS as a SystemD service, then there's an example service file with comments.

1. Setup commands

As root run the following commands to set up PendingDNS:

$ cd /opt
$ git clone git://github.com/postalsys/pending-dns.git
$ cd pending-dns
$ npm install --production
$ cp systemd/pending-dns.service /etc/systemd/system
$ cp config/default.toml /etc/pending-dns.toml

2. Configuration

Edit the configuration file /etc/pending-dns.toml and make sure that you have correct configuration.

Also make sure that /etc/systemd/system/pending-dns.service looks correct.

3. Start

Run the following commands as root

$ systemctl enable pending-dns
$ systemctl start pending-dns

General Name Server setup

Conflicts on port 53

There might be already a recursive DNS server listening on 127.0.0.1:53 (or more commonly, SystemD stub resolver on 127.0.0.53:53) so you can't bind your DNS server to 0.0.0.0. Instead bind directly to your outbound interface, you can usually find these by running ip a.

$ ip a
  …
  inet 172.31.41.89/20 brd 172.31.47.255 scope global ens5
  …
[dns]
  port = 53
  host = "172.31.41.89"

Glue records

If you want to use PendingDNS as an authoritative DNS server for your domains then you need at least 2 instances of the server.

Additionally you need to set up both A and so-called GLUE records for the domain names of your name servers. Not all DNS providers allow to set GLUE records.

Here's an example how A records are set up for ns01.pendingdns.com and ns02.pendingdns.com that manage domains hosted on Project Pending:

Registrar and DNS provider for these domains is OVH but you can use any registrar with GLUE support


And the corresponding GLUE records:


Without proper setup domain registrars do not allow your name server domain names to be used. Here's an example for a successful name server setup:

API

You can see the entire API docs from the swagger page at http://127.0.0.1:5080/docs

List Zone entries

GET /v1/zone/{zone}/records

$ curl -X GET "http://127.0.0.1:5080/v1/zone/mailtanker.com/records"
{
    "zone": "mailtanker.com",
    "records": [
        {
            "id": "Y29tLm1haWx0YW5rZXIBQQEzc3lKWkkzbGo",
            "type": "A",
            "address": "18.203.150.145"
        },
        {
            "id": "Y29tLm1haWx0YW5rZXIud3d3AUNOQU1FAXhhV1lnbnFaMA",
            "type": "CNAME",
            "subdomain": "www",
            "target": "mailtanker.com"
        }
    ]
}

NB! system records (NS, SOA) have id=null and these records can not be modified over API

Create new Resource Record

POST /v1/zone/{zone}/records

$ curl -X POST "http://127.0.0.1:5080/v1/zone/mailtanker.com/records" -H "Content-Type: application/json" -d '{
    "subdomain": "www",
    "type": "CNAME",
    "target": "@"
}'
{
    "zone": "mailtanker.com",
    "record": "Y29tLm1haWx0YW5rZXIud3d3AUNOQU1FAXhhV1lnbnFaMA"
}

All record types have the following properties

  • subdomain (optional) subdomain this record applies to. If blank, or "@" or missing then the record is created for zone domain.
  • type one of A, AAAA, CNAME, ANAME, URL, MX, TXT, CAA, NS

Type specific options

A

  • address is an IPv4 address
  • healthCheck (String) is a health check URI, either tcps?://host:port or https?://host:port/path. When doing TCP checks, successfully opened connection is considered healthy. For HTTP checks 2xx response code is considered healthy. TLS certificate is no validated, self-signed certificates are allowed.

AAAA

  • address is an IPv6 address
  • healthCheck (String) is a health check URI, either tcps?://host:port or https?://host:port/path. When doing TCP checks, successfully opened connection is considered healthy. For HTTP checks 2xx response code is considered healthy. TLS certificates for https/tcps are not validated, so self-signed certificates are allowed to be used for health check hosts.

CNAME

  • target is a domain name or "@" for zone domain

ANAME

  • target is a domain name

TXT

  • data is the data string without quotes. Provide the entire value, do not chop it into substrings

MX

  • exchange is the domain name of the MX server
  • priority is the priority number of the MX

NS

  • ns is the domain name of the NS server

CAA

  • value is the domain name of the provider, eg. letsencrypt.org
  • tag is the CAA tag, eg. issue or issuewild

URL

Modify existing Resource Record

PUT /v1/zone/{zone}/records/{record}

$ curl -X PUT "http://127.0.0.1:5080/v1/zone/mailtanker.com/records/Y29tLm1haWx0YW5rZXIud3d3AUNOQU1FAXhhV1lnbnFaMA" -H "Content-Type: application/json" -d '{
    "subdomain": "www",
    "type": "CNAME",
    "target": "example.com"
}'
{
    "zone": "mailtanker.com",
    "record": "Y29tLm1haWx0YW5rZXIud3d3AUNOQU1FAXhhV1lnbnFaMA"
}

NB! resulting record ID might be different from the original ID

Delete Resource Record

DELETE /v1/zone/{zone}/records/{record}

$ curl -X DELETE "http://127.0.0.1:5080/v1/zone/mailtanker.com/records/Y29tLm1haWx0YW5rZXIBQQFjT2NWd0d6bE4"
{
    "zone": "mailtanker.com",
    "record": "Y29tLm1haWx0YW5rZXIBQQFjT2NWd0d6bE4",
    "deleted": true
}

Generate Certificate

This API endpoint requests a new certificate from Let's Encrypt or returns a previously generated one.

Certificates can only be requested for domains that:

  1. have at least one resource record set for their zone (not important which kind)
  2. have correctly pointed NS records to your PendingDNS servers
$ curl -X POST "http://127.0.0.1:5080/v1/acme" -H "Content-Type: application/json" -d '{
    "domains": [
        "mailtanker.com",
        "*.mailtanker.com"
    ]
}'
{
    "dnsNames": ["*.mailtanker.com", "mailtanker.com"],
    "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIB...",
    "cert": "-----BEGIN CERTIFICATE-----\nMIIFaT...\n",
    "validFrom": "2020-06-03T18:50:52.000Z",
    "expires": "2020-09-01T18:50:52.000Z"
}

Acknowledgments

License

MIT