sq: 0.48.3 -> 0.48.4 (#359055)
[NixPkgs.git] / nixos / modules / security / sudo-rs.nix
blobb920015c491134a993e0f5a4ea42bea2aac8d185
1 { config, lib, pkgs, ... }:
2 let
4   cfg = config.security.sudo-rs;
6   toUserString = user: if (lib.isInt user) then "#${toString user}" else "${user}";
7   toGroupString = group: if (lib.isInt group) then "%#${toString group}" else "%${group}";
9   toCommandOptionsString = options:
10     "${lib.concatStringsSep ":" options}${lib.optionalString (lib.length options != 0) ":"} ";
12   toCommandsString = commands:
13     lib.concatStringsSep ", " (
14       map (command:
15         if (lib.isString command) then
16           command
17         else
18           "${toCommandOptionsString command.options}${command.command}"
19       ) commands
20     );
26   ###### interface
28   options.security.sudo-rs = {
30     defaultOptions = lib.mkOption {
31       type = with lib.types; listOf str;
32       default = [];
33       description = ''
34         Options used for the default rules, granting `root` and the
35         `wheel` group permission to run any command as any user.
36       '';
37     };
39     enable = lib.mkEnableOption ''
40       a memory-safe implementation of the {command}`sudo` command,
41       which allows non-root users to execute commands as root
42     '';
44     package = lib.mkPackageOption pkgs "sudo-rs" { };
46     wheelNeedsPassword = lib.mkOption {
47       type = lib.types.bool;
48       default = true;
49       description = ''
50         Whether users of the `wheel` group must
51         provide a password to run commands as super user via {command}`sudo`.
52       '';
53       };
55     execWheelOnly = lib.mkOption {
56       type = lib.types.bool;
57       default = false;
58       description = ''
59         Only allow members of the `wheel` group to execute sudo by
60         setting the executable's permissions accordingly.
61         This prevents users that are not members of `wheel` from
62         exploiting vulnerabilities in sudo such as CVE-2021-3156.
63       '';
64     };
66     configFile = lib.mkOption {
67       type = lib.types.lines;
68       # Note: if syntax errors are detected in this file, the NixOS
69       # configuration will fail to build.
70       description = ''
71         This string contains the contents of the
72         {file}`sudoers` file.
73       '';
74     };
76     extraRules = lib.mkOption {
77       description = ''
78         Define specific rules to be in the {file}`sudoers` file.
79         More specific rules should come after more general ones in order to
80         yield the expected behavior. You can use `lib.mkBefore`/`lib.mkAfter` to ensure
81         this is the case when configuration options are merged.
82       '';
83       default = [];
84       example = lib.literalExpression ''
85         [
86           # Allow execution of any command by all users in group sudo,
87           # requiring a password.
88           { groups = [ "sudo" ]; commands = [ "ALL" ]; }
90           # Allow execution of "/home/root/secret.sh" by user `backup`, `database`
91           # and the group with GID `1006` without a password.
92           { users = [ "backup" "database" ]; groups = [ 1006 ];
93             commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; }
95           # Allow all users of group `bar` to run two executables as user `foo`
96           # with arguments being pre-set.
97           { groups = [ "bar" ]; runAs = "foo";
98             commands =
99               [ "/home/baz/cmd1.sh hello-sudo"
100                   { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; }
101         ]
102       '';
103       type = with lib.types; listOf (submodule {
104         options = {
105           users = lib.mkOption {
106             type = with lib.types; listOf (either str int);
107             description = ''
108               The usernames / UIDs this rule should apply for.
109             '';
110             default = [];
111           };
113           groups = lib.mkOption {
114             type = with lib.types; listOf (either str int);
115             description = ''
116               The groups / GIDs this rule should apply for.
117             '';
118             default = [];
119           };
121           host = lib.mkOption {
122             type = lib.types.str;
123             default = "ALL";
124             description = ''
125               For what host this rule should apply.
126             '';
127           };
129           runAs = lib.mkOption {
130             type = with lib.types; str;
131             default = "ALL:ALL";
132             description = ''
133               Under which user/group the specified command is allowed to run.
135               A user can be specified using just the username: `"foo"`.
136               It is also possible to specify a user/group combination using `"foo:bar"`
137               or to only allow running as a specific group with `":bar"`.
138             '';
139           };
141           commands = lib.mkOption {
142             description = ''
143               The commands for which the rule should apply.
144             '';
145             type = with lib.types; listOf (either str (submodule {
147               options = {
148                 command = lib.mkOption {
149                   type = with lib.types; str;
150                   description = ''
151                     A command being either just a path to a binary to allow any arguments,
152                     the full command with arguments pre-set or with `""` used as the argument,
153                     not allowing arguments to the command at all.
154                   '';
155                 };
157                 options = lib.mkOption {
158                   type = with lib.types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
159                   description = ''
160                     Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
161                   '';
162                   default = [];
163                 };
164               };
166             }));
167           };
168         };
169       });
170     };
172     extraConfig = lib.mkOption {
173       type = lib.types.lines;
174       default = "";
175       description = ''
176         Extra configuration text appended to {file}`sudoers`.
177       '';
178     };
179   };
182   ###### implementation
184   config = lib.mkIf cfg.enable {
185     assertions = [ {
186       assertion = ! config.security.sudo.enable;
187       message = "`security.sudo` and `security.sudo-rs` cannot both be enabled";
188     }];
189     security.sudo.enable = lib.mkDefault false;
191     security.sudo-rs.extraRules =
192       let
193         defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
194           inherit users groups;
195           commands = [ {
196             command = "ALL";
197             options = opts ++ cfg.defaultOptions;
198           } ];
199         } ];
200       in lib.mkMerge [
201         # This is ordered before users' `lib.mkBefore` rules,
202         # so as not to introduce unexpected changes.
203         (lib.mkOrder 400 (defaultRule { users = [ "root" ]; }))
205         # This is ordered to show before (most) other rules, but
206         # late-enough for a user to `lib.mkBefore` it.
207         (lib.mkOrder 600 (defaultRule {
208           groups = [ "wheel" ];
209           opts = (lib.optional (!cfg.wheelNeedsPassword) "NOPASSWD");
210         }))
211       ];
213     security.sudo-rs.configFile = lib.concatStringsSep "\n" (lib.filter (s: s != "") [
214       ''
215         # Don't edit this file. Set the NixOS options ‘security.sudo-rs.configFile’
216         # or ‘security.sudo-rs.extraRules’ instead.
217       ''
218       (lib.pipe cfg.extraRules [
219         (lib.filter (rule: lib.length rule.commands != 0))
220         (map (rule: [
221           (map (user: "${toUserString user}     ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.users)
222           (map (group: "${toGroupString group}  ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.groups)
223         ]))
224         lib.flatten
225         (lib.concatStringsSep "\n")
226       ])
227       "\n"
228       (lib.optionalString (cfg.extraConfig != "") ''
229         # extraConfig
230         ${cfg.extraConfig}
231       '')
232     ]);
234     security.wrappers = let
235       owner = "root";
236       group = if cfg.execWheelOnly then "wheel" else "root";
237       setuid = true;
238       permissions = if cfg.execWheelOnly then "u+rx,g+x" else "u+rx,g+x,o+x";
239     in {
240       sudo = {
241         source = "${cfg.package.out}/bin/sudo";
242         inherit owner group setuid permissions;
243       };
244     };
246     environment.systemPackages = [ cfg.package ];
248     security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
249     security.pam.services.sudo-i = { sshAgentAuth = true; usshAuth = true; };
251     environment.etc.sudoers =
252       { source =
253           pkgs.runCommand "sudoers"
254           {
255             src = pkgs.writeText "sudoers-in" cfg.configFile;
256             preferLocalBuild = true;
257           }
258           "${pkgs.buildPackages.sudo-rs}/bin/visudo -f $src -c && cp $src $out";
259         mode = "0440";
260       };
262   };
264   meta.maintainers = [ lib.maintainers.nicoo ];