nixos/paretosecurity: Add support for declarative linking (#432756)

Changed files
+133 -49
nixos
modules
services
tests
pkgs
by-name
pa
paretosecurity
+77 -3
nixos/modules/services/security/paretosecurity.nix
···
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 {
···
# 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 {
+44 -38
nixos/tests/paretosecurity.nix
···
imports = [ ./common/user-account.nix ];
services.paretosecurity.enable = true;
-
+
services.paretosecurity.users.alice.inviteId = "test-invite-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
};
nodes.xfce =
···
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"')
+
+
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 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 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")
-
# Test 5: paretosecurity:// URL handler is registered
-
xfce.succeed("su - alice -c 'xdg-open paretosecurity://foo'")
+
with subtest("Test 5: paretosecurity:// URL handler is registered"):
+
xfce.succeed("su - alice -c 'xdg-open paretosecurity://foo'")
'';
}
+12 -8
pkgs/by-name/pa/paretosecurity/package.nix
···
};
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
···
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;