at 25.11-pre 10 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 cfg = config.security.sudo; 10 11 toUserString = user: if (lib.isInt user) then "#${toString user}" else "${user}"; 12 toGroupString = group: if (lib.isInt group) then "%#${toString group}" else "%${group}"; 13 14 toCommandOptionsString = 15 options: "${lib.concatStringsSep ":" options}${lib.optionalString (lib.length options != 0) ":"} "; 16 17 toCommandsString = 18 commands: 19 lib.concatStringsSep ", " ( 20 map ( 21 command: 22 if (lib.isString command) then 23 command 24 else 25 "${toCommandOptionsString command.options}${command.command}" 26 ) commands 27 ); 28 29in 30 31{ 32 33 ###### interface 34 35 options.security.sudo = { 36 37 defaultOptions = lib.mkOption { 38 type = with lib.types; listOf str; 39 default = [ "SETENV" ]; 40 description = '' 41 Options used for the default rules, granting `root` and the 42 `wheel` group permission to run any command as any user. 43 ''; 44 }; 45 46 enable = lib.mkOption { 47 type = lib.types.bool; 48 default = true; 49 description = '' 50 Whether to enable the {command}`sudo` command, which 51 allows non-root users to execute commands as root. 52 ''; 53 }; 54 55 package = lib.mkPackageOption pkgs "sudo" { }; 56 57 wheelNeedsPassword = lib.mkOption { 58 type = lib.types.bool; 59 default = true; 60 description = '' 61 Whether users of the `wheel` group must 62 provide a password to run commands as super user via {command}`sudo`. 63 ''; 64 }; 65 66 execWheelOnly = lib.mkOption { 67 type = lib.types.bool; 68 default = false; 69 description = '' 70 Only allow members of the `wheel` group to execute sudo by 71 setting the executable's permissions accordingly. 72 This prevents users that are not members of `wheel` from 73 exploiting vulnerabilities in sudo such as CVE-2021-3156. 74 ''; 75 }; 76 77 configFile = lib.mkOption { 78 type = lib.types.lines; 79 # Note: if syntax errors are detected in this file, the NixOS 80 # configuration will fail to build. 81 description = '' 82 This string contains the contents of the 83 {file}`sudoers` file. 84 ''; 85 }; 86 87 extraRules = lib.mkOption { 88 description = '' 89 Define specific rules to be in the {file}`sudoers` file. 90 More specific rules should come after more general ones in order to 91 yield the expected behavior. You can use mkBefore/mkAfter to ensure 92 this is the case when configuration options are merged. 93 ''; 94 default = [ ]; 95 example = lib.literalExpression '' 96 [ 97 # Allow execution of any command by all users in group sudo, 98 # requiring a password. 99 { groups = [ "sudo" ]; commands = [ "ALL" ]; } 100 101 # Allow execution of "/home/root/secret.sh" by user `backup`, `database` 102 # and the group with GID `1006` without a password. 103 { users = [ "backup" "database" ]; groups = [ 1006 ]; 104 commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; } 105 106 # Allow all users of group `bar` to run two executables as user `foo` 107 # with arguments being pre-set. 108 { groups = [ "bar" ]; runAs = "foo"; 109 commands = 110 [ "/home/baz/cmd1.sh hello-sudo" 111 { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; } 112 ] 113 ''; 114 type = 115 with lib.types; 116 listOf (submodule { 117 options = { 118 users = lib.mkOption { 119 type = with types; listOf (either str int); 120 description = '' 121 The usernames / UIDs this rule should apply for. 122 ''; 123 default = [ ]; 124 }; 125 126 groups = lib.mkOption { 127 type = with types; listOf (either str int); 128 description = '' 129 The groups / GIDs this rule should apply for. 130 ''; 131 default = [ ]; 132 }; 133 134 host = lib.mkOption { 135 type = types.str; 136 default = "ALL"; 137 description = '' 138 For what host this rule should apply. 139 ''; 140 }; 141 142 runAs = lib.mkOption { 143 type = with types; str; 144 default = "ALL:ALL"; 145 description = '' 146 Under which user/group the specified command is allowed to run. 147 148 A user can be specified using just the username: `"foo"`. 149 It is also possible to specify a user/group combination using `"foo:bar"` 150 or to only allow running as a specific group with `":bar"`. 151 ''; 152 }; 153 154 commands = lib.mkOption { 155 description = '' 156 The commands for which the rule should apply. 157 ''; 158 type = 159 with types; 160 listOf ( 161 either str (submodule { 162 163 options = { 164 command = lib.mkOption { 165 type = with types; str; 166 description = '' 167 A command being either just a path to a binary to allow any arguments, 168 the full command with arguments pre-set or with `""` used as the argument, 169 not allowing arguments to the command at all. 170 ''; 171 }; 172 173 options = lib.mkOption { 174 type = 175 with types; 176 listOf (enum [ 177 "NOPASSWD" 178 "PASSWD" 179 "NOEXEC" 180 "EXEC" 181 "SETENV" 182 "NOSETENV" 183 "LOG_INPUT" 184 "NOLOG_INPUT" 185 "LOG_OUTPUT" 186 "NOLOG_OUTPUT" 187 "MAIL" 188 "NOMAIL" 189 "FOLLOW" 190 "NOFLLOW" 191 "INTERCEPT" 192 "NOINTERCEPT" 193 ]); 194 description = '' 195 Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/docs/man/1.9.15/sudoers.man/#Tag_Spec). 196 ''; 197 default = [ ]; 198 }; 199 }; 200 201 }) 202 ); 203 }; 204 }; 205 }); 206 }; 207 208 extraConfig = lib.mkOption { 209 type = lib.types.lines; 210 default = ""; 211 description = '' 212 Extra configuration text appended to {file}`sudoers`. 213 ''; 214 }; 215 }; 216 217 ###### implementation 218 219 config = lib.mkIf cfg.enable { 220 assertions = [ 221 { 222 assertion = cfg.package.pname != "sudo-rs"; 223 message = '' 224 NixOS' `sudo` module does not support `sudo-rs`; see `security.sudo-rs` instead. 225 ''; 226 } 227 ]; 228 229 security.sudo.extraRules = 230 let 231 defaultRule = 232 { 233 users ? [ ], 234 groups ? [ ], 235 opts ? [ ], 236 }: 237 [ 238 { 239 inherit users groups; 240 commands = [ 241 { 242 command = "ALL"; 243 options = opts ++ cfg.defaultOptions; 244 } 245 ]; 246 } 247 ]; 248 in 249 lib.mkMerge [ 250 # This is ordered before users' `mkBefore` rules, 251 # so as not to introduce unexpected changes. 252 (lib.mkOrder 400 (defaultRule { 253 users = [ "root" ]; 254 })) 255 256 # This is ordered to show before (most) other rules, but 257 # late-enough for a user to `mkBefore` it. 258 (lib.mkOrder 600 (defaultRule { 259 groups = [ "wheel" ]; 260 opts = (lib.optional (!cfg.wheelNeedsPassword) "NOPASSWD"); 261 })) 262 ]; 263 264 security.sudo.configFile = lib.concatStringsSep "\n" ( 265 lib.filter (s: s != "") [ 266 '' 267 # Don't edit this file. Set the NixOS options security.sudo.configFile 268 # or security.sudo.extraRules instead. 269 '' 270 (lib.pipe cfg.extraRules [ 271 (lib.filter (rule: lib.length rule.commands != 0)) 272 (map (rule: [ 273 (map ( 274 user: "${toUserString user} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}" 275 ) rule.users) 276 (map ( 277 group: "${toGroupString group} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}" 278 ) rule.groups) 279 ])) 280 lib.flatten 281 (lib.concatStringsSep "\n") 282 ]) 283 "\n" 284 (lib.optionalString (cfg.extraConfig != "") '' 285 # extraConfig 286 ${cfg.extraConfig} 287 '') 288 ] 289 ); 290 291 security.wrappers = 292 let 293 owner = "root"; 294 group = if cfg.execWheelOnly then "wheel" else "root"; 295 setuid = true; 296 permissions = if cfg.execWheelOnly then "u+rx,g+x" else "u+rx,g+x,o+x"; 297 in 298 { 299 sudo = { 300 source = "${cfg.package.out}/bin/sudo"; 301 inherit 302 owner 303 group 304 setuid 305 permissions 306 ; 307 }; 308 sudoedit = { 309 source = "${cfg.package.out}/bin/sudoedit"; 310 inherit 311 owner 312 group 313 setuid 314 permissions 315 ; 316 }; 317 }; 318 319 environment.systemPackages = [ cfg.package ]; 320 321 security.pam.services.sudo = { 322 sshAgentAuth = true; 323 usshAuth = true; 324 }; 325 326 environment.etc.sudoers = { 327 source = 328 pkgs.runCommand "sudoers" 329 { 330 src = pkgs.writeText "sudoers-in" cfg.configFile; 331 preferLocalBuild = true; 332 } 333 # Make sure that the sudoers file is syntactically valid. 334 # (currently disabled - NIXOS-66) 335 "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out"; 336 mode = "0440"; 337 }; 338 339 }; 340 341}