yoke/nix/module.nix
2026-02-26 15:32:50 +11:00

118 lines
3.5 KiB
Nix

{
pkgs,
lib,
config,
...
}:
let
inherit (lib) mkOption mkPackageOption types;
strNotEmpty = s: lib.stringLength s != 0;
wrapperType = lib.types.submodule {
options = {
package = mkPackageOption "wrapped" { } {
nullable = false;
};
executable = mkOption {
type = types.str;
default = "";
};
args = mkOption {
type = types.listOf types.str;
default = [ ];
};
env = mkOption {
type = types.attrsOf (types.listOf types.str);
default = { };
};
retainEnv = mkOption {
type = types.bool;
default = false;
};
addPwd = mkOption {
type = types.bool;
default = false;
};
pathRules = mkOption {
type = types.listOf types.str;
default = [ ];
};
tcpRules = mkOption {
type = types.listOf types.str;
default = [ ];
};
unrestrictTcp = mkOption {
type = types.bool;
default = false;
};
unrestrictSockets = mkOption {
type = types.bool;
default = false;
};
unrestrictSignals = mkOption {
type = types.bool;
default = false;
};
};
};
in
{
options = {
security.yoke = {
wrappers = mkOption {
type = types.attrsOf wrapperType;
};
wrapperPkg = mkPackageOption "wrapper" { } { nullable = false; };
};
};
config =
let
wrap =
name: opts:
let
envs = lib.concatStringsSep " " (
lib.mapAttrsToList (n: v: "${n}=${lib.concatStringsSep ":" v}") opts.env
);
extraPaths = lib.concatStringsSep " " opts.pathRules;
tcpRules = lib.concatStringsSep " " opts.tcpRules;
sandboxArgs = pkgs.stdenvNoCC.mkDerivation {
name = "${name}-opts";
__structuredAttrs = true;
exportReferencesGraph.closure = [ opts.package ];
preferLocalBuild = true;
nativeBuildInputs = [
pkgs.coreutils
pkgs.jq
];
buildCommand = ''
echo -n "--fs rx=" > $out
jq -r '.closure[].path' < "$NIX_ATTRS_JSON_FILE" \
| tr '\n' ':' | sed 's/:$//' >> $out
${lib.optionalString (lib.length opts.pathRules != 0) "echo -n ' ${extraPaths}' >> $out"}
${lib.optionalString (lib.length opts.tcpRules != 0) "echo -n ' --tcp ${tcpRules}' >> $out"}
${lib.optionalString (strNotEmpty envs) "echo -n ' --env ${envs}' >> $out"}
'';
};
command = lib.getExe' opts.package (
if (strNotEmpty opts.executable) then opts.executable else opts.package.pname
);
wrappedArgs = lib.concatStringsSep " " opts.args;
script = ''
#! /usr/bin/env bash
${lib.getExe' config.security.yoke.wrapperPkg "yoke"} \
${lib.optionalString opts.addPwd "--fs rwx=$PWD"} \
${lib.optionalString opts.retainEnv "--retain-env"} \
${lib.optionalString opts.unrestrictTcp "--no-tcp"} \
${lib.optionalString opts.unrestrictSockets "--sockets"} \
${lib.optionalString opts.unrestrictSignals "--signals"} \
--fd-args -- \
${command} \
${wrappedArgs} $@ \
3< ${sandboxArgs}
'';
in
pkgs.writeScriptBin "${name}" script;
in
{
environment.systemPackages = lib.mapAttrsToList wrap config.security.yoke.wrappers;
};
}