deploy/gitbay-runner-egress.nft

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

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