diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 37e0a09e81fc..9bb34edac28f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1780,6 +1780,7 @@ in userborn-static = runTest ./userborn-static.nix; ustreamer = runTest ./ustreamer.nix; utils = import ./utils { inherit runTest; }; + utmp = runTest ./utmp.nix; uwsgi = runTest ./uwsgi.nix; v2ray = runTest ./v2ray.nix; varnish60 = runTest { diff --git a/nixos/tests/utmp.nix b/nixos/tests/utmp.nix new file mode 100644 index 000000000000..c2d80c17cb76 --- /dev/null +++ b/nixos/tests/utmp.nix @@ -0,0 +1,65 @@ +{ lib, ... }: +{ + + name = "utmp"; + + meta = { + maintainers = with lib.maintainers; [ grimmauld ]; + }; + + nodes = { + machine = { + imports = [ ./common/user-account.nix ]; + security.audit.enable = true; + security.auditd.enable = true; + }; + }; + + testScript = '' + import re + + def extract_utmp_fields(line: str) -> tuple[str, str] | None: + m = re.match(r"^\[\d*\]\s*\[\d*\]\s*\[([^\]\s]*)\s*]\s*\[([^\]\s]*)\s*]", line) + if not m: + return None + return m.group(1), m.group(2) + + machine.wait_for_unit("auditd.service") + machine.wait_for_unit("systemd-update-utmp.service") + machine.wait_for_file("/run/utmp") + + with subtest("systemd updated utmp"): + utmp = machine.succeed("utmpdump /run/utmp").strip().split("\n") + print(utmp) + assert extract_utmp_fields(utmp[0]) == ("~~", "reboot") # first entry is the reboot + + with subtest("Test utmp entries are created by logins"): + machine.wait_for_unit("multi-user.target") + machine.wait_until_tty_matches("1", "login: ") + machine.send_chars("alice\n") + machine.wait_until_tty_matches("1", "Password: ") + machine.send_chars("foobar\n") + machine.wait_until_succeeds("pgrep -u alice bash") + utmp = machine.succeed("utmpdump /run/utmp").strip().split("\n") + print(utmp) + assert extract_utmp_fields(utmp[1]) == ("tty1", "alice") # second entry is alice login + machine.send_chars("exit\n") + machine.wait_until_fails("pgrep -u alice bash") + + with subtest("Test utmp entries are cleaned after logout"): + utmp = machine.succeed("utmpdump /run/utmp").strip().split("\n") + print(utmp) + assert extract_utmp_fields(utmp[1]) in [("tty1", ""), ("tty1", "LOGIN")] # tty1 previously in use by alice is now clear + + with subtest("audit logs utmp system boot"): + audit_log = machine.succeed("ausearch -m SYSTEM_BOOT") + print(audit_log) + # audit 4.2.1 truncates too long comm entries, while systemd shortened the entry from `systemd-update-utmp` (truncated) to `update-utmp` + # see also: + # - systemd: https://github.com/systemd/systemd/pull/43144 + # - audit: https://github.com/linux-audit/audit-userspace/commit/d7ea98263ebdb974b383a4057856a5ec339776fc + # accept either fix in this test + assert 'comm="update-utmp"' in audit_log or f'comm="{"systemd-update-utmp"[:15]}"' in audit_log + ''; + +} diff --git a/pkgs/by-name/au/audit/package.nix b/pkgs/by-name/au/audit/package.nix index 657e74647d6f..21abe380fdd2 100644 --- a/pkgs/by-name/au/audit/package.nix +++ b/pkgs/by-name/au/audit/package.nix @@ -153,7 +153,7 @@ stdenv.mkDerivation (finalAttrs: { musl = pkgsMusl.audit or null; static = pkgsStatic.audit or null; pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage; - inherit (nixosTests) audit audit-testsuite; + inherit (nixosTests) audit audit-testsuite utmp; # Broken on a hardened kernel package = finalAttrs.finalPackage.overrideAttrs (previousAttrs: { pname = previousAttrs.pname + "-test";