deploy/gitbay-runner-egress.nft

v1.40.1
gitbay/deploy/gitbay-runner-egress.nft history · blame · raw

67 lines · 3299 bytes

 1#!/usr/sbin/nft -f
 2# Host egress for CI builds (#260). Loaded by gitbay-runner-egress.service,
 3# which gitbay-runner.service requires, so the runner does not start
 4# without it. `make deploy-runner` installs it as
 5# /etc/gitbay-runner/egress.nft.
 6#
 7# Under rootless podman with pasta, a build's connections are made by
 8# pasta on the host, from sockets owned by the runner's user, ci-runner.
 9# nftables sees them exactly as it sees the runner's own ssh, so this
10# table cannot tell a build from its runner. It limits what that user
11# reaches on this host, and the runner needs little: 127.0.0.1:22, to
12# poll, clone and stream logs. gitbay-runner-builds.nft tells them apart
13# by cgroup and narrows this per build, trusted or not.
14#
15# Every packet to one of the host's own addresses, loopback or public,
16# leaves through lo, so the output hook sees host-bound traffic as
17# oifname "lo". Traffic to other hosts is not matched: builds keep
18# outbound internet access, trusted or not (go mod download needs it).
19#
20# What ci-runner may reach on this host:
21#   127.0.0.1:22      the forge over loopback, for the runner. This
22#                     table cannot close it to builds. A build reaches
23#                     the host at 169.254.1.2, pasta's --map-guest-addr,
24#                     which pasta translates to the host's public
25#                     address; --no-map-gw (podman's default, which the
26#                     runner also states) adds no mapping to loopback.
27#                     Runbook R3 checks from inside a build whether
28#                     127.0.0.1:22 answers.
29#   loopback :53      the host's resolver, for when the host's nameserver
30#                     is a loopback address. That pasta forwards a
31#                     build's DNS there is to be confirmed from inside a
32#                     build by runbook R3, not assumed.
33#   public 22/80/443  the forge, as anyone on the internet reaches it,
34#                     and as a build reaches it through 169.254.1.2.
35# Everything else is rejected: the admin sshd on 2222 on every address,
36# and any service bound to loopback. -isolation none builds run as the
37# same user and get the same rule.
38#
39# The account name is resolved when the file is loaded. A restart of
40# nftables.service (flush ruleset) removes this table; `systemctl
41# reload gitbay-runner-egress` puts it back.
42#
43# The first line creates the table if it is missing, so the delete never
44# fails; the file then replaces it in one transaction, and a reload never
45# leaves a moment without the rule. The uid match sits in the base
46# chain's one rule rather than in a `!=` accept, because a packet with no
47# socket (a reset the kernel sends) matches neither `==` nor `!=` on
48# skuid and would otherwise fall through to the reject.
49
50table inet gitbay_runner
51delete table inet gitbay_runner
52
53table inet gitbay_runner {
54	chain output {
55		type filter hook output priority filter; policy accept;
56		oifname "lo" meta skuid "ci-runner" jump host
57	}
58
59	chain host {
60		ip daddr 127.0.0.1 tcp dport 22 accept
61		ip daddr 127.0.0.0/8 meta l4proto { tcp, udp } th dport 53 accept
62		ip6 daddr ::1 meta l4proto { tcp, udp } th dport 53 accept
63		ip daddr != 127.0.0.0/8 tcp dport { 22, 80, 443 } accept
64		ip6 daddr != ::1 tcp dport { 22, 80, 443 } accept
65		counter reject
66	}
67}