deploy/gitbay-runner-builds.nft
94 lines · 4067 bytes
1#!/usr/sbin/nft -f
2# Egress for CI builds by trust (#260). Installed as
3# /etc/gitbay-runner/builds.nft by `make deploy-runner` and loaded by the
4# runner unit's ExecStartPre (gitbay-runner.override.conf), after the
5# cgroups it names exist.
6#
7# gitbay-runner-egress.nft cannot tell a build from its runner: both run
8# as ci-runner. This table can. The runner starts every podman process
9# for a build inside builds/trusted/build-<id> or
10# builds/untrusted/build-<id> under its service cgroup, and pasta,
11# which podman starts, inherits that cgroup; so every socket pasta opens
12# for a build carries it. The runner itself, its clone and its log
13# stream run in <service>/runner and never match here.
14#
15# nftables resolves a cgroup path to the cgroup's id when the table
16# loads. The runner's service cgroup is new on every start, so the drop-in
17# creates builds/trusted and builds/untrusted and loads this file on
18# every start, and the runner never recreates them. A table loaded
19# against an earlier start's cgroups matches nothing, and builds then
20# get only the uid table.
21#
22# The uid table still applies to builds; a packet must pass both. This
23# table only takes away. Both hook output with policy accept, so their
24# relative order does not matter.
25#
26# Trusted builds (a branch of the repository, with its secrets):
27# internet open, any port.
28# host, public 22, 80 and 443: the forge at GITBAY_SSH
29# (169.254.1.2, which pasta translates to the public
30# address). hutch and orgo publish over 22.
31# host, loopback 53 only: the host's resolver, where pasta forwards
32# a build's DNS when the host's nameserver is a
33# loopback address.
34# private ranges closed (RFC 1918, CGNAT, link-local, ULA).
35# Untrusted builds (a fork's merge request head, no secrets):
36# internet 80 and 443 over TCP, and 53: enough to fetch
37# modules and packages, not to send mail or reach
38# ssh elsewhere.
39# host loopback 53 only. No forge: an untrusted build has
40# no key to use there, and a failing login from it
41# would count against the host's public address.
42# private ranges closed.
43# -isolation none builds run in the runner's own cgroup and get only the
44# uid table; such a runner must not take -untrusted.
45#
46# A host whose /etc/resolv.conf names a nameserver in a private range
47# needs that address let through here, or builds resolve nothing.
48#
49# The first line creates the table if it is missing, so the delete never
50# fails; the file then replaces it in one transaction.
51
52table inet gitbay_builds
53delete table inet gitbay_builds
54
55table inet gitbay_builds {
56 set private4 {
57 type ipv4_addr
58 flags interval
59 elements = { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16 }
60 }
61
62 set private6 {
63 type ipv6_addr
64 flags interval
65 elements = { fc00::/7, fe80::/10 }
66 }
67
68 chain output {
69 type filter hook output priority filter; policy accept;
70 socket cgroupv2 level 4 "system.slice/gitbay-runner.service/builds/trusted" jump trusted
71 socket cgroupv2 level 4 "system.slice/gitbay-runner.service/builds/untrusted" jump untrusted
72 }
73
74 chain trusted {
75 oifname "lo" ip daddr 127.0.0.0/8 meta l4proto { tcp, udp } th dport 53 return
76 oifname "lo" ip6 daddr ::1 meta l4proto { tcp, udp } th dport 53 return
77 oifname "lo" ip daddr != 127.0.0.0/8 tcp dport { 22, 80, 443 } return
78 oifname "lo" ip6 daddr != ::1 tcp dport { 22, 80, 443 } return
79 oifname "lo" counter reject
80 ip daddr @private4 counter reject
81 ip6 daddr @private6 counter reject
82 }
83
84 chain untrusted {
85 oifname "lo" ip daddr 127.0.0.0/8 meta l4proto { tcp, udp } th dport 53 return
86 oifname "lo" ip6 daddr ::1 meta l4proto { tcp, udp } th dport 53 return
87 oifname "lo" counter reject
88 ip daddr @private4 counter reject
89 ip6 daddr @private6 counter reject
90 meta l4proto { tcp, udp } th dport 53 return
91 tcp dport { 80, 443 } return
92 counter reject
93 }
94}