nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix

Merge pull request #305853 from virchau13s-forks/isolate-module

isolate: add module and module tests

authored by

Guillaume Girol and committed by
GitHub
3ed7049c 33812a15

+207 -3
+2
nixos/doc/manual/release-notes/rl-2405.section.md
··· 191 191 192 192 - [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). 193 193 194 + - [isolate](https://github.com/ioi/isolate), a sandbox for securely executing untrusted programs. Available as [security.isolate](#opt-security.isolate.enable). 195 + 194 196 ## Backward Incompatibilities {#sec-release-24.05-incompatibilities} 195 197 196 198 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+1
nixos/modules/module-list.nix
··· 325 325 ./security/duosec.nix 326 326 ./security/google_oslogin.nix 327 327 ./security/ipa.nix 328 + ./security/isolate.nix 328 329 ./security/krb5 329 330 ./security/lock-kernel-modules.nix 330 331 ./security/misc.nix
+133
nixos/modules/security/isolate.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + let 4 + inherit (lib) mkEnableOption mkPackageOption mkOption types mkIf maintainers; 5 + 6 + cfg = config.security.isolate; 7 + configFile = pkgs.writeText "isolate-config.cf" '' 8 + box_root=${cfg.boxRoot} 9 + lock_root=${cfg.lockRoot} 10 + cg_root=${cfg.cgRoot} 11 + first_uid=${toString cfg.firstUid} 12 + first_gid=${toString cfg.firstGid} 13 + num_boxes=${toString cfg.numBoxes} 14 + restricted_init=${if cfg.restrictedInit then "1" else "0"} 15 + ${cfg.extraConfig} 16 + ''; 17 + isolate = pkgs.symlinkJoin { 18 + name = "isolate-wrapped-${pkgs.isolate.version}"; 19 + 20 + paths = [ pkgs.isolate ]; 21 + 22 + nativeBuildInputs = [ pkgs.makeWrapper ]; 23 + 24 + postBuild = '' 25 + wrapProgram $out/bin/isolate \ 26 + --set ISOLATE_CONFIG_FILE ${configFile} 27 + 28 + wrapProgram $out/bin/isolate-cg-keeper \ 29 + --set ISOLATE_CONFIG_FILE ${configFile} 30 + ''; 31 + }; 32 + in 33 + { 34 + options.security.isolate = { 35 + enable = mkEnableOption '' 36 + Sandbox for securely executing untrusted programs 37 + ''; 38 + 39 + package = mkPackageOption pkgs "isolate-unwrapped" { }; 40 + 41 + boxRoot = mkOption { 42 + type = types.path; 43 + default = "/var/lib/isolate/boxes"; 44 + description = '' 45 + All sandboxes are created under this directory. 46 + To avoid symlink attacks, this directory and all its ancestors 47 + must be writeable only by root. 48 + ''; 49 + }; 50 + 51 + lockRoot = mkOption { 52 + type = types.path; 53 + default = "/run/isolate/locks"; 54 + description = '' 55 + Directory where lock files are created. 56 + ''; 57 + }; 58 + 59 + cgRoot = mkOption { 60 + type = types.str; 61 + default = "auto:/run/isolate/cgroup"; 62 + description = '' 63 + Control group which subgroups are placed under. 64 + Either an explicit path to a subdirectory in cgroupfs, or "auto:file" to read 65 + the path from "file", where it is put by `isolate-cg-helper`. 66 + ''; 67 + }; 68 + 69 + firstUid = mkOption { 70 + type = types.numbers.between 1000 65533; 71 + default = 60000; 72 + description = '' 73 + Start of block of UIDs reserved for sandboxes. 74 + ''; 75 + }; 76 + 77 + firstGid = mkOption { 78 + type = types.numbers.between 1000 65533; 79 + default = 60000; 80 + description = '' 81 + Start of block of GIDs reserved for sandboxes. 82 + ''; 83 + }; 84 + 85 + numBoxes = mkOption { 86 + type = types.numbers.between 1000 65533; 87 + default = 1000; 88 + description = '' 89 + Number of UIDs and GIDs to reserve, starting from 90 + {option}`firstUid` and {option}`firstGid`. 91 + ''; 92 + }; 93 + 94 + restrictedInit = mkOption { 95 + type = types.bool; 96 + default = false; 97 + description = '' 98 + If true, only root can create sandboxes. 99 + ''; 100 + }; 101 + 102 + extraConfig = mkOption { 103 + type = types.str; 104 + default = ""; 105 + description = '' 106 + Extra configuration to append to the configuration file. 107 + ''; 108 + }; 109 + }; 110 + 111 + config = mkIf cfg.enable { 112 + environment.systemPackages = [ 113 + isolate 114 + ]; 115 + 116 + systemd.services.isolate = { 117 + description = "Isolate control group hierarchy daemon"; 118 + wantedBy = [ "multi-user.target" ]; 119 + serviceConfig = { 120 + Type = "notify"; 121 + ExecStart = "${isolate}/bin/isolate-cg-keeper"; 122 + Slice = "isolate.slice"; 123 + Delegate = true; 124 + }; 125 + }; 126 + 127 + systemd.slices.isolate = { 128 + description = "Isolate sandbox slice"; 129 + }; 130 + 131 + meta.maintainers = with maintainers; [ virchau13 ]; 132 + }; 133 + }
+1
nixos/tests/all-tests.nix
··· 399 399 honk = runTest ./honk.nix; 400 400 installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); 401 401 invidious = handleTest ./invidious.nix {}; 402 + isolate = handleTest ./isolate.nix {}; 402 403 livebook-service = handleTest ./livebook-service.nix {}; 403 404 pyload = handleTest ./pyload.nix {}; 404 405 oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};
+38
nixos/tests/isolate.nix
··· 1 + import ./make-test-python.nix ({ lib, ... }: 2 + { 3 + name = "isolate"; 4 + meta.maintainers = with lib.maintainers; [ virchau13 ]; 5 + 6 + nodes.machine = 7 + { ... }: 8 + { 9 + security.isolate = { 10 + enable = true; 11 + }; 12 + }; 13 + 14 + testScript = '' 15 + bash_path = machine.succeed('realpath $(which bash)').strip() 16 + sleep_path = machine.succeed('realpath $(which sleep)').strip() 17 + def sleep_test(walltime, sleeptime): 18 + return f'isolate --no-default-dirs --wall-time {walltime} ' + \ 19 + f'--dir=/box={box_path} --dir=/nix=/nix --run -- ' + \ 20 + f"{bash_path} -c 'exec -a sleep {sleep_path} {sleeptime}'" 21 + 22 + def sleep_test_cg(walltime, sleeptime): 23 + return f'isolate --cg --no-default-dirs --wall-time {walltime} ' + \ 24 + f'--dir=/box={box_path} --dir=/nix=/nix --processes=2 --run -- ' + \ 25 + f"{bash_path} -c '( exec -a sleep {sleep_path} {sleeptime} )'" 26 + 27 + with subtest("without cgroups"): 28 + box_path = machine.succeed('isolate --init').strip() 29 + machine.succeed(sleep_test(1, 0.5)) 30 + machine.fail(sleep_test(0.5, 1)) 31 + machine.succeed('isolate --cleanup') 32 + with subtest("with cgroups"): 33 + box_path = machine.succeed('isolate --cg --init').strip() 34 + machine.succeed(sleep_test_cg(1, 0.5)) 35 + machine.fail(sleep_test_cg(0.5, 1)) 36 + machine.succeed('isolate --cg --cleanup') 37 + ''; 38 + })
+13 -3
pkgs/tools/security/isolate/default.nix
··· 3 3 , fetchFromGitHub 4 4 , asciidoc 5 5 , libcap 6 + , pkg-config 7 + , systemdLibs 6 8 , installShellFiles 9 + , nixosTests 7 10 }: 8 11 9 12 stdenv.mkDerivation rec { ··· 23 20 nativeBuildInputs = [ 24 21 asciidoc 25 22 installShellFiles 23 + pkg-config 26 24 ]; 27 25 28 26 buildInputs = [ 29 27 libcap.dev 28 + systemdLibs.dev 30 29 ]; 31 30 32 - buildFlags = [ 33 - "isolate" 34 - "isolate.1" 31 + patches = [ 32 + ./take-config-file-from-env.patch 35 33 ]; 36 34 37 35 installPhase = '' 38 36 runHook preInstall 39 37 40 38 install -Dm755 ./isolate $out/bin/isolate 39 + install -Dm755 ./isolate-cg-keeper $out/bin/isolate-cg-keeper 40 + install -Dm755 ./isolate-check-environment $out/bin/isolate-check-environment 41 41 installManPage isolate.1 42 42 43 43 runHook postInstall 44 44 ''; 45 + 46 + passthru.tests = { 47 + isolate = nixosTests.isolate; 48 + }; 45 49 46 50 meta = { 47 51 description = "Sandbox for securely executing untrusted programs";
+19
pkgs/tools/security/isolate/take-config-file-from-env.patch
··· 1 + diff --git a/config.c b/config.c 2 + index 6477259..c462ed3 100644 3 + --- a/config.c 4 + +++ b/config.c 5 + @@ -114,9 +114,12 @@ cf_check(void) 6 + void 7 + cf_parse(void) 8 + { 9 + - FILE *f = fopen(CONFIG_FILE, "r"); 10 + + char *config_file = getenv("ISOLATE_CONFIG_FILE"); 11 + + if(!config_file) 12 + + die("ISOLATE_CONFIG_FILE environment variable not set"); 13 + + FILE *f = fopen(config_file, "r"); 14 + if (!f) 15 + - die("Cannot open %s: %m", CONFIG_FILE); 16 + + die("Cannot open %s: %m", config_file); 17 + 18 + char line[MAX_LINE_LEN]; 19 + while (fgets(line, sizeof(line), f))