1# This test verifies that systemd-timesyncd can resolve the NTP server hostname when DNSSEC validation
2# fails even though it is enforced in the systemd-resolved settings. It is required in order to solve
3# the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the
4# correct time, we need to connect to an NTP server, which usually requires resolving its hostname.
5#
6# This test does the following:
7# - Sets up a DNS server (tinydns) listening on the eth1 ip addess, serving .ntp and fake.ntp records.
8# - Configures that DNS server as a resolver and enables DNSSEC in systemd-resolved settings.
9# - Configures systemd-timesyncd to use fake.ntp hostname as an NTP server.
10# - Performs a regular DNS lookup, to ensure it fails due to broken DNSSEC.
11# - Waits until systemd-timesyncd resolves fake.ntp by checking its debug output.
12# Here, we don't expect systemd-timesyncd to connect and synchronize time because there is no NTP
13# server running. For this test to succeed, we only need to ensure that systemd-timesyncd
14# resolves the IP address of the fake.ntp host.
15
16import ./make-test-python.nix (
17 { pkgs, ... }:
18
19 let
20 ntpHostname = "fake.ntp";
21 ntpIP = "192.0.2.1";
22 in
23 {
24 name = "systemd-timesyncd";
25 nodes.machine =
26 {
27 pkgs,
28 lib,
29 config,
30 ...
31 }:
32 let
33 eth1IP = (lib.head config.networking.interfaces.eth1.ipv4.addresses).address;
34 in
35 {
36 # Setup a local DNS server for the NTP domain on the eth1 IP address
37 services.tinydns = {
38 enable = true;
39 ip = eth1IP;
40 data = ''
41 .ntp:${eth1IP}
42 +.${ntpHostname}:${ntpIP}
43 '';
44 };
45
46 # Enable systemd-resolved with DNSSEC and use the local DNS as a name server
47 services.resolved.enable = true;
48 services.resolved.dnssec = "true";
49 networking.nameservers = [ eth1IP ];
50
51 # Configure systemd-timesyncd to use our NTP hostname
52 services.timesyncd.enable = lib.mkForce true;
53 services.timesyncd.servers = [ ntpHostname ];
54 services.timesyncd.extraConfig = ''
55 FallbackNTP=${ntpHostname}
56 '';
57
58 # The debug output is necessary to determine whether systemd-timesyncd successfully resolves our NTP hostname or not
59 systemd.services.systemd-timesyncd.environment.SYSTEMD_LOG_LEVEL = "debug";
60 };
61
62 testScript = ''
63 machine.wait_for_unit("tinydns.service")
64 machine.wait_for_unit("systemd-timesyncd.service")
65 machine.fail("resolvectl query ${ntpHostname}")
66 machine.wait_until_succeeds("journalctl -u systemd-timesyncd.service --grep='Resolved address ${ntpIP}:123 for ${ntpHostname}'")
67 '';
68 }
69)