at 18.09-beta 7.7 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 $CC -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 = { 159 fusermount.source = "${pkgs.fuse}/bin/fusermount"; 160 fusermount3.source = "${pkgs.fuse3}/bin/fusermount3"; 161 }; 162 163 boot.specialFileSystems.${parentWrapperDir} = { 164 fsType = "tmpfs"; 165 options = [ "nodev" ]; 166 }; 167 168 # Make sure our wrapperDir exports to the PATH env variable when 169 # initializing the shell 170 environment.extraInit = '' 171 # Wrappers override other bin directories. 172 export PATH="${wrapperDir}:$PATH" 173 ''; 174 175 ###### setcap activation script 176 system.activationScripts.wrappers = 177 lib.stringAfter [ "specialfs" "users" ] 178 '' 179 # Look in the system path and in the default profile for 180 # programs to be wrapped. 181 WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin 182 183 # Remove the old /var/setuid-wrappers path from the system... 184 # 185 # TODO: this is only necessary for upgrades 16.09 => 17.x; 186 # this conditional removal block needs to be removed after 187 # the release. 188 if [ -d /var/setuid-wrappers ]; then 189 rm -rf /var/setuid-wrappers 190 ln -s /run/wrappers/bin /var/setuid-wrappers 191 fi 192 193 # Remove the old /run/setuid-wrappers-dir path from the 194 # system as well... 195 # 196 # TODO: this is only necessary for upgrades 16.09 => 17.x; 197 # this conditional removal block needs to be removed after 198 # the release. 199 if [ -d /run/setuid-wrapper-dirs ]; then 200 rm -rf /run/setuid-wrapper-dirs 201 ln -s /run/wrappers/bin /run/setuid-wrapper-dirs 202 fi 203 204 # TODO: this is only necessary for upgrades 16.09 => 17.x; 205 # this conditional removal block needs to be removed after 206 # the release. 207 if readlink -f /run/booted-system | grep nixos-17 > /dev/null; then 208 rm -rf /run/setuid-wrapper-dirs 209 rm -rf /var/setuid-wrappers 210 fi 211 212 # We want to place the tmpdirs for the wrappers to the parent dir. 213 wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) 214 chmod a+rx $wrapperDir 215 216 ${lib.concatStringsSep "\n" mkWrappedPrograms} 217 218 if [ -L ${wrapperDir} ]; then 219 # Atomically replace the symlink 220 # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ 221 old=$(readlink -f ${wrapperDir}) 222 ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp 223 mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir} 224 rm --force --recursive $old 225 else 226 # For initial setup 227 ln --symbolic $wrapperDir ${wrapperDir} 228 fi 229 ''; 230 }; 231}