Unverified Commit 3ed7049c authored by Guillaume Girol's avatar Guillaume Girol Committed by GitHub
Browse files

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

isolate: add module and module tests
parents 33812a15 4a0a12ef
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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. -->
+1 −0
Original line number Diff line number Diff line
@@ -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
+133 −0
Original line number Diff line number Diff line
{ 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 ];
  };
}
+1 −0
Original line number Diff line number Diff line
@@ -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 {};
+38 −0
Original line number Diff line number Diff line
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')
  '';
})
Loading