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