nixos-config/lib/sandbox.nix

197 lines
7.1 KiB
Nix
Raw Permalink Normal View History

2021-09-04 18:18:25 +00:00
{
stdenv, lib
2021-09-06 16:10:14 +00:00
, runCommandLocal, writeShellScriptBin, makeDesktopItem
2021-09-04 18:18:25 +00:00
, bubblewrap, coreutils, glibc, pkgsi686Linux
}:
2021-09-06 19:15:24 +00:00
{
2022-04-26 15:14:08 +00:00
name # Name of the sandboxed package, e.g. "package-wrapped"
, launchScriptName # Name of the script that will be placed in $out/bin
, binary # The binary to execute inside the sandbox
, extraArgs ? [] # Extra arguments to add to the argv
, desktopFileAttributes ? {} # Arguments to apply to the desktop file
, preDesktopFilePhase ? "" # Code to run during the install phase before building the desktop file
, enableDesktopFile ? false # Whether to build a desktop file
, copyIntoSandbox ? {} # Source -> destination relative to $out
2021-09-04 18:18:25 +00:00
, unshareUser ? true
, unshareIpc ? true
, unsharePid ? true
, unshareNet ? false
, unshareUts ? true
, unshareCgroup ? true
2022-04-26 15:14:08 +00:00
, dieWithParent ? true # Should bubblewrap exit when the parents exits
, mountInHome ? [] # Files, directories to mount inside the sandbox
, chdirTo ? "\"$(pwd)\"" # What should be the CWD when entering the sandbox
, additionalBlacklist ? [] # Root directories that should not be mounted
, additionalMounts ? [] # Files, directories outside the home directory to mount inside the sandbox
, extraEnv ? {} # Extra environment variables to set inside the sandbox
, enableAlsa ? true # Mount ALSA files from /etc/
, enableSudo ? true # Mount sudo files from /etc/
, enableShells ? true # Mount shell related files from /etc/
, enableNix ? true # Mount nix related files from /etc/
, enableOsInfo ? true # Mount /etc/{machine-id,os-release}
2021-09-04 18:18:25 +00:00
}:
let
2022-04-26 15:14:08 +00:00
mountHome = mountInHome == [];
2021-09-04 18:18:25 +00:00
etcBindFlags = let
files = [
# Users, Groups, NSS
"passwd"
"group"
"shadow"
"hosts"
"resolv.conf"
"nsswitch.conf"
# User profiles
"profiles"
# Time
"localtime"
"zoneinfo"
# PAM
"pam.d"
# Fonts
"fonts"
2022-04-26 15:14:08 +00:00
]
++ (lib.optionals enableNix [ "static" "nix" ])
++ (lib.optionals enableShells [ "bashrc" "zshenv" "zshrc" "zinputrc" "zprofile" ])
++ (lib.optionals enableSudo [ "login.defs" "sudoers" "sudoers.d" ])
++ (lib.optionals enableAlsa [ "alsa" "asound.conf" ])
++ (lib.optionals enableOsInfo [ "machine-id" "os-release" ])
# Only add SSL stuff when we have networking enabled
++ (lib.optionals (!unshareNet) [ "ssl/certs" "pki" ]);
in builtins.concatStringsSep "\n " (map (file: "--ro-bind-try /etc/${file} /etc/${file}") files);
2021-09-04 18:18:25 +00:00
# Create this on the fly instead of linking from /nix
# The container might have to modify it and re-run ldconfig if there are
# issues running some binary with LD_LIBRARY_PATH
createLdConfCache = ''
cat > /etc/ld.so.conf <<EOF
/lib
/lib/x86_64-linux-gnu
/lib64
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib64
/lib/i386-linux-gnu
/lib32
/usr/lib/i386-linux-gnu
/usr/lib32
EOF
ldconfig &> /dev/null
'';
2022-04-26 15:14:08 +00:00
sandboxInitScriptName = "${name}-wrapped-init.sh";
sandboxInitScript = writeShellScriptBin sandboxInitScriptName ''
2021-09-04 18:18:25 +00:00
source /etc/profile
${createLdConfCache}
2022-04-26 15:14:08 +00:00
exec ${binary} "$@"
2021-09-04 18:18:25 +00:00
'';
2022-04-26 15:14:08 +00:00
sandboxScriptName = "${name}-wrapped.sh";
sandboxScript = let
extraEnvString = lib.foldl (acc: val: acc + val + "\n") "" (lib.mapAttrsToList (name: value: "--setenv ${name} \"${value}\"") extraEnv);
stringify = x: "\"${x}\"";
safeAdditionalMounts = map stringify additionalMounts;
safeMountInHome = map stringify mountInHome;
in writeShellScriptBin sandboxScriptName ''
2021-09-06 16:10:14 +00:00
blacklist=(/nix /dev /proc /etc ${lib.optionalString (!mountHome) "/home"} ${builtins.toString additionalBlacklist})
2021-09-04 18:18:25 +00:00
ro_mounts=()
symlinks=()
declare -a auto_mounts
# loop through all directories in the root
for dir in /*; do
# if it is a directory and it is not in the blacklist
if [[ -d "$dir" ]] && [[ ! "''${blacklist[@]}" =~ "$dir" ]]; then
# add it to the mount list
auto_mounts+=(--bind "$dir" "$dir")
fi
done
if [[ "${lib.optionalString (!mountHome) "1"}" = "1" ]]; then
2022-04-26 15:14:08 +00:00
for entry in ${builtins.toString safeMountInHome}; do
2021-09-04 18:18:25 +00:00
auto_mounts+=(--bind "/home/$USER/$entry" "/home/$USER/$entry")
done
fi
if [[ ! -z "${builtins.toString additionalMounts}" ]]; then
2022-04-26 15:14:08 +00:00
for entry in ${builtins.toString safeAdditionalMounts}; do
2021-09-04 18:18:25 +00:00
auto_mounts+=(--bind "$entry" "$entry")
done
fi
cmd=(
${bubblewrap}/bin/bwrap
--dev-bind /dev /dev
--proc /proc
--chdir ${chdirTo}
${lib.optionalString unshareUser "--unshare-user"}
${lib.optionalString unshareIpc "--unshare-ipc"}
${lib.optionalString unsharePid "--unshare-pid"}
${lib.optionalString unshareNet "--unshare-net"}
${lib.optionalString unshareUts "--unshare-uts"}
${lib.optionalString unshareCgroup "--unshare-cgroup"}
${lib.optionalString dieWithParent "--die-with-parent"}
--ro-bind /nix /nix
# Our glibc will look for the cache in its own path in `/nix/store`.
# As such, we need a cache to exist there, because pressure-vessel
# depends on the existence of an ld cache. However, adding one
# globally proved to be a bad idea (see #100655), the solution we
# settled on being mounting one via bwrap.
# Also, the cache needs to go to both 32 and 64 bit glibcs, for games
# of both architectures to work.
--tmpfs ${glibc}/etc \
--symlink /etc/ld.so.conf ${glibc}/etc/ld.so.conf \
--symlink /etc/ld.so.cache ${glibc}/etc/ld.so.cache \
--ro-bind ${glibc}/etc/rpc ${glibc}/etc/rpc \
--remount-ro ${glibc}/etc \
--tmpfs ${pkgsi686Linux.glibc}/etc \
--symlink /etc/ld.so.conf ${pkgsi686Linux.glibc}/etc/ld.so.conf \
--symlink /etc/ld.so.cache ${pkgsi686Linux.glibc}/etc/ld.so.cache \
--ro-bind ${pkgsi686Linux.glibc}/etc/rpc ${pkgsi686Linux.glibc}/etc/rpc \
--remount-ro ${pkgsi686Linux.glibc}/etc \
${etcBindFlags}
"''${ro_mounts[@]}"
"''${symlinks[@]}"
"''${auto_mounts[@]}"
${extraEnvString}
2022-04-26 15:14:08 +00:00
${sandboxInitScript}/bin/${sandboxInitScriptName}
2021-09-04 18:18:25 +00:00
)
2022-04-26 15:14:08 +00:00
exec "''${cmd[@]}" $@
2021-09-04 18:18:25 +00:00
'';
in stdenv.mkDerivation {
2022-04-26 15:14:08 +00:00
pname = name;
version = "1.0.0";
2021-09-04 18:18:25 +00:00
unpackPhase = ":";
dontBuild = true;
2022-04-26 15:14:08 +00:00
installPhase = let
desktopFilePhase = let
desktopFile = makeDesktopItem (desktopFileAttributes // {
exec = "${sandboxScript}/bin/${sandboxScriptName}";
});
in ''
cp -r ${desktopFile}/share/applications $out/share
'';
copyIntoSandboxPhase = let
attrs = builtins.attrNames copyIntoSandbox;
cps = map (x: "cp -Lr ${x} $out/${copyIntoSandbox."${x}"}\n") attrs;
in lib.concatStrings cps;
in ''
2021-09-06 19:15:24 +00:00
mkdir -p $out/bin
2022-04-26 15:14:08 +00:00
ln -s ${sandboxScript}/bin/${sandboxScriptName} $out/bin/${launchScriptName}
# Generate the desktop file, if enabled
${lib.optionalString enableDesktopFile "mkdir -p $out/share"}
${lib.optionalString enableDesktopFile preDesktopFilePhase}
${lib.optionalString enableDesktopFile desktopFilePhase}
# Copy extra files if they are specified
${lib.optionalString (copyIntoSandbox != {}) copyIntoSandboxPhase}
'';
2021-09-04 18:18:25 +00:00
}