feat(webnsupdate): allow running in IPv4/6 only mode #81
3 changed files with 279 additions and 29 deletions
|
@ -48,6 +48,16 @@ let
|
||||||
type = types.port;
|
type = types.port;
|
||||||
default = 5353;
|
default = 5353;
|
||||||
};
|
};
|
||||||
|
allowedIPVersion = mkOption {
|
||||||
|
description = ''The allowed IP versions to accept updates from.'';
|
||||||
|
type = types.enum [
|
||||||
|
"both"
|
||||||
|
"ipv4-only"
|
||||||
|
"ipv6-only"
|
||||||
|
];
|
||||||
|
default = "both";
|
||||||
|
example = "ipv4-only";
|
||||||
|
};
|
||||||
passwordFile = mkOption {
|
passwordFile = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
The file where the password is stored.
|
The file where the password is stored.
|
||||||
|
@ -126,6 +136,8 @@ let
|
||||||
cfg.passwordFile
|
cfg.passwordFile
|
||||||
"--address"
|
"--address"
|
||||||
cfg.bindIp
|
cfg.bindIp
|
||||||
|
"--ip-type"
|
||||||
|
cfg.allowedIPVersion
|
||||||
"--port"
|
"--port"
|
||||||
(builtins.toString cfg.bindPort)
|
(builtins.toString cfg.bindPort)
|
||||||
"--ttl"
|
"--ttl"
|
||||||
|
|
|
@ -6,25 +6,25 @@
|
||||||
checks =
|
checks =
|
||||||
let
|
let
|
||||||
testDomain = "webnstest.example";
|
testDomain = "webnstest.example";
|
||||||
|
lastIPPath = "/var/lib/webnsupdate/last-ip.json";
|
||||||
|
|
||||||
zoneFile = pkgs.writeText "${testDomain}.zoneinfo" ''
|
zoneFile = pkgs.writeText "${testDomain}.zoneinfo" ''
|
||||||
$ORIGIN .
|
|
||||||
$TTL 60 ; 1 minute
|
$TTL 60 ; 1 minute
|
||||||
${testDomain} IN SOA ns1.${testDomain}. admin.${testDomain}. (
|
|
||||||
1 ; serial
|
|
||||||
21600 ; refresh (6 hours)
|
|
||||||
3600 ; retry (1 hour)
|
|
||||||
604800 ; expire (1 week)
|
|
||||||
86400) ; negative caching TTL (1 day)
|
|
||||||
|
|
||||||
IN NS ns1.${testDomain}.
|
|
||||||
$ORIGIN ${testDomain}.
|
$ORIGIN ${testDomain}.
|
||||||
${testDomain}. IN A 127.0.0.1
|
@ IN SOA ns1.${testDomain}. admin.${testDomain}. (
|
||||||
${testDomain}. IN AAAA ::1
|
1 ; serial
|
||||||
ns1 IN A 127.0.0.1
|
6h ; refresh
|
||||||
ns1 IN AAAA ::1
|
1h ; retry
|
||||||
nsupdate IN A 127.0.0.1
|
1w ; expire
|
||||||
nsupdate IN AAAA ::1
|
1d) ; negative caching TTL
|
||||||
|
|
||||||
|
IN NS ns1.${testDomain}.
|
||||||
|
@ IN A 127.0.0.1
|
||||||
|
ns1 IN A 127.0.0.1
|
||||||
|
nsupdate IN A 127.0.0.1
|
||||||
|
@ IN AAAA ::1
|
||||||
|
ns1 IN AAAA ::1
|
||||||
|
nsupdate IN AAAA ::1
|
||||||
'';
|
'';
|
||||||
|
|
||||||
bindDynamicZone =
|
bindDynamicZone =
|
||||||
|
@ -121,6 +121,16 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
webnsupdate-ipv4-only-machine = {
|
||||||
|
imports = [ webnsupdate-nginx-machine ];
|
||||||
|
config.services.webnsupdate.allowedIPVersion = "ipv4-only";
|
||||||
|
};
|
||||||
|
|
||||||
|
webnsupdate-ipv6-only-machine = {
|
||||||
|
imports = [ webnsupdate-nginx-machine ];
|
||||||
|
config.services.webnsupdate.allowedIPVersion = "ipv6-only";
|
||||||
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
machine.start(allow_reboot=True)
|
machine.start(allow_reboot=True)
|
||||||
machine.wait_for_unit("bind.service")
|
machine.wait_for_unit("bind.service")
|
||||||
|
@ -128,8 +138,8 @@
|
||||||
|
|
||||||
# ensure base DNS records area available
|
# ensure base DNS records area available
|
||||||
with subtest("query base DNS records"):
|
with subtest("query base DNS records"):
|
||||||
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
||||||
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
||||||
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
||||||
|
|
||||||
# ensure webnsupdate managed records are missing
|
# ensure webnsupdate managed records are missing
|
||||||
|
@ -140,7 +150,7 @@
|
||||||
|
|
||||||
with subtest("update webnsupdate DNS records (invalid auth)"):
|
with subtest("update webnsupdate DNS records (invalid auth)"):
|
||||||
machine.fail("curl --fail --silent -u test1:test1 -X GET http://localhost:5353/update")
|
machine.fail("curl --fail --silent -u test1:test1 -X GET http://localhost:5353/update")
|
||||||
machine.fail("cat /var/lib/webnsupdate/last-ip") # no last-ip set yet
|
machine.fail("cat ${lastIPPath}") # no last-ip set yet
|
||||||
|
|
||||||
# ensure webnsupdate managed records are missing
|
# ensure webnsupdate managed records are missing
|
||||||
with subtest("query webnsupdate DNS records (fail)"):
|
with subtest("query webnsupdate DNS records (fail)"):
|
||||||
|
@ -150,7 +160,7 @@
|
||||||
|
|
||||||
with subtest("update webnsupdate DNS records (valid auth)"):
|
with subtest("update webnsupdate DNS records (valid auth)"):
|
||||||
machine.succeed("curl --fail --silent -u test:test -X GET http://localhost:5353/update")
|
machine.succeed("curl --fail --silent -u test:test -X GET http://localhost:5353/update")
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
# ensure webnsupdate managed records are available
|
# ensure webnsupdate managed records are available
|
||||||
with subtest("query webnsupdate DNS records (succeed)"):
|
with subtest("query webnsupdate DNS records (succeed)"):
|
||||||
|
@ -159,9 +169,9 @@
|
||||||
machine.succeed("dig @127.0.0.1 test3.${testDomain} A test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} A test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
machine.reboot()
|
machine.reboot()
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
machine.wait_for_unit("webnsupdate.service")
|
machine.wait_for_unit("webnsupdate.service")
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
# ensure base DNS records area available after a reboot
|
# ensure base DNS records area available after a reboot
|
||||||
with subtest("query base DNS records"):
|
with subtest("query base DNS records"):
|
||||||
|
@ -197,8 +207,8 @@
|
||||||
|
|
||||||
# ensure base DNS records area available
|
# ensure base DNS records area available
|
||||||
with subtest("query base DNS records"):
|
with subtest("query base DNS records"):
|
||||||
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
||||||
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
||||||
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
||||||
|
|
||||||
# ensure webnsupdate managed records are missing
|
# ensure webnsupdate managed records are missing
|
||||||
|
@ -212,7 +222,7 @@
|
||||||
|
|
||||||
with subtest("update webnsupdate DNS records (invalid auth)"):
|
with subtest("update webnsupdate DNS records (invalid auth)"):
|
||||||
machine.fail("curl --fail --silent -u test1:test1 -X GET http://127.0.0.1/update")
|
machine.fail("curl --fail --silent -u test1:test1 -X GET http://127.0.0.1/update")
|
||||||
machine.fail("cat /var/lib/webnsupdate/last-ip") # no last-ip set yet
|
machine.fail("cat ${lastIPPath}") # no last-ip set yet
|
||||||
|
|
||||||
# ensure webnsupdate managed records are missing
|
# ensure webnsupdate managed records are missing
|
||||||
with subtest("query webnsupdate DNS records (fail)"):
|
with subtest("query webnsupdate DNS records (fail)"):
|
||||||
|
@ -225,7 +235,7 @@
|
||||||
|
|
||||||
with subtest("update webnsupdate IPv4 DNS records (valid auth)"):
|
with subtest("update webnsupdate IPv4 DNS records (valid auth)"):
|
||||||
machine.succeed("curl --fail --silent -u test:test -X GET http://127.0.0.1/update")
|
machine.succeed("curl --fail --silent -u test:test -X GET http://127.0.0.1/update")
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
# ensure webnsupdate managed IPv4 records are available
|
# ensure webnsupdate managed IPv4 records are available
|
||||||
with subtest("query webnsupdate IPv4 DNS records (succeed)"):
|
with subtest("query webnsupdate IPv4 DNS records (succeed)"):
|
||||||
|
@ -241,7 +251,7 @@
|
||||||
|
|
||||||
with subtest("update webnsupdate IPv6 DNS records (valid auth)"):
|
with subtest("update webnsupdate IPv6 DNS records (valid auth)"):
|
||||||
machine.succeed("curl --fail --silent -u test:test -X GET http://[::1]/update")
|
machine.succeed("curl --fail --silent -u test:test -X GET http://[::1]/update")
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
# ensure webnsupdate managed IPv6 records are missing
|
# ensure webnsupdate managed IPv6 records are missing
|
||||||
with subtest("query webnsupdate IPv6 DNS records (fail)"):
|
with subtest("query webnsupdate IPv6 DNS records (fail)"):
|
||||||
|
@ -250,9 +260,9 @@
|
||||||
machine.succeed("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
machine.reboot()
|
machine.reboot()
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
machine.wait_for_unit("webnsupdate.service")
|
machine.wait_for_unit("webnsupdate.service")
|
||||||
machine.succeed("cat /var/lib/webnsupdate/last-ip")
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
# ensure base DNS records area available after a reboot
|
# ensure base DNS records area available after a reboot
|
||||||
with subtest("query base DNS records"):
|
with subtest("query base DNS records"):
|
||||||
|
@ -270,6 +280,172 @@
|
||||||
machine.succeed("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
module-ipv4-only-test = pkgs.testers.runNixOSTest {
|
||||||
|
name = "webnsupdate-ipv4-only-module";
|
||||||
|
nodes.machine = webnsupdate-ipv4-only-machine;
|
||||||
|
testScript = ''
|
||||||
|
machine.start(allow_reboot=True)
|
||||||
|
machine.wait_for_unit("bind.service")
|
||||||
|
machine.wait_for_unit("webnsupdate.service")
|
||||||
|
|
||||||
|
# ensure base DNS records area available
|
||||||
|
with subtest("query base DNS records"):
|
||||||
|
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed records are missing
|
||||||
|
with subtest("query webnsupdate DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
with subtest("update webnsupdate DNS records (invalid auth)"):
|
||||||
|
machine.fail("curl --fail --silent -u test1:test1 -X GET http://127.0.0.1/update")
|
||||||
|
machine.fail("cat ${lastIPPath}") # no last-ip set yet
|
||||||
|
|
||||||
|
# ensure webnsupdate managed records are missing
|
||||||
|
with subtest("query webnsupdate DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
with subtest("update webnsupdate IPv6 DNS records (valid auth)"):
|
||||||
|
machine.fail("curl --fail --silent -u test:test -X GET http://[::1]/update")
|
||||||
|
machine.fail("cat ${lastIPPath}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed IPv6 records are missing
|
||||||
|
with subtest("query webnsupdate IPv6 DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
with subtest("update webnsupdate IPv4 DNS records (valid auth)"):
|
||||||
|
machine.succeed("curl --fail --silent -u test:test -X GET http://127.0.0.1/update")
|
||||||
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed IPv4 records are available
|
||||||
|
with subtest("query webnsupdate IPv4 DNS records (succeed)"):
|
||||||
|
machine.succeed("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed IPv6 records are missing
|
||||||
|
with subtest("query webnsupdate IPv6 DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
machine.reboot()
|
||||||
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
machine.wait_for_unit("webnsupdate.service")
|
||||||
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
|
# ensure base DNS records area available after a reboot
|
||||||
|
with subtest("query base DNS records"):
|
||||||
|
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed records are available after a reboot
|
||||||
|
with subtest("query webnsupdate DNS records (succeed)"):
|
||||||
|
machine.succeed("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
module-ipv6-only-test = pkgs.testers.runNixOSTest {
|
||||||
|
name = "webnsupdate-ipv6-only-module";
|
||||||
|
nodes.machine = webnsupdate-ipv6-only-machine;
|
||||||
|
testScript = ''
|
||||||
|
machine.start(allow_reboot=True)
|
||||||
|
machine.wait_for_unit("bind.service")
|
||||||
|
machine.wait_for_unit("webnsupdate.service")
|
||||||
|
|
||||||
|
# ensure base DNS records area available
|
||||||
|
with subtest("query base DNS records"):
|
||||||
|
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed records are missing
|
||||||
|
with subtest("query webnsupdate DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
with subtest("update webnsupdate DNS records (invalid auth)"):
|
||||||
|
machine.fail("curl --fail --silent -u test1:test1 -X GET http://127.0.0.1/update")
|
||||||
|
machine.fail("cat ${lastIPPath}") # no last-ip set yet
|
||||||
|
|
||||||
|
# ensure webnsupdate managed records are missing
|
||||||
|
with subtest("query webnsupdate DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
with subtest("update webnsupdate IPv4 DNS records (valid auth)"):
|
||||||
|
machine.fail("curl --fail --silent -u test:test -X GET http://127.0.0.1/update")
|
||||||
|
machine.fail("cat ${lastIPPath}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed IPv4 records are missing
|
||||||
|
with subtest("query webnsupdate IPv4 DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
with subtest("update webnsupdate IPv6 DNS records (valid auth)"):
|
||||||
|
machine.succeed("curl --fail --silent -u test:test -X GET http://[::1]/update")
|
||||||
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed IPv6 records are available
|
||||||
|
with subtest("query webnsupdate IPv6 DNS records (succeed)"):
|
||||||
|
machine.succeed("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed IPv4 records are missing
|
||||||
|
with subtest("query webnsupdate IPv4 DNS records (fail)"):
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
|
||||||
|
machine.reboot()
|
||||||
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
machine.wait_for_unit("webnsupdate.service")
|
||||||
|
machine.succeed("cat ${lastIPPath}")
|
||||||
|
|
||||||
|
# ensure base DNS records area available after a reboot
|
||||||
|
with subtest("query base DNS records"):
|
||||||
|
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
|
||||||
|
|
||||||
|
# ensure webnsupdate managed records are available after a reboot
|
||||||
|
with subtest("query webnsupdate DNS records (succeed)"):
|
||||||
|
machine.succeed("dig @127.0.0.1 test1.${testDomain} AAAA | grep ^test1.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test2.${testDomain} AAAA | grep ^test2.${testDomain}")
|
||||||
|
machine.succeed("dig @127.0.0.1 test3.${testDomain} AAAA | grep ^test3.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test1.${testDomain} A | grep ^test1.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test2.${testDomain} A | grep ^test2.${testDomain}")
|
||||||
|
machine.fail("dig @127.0.0.1 test3.${testDomain} A | grep ^test3.${testDomain}")
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
64
src/main.rs
64
src/main.rs
|
@ -81,10 +81,55 @@ struct Opts {
|
||||||
#[clap(long, default_value = "RightmostXForwardedFor")]
|
#[clap(long, default_value = "RightmostXForwardedFor")]
|
||||||
ip_source: SecureClientIpSource,
|
ip_source: SecureClientIpSource,
|
||||||
|
|
||||||
|
/// Set which IPs to allow updating
|
||||||
|
#[clap(long, default_value_t = IpType::Both)]
|
||||||
|
ip_type: IpType,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
subcommand: Option<Cmd>,
|
subcommand: Option<Cmd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
enum IpType {
|
||||||
|
#[default]
|
||||||
|
Both,
|
||||||
|
IPv4Only,
|
||||||
|
IPv6Only,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpType {
|
||||||
|
fn valid_for_type(self, ip: IpAddr) -> bool {
|
||||||
|
match self {
|
||||||
|
IpType::Both => true,
|
||||||
|
IpType::IPv4Only => ip.is_ipv4(),
|
||||||
|
IpType::IPv6Only => ip.is_ipv6(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for IpType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
IpType::Both => f.write_str("both"),
|
||||||
|
IpType::IPv4Only => f.write_str("ipv4-only"),
|
||||||
|
IpType::IPv6Only => f.write_str("ipv6-only"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for IpType {
|
||||||
|
type Err = miette::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"both" => Ok(Self::Both),
|
||||||
|
"ipv4-only" => Ok(Self::IPv4Only),
|
||||||
|
"ipv6-only" => Ok(Self::IPv6Only),
|
||||||
|
_ => bail!("expected one of 'ipv4-only', 'ipv6-only' or 'both', got '{s}'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum Cmd {
|
enum Cmd {
|
||||||
Mkpasswd(password::Mkpasswd),
|
Mkpasswd(password::Mkpasswd),
|
||||||
|
@ -117,6 +162,9 @@ struct AppState<'a> {
|
||||||
|
|
||||||
/// Last recorded IPs
|
/// Last recorded IPs
|
||||||
last_ips: std::sync::Arc<tokio::sync::Mutex<SavedIPs>>,
|
last_ips: std::sync::Arc<tokio::sync::Mutex<SavedIPs>>,
|
||||||
|
|
||||||
|
/// The IP type for which to allow updates
|
||||||
|
ip_type: IpType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
@ -173,13 +221,14 @@ impl AppState<'static> {
|
||||||
salt: _,
|
salt: _,
|
||||||
ttl,
|
ttl,
|
||||||
ip_source: _,
|
ip_source: _,
|
||||||
|
ip_type,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
// Set state
|
// Set state
|
||||||
let ttl = Duration::from_secs(*ttl);
|
let ttl = Duration::from_secs(*ttl);
|
||||||
|
|
||||||
// Use last registered IP address if available
|
// Use last registered IP address if available
|
||||||
let ip_file = Box::leak(data_dir.join("last-ip").into_boxed_path());
|
let ip_file = Box::leak(data_dir.join("last-ip.json").into_boxed_path());
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
ttl,
|
ttl,
|
||||||
|
@ -198,6 +247,7 @@ impl AppState<'static> {
|
||||||
})
|
})
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
ip_file,
|
ip_file,
|
||||||
|
ip_type: *ip_type,
|
||||||
last_ips: std::sync::Arc::new(tokio::sync::Mutex::new(
|
last_ips: std::sync::Arc::new(tokio::sync::Mutex::new(
|
||||||
load_ip(ip_file)?.unwrap_or_default(),
|
load_ip(ip_file)?.unwrap_or_default(),
|
||||||
)),
|
)),
|
||||||
|
@ -276,6 +326,7 @@ fn main() -> Result<()> {
|
||||||
salt,
|
salt,
|
||||||
ttl: _,
|
ttl: _,
|
||||||
ip_source,
|
ip_source,
|
||||||
|
ip_type,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
info!("checking environment");
|
info!("checking environment");
|
||||||
|
@ -312,6 +363,10 @@ fn main() -> Result<()> {
|
||||||
// Update DNS record with previous IPs (if available)
|
// Update DNS record with previous IPs (if available)
|
||||||
let ips = state.last_ips.lock().await.clone();
|
let ips = state.last_ips.lock().await.clone();
|
||||||
for ip in ips.ips() {
|
for ip in ips.ips() {
|
||||||
|
if !ip_type.valid_for_type(ip) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
|
@ -361,6 +416,13 @@ async fn update_records(
|
||||||
SecureClientIp(ip): SecureClientIp,
|
SecureClientIp(ip): SecureClientIp,
|
||||||
) -> axum::response::Result<&'static str> {
|
) -> axum::response::Result<&'static str> {
|
||||||
info!("accepted update from {ip}");
|
info!("accepted update from {ip}");
|
||||||
|
|
||||||
|
if !state.ip_type.valid_for_type(ip) {
|
||||||
|
let ip_type = state.ip_type;
|
||||||
|
tracing::warn!("rejecting update from {ip} as we are running a {ip_type} filter");
|
||||||
|
return Err((StatusCode::CONFLICT, format!("running in {ip_type} mode")).into());
|
||||||
|
}
|
||||||
|
|
||||||
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let ips = {
|
let ips = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue