Constellation
Pluggable authoritative DNS server. Entries can be added & removed from an HTTP REST API.
Constellation is a small authoritative server that lets you manage DNS entries from an HTTP REST API, in a generic way. It can be plugged to your existing infrastructure to manage DNS records for users of your service, eg. to configure outbound email records that cannot be easily wildcarded in a traditional DNS server (DKIM, DMARC, SPF records).
DNS entries are stored in Redis. The DNS database can thus be easily modified and dumped for backup purposes.
🇫🇷 Crafted in Angers, France.

Who uses it?
👋 You use Constellation and you want to be listed there? Contact me.
Features
- Pluggable authoritative DNS server, comes handy if you need to generate eg. email sub-domains for your users (with DKIM, DMARC and SPF records).
- HTTP REST API to check, read, insert, modify and delete DNS records on the fly.
- Persistence layer in Redis. This means you can run multiple Constellations hitting against the same database on the network. You can even shard Redis if you need fault tolerance on the DNS data store.
- Geo-DNS to serve records on a location basis. For instance, serve the IP to your US server for all North America users, and fallback to Europe for the rest. Based on MaxMind GeoLite2 free database, that is automatically updated when necessary.
How to use it?
Installation
Constellation is built in Rust. To install it, either download a version from the Constellation releases page, use cargo install or pull the source code from master.
Ensure that you build Constellation using Rust
nightly-2018-12-14(use thenightlytoolchain). Some Constellation dependencies will not compile with later versions of the compiler.
Install from source:
If you pulled the source code from Git, you can build it using cargo:
You can find the built binaries in the ./target/release directory.
Install libssl-dev (ie. OpenSSL headers) before you compile Constellation. SSL dependencies are required for the Geo-DNS database updater.
Install from Cargo:
You can install Constellation directly with cargo install:
Ensure that your $PATH is properly configured to source the Crates binaries, and then run Constellation using the constellation command.
Configuration
Use the sample config.cfg configuration file and adjust it to your own environment.
Available configuration options are commented below, with allowed values:
[server]
log_level(type: string, allowed:debug,info,warn,error, default:warn) — Verbosity of logging, set it toerrorin production
[dns]
inets(type: array[string], allowed: IPs + ports, default:[0.0.0.0:53, [::]:53]) — Hosts and UDP/TCP ports the DNS server should listen ontcp_timeout(type: integer, allowed: seconds, default:2) — Timeout of DNS over TCP connectionsnameservers(type: array[string], allowed: domain names, default: no default) — Name server domains for all served domainssoa_master(type: string, allowed: domain names, default: no default) — SOA master domain for all zones served by this name server (name of primary NS server)soa_responsible(type: string, allowed: email addresses as domain names, default: no default) — SOA responsible email for all zones served by this name serversoa_refresh(type: integer, allowed: seconds, default:10000) — SOA record refresh valuesoa_retry(type: integer, allowed: seconds, default:2400) — SOA record retry valuesoa_expire(type: integer, allowed: seconds, default:604800) — SOA record expire valuesoa_ttl(type: integer, allowed: seconds, default:3600) — SOA record TTL valuerecord_ttl(type: integer, allowed: seconds, default:3600) — DNS records TTL value
[[dns.zone.'{name}']]
Specify your zone name eg. as:
[[dns.zone.'crisp.email']]for zone base:crisp.email.
[geo]
database_path(type: string, allowed: folder path, default:./res/geo/) — Path to the folder containing the GeoIP databasedatabase_file(type: string, allowed: file name, default:GeoLite2-Country.mmdb) — File name for the GeoIP2 MMDB database in the database folder (either free GeoLite2 or paid GeoIP2; disablegeo.update_enableif you want to use a custom database)update_enable(type: boolean, allowed:true,false, default:true) — Whether to enable GeoIP database updater or notupdate_interval(type: integer, allowed: seconds, default:864000) — Interval for which to refresh GeoIP database in seconds (1 week or more is recommended)update_url(type: string, allowed: HTTP URL, default:http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz) — URL to the compressed GeoIP MMDB file (supported:tar.gz), that is downloaded on refresh
[http]
inet(type: string, allowed: IPv4 / IPv6 + port, default:[::1]:8080) — Host and TCP port the HTTP API server should listen onworkers(type: integer, allowed: any number, default:2) — Number of workers for the HTTP API server to run onrecord_token(type: string, allowed: secret token, default: no default) — Record secret token for management API access (ie. secret password)
[redis]
host(type: string, allowed: hostname, IPv4, IPv6, default:localhost) — Target Redis hostport(type: integer, allowed: TCP port, default:6379) — Target Redis TCP portpassword(type: string, allowed: password values, default: none) — Redis password (if no password, dont set this key)database(type: integer, allowed:0to255, default:0) — Target Redis databasepool_size(type: integer, allowed:0to(2^32)-1, default:8) — Redis connection pool sizemax_lifetime_seconds(type: integer, allowed: seconds, default:20) — Maximum lifetime of a connection to Redis (you want it below 5 minutes, as this affects the reconnect delay to Redis if a connection breaks)idle_timeout_seconds(type: integer, allowed: seconds, default:600) — Timeout of idle/dead pool connections to Redisconnection_timeout_seconds(type: integer, allowed: seconds, default:5) — Timeout in seconds to consider Redis dead and reject DNS and HTTP API queries
Run Constellation
Constellation can be run as such:
./constellation -c /path/to/config.cfg
DNS records management (HTTP REST API)
To check, read, insert, modify and delete DNS records, you need to use the Constellation HTTP REST API, that listens on the configured http.inet interface from your config.cfg file.
API overview
Endpoint URL:
HTTP http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/
Where:
zone_name: The zone name (ie. base domain), eg.crisp.emailrecord_name: The record name to read or alter (ie. sub-domain or base domain), eg.inbound.@for theinbound.crisp.emailFQDN, or@for thecrisp.emailFQDNrecord_type: The DNS record type to read or alter for therecord_name; either:a,aaaa,cname,mx,txtorptr(open an issue if you need support for another record type)
Request headers:
- Add an
Authorizationheader with aBasicauthentication where the password is your configuredhttp.record_token.
Geo-DNS regions:
If you want to serve records to the nearest server using the Geo-DNS feature, you will need to set regions via the API, where:
eu: Europenam: North Americasam: South Americaoc: Oceaniame: Middle Eastin: Indiaas: Asiaaf: Africa
API routes
Check if a DNS record exists
HTTP HEAD http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/
Example request:
HEAD /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Example response:
HTTP/1.1 200 OK
Get a DNS record
HTTP GET http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/
Example request:
GET /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Example response:
HTTP/1.1 200 OK
Content-Type: application/json
{"type":"mx","name":"@","ttl":600,"regions": null,"values":["1 inbound.crisp.email","10 inbound-failover.crisp.email"]}
Write a DNS record (or overwrite existing)
HTTP PUT http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/
Example request (standard):
PUT /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Content-Type: application/json; charset=utf-8
{"values":["1 inbound.crisp.email","10 inbound-failover.crisp.email"],"ttl":600}
Example request (Geo-DNS):
PUT /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Content-Type: application/json; charset=utf-8
{"values":["1 inbound.crisp.email","10 inbound-failover.crisp.email"],"regions":{"eu":["10 inbound-geo.europe.crisp.email"],"nam":["10 inbound-geo.northamerica.crisp.email"],"sam":["10 inbound-geo.southamerica.crisp.email"],"oc":["10 inbound-geo.oceania.crisp.email"],"me":["10 inbound-geo.middleeast.crisp.email"],"in":["10 inbound-geo.india.crisp.email"],"as":["10 inbound-geo.asia.crisp.email"],"af":["10 inbound-geo.africa.crisp.email"]},"ttl":600}
Example response:
HTTP/1.1 200 OK
Delete a DNS record
HTTP DELETE http://constellation.local:8080/zone/<zone_name>/record/<record_name>/<record_type>/
Example request:
DELETE /zone/crisp.email/record/@/mx HTTP/1.1
Authorization: Basic OlJFUExBQ0VfVEhJU19XSVRIX0FfU0VDUkVUX0tFWQ==
Example response:
HTTP/1.1 200 OK
:fire: Report A Vulnerability
If you find a vulnerability in Constellation, you are more than welcome to report it directly to @valeriansaliou by sending an encrypted email to valerian@valeriansaliou.name. Do not report vulnerabilities in public GitHub issues, as they may be exploited by malicious people to target production servers running an unpatched Constellation instance.
:warning: You must encrypt your email using @valeriansaliou GPG public key: :key:valeriansaliou.gpg.pub.asc.
:gift: Based on the severity of the vulnerability, I may offer a $200 (US) bounty to whomever reported it.