1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 mkEnableOption
11 mkPackageOption
12 mkOption
13 types
14 mkIf
15 maintainers
16 ;
17
18 cfg = config.security.isolate;
19 configFile = pkgs.writeText "isolate-config.cf" ''
20 box_root=${cfg.boxRoot}
21 lock_root=${cfg.lockRoot}
22 cg_root=${cfg.cgRoot}
23 first_uid=${toString cfg.firstUid}
24 first_gid=${toString cfg.firstGid}
25 num_boxes=${toString cfg.numBoxes}
26 restricted_init=${if cfg.restrictedInit then "1" else "0"}
27 ${cfg.extraConfig}
28 '';
29 isolate = pkgs.symlinkJoin {
30 name = "isolate-wrapped-${pkgs.isolate.version}";
31
32 paths = [ pkgs.isolate ];
33
34 nativeBuildInputs = [ pkgs.makeWrapper ];
35
36 postBuild = ''
37 wrapProgram $out/bin/isolate \
38 --set ISOLATE_CONFIG_FILE ${configFile}
39
40 wrapProgram $out/bin/isolate-cg-keeper \
41 --set ISOLATE_CONFIG_FILE ${configFile}
42 '';
43 };
44in
45{
46 options.security.isolate = {
47 enable = mkEnableOption ''
48 Sandbox for securely executing untrusted programs
49 '';
50
51 package = mkPackageOption pkgs "isolate-unwrapped" { };
52
53 boxRoot = mkOption {
54 type = types.path;
55 default = "/var/lib/isolate/boxes";
56 description = ''
57 All sandboxes are created under this directory.
58 To avoid symlink attacks, this directory and all its ancestors
59 must be writeable only by root.
60 '';
61 };
62
63 lockRoot = mkOption {
64 type = types.path;
65 default = "/run/isolate/locks";
66 description = ''
67 Directory where lock files are created.
68 '';
69 };
70
71 cgRoot = mkOption {
72 type = types.str;
73 default = "auto:/run/isolate/cgroup";
74 description = ''
75 Control group which subgroups are placed under.
76 Either an explicit path to a subdirectory in cgroupfs, or "auto:file" to read
77 the path from "file", where it is put by `isolate-cg-helper`.
78 '';
79 };
80
81 firstUid = mkOption {
82 type = types.numbers.between 1000 65533;
83 default = 60000;
84 description = ''
85 Start of block of UIDs reserved for sandboxes.
86 '';
87 };
88
89 firstGid = mkOption {
90 type = types.numbers.between 1000 65533;
91 default = 60000;
92 description = ''
93 Start of block of GIDs reserved for sandboxes.
94 '';
95 };
96
97 numBoxes = mkOption {
98 type = types.numbers.between 1000 65533;
99 default = 1000;
100 description = ''
101 Number of UIDs and GIDs to reserve, starting from
102 {option}`firstUid` and {option}`firstGid`.
103 '';
104 };
105
106 restrictedInit = mkOption {
107 type = types.bool;
108 default = false;
109 description = ''
110 If true, only root can create sandboxes.
111 '';
112 };
113
114 extraConfig = mkOption {
115 type = types.str;
116 default = "";
117 description = ''
118 Extra configuration to append to the configuration file.
119 '';
120 };
121 };
122
123 config = mkIf cfg.enable {
124 environment.systemPackages = [
125 isolate
126 ];
127
128 systemd.services.isolate = {
129 description = "Isolate control group hierarchy daemon";
130 wantedBy = [ "multi-user.target" ];
131 documentation = [ "man:isolate(1)" ];
132 serviceConfig = {
133 Type = "notify";
134 ExecStart = "${isolate}/bin/isolate-cg-keeper";
135 Slice = "isolate.slice";
136 Delegate = true;
137 };
138 };
139
140 systemd.slices.isolate = {
141 description = "Isolate Sandbox Slice";
142 };
143 };
144
145 meta.maintainers = with maintainers; [ virchau13 ];
146}