Unverified Commit 6a1a2fe2 authored by Florian Klink's avatar Florian Klink Committed by GitHub
Browse files

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

parents 3630c6cb 93403bda
Loading
Loading
Loading
Loading
+77 −3
Original line number Diff line number Diff line
@@ -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 = [
    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 {
+48 −42
Original line number Diff line number Diff line
@@ -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,10 +46,10 @@
    terminal.systemctl("start 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')

    # Test 2: Test running checks
    with subtest("Test running checks"):
      terminal.succeed(
        "su - alice -c 'paretosecurity check"
        # Disable some checks that need intricate test setup so that this test
@@ -63,7 +63,13 @@
        + "'"
      )

    # Test 3: Test the tray icon
    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',
@@ -77,7 +83,7 @@
      xfce.succeed("xdotool click 1")
      xfce.wait_for_text("Run Checks")

    # Test 4: Desktop entry
    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
@@ -85,7 +91,7 @@
      xfce.succeed("xdotool click 1")
      xfce.wait_for_text("Pareto Security")

    # Test 5: paretosecurity:// URL handler is registered
    with subtest("Test 5: paretosecurity:// URL handler is registered"):
      xfce.succeed("su - alice -c 'xdg-open paretosecurity://foo'")
  '';
}
+12 −8
Original line number Diff line number Diff line
@@ -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;