nixos/rush: init module

+203
+2
nixos/doc/manual/release-notes/rl-2505.section.md
··· 203 203 204 204 - [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) 205 205 206 + - [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). 207 + 206 208 - [ipfs-cluster](https://ipfscluster.io/), Pinset orchestration for IPFS. Available as [services.ipfs-cluster](#opt-services.ipfs-cluster.enable) 207 209 208 210 - [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
nixos/modules/module-list.nix
··· 290 290 ./programs/quark-goldleaf.nix 291 291 ./programs/regreet.nix 292 292 ./programs/rog-control-center.nix 293 + ./programs/rush.nix 293 294 ./programs/rust-motd.nix 294 295 ./programs/ryzen-monitor-ng.nix 295 296 ./programs/screen.nix
+109
nixos/modules/programs/rush.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + let 8 + cfg = config.programs.rush; 9 + 10 + indent = 11 + lines: 12 + lib.pipe lines [ 13 + (lib.splitString "\n") 14 + (builtins.filter (line: line != "")) 15 + (map (line: " " + line)) 16 + (builtins.concatStringsSep "\n") 17 + ]; 18 + in 19 + { 20 + meta.maintainers = pkgs.rush.meta.maintainers; 21 + 22 + options.programs.rush = with lib.types; { 23 + enable = lib.mkEnableOption "Restricted User Shell."; 24 + 25 + package = lib.mkPackageOption pkgs "rush" { } // { 26 + type = shellPackage; 27 + }; 28 + 29 + global = lib.mkOption { 30 + type = lines; 31 + description = "The `global` statement defines global settings."; 32 + default = ""; 33 + }; 34 + 35 + rules = lib.mkOption { 36 + type = attrsOf lines; 37 + default = { }; 38 + 39 + description = '' 40 + The rule statement configures a GNU Rush rule. This is a block statement, which means that all 41 + statements located between it and the next rule statement (or end of file, whichever occurs first) 42 + modify the definition of that rule. 43 + ''; 44 + }; 45 + 46 + shell = lib.mkOption { 47 + readOnly = true; 48 + type = either shellPackage path; 49 + 50 + description = '' 51 + The resolved shell path that users can inherit to set `rush` as their login shell. 52 + This is a convenience option for use in user definitions. Example: 53 + `users.users.alice = { inherit (config.programs.rush) shell; ... };` 54 + ''; 55 + }; 56 + 57 + wrap = lib.mkOption { 58 + type = bool; 59 + default = config.security.enableWrappers; 60 + defaultText = lib.literalExpression "config.security.enableWrappers"; 61 + 62 + description = '' 63 + Whether to wrap the `rush` binary with a SUID-enabled wrapper. 64 + This is required if {option}`security.enableWrappers` is enabled in your configuration. 65 + ''; 66 + }; 67 + }; 68 + 69 + config = lib.mkIf cfg.enable ( 70 + lib.mkMerge [ 71 + (lib.mkIf cfg.wrap { 72 + security.wrappers.rush = lib.mkDefault { 73 + group = "root"; 74 + owner = "root"; 75 + permissions = "u+rx,g+x,o+x"; 76 + setgid = false; 77 + setuid = true; 78 + source = lib.getExe cfg.package; 79 + }; 80 + }) 81 + 82 + { 83 + programs.rush.shell = if cfg.wrap then config.security.wrapperDir + "/rush" else cfg.package; 84 + 85 + environment = { 86 + shells = [ cfg.shell ]; 87 + systemPackages = [ cfg.package ]; 88 + 89 + etc."rush.rc".text = 90 + lib.pipe 91 + [ 92 + "# This file was created by the module `programs.rush`;" 93 + "rush 2.0" 94 + (lib.optionalString (cfg.global != "") "global\n${indent cfg.global}") 95 + (lib.optionals (cfg.rules != { }) ( 96 + lib.mapAttrsToList (name: content: "rule ${name}\n${indent content}") cfg.rules 97 + )) 98 + ] 99 + [ 100 + (lib.flatten) 101 + (builtins.filter (line: line != "")) 102 + (builtins.concatStringsSep "\n\n") 103 + (lib.mkDefault) 104 + ]; 105 + }; 106 + } 107 + ] 108 + ); 109 + }
+1
nixos/tests/all-tests.nix
··· 1169 1169 rsyslogd = handleTest ./rsyslogd.nix { }; 1170 1170 rtkit = runTest ./rtkit.nix; 1171 1171 rtorrent = handleTest ./rtorrent.nix { }; 1172 + rush = runTest ./rush.nix; 1172 1173 rustls-libssl = handleTest ./rustls-libssl.nix { }; 1173 1174 rxe = handleTest ./rxe.nix { }; 1174 1175 sabnzbd = handleTest ./sabnzbd.nix { };
+88
nixos/tests/rush.nix
··· 1 + { pkgs, ... }: 2 + let 3 + inherit (import ./ssh-keys.nix pkgs) snakeOilEd25519PrivateKey snakeOilEd25519PublicKey; 4 + username = "nix-remote-builder"; 5 + in 6 + { 7 + name = "rush"; 8 + meta = { inherit (pkgs.rush.meta) maintainers platforms; }; 9 + 10 + nodes = { 11 + client = 12 + { ... }: 13 + { 14 + nix.settings.extra-experimental-features = [ "nix-command" ]; 15 + }; 16 + 17 + server = 18 + { config, ... }: 19 + { 20 + nix.settings.trusted-users = [ "${username}" ]; 21 + 22 + programs.rush = { 23 + enable = true; 24 + global = "debug 1"; 25 + 26 + rules = { 27 + daemon = '' 28 + match $# == 2 29 + match $0 == "nix-daemon" 30 + match $1 == "--stdio" 31 + match $user == "${username}" 32 + chdir "${config.nix.package}/bin" 33 + ''; 34 + 35 + whoami = '' 36 + match $# == 1 37 + match $0 == "whoami" 38 + match $user == "${username}" 39 + chdir "${dirOf config.environment.usrbinenv}" 40 + ''; 41 + }; 42 + }; 43 + 44 + services.openssh = { 45 + enable = true; 46 + 47 + extraConfig = '' 48 + Match User ${username} 49 + AllowAgentForwarding no 50 + AllowTcpForwarding no 51 + PermitTTY no 52 + PermitTunnel no 53 + X11Forwarding no 54 + Match All 55 + ''; 56 + }; 57 + 58 + users = { 59 + groups."${username}" = { }; 60 + 61 + users."${username}" = { 62 + inherit (config.programs.rush) shell; 63 + group = "${username}"; 64 + isSystemUser = true; 65 + openssh.authorizedKeys.keys = [ snakeOilEd25519PublicKey ]; 66 + }; 67 + }; 68 + }; 69 + }; 70 + 71 + testScript = '' 72 + start_all() 73 + 74 + client.succeed("mkdir -m 700 /root/.ssh") 75 + client.succeed("cat '${snakeOilEd25519PrivateKey}' | tee /root/.ssh/id_ed25519") 76 + client.succeed("chmod 600 /root/.ssh/id_ed25519") 77 + 78 + server.wait_for_unit("sshd") 79 + 80 + client.succeed("ssh-keyscan -H server | tee -a /root/.ssh/known_hosts") 81 + 82 + client.succeed("ssh ${username}@server -- whoami") 83 + client.succeed("nix store info --store 'ssh-ng://${username}@server'") 84 + 85 + client.fail("ssh ${username}@server -- date") 86 + client.fail("nix store info --store 'ssh://${username}@server'") 87 + ''; 88 + }
+2
pkgs/by-name/ru/rush/package.nix
··· 4 4 stdenv, 5 5 bash, 6 6 perl, 7 + nixosTests, 7 8 }: 8 9 9 10 stdenv.mkDerivation rec { ··· 61 62 62 63 passthru = { 63 64 shellPath = "/bin/rush"; 65 + tests = { inherit (nixosTests) rush; }; 64 66 }; 65 67 }