diff options
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2405.section.md | 2 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/default.nix | 2 | ||||
-rw-r--r-- | nixos/modules/hardware/video/webcam/ipu6.nix | 16 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 3 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd/journald-gateway.nix | 135 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd/journald-remote.nix | 163 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd/journald-upload.nix | 111 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd/journald.nix | 21 | ||||
-rw-r--r-- | nixos/modules/system/boot/timesyncd.nix | 7 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 3 | ||||
-rw-r--r-- | nixos/tests/installer.nix | 8 | ||||
-rw-r--r-- | nixos/tests/os-prober.nix | 2 | ||||
-rw-r--r-- | nixos/tests/systemd-journal-gateway.nix | 90 | ||||
-rw-r--r-- | nixos/tests/systemd-journal-upload.nix | 101 | ||||
-rw-r--r-- | nixos/tests/systemd-journal.nix | 8 | ||||
-rw-r--r-- | nixos/tests/systemd-timesyncd-nscd-dnssec.nix | 61 |
16 files changed, 691 insertions, 42 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index 0e6b7b06a3d1..a07f43dcbba8 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -24,6 +24,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable). +- systemd's gateway, upload, and remote services, which provides ways of sending journals across the network. Enable using [services.journald.gateway](#opt-services.journald.gateway.enable), [services.journald.upload](#opt-services.journald.upload.enable), and [services.journald.remote](#opt-services.journald.remote.enable). + - [GNS3](https://www.gns3.com/), a network software emulator. Available as [services.gns3-server](#opt-services.gns3-server.enable). - [rspamd-trainer](https://gitlab.com/onlime/rspamd-trainer), script triggered by a helper which reads mails from a specific mail inbox and feeds them into rspamd for spam/ham training. diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix index 99515b5b8276..284934a7608e 100644 --- a/nixos/lib/make-options-doc/default.nix +++ b/nixos/lib/make-options-doc/default.nix @@ -120,7 +120,7 @@ in rec { { meta.description = "List of NixOS options in JSON format"; nativeBuildInputs = [ pkgs.brotli - pkgs.python3Minimal + pkgs.python3 ]; options = builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix)); diff --git a/nixos/modules/hardware/video/webcam/ipu6.nix b/nixos/modules/hardware/video/webcam/ipu6.nix index fce78cda34c7..c2dbdc217bd6 100644 --- a/nixos/modules/hardware/video/webcam/ipu6.nix +++ b/nixos/modules/hardware/video/webcam/ipu6.nix @@ -13,11 +13,12 @@ in enable = mkEnableOption (lib.mdDoc "support for Intel IPU6/MIPI cameras"); platform = mkOption { - type = types.enum [ "ipu6" "ipu6ep" ]; + type = types.enum [ "ipu6" "ipu6ep" "ipu6epmtl" ]; description = lib.mdDoc '' Choose the version for your hardware platform. - Use `ipu6` for Tiger Lake and `ipu6ep` for Alder Lake respectively. + Use `ipu6` for Tiger Lake, `ipu6ep` for Alder Lake or Raptor Lake, + and `ipu6epmtl` for Meteor Lake. ''; }; @@ -29,9 +30,7 @@ in ipu6-drivers ]; - hardware.firmware = with pkgs; [ ] - ++ optional (cfg.platform == "ipu6") ipu6-camera-bin - ++ optional (cfg.platform == "ipu6ep") ipu6ep-camera-bin; + hardware.firmware = [ pkgs.ipu6-camera-bins ]; services.udev.extraRules = '' SUBSYSTEM=="intel-ipu6-psys", MODE="0660", GROUP="video" @@ -44,14 +43,13 @@ in extraPackages = with pkgs.gst_all_1; [ ] ++ optional (cfg.platform == "ipu6") icamerasrc-ipu6 - ++ optional (cfg.platform == "ipu6ep") icamerasrc-ipu6ep; + ++ optional (cfg.platform == "ipu6ep") icamerasrc-ipu6ep + ++ optional (cfg.platform == "ipu6epmtl") icamerasrc-ipu6epmtl; input = { pipeline = "icamerasrc"; - format = mkIf (cfg.platform == "ipu6ep") (mkDefault "NV12"); + format = mkIf (cfg.platform != "ipu6") (mkDefault "NV12"); }; }; - }; - } diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 65047bdd110a..893a49e5763a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1472,6 +1472,9 @@ ./system/boot/systemd/initrd-secrets.nix ./system/boot/systemd/initrd.nix ./system/boot/systemd/journald.nix + ./system/boot/systemd/journald-gateway.nix + ./system/boot/systemd/journald-remote.nix + ./system/boot/systemd/journald-upload.nix ./system/boot/systemd/logind.nix ./system/boot/systemd/nspawn.nix ./system/boot/systemd/oomd.nix diff --git a/nixos/modules/system/boot/systemd/journald-gateway.nix b/nixos/modules/system/boot/systemd/journald-gateway.nix new file mode 100644 index 000000000000..854965282344 --- /dev/null +++ b/nixos/modules/system/boot/systemd/journald-gateway.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.gateway; + + cliArgs = lib.cli.toGNUCommandLineShell { } { + # If either of these are null / false, they are not passed in the command-line + inherit (cfg) cert key trust system user merge; + }; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.gateway = { + enable = lib.mkEnableOption "the HTTP gateway to the journal"; + + port = lib.mkOption { + default = 19531; + type = lib.types.port; + description = '' + The port to listen to. + ''; + }; + + cert = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = lib.mdDoc '' + The path to a file or `AF_UNIX` stream socket to read the server + certificate from. + + The certificate must be in PEM format. This option switches + `systemd-journal-gatewayd` into HTTPS mode and must be used together + with {option}`services.journald.gateway.key`. + ''; + }; + + key = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = lib.mdDoc '' + Specify the path to a file or `AF_UNIX` stream socket to read the + secret server key corresponding to the certificate specified with + {option}`services.journald.gateway.cert` from. + + The key must be in PEM format. + + This key should not be world-readable, and must be readably by the + `systemd-journal-gateway` user. + ''; + }; + + trust = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = lib.mdDoc '' + Specify the path to a file or `AF_UNIX` stream socket to read a CA + certificate from. + + The certificate must be in PEM format. + + Setting this option enforces client certificate checking. + ''; + }; + + system = lib.mkOption { + default = true; + type = lib.types.bool; + description = lib.mdDoc '' + Serve entries from system services and the kernel. + + This has the same meaning as `--system` for {manpage}`journalctl(1)`. + ''; + }; + + user = lib.mkOption { + default = true; + type = lib.types.bool; + description = lib.mdDoc '' + Serve entries from services for the current user. + + This has the same meaning as `--user` for {manpage}`journalctl(1)`. + ''; + }; + + merge = lib.mkOption { + default = false; + type = lib.types.bool; + description = lib.mdDoc '' + Serve entries interleaved from all available journals, including other + machines. + + This has the same meaning as `--merge` option for + {manpage}`journalctl(1)`. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + # This prevents the weird case were disabling "system" and "user" + # actually enables both because the cli flags are not present. + assertion = cfg.system || cfg.user; + message = '' + systemd-journal-gatewayd cannot serve neither "system" nor "user" + journals. + ''; + } + ]; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-journal-gatewayd.socket" + "systemd-journal-gatewayd.service" + ]; + + users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway; + users.users.systemd-journal-gateway.group = "systemd-journal-gateway"; + users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway; + + systemd.services.systemd-journal-gatewayd.serviceConfig.ExecStart = [ + # Clear the default command line + "" + "${pkgs.systemd}/lib/systemd/systemd-journal-gatewayd ${cliArgs}" + ]; + + systemd.sockets.systemd-journal-gatewayd = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ + # Clear the default port + "" + (toString cfg.port) + ]; + }; + }; +} diff --git a/nixos/modules/system/boot/systemd/journald-remote.nix b/nixos/modules/system/boot/systemd/journald-remote.nix new file mode 100644 index 000000000000..57a0a133e1c6 --- /dev/null +++ b/nixos/modules/system/boot/systemd/journald-remote.nix @@ -0,0 +1,163 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.remote; + format = pkgs.formats.systemd; + + cliArgs = lib.cli.toGNUCommandLineShell { } { + inherit (cfg) output; + # "-3" specifies the file descriptor from the .socket unit. + "listen-${cfg.listen}" = "-3"; + }; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.remote = { + enable = lib.mkEnableOption "receiving systemd journals from the network"; + + listen = lib.mkOption { + default = "https"; + type = lib.types.enum [ "https" "http" ]; + description = lib.mdDoc '' + Which protocol to listen to. + ''; + }; + + output = lib.mkOption { + default = "/var/log/journal/remote/"; + type = lib.types.str; + description = lib.mdDoc '' + The location of the output journal. + + In case the output file is not specified, journal files will be created + underneath the selected directory. Files will be called + {file}`remote-hostname.journal`, where the `hostname` part is the + escaped hostname of the source endpoint of the connection, or the + numerical address if the hostname cannot be determined. + ''; + }; + + port = lib.mkOption { + default = 19532; + type = lib.types.port; + description = '' + The port to listen to. + + Note that this option is used only if + {option}`services.journald.upload.listen` is configured to be either + "https" or "http". + ''; + }; + + settings = lib.mkOption { + default = { }; + + description = lib.mdDoc '' + Configuration in the journal-remote configuration file. See + {manpage}`journal-remote.conf(5)` for available options. + ''; + + type = lib.types.submodule { + freeformType = format.type; + + options.Remote = { + Seal = lib.mkOption { + default = false; + example = true; + type = lib.types.bool; + description = '' + Periodically sign the data in the journal using Forward Secure + Sealing. + ''; + }; + + SplitMode = lib.mkOption { + default = "host"; + example = "none"; + type = lib.types.enum [ "host" "none" ]; + description = lib.mdDoc '' + With "host", a separate output file is used, based on the + hostname of the other endpoint of a connection. With "none", only + one output journal file is used. + ''; + }; + + ServerKeyFile = lib.mkOption { + default = "/etc/ssl/private/journal-remote.pem"; + type = lib.types.str; + description = lib.mdDoc '' + A path to a SSL secret key file in PEM format. + + Note that due to security reasons, `systemd-journal-remote` will + refuse files from the world-readable `/nix/store`. This file + should be readable by the "" user. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the key read from it. + ''; + }; + + ServerCertificateFile = lib.mkOption { + default = "/etc/ssl/certs/journal-remote.pem"; + type = lib.types.str; + description = lib.mdDoc '' + A path to a SSL certificate file in PEM format. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the certificate read from it. + ''; + }; + + TrustedCertificateFile = lib.mkOption { + default = "/etc/ssl/ca/trusted.pem"; + type = lib.types.str; + description = lib.mdDoc '' + A path to a SSL CA certificate file in PEM format, or `all`. + + If `all` is set, then client certificate checking will be + disabled. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the certificate read from it. + ''; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-journal-remote.service" + "systemd-journal-remote.socket" + ]; + + systemd.services.systemd-journal-remote.serviceConfig.ExecStart = [ + # Clear the default command line + "" + "${pkgs.systemd}/lib/systemd/systemd-journal-remote ${cliArgs}" + ]; + + systemd.sockets.systemd-journal-remote = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ + # Clear the default port + "" + (toString cfg.port) + ]; + }; + + # User and group used by systemd-journal-remote.service + users.groups.systemd-journal-remote = { }; + users.users.systemd-journal-remote = { + isSystemUser = true; + group = "systemd-journal-remote"; + }; + + environment.etc."systemd/journal-remote.conf".source = + format.generate "journal-remote.conf" cfg.settings; + }; +} diff --git a/nixos/modules/system/boot/systemd/journald-upload.nix b/nixos/modules/system/boot/systemd/journald-upload.nix new file mode 100644 index 000000000000..6421e5fa486f --- /dev/null +++ b/nixos/modules/system/boot/systemd/journald-upload.nix @@ -0,0 +1,111 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.upload; + format = pkgs.formats.systemd; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.upload = { + enable = lib.mkEnableOption "uploading the systemd journal to a remote server"; + + settings = lib.mkOption { + default = { }; + + description = lib.mdDoc '' + Configuration for journal-upload. See {manpage}`journal-upload.conf(5)` + for available options. + ''; + + type = lib.types.submodule { + freeformType = format.type; + + options.Upload = { + URL = lib.mkOption { + type = lib.types.str; + example = "https://192.168.1.1"; + description = '' + The URL to upload the journal entries to. + + See the description of `--url=` option in + {manpage}`systemd-journal-upload(8)` for the description of + possible values. + ''; + }; + + ServerKeyFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./server-key.pem"; + # Since systemd-journal-upload uses a DynamicUser, permissions must + # be done using groups + description = '' + SSL key in PEM format. + + In contrary to what the name suggests, this option configures the + client private key sent to the remote journal server. + + This key should not be world-readable, and must be readably by + the `systemd-journal` group. + ''; + default = null; + }; + + ServerCertificateFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./server-ca.pem"; + description = '' + SSL CA certificate in PEM format. + + In contrary to what the name suggests, this option configures the + client certificate sent to the remote journal server. + ''; + default = null; + }; + + TrustedCertificateFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./ca"; + description = '' + SSL CA certificate. + + This certificate will be used to check the remote journal HTTPS + server certificate. + ''; + default = null; + }; + + NetworkTimeoutSec = lib.mkOption { + type = with lib.types; nullOr str; + example = "1s"; + description = '' + When network connectivity to the server is lost, this option + configures the time to wait for the connectivity to get restored. + + If the server is not reachable over the network for the + configured time, `systemd-journal-upload` exits. Takes a value in + seconds (or in other time units if suffixed with "ms", "min", + "h", etc). For details, see {manpage}`systemd.time(5)`. + ''; + default = null; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ "systemd-journal-upload.service" ]; + + systemd.services."systemd-journal-upload" = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Restart = "always"; + # To prevent flooding the server in case the server is struggling + RestartSec = "3sec"; + }; + }; + + environment.etc."systemd/journal-upload.conf".source = + format.generate "journal-upload.conf" cfg.settings; + }; +} diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix index 7e62a4c9bfed..9a8e7d592603 100644 --- a/nixos/modules/system/boot/systemd/journald.nix +++ b/nixos/modules/system/boot/systemd/journald.nix @@ -5,6 +5,10 @@ with lib; let cfg = config.services.journald; in { + imports = [ + (mkRenamedOptionModule [ "services" "journald" "enableHttpGateway" ] [ "services" "journald" "gateway" "enable" ]) + ]; + options = { services.journald.console = mkOption { default = ""; @@ -71,14 +75,6 @@ in { ''; }; - services.journald.enableHttpGateway = mkOption { - default = false; - type = types.bool; - description = lib.mdDoc '' - Whether to enable the HTTP gateway to the journal. - ''; - }; - services.journald.forwardToSyslog = mkOption { default = config.services.rsyslogd.enable || config.services.syslog-ng.enable; defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable"; @@ -101,9 +97,6 @@ in { ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [ "systemd-journald-dev-log.socket" "syslog.socket" - ] ++ optionals cfg.enableHttpGateway [ - "systemd-journal-gatewayd.socket" - "systemd-journal-gatewayd.service" ]; environment.etc = { @@ -124,12 +117,6 @@ in { }; users.groups.systemd-journal.gid = config.ids.gids.systemd-journal; - users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway; - users.users.systemd-journal-gateway.group = "systemd-journal-gateway"; - users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway; - - systemd.sockets.systemd-journal-gatewayd.wantedBy = - optional cfg.enableHttpGateway "sockets.target"; systemd.services.systemd-journal-flush.restartIfChanged = false; systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; 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 02e3e91e2e3d..b55e4b9fa71f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -841,6 +841,8 @@ in { systemd-initrd-networkd-openvpn = handleTestOn [ "x86_64-linux" "i686-linux" ] ./initrd-network-openvpn { systemdStage1 = true; }; systemd-initrd-vlan = handleTest ./systemd-initrd-vlan.nix {}; systemd-journal = handleTest ./systemd-journal.nix {}; + systemd-journal-gateway = handleTest ./systemd-journal-gateway.nix {}; + systemd-journal-upload = handleTest ./systemd-journal-upload.nix {}; systemd-machinectl = handleTest ./systemd-machinectl.nix {}; systemd-networkd = handleTest ./systemd-networkd.nix {}; systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {}; @@ -856,6 +858,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/installer.nix b/nixos/tests/installer.nix index d83e49a3e8f7..34fec3aab30b 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -510,14 +510,8 @@ let ntp perlPackages.ListCompare perlPackages.XMLLibXML - python3Minimal # make-options-doc/default.nix - (let - self = (pkgs.python3Minimal.override { - inherit self; - includeSiteCustomize = true; - }); - in self.withPackages (p: [ p.mistune ])) + python3.withPackages (p: [ p.mistune ]) shared-mime-info sudo texinfo diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix index dae1306bd69d..034de0620d88 100644 --- a/nixos/tests/os-prober.nix +++ b/nixos/tests/os-prober.nix @@ -95,7 +95,7 @@ in { ntp perlPackages.ListCompare perlPackages.XMLLibXML - python3Minimal + python3 shared-mime-info stdenv sudo diff --git a/nixos/tests/systemd-journal-gateway.nix b/nixos/tests/systemd-journal-gateway.nix new file mode 100644 index 000000000000..1d20943f2388 --- /dev/null +++ b/nixos/tests/systemd-journal-gateway.nix @@ -0,0 +1,90 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: +{ + name = "systemd-journal-gateway"; + meta = with pkgs.lib.maintainers; { + maintainers = [ minijackson raitobezarius ]; + }; + + # Named client for coherence with the systemd-journal-upload test, and for + # certificate validation + nodes.client = { + services.journald.gateway = { + enable = true; + cert = "/run/secrets/client/cert.pem"; + key = "/run/secrets/client/key.pem"; + trust = "/run/secrets/ca.cert.pem"; + }; + }; + + testScript = '' + import json + import subprocess + import tempfile + + tmpdir_o = tempfile.TemporaryDirectory() + tmpdir = tmpdir_o.name + + def generate_pems(domain: str): + subprocess.run( + [ + "${pkgs.minica}/bin/minica", + "--ca-key=ca.key.pem", + "--ca-cert=ca.cert.pem", + f"--domains={domain}", + ], + cwd=str(tmpdir), + ) + + with subtest("Creating keys and certificates"): + generate_pems("server") + generate_pems("client") + + client.wait_for_unit("multi-user.target") + + def copy_pem(file: str): + machine.copy_from_host(source=f"{tmpdir}/{file}", target=f"/run/secrets/{file}") + machine.succeed(f"chmod 644 /run/secrets/{file}") + + with subtest("Copying keys and certificates"): + machine.succeed("mkdir -p /run/secrets/{client,server}") + copy_pem("server/cert.pem") + copy_pem("server/key.pem") + copy_pem("client/cert.pem") + copy_pem("client/key.pem") + copy_pem("ca.cert.pem") + + client.wait_for_unit("multi-user.target") + + curl = '${pkgs.curl}/bin/curl' + accept_json = '--header "Accept: application/json"' + cacert = '--cacert /run/secrets/ca.cert.pem' + cert = '--cert /run/secrets/server/cert.pem' + key = '--key /run/secrets/server/key.pem' + base_url = 'https://client:19531' + + curl_cli = f"{curl} {accept_json} {cacert} {cert} {key} --fail" + + machine_info = client.succeed(f"{curl_cli} {base_url}/machine") + assert json.loads(machine_info)["hostname"] == "client", "wrong machine name" + + # The HTTP request should have started the gateway service, triggered by + # the .socket unit + client.wait_for_unit("systemd-journal-gatewayd.service") + + identifier = "nixos-test" + message = "Hello from NixOS test infrastructure" + + client.succeed(f"systemd-cat --identifier={identifier} <<< '{message}'") + + # max-time is a workaround against a bug in systemd-journal-gatewayd where + # if TLS is enabled, the connection is never closed. Since it will timeout, + # we ignore the return code. + entries = client.succeed( + f"{curl_cli} --max-time 5 {base_url}/entries?SYSLOG_IDENTIFIER={identifier} || true" + ) + + # Number of entries should be only 1 + added_entry = json.loads(entries) + assert added_entry["SYSLOG_IDENTIFIER"] == identifier and added_entry["MESSAGE"] == message, "journal entry does not correspond" + ''; +}) diff --git a/nixos/tests/systemd-journal-upload.nix b/nixos/tests/systemd-journal-upload.nix new file mode 100644 index 000000000000..4486a1e60b00 --- /dev/null +++ b/nixos/tests/systemd-journal-upload.nix @@ -0,0 +1,101 @@ +import ./make-test-python.nix ({ pkgs, ... }: +{ + name = "systemd-journal-upload"; + meta = with pkgs.lib.maintainers; { + maintainers = [ minijackson raitoezarius ]; + }; + + nodes.server = { nodes, ... }: { + services.journald.remote = { + enable = true; + listen = "http"; + settings.Remote = { + ServerCertificateFile = "/run/secrets/sever.cert.pem"; + ServerKeyFile = "/run/secrets/sever.key.pem"; + TrustedCertificateFile = "/run/secrets/ca.cert.pem"; + Seal = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ nodes.server.services.journald.remote.port ]; + }; + + nodes.client = { lib, nodes, ... }: { + services.journald.upload = { + enable = true; + settings.Upload = { + URL = "http://server:${toString nodes.server.services.journald.remote.port}"; + ServerCertificateFile = "/run/secrets/client.cert.pem"; + ServerKeyFile = "/run/secrets/client.key.pem"; + TrustedCertificateFile = "/run/secrets/ca.cert.pem"; + }; + }; + + # Wait for the PEMs to arrive + systemd.services.systemd-journal-upload.wantedBy = lib.mkForce []; + systemd.paths.systemd-journal-upload = { + wantedBy = [ "default.target" ]; + # This file must be copied last + pathConfig.PathExists = [ "/run/secrets/ca.cert.pem" ]; + }; + }; + + testScript = '' + import subprocess + import tempfile + + tmpdir_o = tempfile.TemporaryDirectory() + tmpdir = tmpdir_o.name + + def generate_pems(domain: str): + subprocess.run( + [ + "${pkgs.minica}/bin/minica", + "--ca-key=ca.key.pem", + "--ca-cert=ca.cert.pem", + f"--domains={domain}", + ], + cwd=str(tmpdir), + ) + + with subtest("Creating keys and certificates"): + generate_pems("server") + generate_pems("client") + + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") + + def copy_pems(machine: Machine, domain: str): + machine.succeed("mkdir /run/secrets") + machine.copy_from_host( + source=f"{tmpdir}/{domain}/cert.pem", + target=f"/run/secrets/{domain}.cert.pem", + ) + machine.copy_from_host( + source=f"{tmpdir}/{domain}/key.pem", + target=f"/run/secrets/{domain}.key.pem", + ) + # Should be last + machine.copy_from_host( + source=f"{tmpdir}/ca.cert.pem", + target="/run/secrets/ca.cert.pem", + ) + + with subtest("Copying keys and certificates"): + copy_pems(server, "server") + copy_pems(client, "client") + + client.wait_for_unit("systemd-journal-upload.service") + # The journal upload should have started the remote service, triggered by + # the .socket unit + server.wait_for_unit("systemd-journal-remote.service") + + identifier = "nixos-test" + message = "Hello from NixOS test infrastructure" + + client.succeed(f"systemd-cat --identifier={identifier} <<< '{message}'") + server.wait_until_succeeds( + f"journalctl --file /var/log/journal/remote/remote-*.journal --identifier={identifier} | grep -F '{message}'" + ) + ''; +}) diff --git a/nixos/tests/systemd-journal.nix b/nixos/tests/systemd-journal.nix index d2063a3b9a44..ad60c0f547a4 100644 --- a/nixos/tests/systemd-journal.nix +++ b/nixos/tests/systemd-journal.nix @@ -6,17 +6,11 @@ import ./make-test-python.nix ({ pkgs, ... }: maintainers = [ lewo ]; }; - nodes.machine = { pkgs, lib, ... }: { - services.journald.enableHttpGateway = true; - }; + nodes.machine = { }; testScript = '' machine.wait_for_unit("multi-user.target") machine.succeed("journalctl --grep=systemd") - - machine.succeed( - "${pkgs.curl}/bin/curl -s localhost:19531/machine | ${pkgs.jq}/bin/jq -e '.hostname == \"machine\"'" - ) ''; }) 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}'") + ''; +}) |