#!/bin/bash # Base nftables ruleset: inbound traffic is dropped, outbound traffic is rejected # except loopback, established connections, the local proxy (web), the local # DNS filter and the time servers that /refresh_rules.sh adds. # Web traffic (ports 80 and 443) of every program is redirected to the local # proxy (contest-proxy.service), which checks the site name. Only the proxy # may connect to the outside on port 443. CONF=/etc/contest-firewall # Remove any persisting nftables rules rm -f /etc/nftables.rules touch /etc/nftables.rules # Configuration of the proxy: the allowed names and the DNS resolver mkdir -p /run/contest-firewall for name in $(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$CONF/allowed_domains.txt"); do echo "$name $name:443;" done > /run/contest-firewall/proxy_names.conf # The proxy resolves the names through the DNS filter below echo "resolver 127.0.0.1 ipv6=off valid=30s;" > /run/contest-firewall/proxy_resolver.conf # Local DNS filter (contest-dns.service): the allowed domains and the time # servers are forwarded to the DNS resolvers, every other name gets no answer. # Programs on the machine ask it through /etc/resolv.conf, and only the proxy # account (which also runs the filter) may reach the DNS resolvers. This # closes DNS as a way to exchange data with a server outside. { echo "listen-address=127.0.0.1" echo "bind-interfaces" echo "no-resolv" echo "no-hosts" echo "user=contest-proxy" echo "group=nogroup" echo "cache-size=1000" echo "log-queries" echo "server=/#/" for name in $(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$CONF/allowed_domains.txt" "$CONF/ntp_servers.txt"); do for dns in $(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$CONF/dns_servers.txt"); do echo "server=/$name/$dns" done done } > /run/contest-firewall/dnsmasq.conf # /run/resolvconf is one of the places the AppArmor profiles (dig, ...) accept, # and the resolvconf package that would also use it is not installed mkdir -p /run/resolvconf echo "nameserver 127.0.0.1" > /run/resolvconf/resolv.conf ln -sfn /run/resolvconf/resolv.conf /etc/resolv.conf # Flush all rules, then create the table, chains and address sets nft flush ruleset nft add table inet filter nft add chain inet filter input '{ type filter hook input priority 0 ; policy drop ; }' nft add chain inet filter forward '{ type filter hook forward priority 0 ; policy drop ; }' nft add chain inet filter output '{ type filter hook output priority 0 ; policy accept ; }' nft add set inet filter ntp_ips '{ type ipv4_addr ; flags interval ; }' # Redirect web traffic to the proxy, except the proxy's own connections and # the local sites of the image (DevDocs) nft add table inet nat nft add chain inet nat output '{ type nat hook output priority -100 ; }' nft add rule inet nat output meta skuid contest-proxy return nft add rule inet nat output ip daddr 127.0.0.0/8 return nft add rule inet nat output meta nfproto ipv4 tcp dport 443 redirect to :8443 nft add rule inet nat output meta nfproto ipv4 tcp dport 80 redirect to :8080 # Established/related connections and loopback nft add rule inet filter input ct state related,established accept nft add rule inet filter output ct state related,established accept nft add rule inet filter input iif lo accept nft add rule inet filter output oif lo accept # Connections redirected to the proxy (they still show the network interface here) nft add rule inet filter output ip daddr 127.0.0.1 tcp dport '{ 8080, 8443 }' ct status dnat accept # Web: only the proxy connects to the outside, by name, to the allowed sites # it resolves itself through the DNS filter (no list of addresses to keep up # to date, content delivery networks change them all the time) nft add rule inet filter output meta skuid contest-proxy tcp dport 443 accept # Time servers, for chrony only (addresses filled by /refresh_rules.sh) nft add rule inet filter output meta skuid _chrony ip daddr @ntp_ips udp dport 123 accept # DNS resolvers, for the proxy and the DNS filter only for dns in $(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$CONF/dns_servers.txt"); do nft add rule inet filter output meta skuid contest-proxy ip daddr "$dns" udp dport 53 accept nft add rule inet filter output meta skuid contest-proxy ip daddr "$dns" tcp dport 53 accept done # Reject any other outgoing connection nft add rule inet filter output reject