at 21.11-pre 7.0 kB view raw
1{ config, lib, pkgs, ... }: 2let 3 4 inherit (config.security) wrapperDir wrappers; 5 6 parentWrapperDir = dirOf wrapperDir; 7 8 programs = 9 (lib.mapAttrsToList 10 (n: v: (if v ? program then v else v // {program=n;})) 11 wrappers); 12 13 securityWrapper = pkgs.callPackage ./wrapper.nix { 14 inherit parentWrapperDir; 15 }; 16 17 ###### Activation script for the setcap wrappers 18 mkSetcapProgram = 19 { program 20 , capabilities 21 , source 22 , owner ? "nobody" 23 , group ? "nogroup" 24 , permissions ? "u+rx,g+x,o+x" 25 , ... 26 }: 27 assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3"); 28 '' 29 cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program} 30 echo -n "${source}" > $wrapperDir/${program}.real 31 32 # Prevent races 33 chmod 0000 $wrapperDir/${program} 34 chown ${owner}.${group} $wrapperDir/${program} 35 36 # Set desired capabilities on the file plus cap_setpcap so 37 # the wrapper program can elevate the capabilities set on 38 # its file into the Ambient set. 39 ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program} 40 41 # Set the executable bit 42 chmod ${permissions} $wrapperDir/${program} 43 ''; 44 45 ###### Activation script for the setuid wrappers 46 mkSetuidProgram = 47 { program 48 , source 49 , owner ? "nobody" 50 , group ? "nogroup" 51 , setuid ? false 52 , setgid ? false 53 , permissions ? "u+rx,g+x,o+x" 54 , ... 55 }: 56 '' 57 cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program} 58 echo -n "${source}" > $wrapperDir/${program}.real 59 60 # Prevent races 61 chmod 0000 $wrapperDir/${program} 62 chown ${owner}.${group} $wrapperDir/${program} 63 64 chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program} 65 ''; 66 67 mkWrappedPrograms = 68 builtins.map 69 (s: if (s ? capabilities) 70 then mkSetcapProgram 71 ({ owner = "root"; 72 group = "root"; 73 } // s) 74 else if 75 (s ? setuid && s.setuid) || 76 (s ? setgid && s.setgid) || 77 (s ? permissions) 78 then mkSetuidProgram s 79 else mkSetuidProgram 80 ({ owner = "root"; 81 group = "root"; 82 setuid = true; 83 setgid = false; 84 permissions = "u+rx,g+x,o+x"; 85 } // s) 86 ) programs; 87in 88{ 89 imports = [ 90 (lib.mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead") 91 (lib.mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead") 92 ]; 93 94 ###### interface 95 96 options = { 97 security.wrappers = lib.mkOption { 98 type = lib.types.attrs; 99 default = {}; 100 example = lib.literalExample 101 '' 102 { sendmail.source = "/nix/store/.../bin/sendmail"; 103 ping = { 104 source = "${pkgs.iputils.out}/bin/ping"; 105 owner = "nobody"; 106 group = "nogroup"; 107 capabilities = "cap_net_raw+ep"; 108 }; 109 } 110 ''; 111 description = '' 112 This option allows the ownership and permissions on the setuid 113 wrappers for specific programs to be overridden from the 114 default (setuid root, but not setgid root). 115 116 <note> 117 <para>The sub-attribute <literal>source</literal> is mandatory, 118 it must be the absolute path to the program to be wrapped. 119 </para> 120 121 <para>The sub-attribute <literal>program</literal> is optional and 122 can give the wrapper program a new name. The default name is the same 123 as the attribute name itself.</para> 124 125 <para>Additionally, this option can set capabilities on a 126 wrapper program that propagates those capabilities down to the 127 wrapped, real program.</para> 128 129 <para>NOTE: cap_setpcap, which is required for the wrapper 130 program to be able to raise caps into the Ambient set is NOT 131 raised to the Ambient set so that the real program cannot 132 modify its own capabilities!! This may be too restrictive for 133 cases in which the real program needs cap_setpcap but it at 134 least leans on the side security paranoid vs. too 135 relaxed.</para> 136 </note> 137 ''; 138 }; 139 140 security.wrapperDir = lib.mkOption { 141 type = lib.types.path; 142 default = "/run/wrappers/bin"; 143 internal = true; 144 description = '' 145 This option defines the path to the wrapper programs. It 146 should not be overriden. 147 ''; 148 }; 149 }; 150 151 ###### implementation 152 config = { 153 154 security.wrappers = { 155 # These are mount related wrappers that require the +s permission. 156 fusermount.source = "${pkgs.fuse}/bin/fusermount"; 157 fusermount3.source = "${pkgs.fuse3}/bin/fusermount3"; 158 mount.source = "${lib.getBin pkgs.util-linux}/bin/mount"; 159 umount.source = "${lib.getBin pkgs.util-linux}/bin/umount"; 160 }; 161 162 boot.specialFileSystems.${parentWrapperDir} = { 163 fsType = "tmpfs"; 164 options = [ "nodev" "mode=755" ]; 165 }; 166 167 # Make sure our wrapperDir exports to the PATH env variable when 168 # initializing the shell 169 environment.extraInit = '' 170 # Wrappers override other bin directories. 171 export PATH="${wrapperDir}:$PATH" 172 ''; 173 174 security.apparmor.includes."nixos/security.wrappers" = '' 175 include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [ 176 securityWrapper 177 pkgs.stdenv.cc.cc 178 pkgs.stdenv.cc.libc 179 ]}" 180 ''; 181 182 ###### setcap activation script 183 system.activationScripts.wrappers = 184 lib.stringAfter [ "specialfs" "users" ] 185 '' 186 # Look in the system path and in the default profile for 187 # programs to be wrapped. 188 WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin 189 190 chmod 755 "${parentWrapperDir}" 191 192 # We want to place the tmpdirs for the wrappers to the parent dir. 193 wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) 194 chmod a+rx $wrapperDir 195 196 ${lib.concatStringsSep "\n" mkWrappedPrograms} 197 198 if [ -L ${wrapperDir} ]; then 199 # Atomically replace the symlink 200 # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ 201 old=$(readlink -f ${wrapperDir}) 202 if [ -e ${wrapperDir}-tmp ]; then 203 rm --force --recursive ${wrapperDir}-tmp 204 fi 205 ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp 206 mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir} 207 rm --force --recursive $old 208 else 209 # For initial setup 210 ln --symbolic $wrapperDir ${wrapperDir} 211 fi 212 ''; 213 }; 214}