Loading maintainers/maintainer-list.nix +6 −0 Original line number Diff line number Diff line Loading @@ -3964,6 +3964,12 @@ name = "Joseph Madden"; keys = [ { fingerprint = "3CF8 E983 2219 AB4B 0E19 158E 6112 1921 C9F8 117C"; } ]; }; brett = { email = "brett@librum.org"; github = "brett"; githubId = 523; name = "Brett Eisenberg"; }; brettlyons = { email = "blyons@fastmail.com"; github = "brettlyons"; Loading nixos/modules/module-list.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1322,6 +1322,7 @@ ./services/networking/nix-store-gcs-proxy.nix ./services/networking/nixops-dns.nix ./services/networking/nm-file-secret-agent.nix ./services/networking/nmtrust.nix ./services/networking/nncp.nix ./services/networking/nntp-proxy.nix ./services/networking/nomad.nix Loading nixos/modules/services/networking/nmtrust.nix 0 → 100644 +389 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let cfg = config.services.nmtrust; # Resolve trusted UUIDs from ensureProfiles + extra profileUUIDs = map ( name: config.networking.networkmanager.ensureProfiles.profiles.${name}.connection.uuid ) cfg.trustedConnections; trustedUUIDs = profileUUIDs ++ cfg.trustedUUIDsExtra; userNames = builtins.attrNames cfg.userUnits; # The package reads config from /etc/nmtrust/config at runtime trustHelper = pkgs.nmtrust; # Trust target names trustTargets = [ "nmtrust-trusted" "nmtrust-untrusted" "nmtrust-offline" ]; # Generate Conflicts= for a target (all other trust targets) conflictsFor = target: map (t: "${t}.target") (builtins.filter (t: t != target) trustTargets); # Generate systemd unit overrides for a system unit. # Uses StopWhenUnneeded instead of PartOf to avoid same-transaction # issues: when transitioning between targets that both want a unit # (e.g. offline -> trusted for allowOffline units), PartOf on the # old target would stop the unit before WantedBy on the new target # can restart it. StopWhenUnneeded only stops the unit when NO # active target wants it. mkSystemUnitOverrides = unitName: unitCfg: let targets = [ "nmtrust-trusted.target" ] ++ lib.optional unitCfg.allowOffline "nmtrust-offline.target"; in { unitConfig.StopWhenUnneeded = true; wantedBy = targets; }; # Generate user unit overrides mkUserUnitOverrides = unitName: unitCfg: let targets = [ "nmtrust-trusted.target" ] ++ lib.optional unitCfg.allowOffline "nmtrust-offline.target"; in { unitConfig.StopWhenUnneeded = true; wantedBy = targets; }; # NM dispatcher script dispatcherScript = pkgs.writeShellScript "nmtrust-dispatcher" '' case "$2" in up|down|vpn-up|vpn-down|connectivity-change) ${config.systemd.package}/bin/systemd-run \ --no-block \ --on-active=1s \ --unit=nmtrust-apply-debounce \ ${config.systemd.package}/bin/systemctl start nmtrust-apply.service \ 2>/dev/null || true ;; esac ''; in { # # Options # options.services.nmtrust = { enable = lib.mkEnableOption "network trust management"; trustedConnections = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = '' List of NetworkManager profile names from {option}`networking.networkmanager.ensureProfiles`. UUIDs are resolved at evaluation time. ''; }; trustedUUIDsExtra = lib.mkOption { type = lib.types.listOf ( lib.types.strMatching "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" ); default = [ ]; description = '' Additional trusted connection UUIDs not managed via {option}`networking.networkmanager.ensureProfiles`. Must be valid UUID format. ''; }; excludedConnectionPatterns = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = '' Glob patterns matched against connection names at runtime using fnmatch(3) with FNM_NOESCAPE. Connection names are treated as literal strings (no backslash interpretation). Matching connections are ignored when computing trust state. ''; }; mixedPolicy = lib.mkOption { type = lib.types.enum [ "trusted" "untrusted" ]; default = "untrusted"; description = '' How to treat mixed trust state (some connections trusted, some untrusted). ''; }; evalFailurePolicy = lib.mkOption { type = lib.types.enum [ "untrusted" "offline" ]; default = "untrusted"; description = '' How to handle trust evaluation failures (D-Bus errors, NM unavailable). `"untrusted"` (default) is fail-closed: trusted-only units stop. `"offline"` allows units with {option}`allowOffline` to run. ''; }; systemUnits = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options.allowOffline = lib.mkOption { type = lib.types.bool; default = false; description = "Whether this unit should also run when offline."; }; } ); default = { }; description = '' System units to bind to the trusted network target. Keys are systemd unit names. ''; }; userUnits = lib.mkOption { type = lib.types.attrsOf ( lib.types.attrsOf ( lib.types.submodule { options.allowOffline = lib.mkOption { type = lib.types.bool; default = false; description = "Whether this unit should also run when offline."; }; } ) ); default = { }; example = lib.literalExpression '' { alice = { "etesync-dav.service" = { }; "syncthing.service" = { allowOffline = true; }; }; } ''; description = '' Per-user units to bind to the trusted network target. Outer keys are usernames, inner keys are systemd unit names. Users must have linger enabled ({option}`users.users.<name>.linger`). ''; }; }; # # Config # config = lib.mkIf cfg.enable { # --- Assertions --- assertions = # NetworkManager is required [ { assertion = config.networking.networkmanager.enable; message = "services.nmtrust requires networking.networkmanager.enable = true."; } ] ++ # trustedConnections -> ensureProfiles UUID resolution (map (name: { assertion = config.networking.networkmanager.ensureProfiles.profiles ? ${name} && config.networking.networkmanager.ensureProfiles.profiles.${name}.connection ? uuid; message = "services.nmtrust.trustedConnections references '${name}' " + "but no matching networking.networkmanager.ensureProfiles entry with a UUID exists."; }) cfg.trustedConnections) ++ # userUnits -> user existence (map (username: { assertion = config.users.users ? ${username}; message = "services.nmtrust.userUnits references user '${username}' " + "but no matching users.users entry exists."; }) userNames) ++ # userUnits -> linger enabled (map (username: { assertion = let l = config.users.users.${username}.linger; in l != null && l; message = "services.nmtrust.userUnits references user '${username}' but " + "linger is not enabled. Set users.users.${username}.linger = true to " + "ensure the user's systemd instance is running for trust-based unit management. " + "Note: enabling linger causes ALL of this user's enabled user services to run " + "persistently, not just trust-managed units."; }) (builtins.filter (u: config.users.users ? ${u}) userNames)); # --- Helper package on PATH --- environment.systemPackages = [ trustHelper ]; # --- Runtime config file --- environment.etc."nmtrust/config" = { text = let toBashArray = xs: "(" + lib.concatMapStringsSep " " (x: lib.escapeShellArg x) xs + ")"; in '' # Generated by NixOS module — do not edit TRUSTED_UUIDS=${toBashArray trustedUUIDs} EXCLUDED_PATTERNS=${toBashArray (cfg.excludedConnectionPatterns)} MIXED_POLICY=${lib.escapeShellArg cfg.mixedPolicy} EVAL_FAILURE_POLICY=${lib.escapeShellArg cfg.evalFailurePolicy} MANAGED_USERS=${toBashArray userNames} ''; }; # --- tmpfiles.d --- systemd.tmpfiles.rules = [ "d /run/nmtrust 0700 root root -" ]; # --- System trust targets --- systemd.targets = lib.listToAttrs ( map (target: { name = target; value = { description = "Network Trust State: ${ if target == "nmtrust-trusted" then "Trusted" else if target == "nmtrust-untrusted" then "Untrusted" else "Offline" }"; unitConfig.Conflicts = conflictsFor target; }; }) trustTargets ); # --- User trust targets --- systemd.user.targets = lib.listToAttrs ( map (target: { name = target; value = { description = "Network Trust State: ${ if target == "nmtrust-trusted" then "Trusted (User)" else if target == "nmtrust-untrusted" then "Untrusted (User)" else "Offline (User)" }"; unitConfig.Conflicts = conflictsFor target; }; }) trustTargets ); # --- System unit overrides + services --- # Strip .service/.timer/.socket suffixes — NixOS appends them automatically systemd.services = lib.mapAttrs' (name: value: { name = lib.removeSuffix ".service" (lib.removeSuffix ".timer" (lib.removeSuffix ".socket" name)); value = mkSystemUnitOverrides name value; }) cfg.systemUnits // { nmtrust-apply = { description = "Evaluate and apply network trust state"; after = [ "NetworkManager.service" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${trustHelper}/bin/nmtrust apply"; ProtectSystem = "strict"; ReadWritePaths = [ "/run/nmtrust" ]; ProtectHome = true; NoNewPrivileges = true; PrivateTmp = true; }; }; nmtrust-eval = { description = "Evaluate network trust state on boot"; wantedBy = [ "network-online.target" ]; wants = [ "network-online.target" ]; after = [ "NetworkManager.service" "network-online.target" ]; restartTriggers = [ config.environment.etc."nmtrust/config".source ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${trustHelper}/bin/nmtrust apply"; ProtectSystem = "strict"; ReadWritePaths = [ "/run/nmtrust" ]; ProtectHome = true; NoNewPrivileges = true; PrivateTmp = true; }; }; }; # --- User unit overrides --- systemd.user.services = lib.foldl' ( acc: username: lib.foldl' ( acc': unitName: let strippedName = lib.removeSuffix ".service" ( lib.removeSuffix ".timer" (lib.removeSuffix ".socket" unitName) ); in acc' // { ${strippedName} = mkUserUnitOverrides unitName cfg.userUnits.${username}.${unitName}; } ) acc (builtins.attrNames cfg.userUnits.${username}) ) { } userNames; # --- NM dispatcher --- networking.networkmanager.dispatcherScripts = [ { source = dispatcherScript; type = "basic"; } ]; }; meta.maintainers = [ lib.maintainers.brett ]; } nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1141,6 +1141,7 @@ in pkgs.callPackage ../../pkgs/stdenv/generic/check-meta-test.nix { }; nixseparatedebuginfod2 = runTest ./nixseparatedebuginfod2.nix; nmtrust = runTest ./nmtrust.nix; node-red = runTest ./node-red.nix; nohang = runTest ./nohang.nix; nomad = runTest ./nomad.nix; Loading nixos/tests/nmtrust.nix 0 → 100644 +92 −0 Original line number Diff line number Diff line { lib, pkgs, ... }: { name = "nmtrust"; nodes.machine = { pkgs, ... }: { networking.networkmanager.enable = true; # Prevent the VM's built-in interfaces from polluting trust state. networking.networkmanager.unmanaged = [ "eth0" "eth1" "lo" ]; networking.networkmanager.ensureProfiles.profiles = { trusted-net = { connection = { id = "trusted-net"; uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; type = "dummy"; interface-name = "dummy-trusted"; autoconnect = "false"; }; ipv4.method = "manual"; ipv4.addresses = "10.99.1.1/24"; }; untrusted-net = { connection = { id = "untrusted-net"; uuid = "11111111-2222-3333-4444-555555555555"; type = "dummy"; interface-name = "dummy-untrusted"; autoconnect = "false"; }; ipv4.method = "manual"; ipv4.addresses = "10.99.2.1/24"; }; }; services.nmtrust = { enable = true; trustedConnections = [ "trusted-net" ]; systemUnits."trust-canary.service" = { }; }; # Canary service: runs only while the trusted target is active. systemd.services.trust-canary = { description = "nmtrust test canary"; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.coreutils}/bin/sleep infinity"; }; }; }; testScript = '' import time def apply(machine): """Trigger nmtrust-apply and wait for it to finish.""" time.sleep(1) machine.succeed("systemctl start nmtrust-apply.service") machine.wait_until_succeeds( "systemctl show nmtrust-apply.service -p ActiveState --value | grep -q inactive", timeout=10, ) machine.wait_for_unit("multi-user.target") with subtest("offline on boot with no connections active"): apply(machine) machine.succeed("systemctl is-active nmtrust-offline.target") machine.fail("systemctl is-active trust-canary.service") with subtest("trusted when trusted connection is up"): machine.succeed("nmcli connection up trusted-net") apply(machine) machine.succeed("systemctl is-active nmtrust-trusted.target") machine.succeed("systemctl is-active trust-canary.service") with subtest("untrusted when untrusted connection replaces trusted"): machine.succeed("nmcli connection down trusted-net") machine.succeed("nmcli connection up untrusted-net") apply(machine) machine.succeed("systemctl is-active nmtrust-untrusted.target") machine.fail("systemctl is-active trust-canary.service") ''; meta.maintainers = with lib.maintainers; [ brett ]; } Loading
maintainers/maintainer-list.nix +6 −0 Original line number Diff line number Diff line Loading @@ -3964,6 +3964,12 @@ name = "Joseph Madden"; keys = [ { fingerprint = "3CF8 E983 2219 AB4B 0E19 158E 6112 1921 C9F8 117C"; } ]; }; brett = { email = "brett@librum.org"; github = "brett"; githubId = 523; name = "Brett Eisenberg"; }; brettlyons = { email = "blyons@fastmail.com"; github = "brettlyons"; Loading
nixos/modules/module-list.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1322,6 +1322,7 @@ ./services/networking/nix-store-gcs-proxy.nix ./services/networking/nixops-dns.nix ./services/networking/nm-file-secret-agent.nix ./services/networking/nmtrust.nix ./services/networking/nncp.nix ./services/networking/nntp-proxy.nix ./services/networking/nomad.nix Loading
nixos/modules/services/networking/nmtrust.nix 0 → 100644 +389 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let cfg = config.services.nmtrust; # Resolve trusted UUIDs from ensureProfiles + extra profileUUIDs = map ( name: config.networking.networkmanager.ensureProfiles.profiles.${name}.connection.uuid ) cfg.trustedConnections; trustedUUIDs = profileUUIDs ++ cfg.trustedUUIDsExtra; userNames = builtins.attrNames cfg.userUnits; # The package reads config from /etc/nmtrust/config at runtime trustHelper = pkgs.nmtrust; # Trust target names trustTargets = [ "nmtrust-trusted" "nmtrust-untrusted" "nmtrust-offline" ]; # Generate Conflicts= for a target (all other trust targets) conflictsFor = target: map (t: "${t}.target") (builtins.filter (t: t != target) trustTargets); # Generate systemd unit overrides for a system unit. # Uses StopWhenUnneeded instead of PartOf to avoid same-transaction # issues: when transitioning between targets that both want a unit # (e.g. offline -> trusted for allowOffline units), PartOf on the # old target would stop the unit before WantedBy on the new target # can restart it. StopWhenUnneeded only stops the unit when NO # active target wants it. mkSystemUnitOverrides = unitName: unitCfg: let targets = [ "nmtrust-trusted.target" ] ++ lib.optional unitCfg.allowOffline "nmtrust-offline.target"; in { unitConfig.StopWhenUnneeded = true; wantedBy = targets; }; # Generate user unit overrides mkUserUnitOverrides = unitName: unitCfg: let targets = [ "nmtrust-trusted.target" ] ++ lib.optional unitCfg.allowOffline "nmtrust-offline.target"; in { unitConfig.StopWhenUnneeded = true; wantedBy = targets; }; # NM dispatcher script dispatcherScript = pkgs.writeShellScript "nmtrust-dispatcher" '' case "$2" in up|down|vpn-up|vpn-down|connectivity-change) ${config.systemd.package}/bin/systemd-run \ --no-block \ --on-active=1s \ --unit=nmtrust-apply-debounce \ ${config.systemd.package}/bin/systemctl start nmtrust-apply.service \ 2>/dev/null || true ;; esac ''; in { # # Options # options.services.nmtrust = { enable = lib.mkEnableOption "network trust management"; trustedConnections = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = '' List of NetworkManager profile names from {option}`networking.networkmanager.ensureProfiles`. UUIDs are resolved at evaluation time. ''; }; trustedUUIDsExtra = lib.mkOption { type = lib.types.listOf ( lib.types.strMatching "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" ); default = [ ]; description = '' Additional trusted connection UUIDs not managed via {option}`networking.networkmanager.ensureProfiles`. Must be valid UUID format. ''; }; excludedConnectionPatterns = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = '' Glob patterns matched against connection names at runtime using fnmatch(3) with FNM_NOESCAPE. Connection names are treated as literal strings (no backslash interpretation). Matching connections are ignored when computing trust state. ''; }; mixedPolicy = lib.mkOption { type = lib.types.enum [ "trusted" "untrusted" ]; default = "untrusted"; description = '' How to treat mixed trust state (some connections trusted, some untrusted). ''; }; evalFailurePolicy = lib.mkOption { type = lib.types.enum [ "untrusted" "offline" ]; default = "untrusted"; description = '' How to handle trust evaluation failures (D-Bus errors, NM unavailable). `"untrusted"` (default) is fail-closed: trusted-only units stop. `"offline"` allows units with {option}`allowOffline` to run. ''; }; systemUnits = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options.allowOffline = lib.mkOption { type = lib.types.bool; default = false; description = "Whether this unit should also run when offline."; }; } ); default = { }; description = '' System units to bind to the trusted network target. Keys are systemd unit names. ''; }; userUnits = lib.mkOption { type = lib.types.attrsOf ( lib.types.attrsOf ( lib.types.submodule { options.allowOffline = lib.mkOption { type = lib.types.bool; default = false; description = "Whether this unit should also run when offline."; }; } ) ); default = { }; example = lib.literalExpression '' { alice = { "etesync-dav.service" = { }; "syncthing.service" = { allowOffline = true; }; }; } ''; description = '' Per-user units to bind to the trusted network target. Outer keys are usernames, inner keys are systemd unit names. Users must have linger enabled ({option}`users.users.<name>.linger`). ''; }; }; # # Config # config = lib.mkIf cfg.enable { # --- Assertions --- assertions = # NetworkManager is required [ { assertion = config.networking.networkmanager.enable; message = "services.nmtrust requires networking.networkmanager.enable = true."; } ] ++ # trustedConnections -> ensureProfiles UUID resolution (map (name: { assertion = config.networking.networkmanager.ensureProfiles.profiles ? ${name} && config.networking.networkmanager.ensureProfiles.profiles.${name}.connection ? uuid; message = "services.nmtrust.trustedConnections references '${name}' " + "but no matching networking.networkmanager.ensureProfiles entry with a UUID exists."; }) cfg.trustedConnections) ++ # userUnits -> user existence (map (username: { assertion = config.users.users ? ${username}; message = "services.nmtrust.userUnits references user '${username}' " + "but no matching users.users entry exists."; }) userNames) ++ # userUnits -> linger enabled (map (username: { assertion = let l = config.users.users.${username}.linger; in l != null && l; message = "services.nmtrust.userUnits references user '${username}' but " + "linger is not enabled. Set users.users.${username}.linger = true to " + "ensure the user's systemd instance is running for trust-based unit management. " + "Note: enabling linger causes ALL of this user's enabled user services to run " + "persistently, not just trust-managed units."; }) (builtins.filter (u: config.users.users ? ${u}) userNames)); # --- Helper package on PATH --- environment.systemPackages = [ trustHelper ]; # --- Runtime config file --- environment.etc."nmtrust/config" = { text = let toBashArray = xs: "(" + lib.concatMapStringsSep " " (x: lib.escapeShellArg x) xs + ")"; in '' # Generated by NixOS module — do not edit TRUSTED_UUIDS=${toBashArray trustedUUIDs} EXCLUDED_PATTERNS=${toBashArray (cfg.excludedConnectionPatterns)} MIXED_POLICY=${lib.escapeShellArg cfg.mixedPolicy} EVAL_FAILURE_POLICY=${lib.escapeShellArg cfg.evalFailurePolicy} MANAGED_USERS=${toBashArray userNames} ''; }; # --- tmpfiles.d --- systemd.tmpfiles.rules = [ "d /run/nmtrust 0700 root root -" ]; # --- System trust targets --- systemd.targets = lib.listToAttrs ( map (target: { name = target; value = { description = "Network Trust State: ${ if target == "nmtrust-trusted" then "Trusted" else if target == "nmtrust-untrusted" then "Untrusted" else "Offline" }"; unitConfig.Conflicts = conflictsFor target; }; }) trustTargets ); # --- User trust targets --- systemd.user.targets = lib.listToAttrs ( map (target: { name = target; value = { description = "Network Trust State: ${ if target == "nmtrust-trusted" then "Trusted (User)" else if target == "nmtrust-untrusted" then "Untrusted (User)" else "Offline (User)" }"; unitConfig.Conflicts = conflictsFor target; }; }) trustTargets ); # --- System unit overrides + services --- # Strip .service/.timer/.socket suffixes — NixOS appends them automatically systemd.services = lib.mapAttrs' (name: value: { name = lib.removeSuffix ".service" (lib.removeSuffix ".timer" (lib.removeSuffix ".socket" name)); value = mkSystemUnitOverrides name value; }) cfg.systemUnits // { nmtrust-apply = { description = "Evaluate and apply network trust state"; after = [ "NetworkManager.service" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${trustHelper}/bin/nmtrust apply"; ProtectSystem = "strict"; ReadWritePaths = [ "/run/nmtrust" ]; ProtectHome = true; NoNewPrivileges = true; PrivateTmp = true; }; }; nmtrust-eval = { description = "Evaluate network trust state on boot"; wantedBy = [ "network-online.target" ]; wants = [ "network-online.target" ]; after = [ "NetworkManager.service" "network-online.target" ]; restartTriggers = [ config.environment.etc."nmtrust/config".source ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${trustHelper}/bin/nmtrust apply"; ProtectSystem = "strict"; ReadWritePaths = [ "/run/nmtrust" ]; ProtectHome = true; NoNewPrivileges = true; PrivateTmp = true; }; }; }; # --- User unit overrides --- systemd.user.services = lib.foldl' ( acc: username: lib.foldl' ( acc': unitName: let strippedName = lib.removeSuffix ".service" ( lib.removeSuffix ".timer" (lib.removeSuffix ".socket" unitName) ); in acc' // { ${strippedName} = mkUserUnitOverrides unitName cfg.userUnits.${username}.${unitName}; } ) acc (builtins.attrNames cfg.userUnits.${username}) ) { } userNames; # --- NM dispatcher --- networking.networkmanager.dispatcherScripts = [ { source = dispatcherScript; type = "basic"; } ]; }; meta.maintainers = [ lib.maintainers.brett ]; }
nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1141,6 +1141,7 @@ in pkgs.callPackage ../../pkgs/stdenv/generic/check-meta-test.nix { }; nixseparatedebuginfod2 = runTest ./nixseparatedebuginfod2.nix; nmtrust = runTest ./nmtrust.nix; node-red = runTest ./node-red.nix; nohang = runTest ./nohang.nix; nomad = runTest ./nomad.nix; Loading
nixos/tests/nmtrust.nix 0 → 100644 +92 −0 Original line number Diff line number Diff line { lib, pkgs, ... }: { name = "nmtrust"; nodes.machine = { pkgs, ... }: { networking.networkmanager.enable = true; # Prevent the VM's built-in interfaces from polluting trust state. networking.networkmanager.unmanaged = [ "eth0" "eth1" "lo" ]; networking.networkmanager.ensureProfiles.profiles = { trusted-net = { connection = { id = "trusted-net"; uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; type = "dummy"; interface-name = "dummy-trusted"; autoconnect = "false"; }; ipv4.method = "manual"; ipv4.addresses = "10.99.1.1/24"; }; untrusted-net = { connection = { id = "untrusted-net"; uuid = "11111111-2222-3333-4444-555555555555"; type = "dummy"; interface-name = "dummy-untrusted"; autoconnect = "false"; }; ipv4.method = "manual"; ipv4.addresses = "10.99.2.1/24"; }; }; services.nmtrust = { enable = true; trustedConnections = [ "trusted-net" ]; systemUnits."trust-canary.service" = { }; }; # Canary service: runs only while the trusted target is active. systemd.services.trust-canary = { description = "nmtrust test canary"; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.coreutils}/bin/sleep infinity"; }; }; }; testScript = '' import time def apply(machine): """Trigger nmtrust-apply and wait for it to finish.""" time.sleep(1) machine.succeed("systemctl start nmtrust-apply.service") machine.wait_until_succeeds( "systemctl show nmtrust-apply.service -p ActiveState --value | grep -q inactive", timeout=10, ) machine.wait_for_unit("multi-user.target") with subtest("offline on boot with no connections active"): apply(machine) machine.succeed("systemctl is-active nmtrust-offline.target") machine.fail("systemctl is-active trust-canary.service") with subtest("trusted when trusted connection is up"): machine.succeed("nmcli connection up trusted-net") apply(machine) machine.succeed("systemctl is-active nmtrust-trusted.target") machine.succeed("systemctl is-active trust-canary.service") with subtest("untrusted when untrusted connection replaces trusted"): machine.succeed("nmcli connection down trusted-net") machine.succeed("nmcli connection up untrusted-net") apply(machine) machine.succeed("systemctl is-active nmtrust-untrusted.target") machine.fail("systemctl is-active trust-canary.service") ''; meta.maintainers = with lib.maintainers; [ brett ]; }