diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix index 7487cf97fe53..2666e4cd6b28 100644 --- a/nixos/modules/system/boot/timesyncd.nix +++ b/nixos/modules/system/boot/timesyncd.nix @@ -46,6 +46,13 @@ with lib; wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.timesync1.service" ]; restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ]; + # systemd-timesyncd disables DNSSEC validation in the nss-resolve module by setting SYSTEMD_NSS_RESOLVE_VALIDATE to 0 in the unit file. + # This is required in order to solve the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the + # correct time, we need to connect to an NTP server, which usually requires resolving its hostname. + # In order for nss-resolve to be able to read this environment variable we patch systemd-timesyncd to disable NSCD and use NSS modules directly. + # This means that systemd-timesyncd needs to have NSS modules path in LD_LIBRARY_PATH. When systemd-resolved is disabled we still need to set + # NSS module path so that systemd-timesyncd keeps using other NSS modules that are configured in the system. + environment.LD_LIBRARY_PATH = config.system.nssModules.path; preStart = ( # Ensure that we have some stored time to prevent diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 6f5ada84701f..85027eb1a71b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -847,6 +847,7 @@ in { systemd-shutdown = handleTest ./systemd-shutdown.nix {}; systemd-sysupdate = runTest ./systemd-sysupdate.nix; systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; + systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {}; systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {}; systemd-misc = handleTest ./systemd-misc.nix {}; systemd-userdbd = handleTest ./systemd-userdbd.nix {}; diff --git a/nixos/tests/systemd-timesyncd-nscd-dnssec.nix b/nixos/tests/systemd-timesyncd-nscd-dnssec.nix new file mode 100644 index 000000000000..697dd824e345 --- /dev/null +++ b/nixos/tests/systemd-timesyncd-nscd-dnssec.nix @@ -0,0 +1,61 @@ +# This test verifies that systemd-timesyncd can resolve the NTP server hostname when DNSSEC validation +# fails even though it is enforced in the systemd-resolved settings. It is required in order to solve +# the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the +# correct time, we need to connect to an NTP server, which usually requires resolving its hostname. +# +# This test does the following: +# - Sets up a DNS server (tinydns) listening on the eth1 ip addess, serving .ntp and fake.ntp records. +# - Configures that DNS server as a resolver and enables DNSSEC in systemd-resolved settings. +# - Configures systemd-timesyncd to use fake.ntp hostname as an NTP server. +# - Performs a regular DNS lookup, to ensure it fails due to broken DNSSEC. +# - Waits until systemd-timesyncd resolves fake.ntp by checking its debug output. +# Here, we don't expect systemd-timesyncd to connect and synchronize time because there is no NTP +# server running. For this test to succeed, we only need to ensure that systemd-timesyncd +# resolves the IP address of the fake.ntp host. + +import ./make-test-python.nix ({ pkgs, ... }: + +let + ntpHostname = "fake.ntp"; + ntpIP = "192.0.2.1"; +in +{ + name = "systemd-timesyncd"; + nodes.machine = { pkgs, lib, config, ... }: + let + eth1IP = (lib.head config.networking.interfaces.eth1.ipv4.addresses).address; + in + { + # Setup a local DNS server for the NTP domain on the eth1 IP address + services.tinydns = { + enable = true; + ip = eth1IP; + data = '' + .ntp:${eth1IP} + +.${ntpHostname}:${ntpIP} + ''; + }; + + # Enable systemd-resolved with DNSSEC and use the local DNS as a name server + services.resolved.enable = true; + services.resolved.dnssec = "true"; + networking.nameservers = [ eth1IP ]; + + # Configure systemd-timesyncd to use our NTP hostname + services.timesyncd.enable = lib.mkForce true; + services.timesyncd.servers = [ ntpHostname ]; + services.timesyncd.extraConfig = '' + FallbackNTP=${ntpHostname} + ''; + + # The debug output is necessary to determine whether systemd-timesyncd successfully resolves our NTP hostname or not + systemd.services.systemd-timesyncd.environment.SYSTEMD_LOG_LEVEL = "debug"; + }; + + testScript = '' + machine.wait_for_unit("tinydns.service") + machine.wait_for_unit("systemd-timesyncd.service") + machine.fail("resolvectl query ${ntpHostname}") + machine.wait_until_succeeds("journalctl -u systemd-timesyncd.service --grep='Resolved address ${ntpIP}:123 for ${ntpHostname}'") + ''; +}) diff --git a/pkgs/os-specific/linux/systemd/0020-timesyncd-disable-NSCD-when-DNSSEC-validation-is-dis.patch b/pkgs/os-specific/linux/systemd/0020-timesyncd-disable-NSCD-when-DNSSEC-validation-is-dis.patch new file mode 100644 index 000000000000..68ae22644835 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/0020-timesyncd-disable-NSCD-when-DNSSEC-validation-is-dis.patch @@ -0,0 +1,46 @@ +From 7a27556920fe1feefd17096841c8f3ca1294a1b3 Mon Sep 17 00:00:00 2001 +From: Yuri Nesterov +Date: Wed, 21 Jun 2023 17:17:38 +0300 +Subject: [PATCH] timesyncd: disable NSCD when DNSSEC validation is disabled + +Systemd-timesyncd sets SYSTEMD_NSS_RESOLVE_VALIDATE=0 in the unit file +to disable DNSSEC validation but it doesn't work when NSCD is used in +the system. This patch disabes NSCD in systemd-timesyncd when +SYSTEMD_NSS_RESOLVE_VALIDATE is set to 0 so that it uses NSS libraries +directly. +--- + src/timesync/timesyncd.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c +index 1d8ebecc91..2b0ae361ff 100644 +--- a/src/timesync/timesyncd.c ++++ b/src/timesync/timesyncd.c +@@ -21,6 +21,11 @@ + #include "timesyncd-conf.h" + #include "timesyncd-manager.h" + #include "user-util.h" ++#include "env-util.h" ++ ++struct traced_file; ++extern void __nss_disable_nscd(void (*)(size_t, struct traced_file *)); ++static void register_traced_file(size_t dbidx, struct traced_file *finfo) {} + + static int advance_tstamp(int fd, const struct stat *st) { + assert_se(fd >= 0); +@@ -198,6 +203,12 @@ static int run(int argc, char *argv[]) { + if (r < 0) + return log_error_errno(r, "Failed to parse fallback server strings: %m"); + ++ r = getenv_bool_secure("SYSTEMD_NSS_RESOLVE_VALIDATE"); ++ if (r == 0) { ++ log_info("Disabling NSCD because DNSSEC validation is turned off"); ++ __nss_disable_nscd(register_traced_file); ++ } ++ + log_debug("systemd-timesyncd running as pid " PID_FMT, getpid_cached()); + + notify_message = notify_start("READY=1\n" +-- +2.34.1 + diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index ff2c22881819..a2f77f231fd3 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -206,6 +206,7 @@ stdenv.mkDerivation (finalAttrs: { ./0017-core-don-t-taint-on-unmerged-usr.patch ./0018-tpm2_context_init-fix-driver-name-checking.patch ./0019-systemctl-edit-suggest-systemdctl-edit-runtime-on-sy.patch + ./0020-timesyncd-disable-NSCD-when-DNSSEC-validation-is-dis.patch ] ++ lib.optional stdenv.hostPlatform.isMusl ( let oe-core = fetchzip {