summary refs log tree commit diff
diff options
context:
space:
mode:
authorMaciej Krüger <mkg20001@gmail.com>2023-09-21 14:56:40 +0200
committerMaciej Krüger <mkg20001@gmail.com>2023-09-22 15:13:56 +0200
commit57d41f97514d95fa6e4dcb73885e6af3a50209be (patch)
treea13363d2f82daa64f95a9e8309325a1149a4647f
parent4d94b454d96c7e07bf500768037176aca2c13a21 (diff)
downloadnixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.tar
nixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.tar.gz
nixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.tar.bz2
nixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.tar.lz
nixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.tar.xz
nixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.tar.zst
nixpkgs-57d41f97514d95fa6e4dcb73885e6af3a50209be.zip
nixos/sudo: revert sudo-rs 922926cfbc08f3e4065b51a41ebf613e59888015 (partial #253876)
This reverts the module changes that were added
by the addition of sudo-rs (merge 922926cfbc08f3e4065b51a41ebf613e59888015) from the sudo module.

Individual commits reverted:
* 409d29ca7373 2023-08-31 | [nicoo] nixos/sudo: Split up `configFile` into individual sections
* 454151375d62 2023-09-04 | [nicoo] nixos/sudo: Don't include empty sections
* 8742134c8053 2023-09-04 | [nicoo] nixos/sudo: Only keep SSH_AUTH_SOCK if used for authentication
* f5aadb56bed0 2023-09-07 | [nicoo] nixos/sudo: Refactor option definitions
* 8b9e867ac83f 2023-09-07 | [nicoo] nixos/sudo: Refactor checks for Todd C. Miller's implemetation
* 3a95964fd5ba 2023-09-07 | [nicoo] nixos/sudo: Drop useless `lib.` qualifiers
* b1eab8ca53dc 2023-09-07 | [nicoo] nixos/sudo: Handle `root`'s default rule through `extraRules`
* 717e51a140d6 2023-09-07 | [nicoo] nixos/sudo: Make the default rules' options configurable
* c11da3911787 2023-09-07 | [nicoo] nixos/sudo: Drop the sudoers comment for `extraRules`
* f0107b4f63a7 2023-09-07 | [nicoo] nixos/sudo: Check syntax using the configured package
* 914bf5836974 2023-09-07 | [nicoo] nixos/{sudo, terminfo}: Adjust defaults for compatibility with `sudo-rs`
* f66eb0df3b23 2023-09-07 | [nicoo] nixos/sudo: Only wrap `sudoedit` when using Miller's sudo
* d63eb55e81ad 2023-09-13 | [nicoo] nixos/sudo: Generate `sudo-i` PAM config for interactive use of `sudo-rs`
* d8d0b8019ff3 2023-09-13 | [nicoo] nixos/sudo: Add myself as maintainer (nbraud/nixos/sudo-rs)
-rw-r--r--nixos/modules/security/sudo-rs.nix296
-rw-r--r--nixos/modules/security/sudo.nix169
2 files changed, 367 insertions, 98 deletions
diff --git a/nixos/modules/security/sudo-rs.nix b/nixos/modules/security/sudo-rs.nix
new file mode 100644
index 00000000000..4bdbe9671e6
--- /dev/null
+++ b/nixos/modules/security/sudo-rs.nix
@@ -0,0 +1,296 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) sudo sudo-rs;
+
+  cfg = config.security.sudo;
+
+  enableSSHAgentAuth =
+    with config.security;
+    pam.enableSSHAgentAuth && pam.sudo.sshAgentAuth;
+
+  usingMillersSudo = cfg.package.pname == sudo.pname;
+  usingSudoRs = cfg.package.pname == sudo-rs.pname;
+
+  toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
+  toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
+
+  toCommandOptionsString = options:
+    "${concatStringsSep ":" options}${optionalString (length options != 0) ":"} ";
+
+  toCommandsString = commands:
+    concatStringsSep ", " (
+      map (command:
+        if (isString command) then
+          command
+        else
+          "${toCommandOptionsString command.options}${command.command}"
+      ) commands
+    );
+
+in
+
+{
+
+  ###### interface
+
+  options.security.sudo = {
+
+    defaultOptions = mkOption {
+      type = with types; listOf str;
+      default = optional usingMillersSudo "SETENV";
+      defaultText = literalMD ''
+        `[ "SETENV" ]` if using the default `sudo` implementation
+      '';
+      description = mdDoc ''
+        Options used for the default rules, granting `root` and the
+        `wheel` group permission to run any command as any user.
+      '';
+    };
+
+    enable = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc ''
+        Whether to enable the {command}`sudo` command, which
+        allows non-root users to execute commands as root.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.sudo;
+      defaultText = literalExpression "pkgs.sudo";
+      description = mdDoc ''
+        Which package to use for `sudo`.
+      '';
+    };
+
+    wheelNeedsPassword = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc ''
+        Whether users of the `wheel` group must
+        provide a password to run commands as super user via {command}`sudo`.
+      '';
+      };
+
+    execWheelOnly = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Only allow members of the `wheel` group to execute sudo by
+        setting the executable's permissions accordingly.
+        This prevents users that are not members of `wheel` from
+        exploiting vulnerabilities in sudo such as CVE-2021-3156.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.lines;
+      # Note: if syntax errors are detected in this file, the NixOS
+      # configuration will fail to build.
+      description = mdDoc ''
+        This string contains the contents of the
+        {file}`sudoers` file.
+      '';
+    };
+
+    extraRules = mkOption {
+      description = mdDoc ''
+        Define specific rules to be in the {file}`sudoers` file.
+        More specific rules should come after more general ones in order to
+        yield the expected behavior. You can use mkBefore/mkAfter to ensure
+        this is the case when configuration options are merged.
+      '';
+      default = [];
+      example = literalExpression ''
+        [
+          # Allow execution of any command by all users in group sudo,
+          # requiring a password.
+          { groups = [ "sudo" ]; commands = [ "ALL" ]; }
+
+          # Allow execution of "/home/root/secret.sh" by user `backup`, `database`
+          # and the group with GID `1006` without a password.
+          { users = [ "backup" "database" ]; groups = [ 1006 ];
+            commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; }
+
+          # Allow all users of group `bar` to run two executables as user `foo`
+          # with arguments being pre-set.
+          { groups = [ "bar" ]; runAs = "foo";
+            commands =
+              [ "/home/baz/cmd1.sh hello-sudo"
+                  { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; }
+        ]
+      '';
+      type = with types; listOf (submodule {
+        options = {
+          users = mkOption {
+            type = with types; listOf (either str int);
+            description = mdDoc ''
+              The usernames / UIDs this rule should apply for.
+            '';
+            default = [];
+          };
+
+          groups = mkOption {
+            type = with types; listOf (either str int);
+            description = mdDoc ''
+              The groups / GIDs this rule should apply for.
+            '';
+            default = [];
+          };
+
+          host = mkOption {
+            type = types.str;
+            default = "ALL";
+            description = mdDoc ''
+              For what host this rule should apply.
+            '';
+          };
+
+          runAs = mkOption {
+            type = with types; str;
+            default = "ALL:ALL";
+            description = mdDoc ''
+              Under which user/group the specified command is allowed to run.
+
+              A user can be specified using just the username: `"foo"`.
+              It is also possible to specify a user/group combination using `"foo:bar"`
+              or to only allow running as a specific group with `":bar"`.
+            '';
+          };
+
+          commands = mkOption {
+            description = mdDoc ''
+              The commands for which the rule should apply.
+            '';
+            type = with types; listOf (either str (submodule {
+
+              options = {
+                command = mkOption {
+                  type = with types; str;
+                  description = mdDoc ''
+                    A command being either just a path to a binary to allow any arguments,
+                    the full command with arguments pre-set or with `""` used as the argument,
+                    not allowing arguments to the command at all.
+                  '';
+                };
+
+                options = mkOption {
+                  type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
+                  description = mdDoc ''
+                    Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
+                  '';
+                  default = [];
+                };
+              };
+
+            }));
+          };
+        };
+      });
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = mdDoc ''
+        Extra configuration text appended to {file}`sudoers`.
+      '';
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    security.sudo.extraRules =
+      let
+        defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
+          inherit users groups;
+          commands = [ {
+            command = "ALL";
+            options = opts ++ cfg.defaultOptions;
+          } ];
+        } ];
+      in mkMerge [
+        # This is ordered before users' `mkBefore` rules,
+        # so as not to introduce unexpected changes.
+        (mkOrder 400 (defaultRule { users = [ "root" ]; }))
+
+        # This is ordered to show before (most) other rules, but
+        # late-enough for a user to `mkBefore` it.
+        (mkOrder 600 (defaultRule {
+          groups = [ "wheel" ];
+          opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
+        }))
+      ];
+
+    security.sudo.configFile = concatStringsSep "\n" (filter (s: s != "") [
+      ''
+        # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
+        # or ‘security.sudo.extraRules’ instead.
+      ''
+      (optionalString enableSSHAgentAuth ''
+        # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
+        Defaults env_keep+=SSH_AUTH_SOCK
+      '')
+      (concatStringsSep "\n" (
+        lists.flatten (
+          map (
+            rule: optionals (length rule.commands != 0) [
+              (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
+              (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
+            ]
+          ) cfg.extraRules
+        )
+      ) + "\n")
+      (optionalString (cfg.extraConfig != "") ''
+        # extraConfig
+        ${cfg.extraConfig}
+      '')
+    ]);
+
+    security.wrappers = let
+      owner = "root";
+      group = if cfg.execWheelOnly then "wheel" else "root";
+      setuid = true;
+      permissions = if cfg.execWheelOnly then "u+rx,g+x" else "u+rx,g+x,o+x";
+    in {
+      sudo = {
+        source = "${cfg.package.out}/bin/sudo";
+        inherit owner group setuid permissions;
+      };
+      # sudo-rs does not yet ship a sudoedit (as of v0.2.0)
+      sudoedit = mkIf usingMillersSudo {
+        source = "${cfg.package.out}/bin/sudoedit";
+        inherit owner group setuid permissions;
+      };
+    };
+
+    environment.systemPackages = [ sudo ];
+
+    security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
+    security.pam.services.sudo-i = mkIf usingSudoRs
+      { sshAgentAuth = true; usshAuth = true; };
+
+    environment.etc.sudoers =
+      { source =
+          pkgs.runCommand "sudoers"
+          {
+            src = pkgs.writeText "sudoers-in" cfg.configFile;
+            preferLocalBuild = true;
+          }
+          "${cfg.package}/bin/visudo -f $src -c && cp $src $out";
+        mode = "0440";
+      };
+
+  };
+
+  meta.maintainers = [ lib.maintainers.nicoo ];
+
+}
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 4bdbe9671e6..d225442773c 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -4,16 +4,9 @@ with lib;
 
 let
 
-  inherit (pkgs) sudo sudo-rs;
-
   cfg = config.security.sudo;
 
-  enableSSHAgentAuth =
-    with config.security;
-    pam.enableSSHAgentAuth && pam.sudo.sshAgentAuth;
-
-  usingMillersSudo = cfg.package.pname == sudo.pname;
-  usingSudoRs = cfg.package.pname == sudo-rs.pname;
+  inherit (pkgs) sudo;
 
   toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
   toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
@@ -37,51 +30,41 @@ in
 
   ###### interface
 
-  options.security.sudo = {
-
-    defaultOptions = mkOption {
-      type = with types; listOf str;
-      default = optional usingMillersSudo "SETENV";
-      defaultText = literalMD ''
-        `[ "SETENV" ]` if using the default `sudo` implementation
-      '';
-      description = mdDoc ''
-        Options used for the default rules, granting `root` and the
-        `wheel` group permission to run any command as any user.
-      '';
-    };
+  options = {
 
-    enable = mkOption {
+    security.sudo.enable = mkOption {
       type = types.bool;
       default = true;
-      description = mdDoc ''
-        Whether to enable the {command}`sudo` command, which
-        allows non-root users to execute commands as root.
-      '';
+      description =
+        lib.mdDoc ''
+          Whether to enable the {command}`sudo` command, which
+          allows non-root users to execute commands as root.
+        '';
     };
 
-    package = mkOption {
+    security.sudo.package = mkOption {
       type = types.package;
       default = pkgs.sudo;
       defaultText = literalExpression "pkgs.sudo";
-      description = mdDoc ''
+      description = lib.mdDoc ''
         Which package to use for `sudo`.
       '';
     };
 
-    wheelNeedsPassword = mkOption {
+    security.sudo.wheelNeedsPassword = mkOption {
       type = types.bool;
       default = true;
-      description = mdDoc ''
-        Whether users of the `wheel` group must
-        provide a password to run commands as super user via {command}`sudo`.
-      '';
+      description =
+        lib.mdDoc ''
+          Whether users of the `wheel` group must
+          provide a password to run commands as super user via {command}`sudo`.
+        '';
       };
 
-    execWheelOnly = mkOption {
+    security.sudo.execWheelOnly = mkOption {
       type = types.bool;
       default = false;
-      description = mdDoc ''
+      description = lib.mdDoc ''
         Only allow members of the `wheel` group to execute sudo by
         setting the executable's permissions accordingly.
         This prevents users that are not members of `wheel` from
@@ -89,18 +72,19 @@ in
       '';
     };
 
-    configFile = mkOption {
+    security.sudo.configFile = mkOption {
       type = types.lines;
       # Note: if syntax errors are detected in this file, the NixOS
       # configuration will fail to build.
-      description = mdDoc ''
-        This string contains the contents of the
-        {file}`sudoers` file.
-      '';
+      description =
+        lib.mdDoc ''
+          This string contains the contents of the
+          {file}`sudoers` file.
+        '';
     };
 
-    extraRules = mkOption {
-      description = mdDoc ''
+    security.sudo.extraRules = mkOption {
+      description = lib.mdDoc ''
         Define specific rules to be in the {file}`sudoers` file.
         More specific rules should come after more general ones in order to
         yield the expected behavior. You can use mkBefore/mkAfter to ensure
@@ -130,7 +114,7 @@ in
         options = {
           users = mkOption {
             type = with types; listOf (either str int);
-            description = mdDoc ''
+            description = lib.mdDoc ''
               The usernames / UIDs this rule should apply for.
             '';
             default = [];
@@ -138,7 +122,7 @@ in
 
           groups = mkOption {
             type = with types; listOf (either str int);
-            description = mdDoc ''
+            description = lib.mdDoc ''
               The groups / GIDs this rule should apply for.
             '';
             default = [];
@@ -147,7 +131,7 @@ in
           host = mkOption {
             type = types.str;
             default = "ALL";
-            description = mdDoc ''
+            description = lib.mdDoc ''
               For what host this rule should apply.
             '';
           };
@@ -155,7 +139,7 @@ in
           runAs = mkOption {
             type = with types; str;
             default = "ALL:ALL";
-            description = mdDoc ''
+            description = lib.mdDoc ''
               Under which user/group the specified command is allowed to run.
 
               A user can be specified using just the username: `"foo"`.
@@ -165,7 +149,7 @@ in
           };
 
           commands = mkOption {
-            description = mdDoc ''
+            description = lib.mdDoc ''
               The commands for which the rule should apply.
             '';
             type = with types; listOf (either str (submodule {
@@ -173,7 +157,7 @@ in
               options = {
                 command = mkOption {
                   type = with types; str;
-                  description = mdDoc ''
+                  description = lib.mdDoc ''
                     A command being either just a path to a binary to allow any arguments,
                     the full command with arguments pre-set or with `""` used as the argument,
                     not allowing arguments to the command at all.
@@ -182,7 +166,7 @@ in
 
                 options = mkOption {
                   type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
-                  description = mdDoc ''
+                  description = lib.mdDoc ''
                     Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
                   '';
                   default = [];
@@ -195,10 +179,10 @@ in
       });
     };
 
-    extraConfig = mkOption {
+    security.sudo.extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = mdDoc ''
+      description = lib.mdDoc ''
         Extra configuration text appended to {file}`sudoers`.
       '';
     };
@@ -208,52 +192,44 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    security.sudo.extraRules =
-      let
-        defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
-          inherit users groups;
-          commands = [ {
-            command = "ALL";
-            options = opts ++ cfg.defaultOptions;
-          } ];
-        } ];
-      in mkMerge [
-        # This is ordered before users' `mkBefore` rules,
-        # so as not to introduce unexpected changes.
-        (mkOrder 400 (defaultRule { users = [ "root" ]; }))
-
-        # This is ordered to show before (most) other rules, but
-        # late-enough for a user to `mkBefore` it.
-        (mkOrder 600 (defaultRule {
-          groups = [ "wheel" ];
-          opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
-        }))
-      ];
-
-    security.sudo.configFile = concatStringsSep "\n" (filter (s: s != "") [
+    assertions = [
+      { assertion = cfg.package.pname != "sudo-rs";
+        message = "The NixOS `sudo` module does not work with `sudo-rs` yet."; }
+    ];
+
+    # We `mkOrder 600` so that the default rule shows up first, but there is
+    # still enough room for a user to `mkBefore` it.
+    security.sudo.extraRules = mkOrder 600 [
+      { groups = [ "wheel" ];
+        commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ];
+      }
+    ];
+
+    security.sudo.configFile =
       ''
         # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
         # or ‘security.sudo.extraRules’ instead.
-      ''
-      (optionalString enableSSHAgentAuth ''
+
         # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
         Defaults env_keep+=SSH_AUTH_SOCK
-      '')
-      (concatStringsSep "\n" (
-        lists.flatten (
-          map (
-            rule: optionals (length rule.commands != 0) [
-              (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
-              (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
-            ]
-          ) cfg.extraRules
-        )
-      ) + "\n")
-      (optionalString (cfg.extraConfig != "") ''
-        # extraConfig
+
+        # "root" is allowed to do anything.
+        root        ALL=(ALL:ALL) SETENV: ALL
+
+        # extraRules
+        ${concatStringsSep "\n" (
+          lists.flatten (
+            map (
+              rule: optionals (length rule.commands != 0) [
+                (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
+                (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
+              ]
+            ) cfg.extraRules
+          )
+        )}
+
         ${cfg.extraConfig}
-      '')
-    ]);
+      '';
 
     security.wrappers = let
       owner = "root";
@@ -265,8 +241,7 @@ in
         source = "${cfg.package.out}/bin/sudo";
         inherit owner group setuid permissions;
       };
-      # sudo-rs does not yet ship a sudoedit (as of v0.2.0)
-      sudoedit = mkIf usingMillersSudo {
+      sudoedit = {
         source = "${cfg.package.out}/bin/sudoedit";
         inherit owner group setuid permissions;
       };
@@ -275,8 +250,6 @@ in
     environment.systemPackages = [ sudo ];
 
     security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
-    security.pam.services.sudo-i = mkIf usingSudoRs
-      { sshAgentAuth = true; usshAuth = true; };
 
     environment.etc.sudoers =
       { source =
@@ -285,12 +258,12 @@ in
             src = pkgs.writeText "sudoers-in" cfg.configFile;
             preferLocalBuild = true;
           }
-          "${cfg.package}/bin/visudo -f $src -c && cp $src $out";
+          # Make sure that the sudoers file is syntactically valid.
+          # (currently disabled - NIXOS-66)
+          "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out";
         mode = "0440";
       };
 
   };
 
-  meta.maintainers = [ lib.maintainers.nicoo ];
-
 }