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

isolate: add module and module tests
This commit is contained in:
Guillaume Girol 2024-04-27 22:48:20 +02:00 committed by GitHub
commit 3ed7049cdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 207 additions and 3 deletions

View File

@ -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). - [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} ## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

View File

@ -325,6 +325,7 @@
./security/duosec.nix ./security/duosec.nix
./security/google_oslogin.nix ./security/google_oslogin.nix
./security/ipa.nix ./security/ipa.nix
./security/isolate.nix
./security/krb5 ./security/krb5
./security/lock-kernel-modules.nix ./security/lock-kernel-modules.nix
./security/misc.nix ./security/misc.nix

View File

@ -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 ];
};
}

View File

@ -399,6 +399,7 @@ in {
honk = runTest ./honk.nix; honk = runTest ./honk.nix;
installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
invidious = handleTest ./invidious.nix {}; invidious = handleTest ./invidious.nix {};
isolate = handleTest ./isolate.nix {};
livebook-service = handleTest ./livebook-service.nix {}; livebook-service = handleTest ./livebook-service.nix {};
pyload = handleTest ./pyload.nix {}; pyload = handleTest ./pyload.nix {};
oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};

38
nixos/tests/isolate.nix Normal file
View File

@ -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')
'';
})

View File

@ -3,7 +3,10 @@
, fetchFromGitHub , fetchFromGitHub
, asciidoc , asciidoc
, libcap , libcap
, pkg-config
, systemdLibs
, installShellFiles , installShellFiles
, nixosTests
}: }:
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -20,26 +23,33 @@ stdenv.mkDerivation rec {
nativeBuildInputs = [ nativeBuildInputs = [
asciidoc asciidoc
installShellFiles installShellFiles
pkg-config
]; ];
buildInputs = [ buildInputs = [
libcap.dev libcap.dev
systemdLibs.dev
]; ];
buildFlags = [ patches = [
"isolate" ./take-config-file-from-env.patch
"isolate.1"
]; ];
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
install -Dm755 ./isolate $out/bin/isolate 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 installManPage isolate.1
runHook postInstall runHook postInstall
''; '';
passthru.tests = {
isolate = nixosTests.isolate;
};
meta = { meta = {
description = "Sandbox for securely executing untrusted programs"; description = "Sandbox for securely executing untrusted programs";
mainProgram = "isolate"; mainProgram = "isolate";

View File

@ -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))