firefox: sandbox with firejail
TODO: get it so open-in-mpv launches an mpv that has access to ~/.config/mpv i guess this is the 'firejail url problem'
This commit is contained in:
parent
ad92a2e158
commit
9ecd0adcbe
|
@ -113,6 +113,8 @@ let
|
||||||
rm $out/lib/${cfg.browser.libName}/browser/omni.ja
|
rm $out/lib/${cfg.browser.libName}/browser/omni.ja
|
||||||
${pkgs.buildPackages.gnused}/bin/sed -i s'/devtools-commandkey-inspector = C/devtools-commandkey-inspector = VK_F12/' omni/localization/en-US/devtools/startup/key-shortcuts.ftl
|
${pkgs.buildPackages.gnused}/bin/sed -i s'/devtools-commandkey-inspector = C/devtools-commandkey-inspector = VK_F12/' omni/localization/en-US/devtools/startup/key-shortcuts.ftl
|
||||||
pushd omni; ${pkgs.buildPackages.zip}/bin/zip $out/lib/${cfg.browser.libName}/browser/omni.ja -r ./*; popd
|
pushd omni; ${pkgs.buildPackages.zip}/bin/zip $out/lib/${cfg.browser.libName}/browser/omni.ja -r ./*; popd
|
||||||
|
|
||||||
|
runHook postFixup
|
||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -189,6 +191,7 @@ in
|
||||||
enable = lib.mkDefault config.services.i2p.enable;
|
enable = lib.mkDefault config.services.i2p.enable;
|
||||||
};
|
};
|
||||||
open-in-mpv = {
|
open-in-mpv = {
|
||||||
|
# test: `open-in-mpv 'mpv:///open?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ'`
|
||||||
package = pkgs.firefox-extensions.open-in-mpv;
|
package = pkgs.firefox-extensions.open-in-mpv;
|
||||||
enable = lib.mkDefault config.sane.programs.open-in-mpv.enabled;
|
enable = lib.mkDefault config.sane.programs.open-in-mpv.enabled;
|
||||||
};
|
};
|
||||||
|
@ -213,6 +216,7 @@ in
|
||||||
({
|
({
|
||||||
sane.programs.firefox = {
|
sane.programs.firefox = {
|
||||||
inherit packageUnwrapped;
|
inherit packageUnwrapped;
|
||||||
|
sandbox.method = "firejail";
|
||||||
|
|
||||||
suggestedPrograms = [
|
suggestedPrograms = [
|
||||||
"open-in-mpv"
|
"open-in-mpv"
|
||||||
|
@ -288,6 +292,21 @@ in
|
||||||
[General]
|
[General]
|
||||||
StartWithLastProfile=1
|
StartWithLastProfile=1
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
env.PASSWORD_STORE_DIR = "/home/colin/private/knowledge/secrets/accounts";
|
||||||
|
# alternative to PASSWORD_STORE_DIR, but firejail doesn't handle this symlink well
|
||||||
|
# fs.".password-store".symlink.target = lib.mkIf cfg.addons.browserpass-extension.enable "private/knowledge/secrets/accounts";
|
||||||
|
|
||||||
|
# browserpass needs these paths:
|
||||||
|
# - .ssh: to unlock the sops key, if not unlocked (`sane-secrets-unlock`(
|
||||||
|
# - .config/sops: where the key to decrypt account secrets
|
||||||
|
# - private/knowledge/secrets/accounts: where the encrypted account secrets live
|
||||||
|
# TODO: find a way to not expose ~/.ssh to firefox
|
||||||
|
# - unlock sops at login?
|
||||||
|
fs.".ssh" = lib.mkIf cfg.addons.browserpass-extension.enable {};
|
||||||
|
fs.".ssh/id_ed25519" = lib.mkIf cfg.addons.browserpass-extension.enable {};
|
||||||
|
fs.".config/sops" = lib.mkIf cfg.addons.browserpass-extension.enable {};
|
||||||
|
fs."private/knowledge/secrets/accounts" = lib.mkIf cfg.addons.browserpass-extension.enable {};
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
(mkIf config.sane.programs.firefox.enabled {
|
(mkIf config.sane.programs.firefox.enabled {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
net = "vpn";
|
net = "vpn";
|
||||||
|
sandbox.method = "firejail";
|
||||||
# ".config/nicotine": contains the config file, with plaintext creds.
|
# ".config/nicotine": contains the config file, with plaintext creds.
|
||||||
# TODO: define this as a secret instead of persisting it.
|
# TODO: define this as a secret instead of persisting it.
|
||||||
persist.byStore.private = [ ".config/nicotine" ];
|
persist.byStore.private = [ ".config/nicotine" ];
|
||||||
|
|
|
@ -89,9 +89,5 @@
|
||||||
fs."Videos/servo".symlink.target = "/mnt/servo-media/Videos";
|
fs."Videos/servo".symlink.target = "/mnt/servo-media/Videos";
|
||||||
# fs."Music/servo".symlink.target = "/mnt/servo-media/Music";
|
# fs."Music/servo".symlink.target = "/mnt/servo-media/Music";
|
||||||
fs."Pictures/servo-macros".symlink.target = "/mnt/servo-media/Pictures/macros";
|
fs."Pictures/servo-macros".symlink.target = "/mnt/servo-media/Pictures/macros";
|
||||||
|
|
||||||
# used by password managers, e.g. unix `pass`
|
|
||||||
# TODO: move this to the specific programs which need it
|
|
||||||
fs.".password-store".symlink.target = "knowledge/secrets/accounts";
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,18 +33,36 @@ let
|
||||||
defaultEnables = solveDefaultEnableFor cfg;
|
defaultEnables = solveDefaultEnableFor cfg;
|
||||||
|
|
||||||
# wrap a package so that its binaries (maybe) run in a sandbox
|
# wrap a package so that its binaries (maybe) run in a sandbox
|
||||||
wrapPkg = { net }: package: (
|
wrapPkg = { sandbox, fs, net, ... }: package: (
|
||||||
if net == "clearnet" then
|
if sandbox.method == null then
|
||||||
package
|
package
|
||||||
else if net == "vpn" then
|
else if sandbox.method == "firejail" then
|
||||||
let
|
let
|
||||||
vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn);
|
|
||||||
# XXX: firejail needs suid bit for some (not all) of its sandboxing methods. hence, rely on the user installing it system-wide and call it by suid path.
|
# XXX: firejail needs suid bit for some (not all) of its sandboxing methods. hence, rely on the user installing it system-wide and call it by suid path.
|
||||||
firejailBin = "/run/wrappers/bin/firejail";
|
firejailBin = "/run/wrappers/bin/firejail";
|
||||||
firejailFlags = [
|
vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn);
|
||||||
|
vpnFlags = [
|
||||||
"--net=${vpn.bridgeDevice}"
|
"--net=${vpn.bridgeDevice}"
|
||||||
] ++ (builtins.map (addr: "--dns=${addr}") vpn.dns);
|
] ++ (builtins.map (addr: "--dns=${addr}") vpn.dns);
|
||||||
in
|
allowPath = p: [
|
||||||
|
"--noblacklist=${p}"
|
||||||
|
"--whitelist=${p}"
|
||||||
|
];
|
||||||
|
fsFlags = lib.flatten (builtins.map
|
||||||
|
(p: allowPath ''''${HOME}/${p}'')
|
||||||
|
(builtins.attrNames fs)
|
||||||
|
);
|
||||||
|
firejailFlags = [
|
||||||
|
# "--quiet" #< TODO: enable
|
||||||
|
# "--tracelog" # logs blacklist violations to syslog (but default firejail disallows this)
|
||||||
|
] ++ allowPath "/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms`
|
||||||
|
++ fsFlags
|
||||||
|
++ lib.optionals (net == "vpn") vpnFlags;
|
||||||
|
|
||||||
|
firejailBase = pkgs.writeShellScript
|
||||||
|
"firejail-${package.pname}-base"
|
||||||
|
''exec ${firejailBin} ${lib.escapeShellArgs firejailFlags} \'';
|
||||||
|
|
||||||
# two ways i could wrap a package in a sandbox:
|
# two ways i could wrap a package in a sandbox:
|
||||||
# 1. package.overrideAttrs, with `postFixup`.
|
# 1. package.overrideAttrs, with `postFixup`.
|
||||||
# 2. pkgs.symlinkJoin, or pkgs.runCommand, creating an entirely new package which calls into the inner binaries.
|
# 2. pkgs.symlinkJoin, or pkgs.runCommand, creating an entirely new package which calls into the inner binaries.
|
||||||
|
@ -55,9 +73,9 @@ let
|
||||||
# no.1 may bloat rebuild times.
|
# no.1 may bloat rebuild times.
|
||||||
#
|
#
|
||||||
# ultimately, no.1 is probably more reliable, but i expect i'll factor out a switch to allow either approach -- particularly when debugging package buld failures.
|
# ultimately, no.1 is probably more reliable, but i expect i'll factor out a switch to allow either approach -- particularly when debugging package buld failures.
|
||||||
package.overrideAttrs (unwrapped: {
|
packageWrapped = package.overrideAttrs (unwrapped: {
|
||||||
postFixup = (unwrapped.postFixup or "") + ''
|
postFixup = (unwrapped.postFixup or "") + ''
|
||||||
getFirejailProfile() {
|
getFirejailProfile() {
|
||||||
_maybeProfile="${pkgs.firejail}/etc/firejail/$1.profile"
|
_maybeProfile="${pkgs.firejail}/etc/firejail/$1.profile"
|
||||||
if [ -e "$_maybeProfile" ]; then
|
if [ -e "$_maybeProfile" ]; then
|
||||||
firejailProfileFlags="--profile=$_maybeProfile"
|
firejailProfileFlags="--profile=$_maybeProfile"
|
||||||
|
@ -70,22 +88,39 @@ let
|
||||||
name="$1"
|
name="$1"
|
||||||
getFirejailProfile "$name"
|
getFirejailProfile "$name"
|
||||||
mv "$out/bin/$name" "$out/bin/.$name-firejailed"
|
mv "$out/bin/$name" "$out/bin/.$name-firejailed"
|
||||||
cat <<EOF >> "$out/bin/$name"
|
cat <<EOF >> "tmp-firejail-$name"
|
||||||
#!/bin/sh
|
$firejailProfileFlags \
|
||||||
exec ${firejailBin} ${lib.concatStringsSep " " firejailFlags} $firejailProfileFlags "$out/bin/.$name-firejailed" "\$@"
|
--join-or-start="${package.name}-$name" \
|
||||||
|
-- "$out/bin/.$name-firejailed" "\$@"
|
||||||
EOF
|
EOF
|
||||||
chmod +x "$out/bin/$p"
|
cat ${firejailBase} "tmp-firejail-$name" > "$out/bin/$name"
|
||||||
|
chmod +x "$out/bin/$name"
|
||||||
}
|
}
|
||||||
|
|
||||||
for p in $(ls "$out/bin/"); do
|
for _p in $(ls "$out/bin/"); do
|
||||||
firejailWrap "$p"
|
firejailWrap "$_p"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# stamp file which can be consumed to ensure this wrapping code was actually called.
|
||||||
|
mkdir -p $out/nix-support
|
||||||
|
touch $out/nix-support/sandboxed-firejail
|
||||||
'';
|
'';
|
||||||
meta = (unwrapped.meta or {}) // {
|
meta = (unwrapped.meta or {}) // {
|
||||||
# take precedence over non-sandboxed versions of the same binary.
|
# take precedence over non-sandboxed versions of the same binary.
|
||||||
priority = ((unwrapped.meta or {}).priority or 0) - 1;
|
priority = ((unwrapped.meta or {}).priority or 0) - 1;
|
||||||
};
|
};
|
||||||
})
|
passthru = (unwrapped.passthru or {}) // {
|
||||||
|
checkSandboxed = pkgs.runCommand "${package.name}-check-sandboxed" {} ''
|
||||||
|
# this pseudo-package gets "built" as part of toplevel system build.
|
||||||
|
# if the build is failing here, that means the program isn't properly sandboxed:
|
||||||
|
# make sure that "postFixup" gets called as part of the package's build script
|
||||||
|
test -f "${packageWrapped}/nix-support/sandboxed-${sandbox.method}" \
|
||||||
|
&& touch "$out"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
in
|
||||||
|
packageWrapped
|
||||||
else
|
else
|
||||||
throw "unknown net type '${net}'"
|
throw "unknown net type '${net}'"
|
||||||
);
|
);
|
||||||
|
@ -235,6 +270,13 @@ let
|
||||||
- "vpn" to route all traffic over the default VPN.
|
- "vpn" to route all traffic over the default VPN.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
sandbox.method = mkOption {
|
||||||
|
type = types.nullOr (types.enum [ "firejail" ]);
|
||||||
|
default = null; #< TODO: default to firejail
|
||||||
|
description = ''
|
||||||
|
how/whether to sandbox all binaries in the package.
|
||||||
|
'';
|
||||||
|
};
|
||||||
configOption = mkOption {
|
configOption = mkOption {
|
||||||
type = types.raw;
|
type = types.raw;
|
||||||
default = mkOption {
|
default = mkOption {
|
||||||
|
@ -258,18 +300,27 @@ let
|
||||||
package = if config.packageUnwrapped == null then
|
package = if config.packageUnwrapped == null then
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
wrapPkg { inherit (config) net; } config.packageUnwrapped
|
wrapPkg config config.packageUnwrapped
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
toPkgSpec = with lib; types.coercedTo types.package (p: { package = p; }) pkgSpec;
|
toPkgSpec = with lib; types.coercedTo types.package (p: { package = p; }) pkgSpec;
|
||||||
|
|
||||||
configs = lib.mapAttrsToList (name: p: {
|
configs = lib.mapAttrsToList (name: p: {
|
||||||
assertions = builtins.map (sug: {
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = (p.net == "clearnet") || p.sandbox.method != null;
|
||||||
|
message = ''program "${name}" requests net "${p.net}", which requires sandboxing, but sandboxing was disabled'';
|
||||||
|
}
|
||||||
|
] ++ builtins.map (sug: {
|
||||||
assertion = cfg ? "${sug}";
|
assertion = cfg ? "${sug}";
|
||||||
message = ''program "${sug}" referenced by "${name}", but not defined'';
|
message = ''program "${sug}" referenced by "${name}", but not defined'';
|
||||||
}) p.suggestedPrograms;
|
}) p.suggestedPrograms;
|
||||||
|
|
||||||
|
system.checks = lib.optionals (p.enabled && p.sandbox.method != null && p.package != null) [
|
||||||
|
p.package.passthru.checkSandboxed
|
||||||
|
];
|
||||||
|
|
||||||
# conditionally add to system PATH and env
|
# conditionally add to system PATH and env
|
||||||
environment = lib.optionalAttrs (p.enabled && p.enableFor.system) {
|
environment = lib.optionalAttrs (p.enabled && p.enableFor.system) {
|
||||||
systemPackages = lib.optional (p.package != null) p.package;
|
systemPackages = lib.optional (p.package != null) p.package;
|
||||||
|
@ -343,6 +394,7 @@ in
|
||||||
users.users = f.users.users;
|
users.users = f.users.users;
|
||||||
sane.users = f.sane.users;
|
sane.users = f.sane.users;
|
||||||
sops.secrets = f.sops.secrets;
|
sops.secrets = f.sops.secrets;
|
||||||
|
system.checks = f.system.checks;
|
||||||
};
|
};
|
||||||
in lib.mkMerge [
|
in lib.mkMerge [
|
||||||
(take (sane-lib.mkTypedMerge take configs))
|
(take (sane-lib.mkTypedMerge take configs))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user