summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/security/isolate.nix133
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/isolate.nix38
-rw-r--r--pkgs/tools/security/isolate/default.nix16
-rw-r--r--pkgs/tools/security/isolate/take-config-file-from-env.patch19
7 files changed, 207 insertions, 3 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index 17df5d71f972..bd3ae81cc2ec 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -191,6 +191,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
- [prometheus-nats-exporter](https://github.com/nats-io/prometheus-nats-exporter), a Prometheus exporter for NATS. Available as [services.prometheus.exporters.nats](#opt-services.prometheus.exporters.nats.enable).
+- [isolate](https://github.com/ioi/isolate), a sandbox for securely executing untrusted programs. Available as [security.isolate](#opt-security.isolate.enable).
+
## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 50157f62ed32..78107e33b9e9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -325,6 +325,7 @@
./security/duosec.nix
./security/google_oslogin.nix
./security/ipa.nix
+ ./security/isolate.nix
./security/krb5
./security/lock-kernel-modules.nix
./security/misc.nix
diff --git a/nixos/modules/security/isolate.nix b/nixos/modules/security/isolate.nix
new file mode 100644
index 000000000000..3cc0176f3db3
--- /dev/null
+++ b/nixos/modules/security/isolate.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkEnableOption mkPackageOption mkOption types mkIf maintainers;
+
+ cfg = config.security.isolate;
+ configFile = pkgs.writeText "isolate-config.cf" ''
+ box_root=${cfg.boxRoot}
+ lock_root=${cfg.lockRoot}
+ cg_root=${cfg.cgRoot}
+ first_uid=${toString cfg.firstUid}
+ first_gid=${toString cfg.firstGid}
+ num_boxes=${toString cfg.numBoxes}
+ restricted_init=${if cfg.restrictedInit then "1" else "0"}
+ ${cfg.extraConfig}
+ '';
+ isolate = pkgs.symlinkJoin {
+ name = "isolate-wrapped-${pkgs.isolate.version}";
+
+ paths = [ pkgs.isolate ];
+
+ nativeBuildInputs = [ pkgs.makeWrapper ];
+
+ postBuild = ''
+ wrapProgram $out/bin/isolate \
+ --set ISOLATE_CONFIG_FILE ${configFile}
+
+ wrapProgram $out/bin/isolate-cg-keeper \
+ --set ISOLATE_CONFIG_FILE ${configFile}
+ '';
+ };
+in
+{
+ options.security.isolate = {
+ enable = mkEnableOption ''
+ Sandbox for securely executing untrusted programs
+ '';
+
+ package = mkPackageOption pkgs "isolate-unwrapped" { };
+
+ boxRoot = mkOption {
+ type = types.path;
+ default = "/var/lib/isolate/boxes";
+ description = ''
+ All sandboxes are created under this directory.
+ To avoid symlink attacks, this directory and all its ancestors
+ must be writeable only by root.
+ '';
+ };
+
+ lockRoot = mkOption {
+ type = types.path;
+ default = "/run/isolate/locks";
+ description = ''
+ Directory where lock files are created.
+ '';
+ };
+
+ cgRoot = mkOption {
+ type = types.str;
+ default = "auto:/run/isolate/cgroup";
+ description = ''
+ Control group which subgroups are placed under.
+ Either an explicit path to a subdirectory in cgroupfs, or "auto:file" to read
+ the path from "file", where it is put by `isolate-cg-helper`.
+ '';
+ };
+
+ firstUid = mkOption {
+ type = types.numbers.between 1000 65533;
+ default = 60000;
+ description = ''
+ Start of block of UIDs reserved for sandboxes.
+ '';
+ };
+
+ firstGid = mkOption {
+ type = types.numbers.between 1000 65533;
+ default = 60000;
+ description = ''
+ Start of block of GIDs reserved for sandboxes.
+ '';
+ };
+
+ numBoxes = mkOption {
+ type = types.numbers.between 1000 65533;
+ default = 1000;
+ description = ''
+ Number of UIDs and GIDs to reserve, starting from
+ {option}`firstUid` and {option}`firstGid`.
+ '';
+ };
+
+ restrictedInit = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true, only root can create sandboxes.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Extra configuration to append to the configuration file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [
+ isolate
+ ];
+
+ systemd.services.isolate = {
+ description = "Isolate control group hierarchy daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${isolate}/bin/isolate-cg-keeper";
+ Slice = "isolate.slice";
+ Delegate = true;
+ };
+ };
+
+ systemd.slices.isolate = {
+ description = "Isolate sandbox slice";
+ };
+
+ meta.maintainers = with maintainers; [ virchau13 ];
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 7b47fbf5662e..2c9d1aa568bf 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -399,6 +399,7 @@ in {
honk = runTest ./honk.nix;
installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
invidious = handleTest ./invidious.nix {};
+ isolate = handleTest ./isolate.nix {};
livebook-service = handleTest ./livebook-service.nix {};
pyload = handleTest ./pyload.nix {};
oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};
diff --git a/nixos/tests/isolate.nix b/nixos/tests/isolate.nix
new file mode 100644
index 000000000000..327231be1cd4
--- /dev/null
+++ b/nixos/tests/isolate.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+ name = "isolate";
+ meta.maintainers = with lib.maintainers; [ virchau13 ];
+
+ nodes.machine =
+ { ... }:
+ {
+ security.isolate = {
+ enable = true;
+ };
+ };
+
+ testScript = ''
+ bash_path = machine.succeed('realpath $(which bash)').strip()
+ sleep_path = machine.succeed('realpath $(which sleep)').strip()
+ def sleep_test(walltime, sleeptime):
+ return f'isolate --no-default-dirs --wall-time {walltime} ' + \
+ f'--dir=/box={box_path} --dir=/nix=/nix --run -- ' + \
+ f"{bash_path} -c 'exec -a sleep {sleep_path} {sleeptime}'"
+
+ def sleep_test_cg(walltime, sleeptime):
+ return f'isolate --cg --no-default-dirs --wall-time {walltime} ' + \
+ f'--dir=/box={box_path} --dir=/nix=/nix --processes=2 --run -- ' + \
+ f"{bash_path} -c '( exec -a sleep {sleep_path} {sleeptime} )'"
+
+ with subtest("without cgroups"):
+ box_path = machine.succeed('isolate --init').strip()
+ machine.succeed(sleep_test(1, 0.5))
+ machine.fail(sleep_test(0.5, 1))
+ machine.succeed('isolate --cleanup')
+ with subtest("with cgroups"):
+ box_path = machine.succeed('isolate --cg --init').strip()
+ machine.succeed(sleep_test_cg(1, 0.5))
+ machine.fail(sleep_test_cg(0.5, 1))
+ machine.succeed('isolate --cg --cleanup')
+ '';
+})
diff --git a/pkgs/tools/security/isolate/default.nix b/pkgs/tools/security/isolate/default.nix
index b745af75d8b7..a1d67c49d531 100644
--- a/pkgs/tools/security/isolate/default.nix
+++ b/pkgs/tools/security/isolate/default.nix
@@ -3,7 +3,10 @@
, fetchFromGitHub
, asciidoc
, libcap
+, pkg-config
+, systemdLibs
, installShellFiles
+, nixosTests
}:
stdenv.mkDerivation rec {
@@ -20,26 +23,33 @@ stdenv.mkDerivation rec {
nativeBuildInputs = [
asciidoc
installShellFiles
+ pkg-config
];
buildInputs = [
libcap.dev
+ systemdLibs.dev
];
- buildFlags = [
- "isolate"
- "isolate.1"
+ patches = [
+ ./take-config-file-from-env.patch
];
installPhase = ''
runHook preInstall
install -Dm755 ./isolate $out/bin/isolate
+ install -Dm755 ./isolate-cg-keeper $out/bin/isolate-cg-keeper
+ install -Dm755 ./isolate-check-environment $out/bin/isolate-check-environment
installManPage isolate.1
runHook postInstall
'';
+ passthru.tests = {
+ isolate = nixosTests.isolate;
+ };
+
meta = {
description = "Sandbox for securely executing untrusted programs";
mainProgram = "isolate";
diff --git a/pkgs/tools/security/isolate/take-config-file-from-env.patch b/pkgs/tools/security/isolate/take-config-file-from-env.patch
new file mode 100644
index 000000000000..c42d9a6e45b8
--- /dev/null
+++ b/pkgs/tools/security/isolate/take-config-file-from-env.patch
@@ -0,0 +1,19 @@
+diff --git a/config.c b/config.c
+index 6477259..c462ed3 100644
+--- a/config.c
++++ b/config.c
+@@ -114,9 +114,12 @@ cf_check(void)
+ void
+ cf_parse(void)
+ {
+- FILE *f = fopen(CONFIG_FILE, "r");
++ char *config_file = getenv("ISOLATE_CONFIG_FILE");
++ if(!config_file)
++ die("ISOLATE_CONFIG_FILE environment variable not set");
++ FILE *f = fopen(config_file, "r");
+ if (!f)
+- die("Cannot open %s: %m", CONFIG_FILE);
++ die("Cannot open %s: %m", config_file);
+
+ char line[MAX_LINE_LEN];
+ while (fgets(line, sizeof(line), f))