Skip to content

jgaa/nsblast

Repository files navigation

Security
Container

nsblast

Massively scalable authoritative DNS server

Why?

I needed a DNS server to make it easy to deploy stuff in Kubernetes with my own tool, k8deployer. The tool will generate all the required TLS certificates with Let's Encrypt and configure the ingress controller in Kubernetes automatically. This applies even for zones on local networks in the 192.168.. IP address space. That means that my tool need fast and reliable access to a DNS server so it can use Let's Encrypt DNS option for validating ownership of the domains.

I also needed something fun to work with after I ran into a severe work burn-out in 2022 that almost killed me. A DNS server was a perfect project for me to take on to re-gain my full energy, health and enthusiasm.

Design Goals

When I worked with a startup in 2022, we frequently bumped in to problems with the commercial DNS server provider the company was using (and paying a lot of cash for every month). When we exceeded 1 million host-names (fqdn's) we had reached our limit, and our tests failed. The number of production host-names, and host-names provided for QA and individual teams and developers were just enormous. Prior to this, I never even thought of a use-case for a DNS zone with more than a few hundred names.

When I worked with DNS servers in the past (yea, I was kind of a part time sysadmin back in the days) I always ended up writing my own user interfaces to simplify my work. I would use a DNS server that supported a SQL database back-end and then wrote a web-app or real application to manage the zones in the database server.

So, when I started on nsblast, I had a few requirements:

  • Nsblast's primary role is to be the perfect Authoritative DNS server for me.
  • It must be secure and reliable, following best practices and modern engineering principles.
  • It must be very easy to install, manage and use (because I'm lazy and like things that are simple, yet powerful).
  • It must have a REST API that is intuitive and easy to use for developers. I choose REST over for example gRPC because it's very simple to use directly for all kinds of use-cases, including bash scripts.
  • It must support SaaS use-cases, where a user (for example "Alice") can sign up and create her own logical sub-zone (like alice.k8deployer.nsblast.com if k8deployer.nsblast.com is an existing zone for SaaS users). Then, Alice can manager a number of Resource Records in her sub-zone (or rather, k8deployer could do that for her and also create and deploy Let's Encrypt certificates for those host-names, giving her the opportunity to focus 100% on whatever she is making or experimenting with in the Kubernetes cluster).
  • It must be massively scalable. A server should be able to handle millions of real zones and tens of millions of Resource Records for each zone.
  • It must handle a huge number of DNS and API request per second on each instance.
  • It must be CPU, memory and energy-efficient. Cloud infrastructure is expensive. Ideally, it should run comfortably on a raspberry PI 3 for most real use-cases (developers, developer-teams or small or medium companies and organizations running their own DNS servers for fun or to save costs).

Status

Under initial development.

Public beta expected soon.

RFC compliance (MVP)

  • RFC 1034 DOMAIN NAMES - CONCEPTS AND FACILITIES
  • RFC 1035 DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
  • RFC 1123 Requirements for Internet Hosts -- Application and Support
  • RFC 1183 New DNS RR Definitions (Rr, Asfdb)
  • RFC 1995 Incremental Zone Transfer in DNS
  • RFC 1996 A Mechanism for Prompt Notification of Zone Changes (DNS NOTIFY)
  • RFC 2181 Clarifications to the DNS Specification
  • RFC 2782 A DNS RR for specifying the location of services (DNS SRV)
  • RFC 3596 DNS Extensions to Support IP Version 6
  • RFC 3597 Handling of Unknown DNS Resource Record (RR) Types
  • RFC 4701 A DNS Resource Record (RR) for Encoding Dynamic Host Configuration Protocol (DHCP) Information (DHCID RR)
  • RFC 5936 DNS Zone Transfer Protocol (AXFR)
  • RFC 6891 Extension Mechanisms for DNS (EDNS(0))
  • RFC 7766 DNS Transport over TCP - Implementation Requirements
  • RFC 7929 DNS-Based Authentication of Named Entities (DANE) Bindings for OpenPGP
  • RFC 8482 Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY

note: Things in old RFC's that has later been obsoleted are ignored.

Features

  • Standards compliant, authoritative DNS server
    • Can ast as primary DNS server in a group of standard DNS servers
    • Can act as a follower DNS server in a group of standard DNS servers
  • HTTP REST API
    • Optional swagger endpoint to document/experiment with the API
    • RBAC (Role Based Access Control)
  • Rust operator CLIs
    • nsblast-dynip for DynIP capability-token updates
    • nsblastctl for tenant, zone, RR, import/export, and basic admin workflows
  • Support SaaS use cases with individual users grouped under organizations/tenants.
  • Simple Cluster mode
    • Allows a cluster of Nsblast server to work as a typical database cluster with one primary server for all writes, and any number of follower servers for reads.
    • Replication on the database layer via fast/memory efficient gRPC streams (no separate streaming components like Kafka or Pulsar).
    • In this mode, users of the REST API can wait until a change is propagated to all the servers before the request returns. Useful for example for letsencrypt wildchar certs or certs for fqdn's that resolve to private IP ranges.
  • Uses RocksDB as it's internal database engine. This is one of the most battle-tested and fastest database engines available.
  • Backup and restore at the database layer.
    • Incremental backups
    • Backups can be created, deleted, listed and verified from the REST API
    • Backups can be listed, verified and restored from the command-line. The server must be off-line in order to perform a restore.
    • Backups can easily be copied to/from remote storage via rsync or for example restic.

Building

The project use CMake.

It uses C++ 20 features and require g++-13 / clang-15 or newer.

It requires boost version 1.85 or newer by default.

Operator CLIs are built from the Rust workspace in cli/:

cd cli
cargo build -p nsblastctl
cargo build -p dynip-client

Book documentation for the operator CLI is in nsblast-book/src/devops_cli.md.

Other dependencies that are handled automatically by CMake:

  • logfault: For logging
  • yahat-cpp: Embedded HTTP server for the REST API interface
  • rocksdb: Database-engine

Build dependencies by distro

Required by default CMake options (NSBLAST_CLUSTER=ON, NSBLAST_WITH_SWAGGER=ON):

Debian / Ubuntu

sudo apt update
sudo apt install -y \
  build-essential cmake git pkg-config \
  libboost-all-dev \
  libssl-dev zlib1g-dev libbz2-dev liblz4-dev libsnappy-dev librocksdb-dev \
  libprotobuf-dev protobuf-compiler \
  libgrpc-dev libgrpc++-dev protobuf-compiler-grpc

Fedora

sudo dnf install -y \
  gcc-c++ cmake git pkgconf-pkg-config make \
  boost-devel \
  openssl-devel zlib-devel bzip2-devel lz4-devel snappy-devel rocksdb-devel \
  protobuf-devel protobuf-compiler \
  grpc-devel grpc-plugins

Arch Linux

sudo pacman -S --needed \
  base-devel cmake git pkgconf \
  boost \
  openssl zlib bzip2 lz4 snappy rocksdb \
  protobuf grpc

Optional features:

  • Tests (NSBLAST_WITH_TESTS=ON): libgtest-dev (Debian/Ubuntu), gtest-devel (Fedora), gtest (Arch)
  • Docs (NSBLAST_WITH_DOCS=ON): doxygen
  • UI (NSBLAST_WITH_UI=ON): npm/nodejs
  • UI local HTTP login override (NSBLAST_UI_ALLOW_HTTP_LOGIN=ON): disable HTTPS-only login checks in the built UI (local testing only, default OFF)

Example on building the application (with custom built boost-library in /opt):

cd nsblast
mkdir build
cd build
cmake -DBOOST_ROOT=/opt/boost/boost_1_85_0 ..
make -j `nproc`
LD_LIBRARY_PATH=/opt/boost/boost_1_85_0/stage/lib/ ctest
LD_LIBRARY_PATH=/opt/boost/boost_1_85_0/stage/lib/ ./bin/nsblast --help

Docker image

Building:

./build-docker-image.sh

Official image The official image is published by GitHub Actions to GitHub Container Registry: ghcr.io/jgaa/nsblast

Docker storage and certificates

The image does not declare Docker VOLUMEs on purpose.

For nsblast, explicit runtime mounts are the better operational model:

  • mount the database directory explicitly so the data location is obvious
  • mount TLS material explicitly so certificate lifecycle stays under operator control
  • mount backup directories explicitly if you want them persisted outside the container filesystem

Important container paths:

  • /var/lib/nsblast Default location for database state and generated password.txt
  • /etc/nsblast Good place to mount config files and TLS material

The image runs as UID 999, so mounted directories and files must be readable or writable by that user as required.

Recommended practices:

  • use a bind mount or named volume for /var/lib/nsblast
  • mount certs read-only when possible
  • keep private keys outside the image and inject them at runtime
  • be explicit about --db-path and any backup path you want persisted

Starting: The example uses explicit mounts for state and certs. Substitute paths and IPs for your environment.

mkdir -p ./nsblast-data ./nsblast-certs

docker run --name nsblast --rm -it \
  -v "$(pwd)/nsblast-data:/var/lib/nsblast" \
  -v "$(pwd)/nsblast-certs:/etc/nsblast:ro" \
  -p 172.17.0.1:53:5353/udp \
  -p 172.17.0.1:53:5353/tcp \
  -p 172.17.0.1:80:8080/tcp \
  ghcr.io/jgaa/nsblast \
  --db-path /var/lib/nsblast \
  --dns-udp-port 5353 \
  --dns-tcp-port 5353 \
  --http-port 8080 \
  --dns-endpoint 0.0.0.0 \
  --http-endpoint 0.0.0.0

Bootstrap a brand new database explicitly before normal startup:

docker run --name nsblast-bootstrap --rm -it \
  -e NSBLAST_ADMIN_PASSWORD='change-me' \
  -v "$(pwd)/nsblast-data:/var/lib/nsblast" \
  ghcr.io/jgaa/nsblast \
  --db-path /var/lib/nsblast \
  bootstrap \
  --cluster-role none

About

Massively scalable authorative DNS server

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors