lib/sandbox/src/policy.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const audit_data = @import("audit.zig");
 3 
 4 pub const Preference = enum {
 5     allow_inherited,
 6     prefer_isolated,
 7     require_isolated,
 8 };
 9 
10 /// States what isolation a run has to have before the run starts: the policy
11 /// records what the caller asks of the filesystem, process, and network
12 /// dimensions, each judged on its own. A required dimension rejects an audit
13 /// weaker than isolated, returning `error.UnmetFilesystemIsolation` ,
14 /// `error.UnmetProcessIsolation` , or `error.UnmetNetworkIsolation` before the
15 /// child runs. By contrast, a preferred dimension records what the caller wants
16 /// and admits every audit, which backend selection reads to pick a runner. Both
17 /// filesystem values count as isolated, so requiring the filesystem dimension
18 /// admits every audit the package can produce. The environment dimension
19 /// appears in the audit and has no entry here, so a policy constrains it in no
20 /// way.
21 pub const Policy = struct {
22     filesystem: Preference = .require_isolated,
23     process: Preference = .allow_inherited,
24     network: Preference = .allow_inherited,
25 
26     pub fn validate(self: Policy, audit: audit_data.Audit) !void {
27         if (self.filesystem == .require_isolated and !filesystemIsolated(audit.filesystem)) return error.UnmetFilesystemIsolation;
28         if (self.process == .require_isolated and !processIsolated(audit.process)) return error.UnmetProcessIsolation;
29         if (self.network == .require_isolated and !networkIsolated(audit.network)) return error.UnmetNetworkIsolation;
30     }
31 
32     pub fn prefersFilesystemIsolation(self: Policy) bool {
33         return self.filesystem != .allow_inherited;
34     }
35 
36     pub fn prefersProcessIsolation(self: Policy) bool {
37         return self.process != .allow_inherited;
38     }
39 
40     pub fn prefersNetworkIsolation(self: Policy) bool {
41         return self.network != .allow_inherited;
42     }
43 };
44 
45 pub fn filesystemIsolated(value: audit_data.Filesystem) bool {
46     return switch (value) {
47         .copied, .overlay => true,
48     };
49 }
50 
51 pub fn processIsolated(value: audit_data.Process) bool {
52     return switch (value) {
53         .host => false,
54         .bubblewrap_namespace => true,
55     };
56 }
57 
58 pub fn networkIsolated(value: audit_data.Network) bool {
59     return switch (value) {
60         .inherited => false,
61         .bubblewrap_namespace => true,
62     };
63 }
64 
65 test "policy validates required isolation dimensions" {
66     const inherited = audit_data.Audit{};
67     try (Policy{}).validate(inherited);
68     try std.testing.expectError(error.UnmetProcessIsolation, (Policy{ .process = .require_isolated }).validate(inherited));
69     try std.testing.expectError(error.UnmetNetworkIsolation, (Policy{ .network = .require_isolated }).validate(inherited));
70 
71     const inherited_bwrap = audit_data.Audit{
72         .runner = .bubblewrap_overlay,
73         .filesystem = .overlay,
74         .process = .bubblewrap_namespace,
75         .network = .inherited,
76     };
77     try (Policy{ .process = .require_isolated }).validate(inherited_bwrap);
78     try std.testing.expectError(
79         error.UnmetNetworkIsolation,
80         (Policy{ .network = .require_isolated }).validate(inherited_bwrap),
81     );
82 
83     var isolated_bwrap = inherited_bwrap;
84     isolated_bwrap.network = .bubblewrap_namespace;
85     try (Policy{ .network = .require_isolated }).validate(isolated_bwrap);
86 }
87 
88 test "policy exposes preferences without enforcing them" {
89     const policy = Policy{
90         .filesystem = .prefer_isolated,
91         .process = .prefer_isolated,
92         .network = .allow_inherited,
93     };
94     try policy.validate(.{});
95     try std.testing.expect(policy.prefersFilesystemIsolation());
96     try std.testing.expect(policy.prefersProcessIsolation());
97     try std.testing.expect(!policy.prefersNetworkIsolation());
98 }