python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / security / doas.nix
blob4d15ed9a80259bb352a0936dd01a5c864a4d0830
1 { config, lib, pkgs, ... }:
3 with lib;
4 let
5   cfg = config.security.doas;
7   inherit (pkgs) doas;
9   mkUsrString = user: toString user;
11   mkGrpString = group: ":${toString group}";
13   mkOpts = rule: concatStringsSep " " [
14     (optionalString rule.noPass "nopass")
15     (optionalString rule.noLog "nolog")
16     (optionalString rule.persist "persist")
17     (optionalString rule.keepEnv "keepenv")
18     "setenv { SSH_AUTH_SOCK TERMINFO TERMINFO_DIRS ${concatStringsSep " " rule.setEnv} }"
19   ];
21   mkArgs = rule:
22     if (isNull rule.args) then ""
23     else if (length rule.args == 0) then "args"
24     else "args ${concatStringsSep " " rule.args}";
26   mkRule = rule:
27     let
28       opts = mkOpts rule;
30       as = optionalString (!isNull rule.runAs) "as ${rule.runAs}";
32       cmd = optionalString (!isNull rule.cmd) "cmd ${rule.cmd}";
34       args = mkArgs rule;
35     in
36     optionals (length cfg.extraRules > 0) [
37       (
38         optionalString (length rule.users > 0)
39           (map (usr: "permit ${opts} ${mkUsrString usr} ${as} ${cmd} ${args}") rule.users)
40       )
41       (
42         optionalString (length rule.groups > 0)
43           (map (grp: "permit ${opts} ${mkGrpString grp} ${as} ${cmd} ${args}") rule.groups)
44       )
45     ];
49   ###### interface
51   options.security.doas = {
53     enable = mkOption {
54       type = with types; bool;
55       default = false;
56       description = lib.mdDoc ''
57         Whether to enable the {command}`doas` command, which allows
58         non-root users to execute commands as root.
59       '';
60     };
62     wheelNeedsPassword = mkOption {
63       type = with types; bool;
64       default = true;
65       description = lib.mdDoc ''
66         Whether users of the `wheel` group must provide a password to
67         run commands as super user via {command}`doas`.
68       '';
69     };
71     extraRules = mkOption {
72       default = [];
73       description = lib.mdDoc ''
74         Define specific rules to be set in the
75         {file}`/etc/doas.conf` file. More specific rules should
76         come after more general ones in order to yield the expected behavior.
77         You can use `mkBefore` and/or `mkAfter` to ensure
78         this is the case when configuration options are merged.
79       '';
80       example = literalExpression ''
81         [
82           # Allow execution of any command by any user in group doas, requiring
83           # a password and keeping any previously-defined environment variables.
84           { groups = [ "doas" ]; noPass = false; keepEnv = true; }
86           # Allow execution of "/home/root/secret.sh" by user `backup` OR user
87           # `database` OR any member of the group with GID `1006`, without a
88           # password.
89           { users = [ "backup" "database" ]; groups = [ 1006 ];
90             cmd = "/home/root/secret.sh"; noPass = true; }
92           # Allow any member of group `bar` to run `/home/baz/cmd1.sh` as user
93           # `foo` with argument `hello-doas`.
94           { groups = [ "bar" ]; runAs = "foo";
95             cmd = "/home/baz/cmd1.sh"; args = [ "hello-doas" ]; }
97           # Allow any member of group `bar` to run `/home/baz/cmd2.sh` as user
98           # `foo` with no arguments.
99           { groups = [ "bar" ]; runAs = "foo";
100             cmd = "/home/baz/cmd2.sh"; args = [ ]; }
102           # Allow user `abusers` to execute "nano" and unset the value of
103           # SSH_AUTH_SOCK, override the value of ALPHA to 1, and inherit the
104           # value of BETA from the current environment.
105           { users = [ "abusers" ]; cmd = "nano";
106             setEnv = [ "-SSH_AUTH_SOCK" "ALPHA=1" "BETA" ]; }
107         ]
108       '';
109       type = with types; listOf (
110         submodule {
111           options = {
113             noPass = mkOption {
114               type = with types; bool;
115               default = false;
116               description = lib.mdDoc ''
117                 If `true`, the user is not required to enter a
118                 password.
119               '';
120             };
122             noLog = mkOption {
123               type = with types; bool;
124               default = false;
125               description = lib.mdDoc ''
126                 If `true`, successful executions will not be logged
127                 to
128                 {manpage}`syslogd(8)`.
129               '';
130             };
132             persist = mkOption {
133               type = with types; bool;
134               default = false;
135               description = lib.mdDoc ''
136                 If `true`, do not ask for a password again for some
137                 time after the user successfully authenticates.
138               '';
139             };
141             keepEnv = mkOption {
142               type = with types; bool;
143               default = false;
144               description = lib.mdDoc ''
145                 If `true`, environment variables other than those
146                 listed in
147                 {manpage}`doas(1)`
148                 are kept when creating the environment for the new process.
149               '';
150             };
152             setEnv = mkOption {
153               type = with types; listOf str;
154               default = [];
155               description = lib.mdDoc ''
156                 Keep or set the specified variables. Variables may also be
157                 removed with a leading '-' or set using
158                 `variable=value`. If the first character of
159                 `value` is a '$', the value to be set is taken from
160                 the existing environment variable of the indicated name. This
161                 option is processed after the default environment has been
162                 created.
164                 NOTE: All rules have `setenv { SSH_AUTH_SOCK }` by
165                 default. To prevent `SSH_AUTH_SOCK` from being
166                 inherited, add `"-SSH_AUTH_SOCK"` anywhere in this
167                 list.
168               '';
169             };
171             users = mkOption {
172               type = with types; listOf (either str int);
173               default = [];
174               description = lib.mdDoc "The usernames / UIDs this rule should apply for.";
175             };
177             groups = mkOption {
178               type = with types; listOf (either str int);
179               default = [];
180               description = lib.mdDoc "The groups / GIDs this rule should apply for.";
181             };
183             runAs = mkOption {
184               type = with types; nullOr str;
185               default = null;
186               description = lib.mdDoc ''
187                 Which user or group the specified command is allowed to run as.
188                 When set to `null` (the default), all users are
189                 allowed.
191                 A user can be specified using just the username:
192                 `"foo"`. It is also possible to only allow running as
193                 a specific group with `":bar"`.
194               '';
195             };
197             cmd = mkOption {
198               type = with types; nullOr str;
199               default = null;
200               description = lib.mdDoc ''
201                 The command the user is allowed to run. When set to
202                 `null` (the default), all commands are allowed.
204                 NOTE: It is best practice to specify absolute paths. If a
205                 relative path is specified, only a restricted PATH will be
206                 searched.
207               '';
208             };
210             args = mkOption {
211               type = with types; nullOr (listOf str);
212               default = null;
213               description = lib.mdDoc ''
214                 Arguments that must be provided to the command. When set to
215                 `[]`, the command must be run without any arguments.
216               '';
217             };
218           };
219         }
220       );
221     };
223     extraConfig = mkOption {
224       type = with types; lines;
225       default = "";
226       description = lib.mdDoc ''
227         Extra configuration text appended to {file}`doas.conf`.
228       '';
229     };
230   };
233   ###### implementation
235   config = mkIf cfg.enable {
237     security.doas.extraRules = mkOrder 600 [
238       {
239         groups = [ "wheel" ];
240         noPass = !cfg.wheelNeedsPassword;
241       }
242     ];
244     security.wrappers.doas =
245       { setuid = true;
246         owner = "root";
247         group = "root";
248         source = "${doas}/bin/doas";
249       };
251     environment.systemPackages = [
252       doas
253     ];
255     security.pam.services.doas = {
256       allowNullPassword = true;
257       sshAgentAuth = true;
258     };
260     environment.etc."doas.conf" = {
261       source = pkgs.runCommand "doas-conf"
262         {
263           src = pkgs.writeText "doas-conf-in" ''
264             # To modify this file, set the NixOS options
265             # `security.doas.extraRules` or `security.doas.extraConfig`. To
266             # completely replace the contents of this file, use
267             # `environment.etc."doas.conf"`.
269             # "root" is allowed to do anything.
270             permit nopass keepenv root
272             # extraRules
273             ${concatStringsSep "\n" (lists.flatten (map mkRule cfg.extraRules))}
275             # extraConfig
276             ${cfg.extraConfig}
277           '';
278           preferLocalBuild = true;
279         }
280         # Make sure that the doas.conf file is syntactically valid.
281         "${pkgs.buildPackages.doas}/bin/doas -C $src && cp $src $out";
282       mode = "0440";
283     };
285   };
287   meta.maintainers = with maintainers; [ cole-h ];