fix: Caddy TLS cert acquisition — two DNS-01 blockers
Unit Tests / test (push) Successful in 7m32s

1. caddy_manager: embed ddns.token (registration bearer token) in
   Caddyfile, not DDNS_TOTP_SECRET. The pic_ngo plugin sends the token
   to POST /api/v1/dns-challenge; using the TOTP secret caused 401 on
   every attempt.

2. firewall_manager: add _acme-challenge.<zone> forwarding block before
   each split-horizon zone in the Corefile. Without this, CoreDNS was
   authoritative for the challenge name and returned NODATA for TXT
   queries (wildcard A record matches but wrong type), blocking Caddy's
   internal DNS pre-verification step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 10:45:15 -04:00
parent 76bbc2b67a
commit 85d265187d
4 changed files with 36 additions and 15 deletions
+6 -8
View File
@@ -59,17 +59,16 @@ class TestGenerateCaddyfileLan(unittest.TestCase):
class TestGenerateCaddyfilePicNgo(unittest.TestCase):
def test_pic_ngo_has_dns_plugin_and_wildcard(self):
mgr = _mgr()
mgr.config_manager.configs = {
'ddns': {'token': 'TESTSECRET123', 'url': 'https://ddns.pic.ngo/api/v1'},
}
identity = {'cell_name': 'alpha', 'domain_mode': 'pic_ngo'}
import os
with unittest.mock.patch.dict(os.environ, {
'DDNS_TOTP_SECRET': 'TESTSECRET123',
'DDNS_URL': 'https://ddns.pic.ngo/api/v1',
}):
with unittest.mock.patch.dict(os.environ, {'DDNS_URL': 'https://ddns.pic.ngo/api/v1'}):
out = mgr.generate_caddyfile(identity, [])
self.assertIn('dns pic_ngo', out)
self.assertIn('*.alpha.pic.ngo', out)
self.assertIn('alpha.pic.ngo', out)
# Credentials are resolved at write time and embedded — no {$VAR} placeholders
# Registration token (not TOTP secret) is embedded — no {$VAR} placeholders
self.assertIn('token TESTSECRET123', out)
self.assertIn('api_base_url https://ddns.pic.ngo/api/v1', out)
self.assertNotIn('{$PIC_NGO_DDNS_TOKEN}', out)
@@ -80,10 +79,9 @@ class TestGenerateCaddyfilePicNgo(unittest.TestCase):
def test_pic_ngo_acme_ca_included_when_env_set(self):
mgr = _mgr()
mgr.config_manager.configs = {'ddns': {'token': 'TESTSECRET123'}}
identity = {'cell_name': 'alpha', 'domain_mode': 'pic_ngo'}
import os
with unittest.mock.patch.dict(os.environ, {
'DDNS_TOTP_SECRET': 'TESTSECRET123',
'DDNS_URL': 'https://ddns.pic.ngo/api/v1',
'ACME_CA_URL': 'https://acme-staging-v02.api.letsencrypt.org/directory',
}):