Update post
This commit is contained in:
parent
9083331238
commit
cd21f27356
@ -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 :
|
|
||||||
|
Loading…
Reference in New Issue
Block a user