diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml index 3f53d67f72c..a0f001d0f92 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml @@ -216,6 +216,13 @@ services.hadoop.hbase. + + + Please, + a Sudo clone written in Rust. Available as + security.please + + Sachet, diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md index 505c10376b5..76b998d9e87 100644 --- a/nixos/doc/manual/release-notes/rl-2211.section.md +++ b/nixos/doc/manual/release-notes/rl-2211.section.md @@ -79,6 +79,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [HBase cluster](https://hbase.apache.org/), a distributed, scalable, big data store. Available as [services.hadoop.hbase](options.html#opt-services.hadoop.hbase.enable). +- [Please](https://github.com/edneville/please), a Sudo clone written in Rust. Available as [security.please](#opt-security.please.enable) + - [Sachet](https://github.com/messagebird/sachet/), an SMS alerting tool for the Prometheus Alertmanager. Available as [services.prometheus.sachet](#opt-services.prometheus.sachet.enable). - [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 9fc3af4b1ce..c400fadef3a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -263,6 +263,7 @@ ./security/pam.nix ./security/pam_usb.nix ./security/pam_mount.nix + ./security/please.nix ./security/polkit.nix ./security/rngd.nix ./security/rtkit.nix diff --git a/nixos/modules/security/please.nix b/nixos/modules/security/please.nix new file mode 100644 index 00000000000..88bb9cba2bf --- /dev/null +++ b/nixos/modules/security/please.nix @@ -0,0 +1,122 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.security.please; + ini = pkgs.formats.ini { }; +in +{ + options.security.please = { + enable = mkEnableOption (mdDoc '' + please, a Sudo clone which allows a users to execute a command or edit a + file as another user + ''); + + package = mkOption { + type = types.package; + default = pkgs.please; + defaultText = literalExpression "pkgs.please"; + description = mdDoc '' + Which package to use for {command}`please`. + ''; + }; + + wheelNeedsPassword = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether users of the `wheel` group must provide a password to run + commands or edit files with {command}`please` and + {command}`pleaseedit` respectively. + ''; + }; + + settings = mkOption { + type = ini.type; + default = { }; + example = { + jim_run_any_as_root = { + name = "jim"; + type = "run"; + target = "root"; + rule = ".*"; + require_pass = false; + }; + jim_edit_etc_hosts_as_root = { + name = "jim"; + type = "edit"; + target = "root"; + rule = "/etc/hosts"; + editmode = 644; + require_pass = true; + }; + }; + description = mdDoc '' + Please configuration. Refer to + for + details. + ''; + }; + }; + + config = mkIf cfg.enable { + security.wrappers = + let + owner = "root"; + group = "root"; + setuid = true; + in + { + please = { + source = "${cfg.package}/bin/please"; + inherit owner group setuid; + }; + pleaseedit = { + source = "${cfg.package}/bin/pleaseedit"; + inherit owner group setuid; + }; + }; + + security.please.settings = rec { + # The "wheel" group is allowed to do anything by default but this can be + # overridden. + wheel_run_as_any = { + type = "run"; + group = true; + name = "wheel"; + target = ".*"; + rule = ".*"; + require_pass = cfg.wheelNeedsPassword; + }; + wheel_edit_as_any = wheel_run_as_any // { type = "edit"; }; + wheel_list_as_any = wheel_run_as_any // { type = "list"; }; + }; + + environment = { + systemPackages = [ cfg.package ]; + + etc."please.ini".source = ini.generate "please.ini" + (cfg.settings // (rec { + # The "root" user is allowed to do anything by default and this cannot + # be overridden. + root_run_as_any = { + type = "run"; + name = "root"; + target = ".*"; + rule = ".*"; + require_pass = false; + }; + root_edit_as_any = root_run_as_any // { type = "edit"; }; + root_list_as_any = root_run_as_any // { type = "list"; }; + })); + }; + + security.pam.services.please = { + sshAgentAuth = true; + usshAuth = true; + }; + + meta.maintainers = with maintainers; [ azahi ]; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5026bbf36dd..0fc08e841ec 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -491,6 +491,7 @@ in { plasma5 = handleTest ./plasma5.nix {}; plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {}; plausible = handleTest ./plausible.nix {}; + please = handleTest ./please.nix {}; pleroma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./pleroma.nix {}; plikd = handleTest ./plikd.nix {}; plotinus = handleTest ./plotinus.nix {}; diff --git a/nixos/tests/please.nix b/nixos/tests/please.nix new file mode 100644 index 00000000000..2437cfe1613 --- /dev/null +++ b/nixos/tests/please.nix @@ -0,0 +1,66 @@ +import ./make-test-python.nix ({ lib, ... }: +{ + name = "please"; + meta.maintainers = with lib.maintainers; [ azahi ]; + + nodes.machine = + { ... }: + { + users.users = with lib; mkMerge [ + (listToAttrs (map + (n: nameValuePair n { isNormalUser = true; }) + (genList (x: "user${toString x}") 6))) + { + user0.extraGroups = [ "wheel" ]; + } + ]; + + security.please = { + enable = true; + wheelNeedsPassword = false; + settings = { + user2_run_true_as_root = { + name = "user2"; + target = "root"; + rule = "/run/current-system/sw/bin/true"; + require_pass = false; + }; + user4_edit_etc_hosts_as_root = { + name = "user4"; + type = "edit"; + target = "root"; + rule = "/etc/hosts"; + editmode = 644; + require_pass = false; + }; + }; + }; + }; + + testScript = '' + with subtest("root: can run anything by default"): + machine.succeed('please true') + with subtest("root: can edit anything by default"): + machine.succeed('EDITOR=cat pleaseedit /etc/hosts') + + with subtest("user0: can run as root because it's in the wheel group"): + machine.succeed('su - user0 -c "please -u root true"') + with subtest("user1: cannot run as root because it's not in the wheel group"): + machine.fail('su - user1 -c "please -u root true"') + + with subtest("user0: can edit as root"): + machine.succeed('su - user0 -c "EDITOR=cat pleaseedit /etc/hosts"') + with subtest("user1: cannot edit as root"): + machine.fail('su - user1 -c "EDITOR=cat pleaseedit /etc/hosts"') + + with subtest("user2: can run 'true' as root"): + machine.succeed('su - user2 -c "please -u root true"') + with subtest("user3: cannot run 'true' as root"): + machine.fail('su - user3 -c "please -u root true"') + + with subtest("user4: can edit /etc/hosts"): + machine.succeed('su - user4 -c "EDITOR=cat pleaseedit /etc/hosts"') + with subtest("user5: cannot edit /etc/hosts"): + machine.fail('su - user5 -c "EDITOR=cat pleaseedit /etc/hosts"') + ''; +}) diff --git a/pkgs/tools/security/please/default.nix b/pkgs/tools/security/please/default.nix index b3317dc2a82..40640ba4ed8 100644 --- a/pkgs/tools/security/please/default.nix +++ b/pkgs/tools/security/please/default.nix @@ -29,6 +29,8 @@ rustPlatform.buildRustPackage rec { installManPage man/* ''; + passthru.tests = { inherit (nixosTests) please; }; + meta = with lib; { description = "A polite regex-first sudo alternative"; longDescription = ''