Commit 7acfcebdf5
7acfcebdf5445e4c73e674ee6be54f499f243c98
parent: 2258972d20
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-29 03:45 UTC
runner: refuse to start without build cgroups when taking untrusted builds or polling over loopback
Ref #260
Layout: unified · split
cmd/gitbay-runner/cgroup.go
+19
| @@ -31,6 +31,25 @@ func buildCgroupDir(builds string, id int64, trusted bool) string { |
| 31 | return filepath.Join(builds, class, fmt.Sprintf("build-%d", id)) |
31 | return filepath.Join(builds, class, fmt.Sprintf("build-%d", id)) |
| 32 | } |
32 | } |
| 33 | |
33 | |
| |
34 | // buildCgroupsRequired names what makes build cgroups mandatory, or "" |
| |
35 | // when a podman runner may run its builds in its own service cgroup. |
| |
36 | // Limits that cannot be applied are refused, not dropped: a runner that |
| |
37 | // accepted -memory and ran uncapped is what #188 was. A runner taking |
| |
38 | // untrusted builds, or polling over loopback on the daemon's host, is |
| |
39 | // the shape the builds nftables table guards, and that table matches |
| |
40 | // builds by these cgroups; without them it matches nothing (#260). |
| |
41 | func buildCgroupsRequired(memory, cpus string, untrusted, loopback bool) string { |
| |
42 | switch { |
| |
43 | case memory != "" || cpus != "": |
| |
44 | return "-memory/-cpus" |
| |
45 | case untrusted: |
| |
46 | return "-untrusted" |
| |
47 | case loopback: |
| |
48 | return "a loopback -remote" |
| |
49 | } |
| |
50 | return "" |
| |
51 | } |
| |
52 | |
| 34 | // memoryBytes parses podman's memory units — a whole number with an |
53 | // memoryBytes parses podman's memory units — a whole number with an |
| 35 | // optional b, k, m or g suffix — into bytes. |
54 | // optional b, k, m or g suffix — into bytes. |
| 36 | func memoryBytes(s string) (int64, error) { |
55 | func memoryBytes(s string) (int64, error) { |
cmd/gitbay-runner/cgroup_test.go
+23
| @@ -134,3 +134,26 @@ func TestBuildsTableNamesTheClassCgroups(t *testing.T) { |
| 134 | t.Error("the drop-in does not load the builds table") |
134 | t.Error("the drop-in does not load the builds table") |
| 135 | } |
135 | } |
| 136 | } |
136 | } |
| |
137 | |
| |
138 | // Without build cgroups a podman runner may carry on only where nothing |
| |
139 | // depends on them: no limits, no untrusted builds, and not on the |
| |
140 | // daemon's host, where the builds table matches by cgroup (#260). |
| |
141 | func TestBuildCgroupsRequired(t *testing.T) { |
| |
142 | cases := []struct { |
| |
143 | memory, cpus string |
| |
144 | untrusted, loopback bool |
| |
145 | want string |
| |
146 | }{ |
| |
147 | {"", "", false, false, ""}, |
| |
148 | {"6g", "", false, false, "-memory/-cpus"}, |
| |
149 | {"", "3", false, false, "-memory/-cpus"}, |
| |
150 | {"", "", true, false, "-untrusted"}, |
| |
151 | {"", "", false, true, "a loopback -remote"}, |
| |
152 | {"", "", true, true, "-untrusted"}, |
| |
153 | } |
| |
154 | for _, c := range cases { |
| |
155 | if got := buildCgroupsRequired(c.memory, c.cpus, c.untrusted, c.loopback); got != c.want { |
| |
156 | t.Errorf("buildCgroupsRequired(%q, %q, %v, %v) = %q, want %q", c.memory, c.cpus, c.untrusted, c.loopback, got, c.want) |
| |
157 | } |
| |
158 | } |
| |
159 | } |
cmd/gitbay-runner/main.go
+3 −6
| @@ -132,14 +132,11 @@ func main() { |
| 132 | // cgroup of the first invocation, and that must be the runner's |
132 | // cgroup of the first invocation, and that must be the runner's |
| 133 | // leaf, not a build's. |
133 | // leaf, not a build's. |
| 134 | cg, err := prepareBuildCgroups() |
134 | cg, err := prepareBuildCgroups() |
| 135 | switch { |
135 | switch why := buildCgroupsRequired(r.memory, r.cpus, *untrusted, r.loopbackRemote()); { |
| 136 | case err == nil: |
136 | case err == nil: |
| 137 | r.cgroups = cg |
137 | r.cgroups = cg |
| 138 | case r.memory != "" || r.cpus != "": |
138 | case why != "": |
| 139 | // Limits that cannot be applied are refused, not dropped: |
139 | log.Fatalf("%s: build cgroups unavailable: %v", why, err) |
| 140 | // a runner that accepted -memory and ran uncapped is what |
| |
| 141 | // #188 was. |
| |
| 142 | log.Fatalf("-memory/-cpus: build cgroups unavailable: %v", err) |
| |
| 143 | default: |
140 | default: |
| 144 | log.Printf("build cgroups unavailable (%v); builds run unconfined in the service cgroup", err) |
141 | log.Printf("build cgroups unavailable (%v); builds run unconfined in the service cgroup", err) |
| 145 | } |
142 | } |