1{ config, lib, pkgs, ... }:
2
3let
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 };
32in
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}