Unverified Commit 24ad63ef authored by João Nogueira's avatar João Nogueira
Browse files

nixos/rush: init module

parent 20b2600f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -203,6 +203,8 @@

- [Pareto Security](https://paretosecurity.com/) is an alternative to corporate compliance solutions for companies that care about security but know it doesn't have to be invasive. Available as [services.paretosecurity](#opt-services.paretosecurity.enable)

- [GNU Rush](https://gnu.org/software/rush/) is a Restricted User Shell, designed for systems providing limited remote access to their resources. Available as [programs.rush](#opt-programs.rush.enable).

- [ipfs-cluster](https://ipfscluster.io/), Pinset orchestration for IPFS. Available as [services.ipfs-cluster](#opt-services.ipfs-cluster.enable)

- [bitbox-bridge](https://github.com/BitBoxSwiss/bitbox-bridge), a bridge software that connects BitBox hardware wallets to computers & web wallets like [Rabby](https://rabby.io/). Allows one to interact & transact with smart contracts, Web3 websites & financial services without storing private keys anywhere other than the hardware wallet. Available as [services.bitbox-bridge](#opt-services.bitbox-bridge.enable).
+1 −0
Original line number Diff line number Diff line
@@ -290,6 +290,7 @@
  ./programs/quark-goldleaf.nix
  ./programs/regreet.nix
  ./programs/rog-control-center.nix
  ./programs/rush.nix
  ./programs/rust-motd.nix
  ./programs/ryzen-monitor-ng.nix
  ./programs/screen.nix
+109 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:
let
  cfg = config.programs.rush;

  indent =
    lines:
    lib.pipe lines [
      (lib.splitString "\n")
      (builtins.filter (line: line != ""))
      (map (line: "  " + line))
      (builtins.concatStringsSep "\n")
    ];
in
{
  meta.maintainers = pkgs.rush.meta.maintainers;

  options.programs.rush = with lib.types; {
    enable = lib.mkEnableOption "Restricted User Shell.";

    package = lib.mkPackageOption pkgs "rush" { } // {
      type = shellPackage;
    };

    global = lib.mkOption {
      type = lines;
      description = "The `global` statement defines global settings.";
      default = "";
    };

    rules = lib.mkOption {
      type = attrsOf lines;
      default = { };

      description = ''
        The rule statement configures a GNU Rush rule. This is a block statement, which means that all
        statements located between it and the next rule statement (or end of file, whichever occurs first)
        modify the definition of that rule.
      '';
    };

    shell = lib.mkOption {
      readOnly = true;
      type = either shellPackage path;

      description = ''
        The resolved shell path that users can inherit to set `rush` as their login shell.
        This is a convenience option for use in user definitions. Example:
          `users.users.alice = { inherit (config.programs.rush) shell; ... };`
      '';
    };

    wrap = lib.mkOption {
      type = bool;
      default = config.security.enableWrappers;
      defaultText = lib.literalExpression "config.security.enableWrappers";

      description = ''
        Whether to wrap the `rush` binary with a SUID-enabled wrapper.
        This is required if {option}`security.enableWrappers` is enabled in your configuration.
      '';
    };
  };

  config = lib.mkIf cfg.enable (
    lib.mkMerge [
      (lib.mkIf cfg.wrap {
        security.wrappers.rush = lib.mkDefault {
          group = "root";
          owner = "root";
          permissions = "u+rx,g+x,o+x";
          setgid = false;
          setuid = true;
          source = lib.getExe cfg.package;
        };
      })

      {
        programs.rush.shell = if cfg.wrap then config.security.wrapperDir + "/rush" else cfg.package;

        environment = {
          shells = [ cfg.shell ];
          systemPackages = [ cfg.package ];

          etc."rush.rc".text =
            lib.pipe
              [
                "# This file was created by the module `programs.rush`;"
                "rush 2.0"
                (lib.optionalString (cfg.global != "") "global\n${indent cfg.global}")
                (lib.optionals (cfg.rules != { }) (
                  lib.mapAttrsToList (name: content: "rule ${name}\n${indent content}") cfg.rules
                ))
              ]
              [
                (lib.flatten)
                (builtins.filter (line: line != ""))
                (builtins.concatStringsSep "\n\n")
                (lib.mkDefault)
              ];
        };
      }
    ]
  );
}
+1 −0
Original line number Diff line number Diff line
@@ -1169,6 +1169,7 @@ in
  rsyslogd = handleTest ./rsyslogd.nix { };
  rtkit = runTest ./rtkit.nix;
  rtorrent = handleTest ./rtorrent.nix { };
  rush = runTest ./rush.nix;
  rustls-libssl = handleTest ./rustls-libssl.nix { };
  rxe = handleTest ./rxe.nix { };
  sabnzbd = handleTest ./sabnzbd.nix { };

nixos/tests/rush.nix

0 → 100644
+88 −0
Original line number Diff line number Diff line
{ pkgs, ... }:
let
  inherit (import ./ssh-keys.nix pkgs) snakeOilEd25519PrivateKey snakeOilEd25519PublicKey;
  username = "nix-remote-builder";
in
{
  name = "rush";
  meta = { inherit (pkgs.rush.meta) maintainers platforms; };

  nodes = {
    client =
      { ... }:
      {
        nix.settings.extra-experimental-features = [ "nix-command" ];
      };

    server =
      { config, ... }:
      {
        nix.settings.trusted-users = [ "${username}" ];

        programs.rush = {
          enable = true;
          global = "debug 1";

          rules = {
            daemon = ''
              match $# == 2
              match $0 == "nix-daemon"
              match $1 == "--stdio"
              match $user == "${username}"
              chdir "${config.nix.package}/bin"
            '';

            whoami = ''
              match $# == 1
              match $0 == "whoami"
              match $user == "${username}"
              chdir "${dirOf config.environment.usrbinenv}"
            '';
          };
        };

        services.openssh = {
          enable = true;

          extraConfig = ''
            Match User ${username}
              AllowAgentForwarding no
              AllowTcpForwarding no
              PermitTTY no
              PermitTunnel no
              X11Forwarding no
            Match All
          '';
        };

        users = {
          groups."${username}" = { };

          users."${username}" = {
            inherit (config.programs.rush) shell;
            group = "${username}";
            isSystemUser = true;
            openssh.authorizedKeys.keys = [ snakeOilEd25519PublicKey ];
          };
        };
      };
  };

  testScript = ''
    start_all()

    client.succeed("mkdir -m 700 /root/.ssh")
    client.succeed("cat '${snakeOilEd25519PrivateKey}' | tee /root/.ssh/id_ed25519")
    client.succeed("chmod 600 /root/.ssh/id_ed25519")

    server.wait_for_unit("sshd")

    client.succeed("ssh-keyscan -H server | tee -a /root/.ssh/known_hosts")

    client.succeed("ssh ${username}@server -- whoami")
    client.succeed("nix store info --store 'ssh-ng://${username}@server'")

    client.fail("ssh ${username}@server -- date")
    client.fail("nix store info --store 'ssh://${username}@server'")
  '';
}
Loading