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 (isNull rule.args) then ""
23 else if (length rule.args == 0) then "args"
24 else "args ${concatStringsSep " " rule.args}";
30 as = optionalString (!isNull rule.runAs) "as ${rule.runAs}";
32 cmd = optionalString (!isNull rule.cmd) "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;
56 description = lib.mdDoc ''
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;
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`.
71 extraRules = mkOption {
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.
80 example = literalExpression ''
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
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" ]; }
109 type = with types; listOf (
114 type = with types; bool;
116 description = lib.mdDoc ''
117 If `true`, the user is not required to enter a
123 type = with types; bool;
125 description = lib.mdDoc ''
126 If `true`, successful executions will not be logged
128 {manpage}`syslogd(8)`.
133 type = with types; bool;
135 description = lib.mdDoc ''
136 If `true`, do not ask for a password again for some
137 time after the user successfully authenticates.
142 type = with types; bool;
144 description = lib.mdDoc ''
145 If `true`, environment variables other than those
148 are kept when creating the environment for the new process.
153 type = with types; listOf str;
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
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
172 type = with types; listOf (either str int);
174 description = lib.mdDoc "The usernames / UIDs this rule should apply for.";
178 type = with types; listOf (either str int);
180 description = lib.mdDoc "The groups / GIDs this rule should apply for.";
184 type = with types; nullOr str;
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
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"`.
198 type = with types; nullOr str;
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
211 type = with types; nullOr (listOf str);
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.
223 extraConfig = mkOption {
224 type = with types; lines;
226 description = lib.mdDoc ''
227 Extra configuration text appended to {file}`doas.conf`.
233 ###### implementation
235 config = mkIf cfg.enable {
237 security.doas.extraRules = mkOrder 600 [
239 groups = [ "wheel" ];
240 noPass = !cfg.wheelNeedsPassword;
244 security.wrappers.doas =
248 source = "${doas}/bin/doas";
251 environment.systemPackages = [
255 security.pam.services.doas = {
256 allowNullPassword = true;
260 environment.etc."doas.conf" = {
261 source = pkgs.runCommand "doas-conf"
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
273 ${concatStringsSep "\n" (lists.flatten (map mkRule cfg.extraRules))}
278 preferLocalBuild = true;
280 # Make sure that the doas.conf file is syntactically valid.
281 "${pkgs.buildPackages.doas}/bin/doas -C $src && cp $src $out";
287 meta.maintainers = with maintainers; [ cole-h ];