1 { config, lib, pkgs, ... }:
5 cfg = config.security.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} }"
22 if (rule.args == null) then ""
23 else if (length rule.args == 0) then "args"
24 else "args ${concatStringsSep " " rule.args}";
30 as = optionalString (rule.runAs != null) "as ${rule.runAs}";
32 cmd = optionalString (rule.cmd != null) "cmd ${rule.cmd}";
36 optionals (length cfg.extraRules > 0) [
38 optionalString (length rule.users > 0)
39 (map (usr: "permit ${opts} ${mkUsrString usr} ${as} ${cmd} ${args}") rule.users)
42 optionalString (length rule.groups > 0)
43 (map (grp: "permit ${opts} ${mkGrpString grp} ${as} ${cmd} ${args}") rule.groups)
51 options.security.doas = {
54 type = with types; bool;
57 Whether to enable the {command}`doas` command, which allows
58 non-root users to execute commands as root.
62 wheelNeedsPassword = mkOption {
63 type = with types; bool;
66 Whether users of the `wheel` group must provide a password to
67 run commands as super user via {command}`doas`.
71 extraRules = mkOption {
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. Be aware that
79 this option cannot be used to override the behaviour allowing
80 passwordless operation for root.
82 example = literalExpression ''
84 # Allow execution of any command by any user in group doas, requiring
85 # a password and keeping any previously-defined environment variables.
86 { groups = [ "doas" ]; noPass = false; keepEnv = true; }
88 # Allow execution of "/home/root/secret.sh" by user `backup` OR user
89 # `database` OR any member of the group with GID `1006`, without a
91 { users = [ "backup" "database" ]; groups = [ 1006 ];
92 cmd = "/home/root/secret.sh"; noPass = true; }
94 # Allow any member of group `bar` to run `/home/baz/cmd1.sh` as user
95 # `foo` with argument `hello-doas`.
96 { groups = [ "bar" ]; runAs = "foo";
97 cmd = "/home/baz/cmd1.sh"; args = [ "hello-doas" ]; }
99 # Allow any member of group `bar` to run `/home/baz/cmd2.sh` as user
100 # `foo` with no arguments.
101 { groups = [ "bar" ]; runAs = "foo";
102 cmd = "/home/baz/cmd2.sh"; args = [ ]; }
104 # Allow user `abusers` to execute "nano" and unset the value of
105 # SSH_AUTH_SOCK, override the value of ALPHA to 1, and inherit the
106 # value of BETA from the current environment.
107 { users = [ "abusers" ]; cmd = "nano";
108 setEnv = [ "-SSH_AUTH_SOCK" "ALPHA=1" "BETA" ]; }
111 type = with types; listOf (
116 type = with types; bool;
119 If `true`, the user is not required to enter a
125 type = with types; bool;
128 If `true`, successful executions will not be logged
130 {manpage}`syslogd(8)`.
135 type = with types; bool;
138 If `true`, do not ask for a password again for some
139 time after the user successfully authenticates.
144 type = with types; bool;
147 If `true`, environment variables other than those
150 are kept when creating the environment for the new process.
155 type = with types; listOf str;
158 Keep or set the specified variables. Variables may also be
159 removed with a leading '-' or set using
160 `variable=value`. If the first character of
161 `value` is a '$', the value to be set is taken from
162 the existing environment variable of the indicated name. This
163 option is processed after the default environment has been
166 NOTE: All rules have `setenv { SSH_AUTH_SOCK }` by
167 default. To prevent `SSH_AUTH_SOCK` from being
168 inherited, add `"-SSH_AUTH_SOCK"` anywhere in this
174 type = with types; listOf (either str int);
176 description = "The usernames / UIDs this rule should apply for.";
180 type = with types; listOf (either str int);
182 description = "The groups / GIDs this rule should apply for.";
186 type = with types; nullOr str;
189 Which user or group the specified command is allowed to run as.
190 When set to `null` (the default), all users are
193 A user can be specified using just the username:
194 `"foo"`. It is also possible to only allow running as
195 a specific group with `":bar"`.
200 type = with types; nullOr str;
203 The command the user is allowed to run. When set to
204 `null` (the default), all commands are allowed.
206 NOTE: It is best practice to specify absolute paths. If a
207 relative path is specified, only a restricted PATH will be
213 type = with types; nullOr (listOf str);
216 Arguments that must be provided to the command. When set to
217 `[]`, the command must be run without any arguments.
225 extraConfig = mkOption {
226 type = with types; lines;
229 Extra configuration text appended to {file}`doas.conf`. Be aware that
230 this option cannot be used to override the behaviour allowing
231 passwordless operation for root.
237 ###### implementation
239 config = mkIf cfg.enable {
241 security.doas.extraRules = mkOrder 600 [
243 groups = [ "wheel" ];
244 noPass = !cfg.wheelNeedsPassword;
248 security.wrappers.doas =
252 source = "${doas}/bin/doas";
255 environment.systemPackages = [
259 security.pam.services.doas = {
260 allowNullPassword = true;
264 environment.etc."doas.conf" = {
265 source = pkgs.runCommand "doas-conf"
267 src = pkgs.writeText "doas-conf-in" ''
268 # To modify this file, set the NixOS options
269 # `security.doas.extraRules` or `security.doas.extraConfig`. To
270 # completely replace the contents of this file, use
271 # `environment.etc."doas.conf"`.
274 ${concatStringsSep "\n" (lists.flatten (map mkRule cfg.extraRules))}
279 # "root" is allowed to do anything.
280 permit nopass keepenv root
282 preferLocalBuild = true;
284 # Make sure that the doas.conf file is syntactically valid.
285 "${pkgs.buildPackages.doas}/bin/doas -C $src && cp $src $out";
291 meta.maintainers = with maintainers; [ cole-h ];