Update post

This commit is contained in:
Valentin Brandl 2018-07-15 21:25:12 +02:00
parent 9083331238
commit cd21f27356
Signed by: vbrandl
GPG Key ID: CAD4DA1A789125F9

View File

@ -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 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 editing the zonefile by hand. This worked fine since I rarely needed
to change any DNS records. Then earlier this year, [LetsEncrypt][0] 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 wildcard certificates using the DNS challenge. This put me into a
situation where I needed to create, update and delete DNS records situation where I needed to create, update and delete DNS records
automatically. automatically.
<!-- more --> <!-- more -->
While the LetsEncrypt HTTP challenge requires the user to make the The LetsEncrypt HTTP challenge requires the user to make the challenge
challenge flag available via HTTP under flag available via HTTP under
`http://www.example.com/.well-known/acme-challenge`. This way, the `http://www.example.com/.well-known/acme-challenge`. This way, the
ACME endpoint can only verify ownership over a specific subdomain ACME endpoint can only verify ownership over a specific subdomain
(`www.example.com` in this case). The DNS challenge looks for the flag (`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`. possible to issue a wildcard certificate for `*.example.com`.
Since DNS setups vary depending on the domain provider or used DNS 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 the domain name and challenge flag via the environment variables
`$CERTBOT_DOMAIN` and `$CERTBOT_VALIDATION` respectively. `$CERTBOT_DOMAIN` and `$CERTBOT_VALIDATION` respectively.
Once the challenge mechanism was understood, I needed a way to Once the challenge mechanism was understood, I needed a way to
programmatically create and delete records on my BIND9 server. I programmatically create and delete records on my BIND9 server. I
decided to implement I REST-like webservice to run on the same machine decided to implement a REST-like webservice to run on the same machine
as BIND9 and modify records using the `nsupdate` command. as BIND9 and modify records using the [`nsupdate` command][7].
The REST API offers two methods: 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 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 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 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 doing it, make the server listen on a private IP address inside an
encrypted VLAN ([tinc][1] in my case). 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 <challenge flag>
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 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 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 make HTTP requests on the client side. The implementation along with
with other Rust web frameworks, namely [Rocket][4] for my Bachelor installation instructions can be found [on Github][8] or [my Gitea
thesis but it depends on the nightly branch of the compiler and is a instance][9]. I have already worked with the [Rocket][4] web framework
pain to maintain over a longer period of time. Also actix-web is for my Bachelor thesis but it depends on the nightly branch of the
_really_ fast[^actix-performance]. 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 The client itself is independent of the way, certbot works and the
integration into the workflow is archived by bash scripts inspired by 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 The client is configured using the configuration file
`/etc/bind9apiclient.toml` that contains the API URL and secret. `/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 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: 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' --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/ [0]: https://letsencrypt.org/
[1]: https://www.tinc-vpn.org/ [1]: https://www.tinc-vpn.org/
[2]: https://github.com/actix/actix-web/ [2]: https://actix.rs/
[3]: https://github.com/seanmonstar/reqwest/ [3]: https://github.com/seanmonstar/reqwest/
[4]: https://github.com/SergioBenitez/Rocket/ [4]: https://rocket.rs/
[5]: https://github.com/kegato/letsencrypt-inwx/ [5]: https://github.com/kegato/letsencrypt-inwx/
[6]: https://github.com/vbrandl/bind9-api#server [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 [^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 :