|
1 | 1 | #!/usr/sbin/nft -f |
2 | 2 | # Egress firewall applied inside the scraper container's network namespace at |
3 | | -# startup (see docker-entrypoint.sh). Allows the public internet; drops NEW |
4 | | -# connections to internal/reserved ranges. Because every connection the |
5 | | -# headless browser makes is locally generated, the output hook covers it too. |
| 3 | +# startup (see docker-entrypoint.sh). Allows the public internet on HTTP/HTTPS |
| 4 | +# ports only; drops NEW connections to internal/reserved ranges and to non-web |
| 5 | +# ports. Because every connection the headless browser makes is locally |
| 6 | +# generated, the output hook covers it too. |
6 | 7 | # |
7 | 8 | # Do NOT `flush ruleset`: that would wipe Docker's embedded-DNS NAT rules in |
8 | 9 | # this netns and break name resolution. Replace only our own table (the |
@@ -73,6 +74,16 @@ table inet egress { |
73 | 74 | # Block NEW outbound connections to internal/reserved space. |
74 | 75 | ip daddr @blocked4 drop |
75 | 76 | ip6 daddr @blocked6 drop |
76 | | - # policy accept -> NEW connections to public addresses pass. |
| 77 | + |
| 78 | + # Of the remaining (public) destinations, the scraper only ever needs |
| 79 | + # HTTP/HTTPS. Drop any other NEW outbound so a malicious URL/redirect — or a |
| 80 | + # compromised scraper — can't reach public services on non-web ports |
| 81 | + # (port-scanning, SMTP/Redis/SSH, odd-port exfiltration, etc.). Loopback, |
| 82 | + # established replies, and DNS to the resolver(s) were accepted above. udp |
| 83 | + # 443 is allowed for HTTP/3 (QUIC); all other new UDP — including DNS to |
| 84 | + # non-resolvers — is dropped, keeping name resolution scoped to @resolvers. |
| 85 | + ct state new tcp dport != { 80, 443 } drop |
| 86 | + ct state new udp dport != 443 drop |
| 87 | + # policy accept -> NEW connections to public addresses on 80/443 pass. |
77 | 88 | } |
78 | 89 | } |
0 commit comments