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