#!/usr/bin/env bash # Netlock, run as root (opencode-sandbox netlock setup|teardown calls it through sudo). # Processes whose primary group is GROUP may only talk to loopback (where the egress filter # listens) and to DNS servers from resolv.conf; everything else is rejected. The rules are # loaded by a systemd unit and survive reboots. # netlock-root.sh setup # netlock-root.sh teardown set -euo pipefail TABLE=opencode_sandbox CONF_DIR=/etc/opencode-sandbox RULES="${CONF_DIR}/netlock.nft" UNIT=opencode-sandbox-netlock.service UNIT_PATH="/etc/systemd/system/${UNIT}" [[ ${EUID} -eq 0 ]] || { echo "must run as root" >&2; exit 1; } setup() { local group=$1 user=${2:-} gid ns dns_rules="" getent group "${group}" >/dev/null || groupadd "${group}" gid=$(getent group "${group}" | cut -d: -f3) if [[ -n ${user} ]] && ! id -nG "${user}" | tr ' ' '\n' | grep -qx "${group}"; then usermod -aG "${group}" "${user}" echo " ${user} added to group ${group} (log out and in, or use the new group via sg)" fi # The loopback resolver (systemd-resolved) is covered by the loopback rule; non-local # nameservers are allowed one by one, not port 53 anywhere while read -r ns; do case "${ns}" in 127.* | ::1) ;; *:*) dns_rules+=" meta skgid ${gid} ip6 daddr ${ns} meta l4proto { tcp, udp } th dport 53 accept"$'\n' ;; *) dns_rules+=" meta skgid ${gid} ip daddr ${ns} meta l4proto { tcp, udp } th dport 53 accept"$'\n' ;; esac done < <(awk '$1 == "nameserver" { print $2 }' /etc/resolv.conf) mkdir -p "${CONF_DIR}" cat >"${RULES}" <"${UNIT_PATH}" </dev/null 2>&1 systemctl restart "${UNIT}" echo "✓ netlock: group ${group} (gid ${gid}), unit ${UNIT} enabled" echo " allowed: 127.0.0.0/8, ::1${dns_rules:+, DNS servers from resolv.conf}" } teardown() { if systemctl list-unit-files "${UNIT}" >/dev/null 2>&1; then systemctl disable --now "${UNIT}" >/dev/null 2>&1 || true rm -f "${UNIT_PATH}" systemctl daemon-reload fi nft list table inet "${TABLE}" >/dev/null 2>&1 && nft delete table inet "${TABLE}" rm -rf "${CONF_DIR}" echo "✓ netlock removed (unit ${UNIT}, table ${TABLE}); the group is kept" } case "${1:-}" in setup) shift; setup "$@" ;; teardown) teardown ;; *) echo "usage: $0 setup [user] | teardown" >&2; exit 1 ;; esac