From cd21f27356e2a1e0eb3b95bfafaa5e535bde4f1f Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Sun, 15 Jul 2018 21:25:12 +0200 Subject: [PATCH] Update post --- content/post/bind9-api.md | 87 +++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/content/post/bind9-api.md b/content/post/bind9-api.md index b7b11f5..49a9004 100644 --- a/content/post/bind9-api.md +++ b/content/post/bind9-api.md @@ -14,15 +14,15 @@ two Debian VPS located in Italy (master) and France (slave). Until now, I've been changing the DNS records by SSHing into the machine and editing the zonefile by hand. This worked fine since I rarely needed to change any DNS records. Then earlier this year, [LetsEncrypt][0] -put the ACME v2 endpoint into production which allows users to issue +put the ACME v2 endpoint into production which allows users to obtain wildcard certificates using the DNS challenge. This put me into a situation where I needed to create, update and delete DNS records automatically. -While the LetsEncrypt HTTP challenge requires the user to make the -challenge flag available via HTTP under +The LetsEncrypt HTTP challenge requires the user to make the challenge +flag available via HTTP under `http://www.example.com/.well-known/acme-challenge`. This way, the ACME endpoint can only verify ownership over a specific subdomain (`www.example.com` in this case). The DNS challenge looks for the flag @@ -31,14 +31,14 @@ endpoint to validate ownership over the whole domain and it is possible to issue a wildcard certificate for `*.example.com`. Since DNS setups vary depending on the domain provider or used DNS -server, certbot can use manual auth and cleanup hooks, that receive +server, [certbot][10] can use manual auth and cleanup hooks, that receive the domain name and challenge flag via the environment variables `$CERTBOT_DOMAIN` and `$CERTBOT_VALIDATION` respectively. Once the challenge mechanism was understood, I needed a way to programmatically create and delete records on my BIND9 server. I -decided to implement I REST-like webservice to run on the same machine -as BIND9 and modify records using the `nsupdate` command. +decided to implement a REST-like webservice to run on the same machine +as BIND9 and modify records using the [`nsupdate` command][7]. The REST API offers two methods: @@ -70,17 +70,44 @@ API but this still does not protect against replay attacks. If an attacker managed to intercept an request to the API, (s)he would be able to resend the same request to the server and re-execute the command. To prevent this, the API server has to be placed behind a -reverse proxy like nginx to encrypt the requests using TLS or as I am +reverse proxy like [nginx][11] to encrypt the requests using TLS or as I am doing it, make the server listen on a private IP address inside an encrypted VLAN ([tinc][1] in my case). +Once the body was verified using the pre-shared secret `nsupdate` is +invoked and the following update or delete scripts are passed via +stdin: + +``` +server 127.0.0.1 +update add _acme-challenge.example.com 1337 TXT +send +``` + +``` +server 127.0.0.1 +update delete _acme-challenge.example.com TXT +send +``` + For the implementation of the API and the client, I chose to use Rust with the [actix-web][2] framework for the server and [reqwest][3] to -make HTTP requests on the client side. While I have already worked -with other Rust web frameworks, namely [Rocket][4] for my Bachelor -thesis but it depends on the nightly branch of the compiler and is a -pain to maintain over a longer period of time. Also actix-web is -_really_ fast[^actix-performance]. +make HTTP requests on the client side. The implementation along with +installation instructions can be found [on Github][8] or [my Gitea +instance][9]. I have already worked with the [Rocket][4] web framework +for my Bachelor thesis but it depends on the nightly branch of the +compiler and is a pain to maintain over a longer period of time due to +breaking changes in the nightly compiler. Also actix-web is _really_ +fast[^actix-performance]. Further crates that were used and should be +mentioned include [ring][12] for cryptographic operations, [serde][13] +for (de)serialization of data and [proptest][14] to verify some +properties of my code (e.g. `verify_signature(key, msg, sign(key, +msg))` must be true for every input of `key` and `msg`). Rust made it +easy to exchange data between the client and the server in a typesafe +manner and actix-web offers an well designed API to build fast web +applications. While actix-web lacks the incredible ergonomics of +Rocket (it's not bad, just not as good as Rocket), I prioritize using +the stable compiler branch over API ergonomics. The client itself is independent of the way, certbot works and the integration into the workflow is archived by bash scripts inspired by @@ -106,8 +133,21 @@ WantedBy=multi-user.target The client is configured using the configuration file `/etc/bind9apiclient.toml` that contains the API URL and secret. +``` +# API server host +host = "http://127.0.0.1:8080" +# API secret +secret = "topsecret" +``` + +The final binaries, I use in production are compiled using the +[`ekidd/rust-musl-builder` Docker image][16] to build completely +static binaries by linking against the [musl libc][17]. (Linking +against the default glibc target, produces dynamically linked binaries +that depend to the systems glibc and OpenSSL version). + After placing the client somewhere in `$PATH` and putting the certbot -hooks on the machine that should issue the certificates, I can invoke +hooks on the machine that should obtain the certificates, I can invoke certbot like followed: ``` @@ -118,15 +158,28 @@ https://acme-v02.api.letsencrypt.org/directory --preferred-challenges=dns-01 \ --manual-public-ip-logging-ok -d example.com -d '*.example.com' ``` +I already obtained a wildcard certificate for my domain +[oldsql.cc][15], even if I'm using only a single subdomain, to test my +code. Obtaining the certificate worked fine, and I guess renewal won't +pose any problems either. [0]: https://letsencrypt.org/ [1]: https://www.tinc-vpn.org/ -[2]: https://github.com/actix/actix-web/ +[2]: https://actix.rs/ [3]: https://github.com/seanmonstar/reqwest/ -[4]: https://github.com/SergioBenitez/Rocket/ +[4]: https://rocket.rs/ [5]: https://github.com/kegato/letsencrypt-inwx/ [6]: https://github.com/vbrandl/bind9-api#server +[7]: https://linux.die.net/man/8/nsupdate +[8]: https://github.com/vbrandl/bind9-api +[9]: https://git.vbrandl.net/vbrandl/bind9-api +[10]: https://certbot.eff.org/ +[11]: https://nginx.org/ +[12]: https://crates.io/crates/ring +[13]: https://crates.io/crates/serde +[14]: https://crates.io/crates/proptest +[15]: https://oldsql.cc +[16]: https://hub.docker.com/r/ekidd/rust-musl-builder/ +[17]: https://www.musl-libc.org/ [^actix-performance]: https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext - -> vim: set filetype=markdown ts=4 sw=4 tw=70 noet :