diff --git a/nixos/modules/services/security/paretosecurity.nix b/nixos/modules/services/security/paretosecurity.nix index d942fe67c62a..c5533cbc8906 100644 --- a/nixos/modules/services/security/paretosecurity.nix +++ b/nixos/modules/services/security/paretosecurity.nix @@ -17,6 +17,25 @@ in default = true; description = "Set to false to disable the tray icon and run as a CLI tool only."; }; + users = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + inviteId = lib.mkOption { + type = lib.types.str; + description = '' + A unique ID that links the agent to Pareto Cloud. + Get it from the Join Team page on `https://cloud.paretosecurity.com/team/join/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. + In Step 2, under Linux tab, enter your email then copy it from the generated command. + ''; + example = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + }; + }; + } + ); + default = { }; + description = "Per-user Pareto Security configuration."; + }; }; config = lib.mkIf cfg.enable { @@ -38,9 +57,64 @@ in # if one is installed. # The `paretosecurity-user` timer service that is configured lower has # the same need. - systemd.services.paretosecurity.serviceConfig.Environment = [ - "PATH=${config.system.path}/bin:${config.system.path}/sbin" - ]; + systemd.services = { + paretosecurity.serviceConfig.Environment = [ + "PATH=${config.system.path}/bin:${config.system.path}/sbin" + ]; + } + // ( + + # Each user can set their inviteID, which creates a systemd service + # that runs `paretosecurity link ...` to link their device to Pareto Cloud. + lib.mapAttrs' ( + username: userConfig: + lib.nameValuePair "paretosecurity-link-${username}" { + description = "Link Pareto Desktop to Pareto Cloud for user ${username}"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = username; + StateDirectory = "paretosecurity/${username}"; + + ExecStart = pkgs.writeShellScript "paretosecurity-link-${username}" '' + set -euo pipefail + + INVITE_ID="${userConfig.inviteId}" + STATE_FILE="/var/lib/paretosecurity/${username}/linked-$INVITE_ID" + CONFIG_FILE="$HOME/.config/pareto.toml" + + # Check if already linked with this specific invite + if [ -f "$STATE_FILE" ]; then + echo "Device already linked with invite $INVITE_ID for user ${username}" + exit 0 + fi + + # Ensure config directory exists + mkdir -p "$(dirname "$CONFIG_FILE")" + + # Perform linking + echo "Linking device to Pareto Cloud for user ${username}..." + ${cfg.package}/bin/paretosecurity link \ + "paretosecurity://linkDevice/?invite_id=$INVITE_ID" + + # Verify linking succeeded + if [ -f "$CONFIG_FILE" ] && grep -q "TeamID" "$CONFIG_FILE"; then + echo "Successfully linked to Pareto Cloud for user ${username}" + touch "$STATE_FILE" + else + echo "Failed to link to Pareto Cloud for user ${username}" + exit 1 + fi + ''; + }; + + wantedBy = [ "multi-user.target" ]; + } + ) cfg.users + ); # Enable the tray icon and timer services if the trayIcon option is enabled systemd.user = lib.mkIf cfg.trayIcon { diff --git a/nixos/tests/paretosecurity.nix b/nixos/tests/paretosecurity.nix index 9c4c51271ad5..17f64a240847 100644 --- a/nixos/tests/paretosecurity.nix +++ b/nixos/tests/paretosecurity.nix @@ -9,7 +9,7 @@ imports = [ ./common/user-account.nix ]; services.paretosecurity.enable = true; - + services.paretosecurity.users.alice.inviteId = "test-invite-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; }; nodes.xfce = @@ -46,46 +46,52 @@ terminal.systemctl("start network-online.target") terminal.wait_for_unit("network-online.target") - # Test 1: Test the systemd socket is installed & enabled - terminal.succeed('systemctl is-enabled paretosecurity.socket') + with subtest("Test the systemd socket is installed & enabled"): + terminal.succeed('systemctl is-enabled paretosecurity.socket') - # Test 2: Test running checks - terminal.succeed( - "su - alice -c 'paretosecurity check" - # Disable some checks that need intricate test setup so that this test - # remains simple and fast. Tests for all checks and edge cases available - # at https://github.com/ParetoSecurity/agent/tree/main/test/integration - + " --skip c96524f2-850b-4bb9-abc7-517051b6c14e" # SecureBoot - + " --skip 37dee029-605b-4aab-96b9-5438e5aa44d8" # Screen lock - + " --skip 21830a4e-84f1-48fe-9c5b-beab436b2cdb" # Disk encryption - + " --skip 44e4754a-0b42-4964-9cc2-b88b2023cb1e" # Pareto Security is up to date - + " --skip f962c423-fdf5-428a-a57a-827abc9b253e" # Password manager installed - + "'" - ) + with subtest("Test running checks"): + terminal.succeed( + "su - alice -c 'paretosecurity check" + # Disable some checks that need intricate test setup so that this test + # remains simple and fast. Tests for all checks and edge cases available + # at https://github.com/ParetoSecurity/agent/tree/main/test/integration + + " --skip c96524f2-850b-4bb9-abc7-517051b6c14e" # SecureBoot + + " --skip 37dee029-605b-4aab-96b9-5438e5aa44d8" # Screen lock + + " --skip 21830a4e-84f1-48fe-9c5b-beab436b2cdb" # Disk encryption + + " --skip 44e4754a-0b42-4964-9cc2-b88b2023cb1e" # Pareto Security is up to date + + " --skip f962c423-fdf5-428a-a57a-827abc9b253e" # Password manager installed + + "'" + ) - # Test 3: Test the tray icon - xfce.wait_for_x() - for unit in [ - 'paretosecurity-trayicon', - 'paretosecurity-user', - 'paretosecurity-user.timer' - ]: - status, out = xfce.systemctl("is-enabled " + unit, "alice") - assert status == 0, f"Unit {unit} is not enabled (status: {status}): {out}" - xfce.succeed("xdotool mousemove 460 10") - xfce.wait_for_text("Pareto Security") - xfce.succeed("xdotool click 1") - xfce.wait_for_text("Run Checks") + with subtest("Test linking to Pareto Cloud"): + # The linking service will fail because there is no Internet, + # but we can check that it tried + terminal.succeed('systemctl list-units --type=service | grep paretosecurity-link-alice') + terminal.succeed('journalctl -u paretosecurity-link-alice.service | grep "Linking device to Pareto Cloud for user alice"') - # Test 4: Desktop entry - xfce.succeed("xdotool mousemove 10 10") - xfce.succeed("xdotool click 1") # hide the tray icon window - xfce.succeed("xdotool click 1") # show the Applications menu - xfce.succeed("xdotool mousemove 10 200") - xfce.succeed("xdotool click 1") - xfce.wait_for_text("Pareto Security") + with subtest("Test 3: Test the tray icon"): + xfce.wait_for_x() + for unit in [ + 'paretosecurity-trayicon', + 'paretosecurity-user', + 'paretosecurity-user.timer' + ]: + status, out = xfce.systemctl("is-enabled " + unit, "alice") + assert status == 0, f"Unit {unit} is not enabled (status: {status}): {out}" + xfce.succeed("xdotool mousemove 460 10") + xfce.wait_for_text("Pareto Security") + xfce.succeed("xdotool click 1") + xfce.wait_for_text("Run Checks") - # Test 5: paretosecurity:// URL handler is registered - xfce.succeed("su - alice -c 'xdg-open paretosecurity://foo'") + with subtest("Test 4: Desktop entry"): + xfce.succeed("xdotool mousemove 10 10") + xfce.succeed("xdotool click 1") # hide the tray icon window + xfce.succeed("xdotool click 1") # show the Applications menu + xfce.succeed("xdotool mousemove 10 200") + xfce.succeed("xdotool click 1") + xfce.wait_for_text("Pareto Security") + + with subtest("Test 5: paretosecurity:// URL handler is registered"): + xfce.succeed("su - alice -c 'xdg-open paretosecurity://foo'") ''; } diff --git a/pkgs/by-name/pa/paretosecurity/package.nix b/pkgs/by-name/pa/paretosecurity/package.nix index 791b7cba2f04..5f7dc0e2e15f 100644 --- a/pkgs/by-name/pa/paretosecurity/package.nix +++ b/pkgs/by-name/pa/paretosecurity/package.nix @@ -78,13 +78,15 @@ buildGoModule (finalAttrs: { }; meta = { - description = "Agent that makes sure your laptop is correctly configured for security"; + description = "A simple trayicon app that makes sure your laptop is correctly configured for security"; longDescription = '' - The Pareto Security agent is a free and open source app to help you make - sure that your laptop is configured for security. + [Pareto Desktop](https://paretosecurity.com/linux) is a free and open + source trayicon app to help you configure your laptop for security. It + nudges you to take care of 20% of security-related tasks that bring 80% of + protection. - By default, it's a CLI command that prints out a report on basic security - settings such as if you have disk encryption and firewall enabled. + In it's simplest form, it's a CLI command that prints out a report on basic + security settings such as if you have disk encryption and firewall enabled. If you use the `services.paretosecurity` NixOS module, you also get a root helper that allows you to run the checker in userspace. Some checks @@ -96,9 +98,11 @@ buildGoModule (finalAttrs: { once per hour. If you want to use just the CLI mode, set `services.paretosecurity.trayIcon` to `false`. - Finally, you can run `paretosecurity link` to configure the agent - to send the status of checks to https://dash.paretosecurity.com to make - compliance people happy. No sending happens until your device is linked. + Finally, if you set `users.users.alice.paretosecurity.inviteId = "..."` + to the `inviteId` you get on [Pareto Cloud](https://cloud.paretosecurity.com/), + then Pareto Desktop acts as a read-only and push-only agent that sends the + status of checks to https://cloud.paretosecurity.com which makes + compliance people happy and your privacy intact. ''; homepage = "https://github.com/ParetoSecurity/agent"; license = lib.licenses.gpl3Only;