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