Compare commits
2 commits
f4f2771ab3
...
ea8aedd0d6
Author | SHA1 | Date | |
---|---|---|---|
ea8aedd0d6 | |||
7dd8343680 |
2 changed files with 179 additions and 6 deletions
|
@ -15,9 +15,6 @@ dataDir = "data"
|
||||||
layoutDir = "layouts"
|
layoutDir = "layouts"
|
||||||
publishDir = "public"
|
publishDir = "public"
|
||||||
|
|
||||||
[author]
|
|
||||||
name = "Melora Hugues"
|
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
category = "blog"
|
category = "blog"
|
||||||
tags = "tags"
|
tags = "tags"
|
||||||
|
@ -56,6 +53,9 @@ author = true
|
||||||
logoText = "Hello there!"
|
logoText = "Hello there!"
|
||||||
logoHomeLink = "/"
|
logoHomeLink = "/"
|
||||||
|
|
||||||
|
[params.author]
|
||||||
|
name = "Melora Hugues"
|
||||||
|
|
||||||
[[params.social]]
|
[[params.social]]
|
||||||
name = "git"
|
name = "git"
|
||||||
url = "https://git.faercol.me"
|
url = "https://git.faercol.me"
|
||||||
|
|
|
@ -142,12 +142,185 @@ server for that.
|
||||||
|
|
||||||
### Configuring the DNS server
|
### Configuring the DNS server
|
||||||
|
|
||||||
|
The first step is configuring the DNS server. For this, I'll just use a [bind](https://bind9.readthedocs.io/en/v9.18.27/)
|
||||||
|
server installed from my usual package manager.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# example on Debian 12
|
||||||
|
sudo apt install bind9
|
||||||
|
```
|
||||||
|
|
||||||
|
Most of the configuration happens in the `/etc/bind` directory, mostly in `/etc/bind/named.conf.local`
|
||||||
|
|
||||||
|
```text
|
||||||
|
root@dns-server: ls /etc/bind/
|
||||||
|
bind.keys db.127 db.empty named.conf named.conf.local rndc.key
|
||||||
|
db.0 db.255 db.local named.conf.default-zones named.conf.options zones.rfc1918
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's declare a first zone, for `internal.example.com`. Add the following config to
|
||||||
|
`/etc/bind/named.conf.local`
|
||||||
|
|
||||||
|
```text
|
||||||
|
zone "internal.example.com." IN {
|
||||||
|
type master;
|
||||||
|
file "/var/lib/bind/internal.example.com.zone";
|
||||||
|
```
|
||||||
|
|
||||||
|
This simply declares a new zone which is described in the file `/var/lib/bind/internal.example.com.zone`
|
||||||
|
|
||||||
|
Let's now create the zone itself. A DNS zone has a base structure that you must follow
|
||||||
|
|
||||||
|
```dns
|
||||||
|
$ORIGIN .
|
||||||
|
$TTL 7200 ; 2 hours
|
||||||
|
internal.example.com IN SOA ns.internal.example.com. admin.example.com. (
|
||||||
|
2024070301 ; serial
|
||||||
|
3600 ; refresh (1 hour)
|
||||||
|
600 ; retry (10 minutes)
|
||||||
|
86400 ; expire (1 day)
|
||||||
|
600 ; minimum (10 minutes)
|
||||||
|
)
|
||||||
|
NS ns.internal.example.com.
|
||||||
|
|
||||||
|
$ORIGIN internal.example.com.
|
||||||
|
ns A 1.2.3.4
|
||||||
|
test A 192.168.1.2
|
||||||
|
```
|
||||||
|
|
||||||
|
This file declares a zone `internal.example.com` which master is `ns.internal.example.com`.
|
||||||
|
It also sets the parameters (time to live for the records, and the current serial for the
|
||||||
|
zone config).
|
||||||
|
|
||||||
|
Finally, two (2) A records are created, associating the name `ns.internal.example.com` to
|
||||||
|
the IP address `1.2.3.4`, and `test.internal.example.com` (the domain for which we want
|
||||||
|
a certificate) to a local IP address `192.168.1.2`.
|
||||||
|
|
||||||
|
A simple `systemctl restart bind9` would be enough to apply the modification, but we still
|
||||||
|
have one thing to do, which is allowing remote modifications to the zone.
|
||||||
|
|
||||||
### Enabling remote DNS zone modification
|
### Enabling remote DNS zone modification
|
||||||
|
|
||||||
|
To allow remote modification of our DNS zone, we are going to use [TSIG](https://www.ibm.com/docs/en/aix/7.3?topic=ssw_aix_73/network/bind9_tsig.htm)
|
||||||
|
which stands for **Transaction signature**. It's a way to secure server to server operations
|
||||||
|
to edit a DNS zone, and is preferred to access control based on IP addresses.
|
||||||
|
|
||||||
|
Let's start with creating a key using the command `tsig-keygen <keyname>`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
➜ tsig-keygen letsencrypt
|
||||||
|
key "letsencrypt" {
|
||||||
|
algorithm hmac-sha256;
|
||||||
|
secret "oK6SqKRvGNXHyNyIEy3hijQ1pclreZw4Vn5v+Q4rTLs=";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a key with the given name using the default algorithm (which is `hmac-sha256`).
|
||||||
|
The entire output of this command is actually a code block that you can add to your bind9
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Finally, using `update-policy`, allow this key to be used to update the zone.
|
||||||
|
|
||||||
|
```text
|
||||||
|
update-policy {
|
||||||
|
grant letsencrypt. zonesub txt;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< callout type="note" >}}
|
||||||
|
Doing so allows users to update everything in your zone using this key. In fact
|
||||||
|
you would only need to update `_acme-challenge.test.internal.example.com` as seen
|
||||||
|
in the DNS challenge description.
|
||||||
|
|
||||||
|
If you want a better restriction, then you can use the following configuration instead
|
||||||
|
|
||||||
|
```text
|
||||||
|
update-policy {
|
||||||
|
grant letsencrypt. name _acme-challenge.test.internal.example.com. txt;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /callout >}}
|
||||||
|
|
||||||
|
This means your entire `named.conf.local` would become something like this
|
||||||
|
|
||||||
|
```text
|
||||||
|
key "letsencrypt" {
|
||||||
|
algorithm hmac-sha256;
|
||||||
|
secret "oK6SqKRvGNXHyNyIEy3hijQ1pclreZw4Vn5v+Q4rTLs=";
|
||||||
|
};
|
||||||
|
|
||||||
|
zone "internal.example.com." IN {
|
||||||
|
type master;
|
||||||
|
file "/var/lib/bind/internal.example.com.zone";
|
||||||
|
update-policy {
|
||||||
|
grant letsencrypt. zonesub txt;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< callout type="warning" >}}
|
||||||
|
Be **very cautious** about the `.` at the end of the zone name and the key name, they are
|
||||||
|
easy to miss, and forgetting them will cause issues that would be hard to detect.
|
||||||
|
{{< /callout >}}
|
||||||
|
|
||||||
|
With that being done, you can restart the DNS server and everything is ready server side,
|
||||||
|
the only remaining thing to do would be the DNS challenge itself.
|
||||||
|
|
||||||
### Performing the challenge
|
### Performing the challenge
|
||||||
|
|
||||||
## Bonus: completely hiding your private domains from outside
|
Start by installing the certbot with the RFC2136 plugin (to perform the DNS challenge).
|
||||||
|
|
||||||
|
```shell
|
||||||
|
apt install python3-certbot-dns-rfc2136
|
||||||
|
```
|
||||||
|
|
||||||
|
It's handled using a `.ini` configuration file, let's put it in `/etc/certbot/credentials.ini`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
dns_rfc2136_server = <you_dns_ip>
|
||||||
|
dns_rfc2136_port = 53
|
||||||
|
dns_rfc2136_name = letsencrypt.
|
||||||
|
dns_rfc2136_secret = oK6SqKRvGNXHyNyIEy3hijQ1pclreZw4Vn5v+Q4rTLs=
|
||||||
|
dns_rfc2136_algorithm = HMAC-SHA512
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, run the challenge using certbot (if it's the first time you're using certbot on
|
||||||
|
that machine, it might ask for an email to handle admin stuff).
|
||||||
|
|
||||||
|
```shell
|
||||||
|
root@toolbox:~# certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/certbot/credentials.ini -d 'test.internal.example.com'
|
||||||
|
|
||||||
|
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||||
|
Requesting a certificate for test.internal.example.com
|
||||||
|
Waiting 60 seconds for DNS changes to propagate
|
||||||
|
|
||||||
|
Successfully received certificate.
|
||||||
|
Certificate is saved at: /etc/letsencrypt/live/test.internal.example.com/fullchain.pem
|
||||||
|
Key is saved at: /etc/letsencrypt/live/test.internal.example.com/privkey.pem
|
||||||
|
This certificate expires on 2024-09-30.
|
||||||
|
These files will be updated when the certificate renews.
|
||||||
|
Certbot has set up a scheduled task to automatically renew this certificate in the background.
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
If you like Certbot, please consider supporting our work by:
|
||||||
|
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||||
|
* Donating to EFF: https://eff.org/donate-le
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's done, you have a certificate, and a no point in time did you need to
|
||||||
|
actually expose your application to the outside world.
|
||||||
|
|
||||||
|
Now because I like to go way too far, I can propose two (2) improvements to this
|
||||||
|
setup:
|
||||||
|
|
||||||
|
- Using ACL in addition to the TSIG key to secure operations on the DNS server
|
||||||
|
- Using a second DNS server only locally accessible for your private records, and
|
||||||
|
using the public server to only perform challenges
|
||||||
|
|
||||||
|
## Bonus 1: adding a second layer of authentication to connect to the DNS
|
||||||
|
|
||||||
|
## Bonus 2: completely hiding your private domains from outside
|
||||||
|
|
||||||
![A schema describing the DNS challenge workflow for the ACME protocol using a public and private DNS servers](/images/dns_article_dns_challenge_2.svg)
|
![A schema describing the DNS challenge workflow for the ACME protocol using a public and private DNS servers](/images/dns_article_dns_challenge_2.svg)
|
||||||
|
|
||||||
![alt text](image.png)
|
|
||||||
|
|
Loading…
Reference in a new issue