This repository has been archived on 2026-09-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nixos-config/hosts/pinix/localServices/acme.nix
pijukka 67a2a6541e host(pinix): Wildcard-Zertifikat ausgestellt, Propagationspruefung aus
Let's Encrypt hat *.lukasrosier.com und lukasrosier.com ausgestellt
(issuer YE1, gueltig bis 11.12.2026), per DNS-01 gegen das eigene BIND,
ohne fremden API-Token. Delegiert ist nur _acme-challenge, der Apex samt
MX, SPF und DKIM bleibt bei Cloudflare.

Der Weg dorthin ging ueber drei Fehlerbilder, die alle dieselbe Ursache
hatten, und das ist der eigentliche Merkposten: von innerhalb dieses
Netzes ist die Propagationsvorpruefung nicht durchfuehrbar.

1. Systemresolver (Mango) liefert die SOA, auf NS-Abfragen aber nichts --
   sein Forwarder bedient NS nicht. lego: "could not determine
   authoritative nameservers".
2. Eigenes unbound: SERVFAIL. Es rekursiert bis zur Delegation, die auf
   die oeffentliche Mango-Adresse zeigt, und die ist von innen nicht
   erreichbar, weil die DNAT-Regeln reflection=0 tragen.
3. Fremder Resolver (Quad9) findet die NS korrekt, aber lego fragt den
   autoritativen Server danach direkt ab -- und landet wieder auf der
   oeffentlichen Adresse, wo Mangos eigener Forwarder statt unseres BIND
   antwortet: "did not return the expected TXT record".

Also dnsPropagationCheck = false. Das ist keine Nachlaessigkeit: pruefen
kann nur Let's Encrypt von aussen, und genau das passiert. Dass die
Delegation von aussen traegt, ist unabhaengig belegt -- Quad9 und Google
liefern beide NS, SOA und TXT aus unserem BIND. Wieder einschaltbar,
sobald am Mango Hairpin fuer Port 53 aktiv ist.

Nebenbefund, ebenfalls im Kommentar: der NS einer delegierten Zone muss
mit der Delegation im Elternteil uebereinstimmen. Die Challenge-Zone
nannte zuerst ns1.lukasrosier.com, das oeffentlich ueber den Wildcard auf
blunix' IPv4 auflaest -- kein Nameserver. Jetzt ipv6-test.0ff.space,
derselbe Name wie in der Cloudflare-Delegation, dessen AAAA der
mango-dns-Timer nachfuehrt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw4viUJyXGP8ANocHiF15q
2026-09-12 19:28:28 +02:00

66 lines
3.2 KiB
Nix

{ config, ... }:
{
# Wildcard-Zertifikat fuer domain2 ueber DNS-01 gegen das *eigene* BIND.
#
# Bewusst ueber das NixOS-Modul `security.acme` und nicht ueber Traefiks
# eigenen certificatesResolver, obwohl blunix es dort so macht:
#
# - Der Zustand liegt unter /var/lib/acme pro Zertifikat statt in Traefiks
# undurchsichtiger acme.json, in der alle Zertifikate zusammen stecken.
# - Andere Dienste auf diesem Host koennen dasselbe Zertifikat lesen,
# ohne Traefik zu sein -- Prosody oder ein Mailserver spaeter genauso.
# - Erneuerungs-Timer, Units und Reload-Hooks kommen vom Modul, das ist
# der Upstream-Weg statt eigener Mechanik (CLAUDE.md: Upstream-Module
# gegenueber eigenen Patches bevorzugen).
#
# Der TSIG-Schluessel ist dasselbe agenix-Secret, das bind.nix zu BINDs
# key-Syntax rendert. Es liegt in Environment-Form vor, genau was
# `environmentFile` erwartet (RFC2136_NAMESERVER, _TSIG_KEY,
# _TSIG_ALGORITHM, _TSIG_SECRET) -- ein Secret, kein zweites Format.
security.acme = {
acceptTerms = true;
defaults.email = config.adminEmail;
certs."${config.domain2}" = {
domain = config.domain2;
# Der eigentliche Zweck: HTTP-01 kann keine Wildcards, DNS-01 kann es.
extraDomainNames = [ "*.${config.domain2}" ];
dnsProvider = "rfc2136";
environmentFile = config.age.secrets.bindAcmeTsig.path;
# Aus: die Pruefung ist von diesem Host aus strukturell nicht
# durchfuehrbar. Am 12.09.2026 in drei Schritten durchgemessen, jeder
# mit eigenem Fehlerbild:
#
# 1. Systemresolver (Mango): liefert die SOA, auf eine NS-Abfrage aber
# gar nichts -- sein Forwarder bedient NS-Eintraege nicht. lego
# meldet "could not determine authoritative nameservers".
# 2. Eigenes unbound: SERVFAIL. Es rekursiert korrekt bis zur
# Delegation, die auf die *oeffentliche* Adresse des Mango zeigt,
# und die ist von innen nicht erreichbar -- die DNAT-Regeln tragen
# `reflection=0`.
# 3. Fremder Resolver (Quad9): findet die NS korrekt, aber lego fragt
# den autoritativen Server danach *direkt* ab. Von innen landet das
# wieder auf der oeffentlichen Mango-Adresse, wo derselbe Hairpin
# greift und Mangos eigener Forwarder antwortet statt unseres BIND
# -- "did not return the expected TXT record".
#
# Kern: jeder Weg von innen endet an derselben Hairpin-Grenze. Der
# Verzicht auf die Vorpruefung ist deshalb kein Nachlassen der
# Sorgfalt, sondern die Anerkennung, dass nur Let's Encrypt selbst von
# aussen pruefen kann -- und genau das tut es. Dass die Delegation von
# aussen funktioniert, ist unabhaengig belegt: Quad9 und Googles
# Resolver liefern beide NS, SOA und TXT aus unserem BIND.
#
# Wieder einschaltbar, sobald am Mango Reflection/Hairpin fuer Port 53
# aktiv ist; dann ist "[2620:fe::fe]:53" als dnsResolver der Weg.
dnsPropagationCheck = false;
# Traefik liest die Dateien aus /var/lib/acme; ueber die Gruppe bekommt
# es Leserecht, ohne dass die Schluesseldatei weltlesbar wird.
group = "traefik";
reloadServices = [ "traefik.service" ];
};
};
}