nixos/paretosecurity: Add support for declarative linking
Before this PR, users that wanted to link their Pareto Desktop to Pareto Cloud had to imperatively run `paretosecurity link ...`. This PR adds a oneshot systemd service that does the linking so the entire Pareto app can be configured declaratively with nix.
This commit is contained in:
@@ -17,6 +17,25 @@ in
|
|||||||
default = true;
|
default = true;
|
||||||
description = "Set to false to disable the tray icon and run as a CLI tool only.";
|
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 {
|
config = lib.mkIf cfg.enable {
|
||||||
@@ -38,9 +57,64 @@ in
|
|||||||
# if one is installed.
|
# if one is installed.
|
||||||
# The `paretosecurity-user` timer service that is configured lower has
|
# The `paretosecurity-user` timer service that is configured lower has
|
||||||
# the same need.
|
# the same need.
|
||||||
systemd.services.paretosecurity.serviceConfig.Environment = [
|
systemd.services = {
|
||||||
"PATH=${config.system.path}/bin:${config.system.path}/sbin"
|
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
|
# Enable the tray icon and timer services if the trayIcon option is enabled
|
||||||
systemd.user = lib.mkIf cfg.trayIcon {
|
systemd.user = lib.mkIf cfg.trayIcon {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
imports = [ ./common/user-account.nix ];
|
imports = [ ./common/user-account.nix ];
|
||||||
|
|
||||||
services.paretosecurity.enable = true;
|
services.paretosecurity.enable = true;
|
||||||
|
services.paretosecurity.users.alice.inviteId = "test-invite-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes.xfce =
|
nodes.xfce =
|
||||||
@@ -46,46 +46,52 @@
|
|||||||
terminal.systemctl("start network-online.target")
|
terminal.systemctl("start network-online.target")
|
||||||
terminal.wait_for_unit("network-online.target")
|
terminal.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
# Test 1: Test the systemd socket is installed & enabled
|
with subtest("Test the systemd socket is installed & enabled"):
|
||||||
terminal.succeed('systemctl is-enabled paretosecurity.socket')
|
terminal.succeed('systemctl is-enabled paretosecurity.socket')
|
||||||
|
|
||||||
# Test 2: Test running checks
|
with subtest("Test running checks"):
|
||||||
terminal.succeed(
|
terminal.succeed(
|
||||||
"su - alice -c 'paretosecurity check"
|
"su - alice -c 'paretosecurity check"
|
||||||
# Disable some checks that need intricate test setup so that this test
|
# Disable some checks that need intricate test setup so that this test
|
||||||
# remains simple and fast. Tests for all checks and edge cases available
|
# remains simple and fast. Tests for all checks and edge cases available
|
||||||
# at https://github.com/ParetoSecurity/agent/tree/main/test/integration
|
# at https://github.com/ParetoSecurity/agent/tree/main/test/integration
|
||||||
+ " --skip c96524f2-850b-4bb9-abc7-517051b6c14e" # SecureBoot
|
+ " --skip c96524f2-850b-4bb9-abc7-517051b6c14e" # SecureBoot
|
||||||
+ " --skip 37dee029-605b-4aab-96b9-5438e5aa44d8" # Screen lock
|
+ " --skip 37dee029-605b-4aab-96b9-5438e5aa44d8" # Screen lock
|
||||||
+ " --skip 21830a4e-84f1-48fe-9c5b-beab436b2cdb" # Disk encryption
|
+ " --skip 21830a4e-84f1-48fe-9c5b-beab436b2cdb" # Disk encryption
|
||||||
+ " --skip 44e4754a-0b42-4964-9cc2-b88b2023cb1e" # Pareto Security is up to date
|
+ " --skip 44e4754a-0b42-4964-9cc2-b88b2023cb1e" # Pareto Security is up to date
|
||||||
+ " --skip f962c423-fdf5-428a-a57a-827abc9b253e" # Password manager installed
|
+ " --skip f962c423-fdf5-428a-a57a-827abc9b253e" # Password manager installed
|
||||||
+ "'"
|
+ "'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 3: Test the tray icon
|
with subtest("Test linking to Pareto Cloud"):
|
||||||
xfce.wait_for_x()
|
# The linking service will fail because there is no Internet,
|
||||||
for unit in [
|
# but we can check that it tried
|
||||||
'paretosecurity-trayicon',
|
terminal.succeed('systemctl list-units --type=service | grep paretosecurity-link-alice')
|
||||||
'paretosecurity-user',
|
terminal.succeed('journalctl -u paretosecurity-link-alice.service | grep "Linking device to Pareto Cloud for user alice"')
|
||||||
'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
|
with subtest("Test 3: Test the tray icon"):
|
||||||
xfce.succeed("xdotool mousemove 10 10")
|
xfce.wait_for_x()
|
||||||
xfce.succeed("xdotool click 1") # hide the tray icon window
|
for unit in [
|
||||||
xfce.succeed("xdotool click 1") # show the Applications menu
|
'paretosecurity-trayicon',
|
||||||
xfce.succeed("xdotool mousemove 10 200")
|
'paretosecurity-user',
|
||||||
xfce.succeed("xdotool click 1")
|
'paretosecurity-user.timer'
|
||||||
xfce.wait_for_text("Pareto Security")
|
]:
|
||||||
|
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
|
with subtest("Test 4: Desktop entry"):
|
||||||
xfce.succeed("su - alice -c 'xdg-open paretosecurity://foo'")
|
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'")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,13 +78,15 @@ buildGoModule (finalAttrs: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
meta = {
|
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 = ''
|
longDescription = ''
|
||||||
The Pareto Security agent is a free and open source app to help you make
|
[Pareto Desktop](https://paretosecurity.com/linux) is a free and open
|
||||||
sure that your laptop is configured for security.
|
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
|
In it's simplest form, it's a CLI command that prints out a report on basic
|
||||||
settings such as if you have disk encryption and firewall enabled.
|
security settings such as if you have disk encryption and firewall enabled.
|
||||||
|
|
||||||
If you use the `services.paretosecurity` NixOS module, you also get a
|
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
|
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
|
once per hour. If you want to use just the CLI mode, set
|
||||||
`services.paretosecurity.trayIcon` to `false`.
|
`services.paretosecurity.trayIcon` to `false`.
|
||||||
|
|
||||||
Finally, you can run `paretosecurity link` to configure the agent
|
Finally, if you set `users.users.alice.paretosecurity.inviteId = "..."`
|
||||||
to send the status of checks to https://dash.paretosecurity.com to make
|
to the `inviteId` you get on [Pareto Cloud](https://cloud.paretosecurity.com/),
|
||||||
compliance people happy. No sending happens until your device is linked.
|
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";
|
homepage = "https://github.com/ParetoSecurity/agent";
|
||||||
license = lib.licenses.gpl3Only;
|
license = lib.licenses.gpl3Only;
|
||||||
|
|||||||
Reference in New Issue
Block a user