Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 55 additions & 16 deletions .devcontainer/init-firewall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
IFS=$'\n\t' # Stricter word splitting

# Configuration - Domain list as shell array for easy maintenance
declare -a DYNAMIC_DOMAINS=(
Copy link

@MarkS-AL MarkS-AL Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow additional domains, eg "pypi.org" to be passed in externally, eg via WHITELIST_DOMAINS env var?
Concat the user whitelist with this list of domain names?

"registry.npmjs.org"
"api.anthropic.com"
"sentry.io"
"statsig.anthropic.com"
"statsig.com"
)

# IPSet configuration
IPSET_STATIC="allowed-static" # For GitHub and other static IPs
IPSET_DYNAMIC="allowed-dynamic" # For dynamically resolved domains
DNS_TTL=600 # DNS cache timeout in seconds

# 1. Extract Docker DNS info BEFORE any flushing
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)

Expand All @@ -12,6 +26,8 @@ iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ipset destroy "$IPSET_STATIC" 2>/dev/null || true
ipset destroy "$IPSET_DYNAMIC" 2>/dev/null || true
ipset destroy allowed-domains 2>/dev/null || true

# 2. Selectively restore ONLY internal Docker DNS resolution
Expand All @@ -37,8 +53,9 @@ iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Create ipset with CIDR support
ipset create allowed-domains hash:net
# Create ipsets
ipset create "$IPSET_STATIC" hash:net
ipset create "$IPSET_DYNAMIC" hash:ip timeout "$DNS_TTL"

# Fetch GitHub meta information and aggregate + add their IP ranges
echo "Fetching GitHub IP ranges..."
Expand All @@ -59,17 +76,11 @@ while read -r cidr; do
echo "ERROR: Invalid CIDR range from GitHub meta: $cidr"
exit 1
fi
echo "Adding GitHub range $cidr"
ipset add allowed-domains "$cidr"
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q)

# Resolve and add other allowed domains
for domain in \
"registry.npmjs.org" \
"api.anthropic.com" \
"sentry.io" \
"statsig.anthropic.com" \
"statsig.com"; do
ipset add "$IPSET_STATIC" "$cidr"
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q 2>/dev/null || echo "$gh_ranges" | jq -r '(.web + .api + .git)[]')

# Resolve and add other allowed domains with TTL
for domain in "${DYNAMIC_DOMAINS[@]}"; do
echo "Resolving $domain..."
ips=$(dig +short A "$domain")
if [ -z "$ips" ]; then
Expand All @@ -82,8 +93,7 @@ for domain in \
echo "ERROR: Invalid IP from DNS for $domain: $ip"
exit 1
fi
echo "Adding $ip for $domain"
ipset add allowed-domains "$ip"
ipset add "$IPSET_DYNAMIC" "$ip" timeout "$DNS_TTL" -exist
done < <(echo "$ips")
done

Expand Down Expand Up @@ -111,7 +121,36 @@ iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Then allow only specific outbound traffic to allowed domains
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
iptables -A OUTPUT -m set --match-set "$IPSET_STATIC" dst -j ACCEPT
iptables -A OUTPUT -m set --match-set "$IPSET_DYNAMIC" dst -j ACCEPT

# Create DNS refresh script
cat > /usr/local/bin/refresh-dynamic-domains.sh << EOF
#!/bin/bash
IPSET_DYNAMIC="$IPSET_DYNAMIC"
DNS_TTL=$DNS_TTL

# Domain list passed as arguments
for domain in $@; do
ips=\$(dig +short A "\$domain" 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\$')
if [ -n "\$ips" ]; then
while IFS= read -r ip; do
ipset add "\$IPSET_DYNAMIC" "\$ip" timeout "\$DNS_TTL" -exist 2>/dev/null
done <<< "\$ips"
fi
done
EOF

chmod +x /usr/local/bin/refresh-dynamic-domains.sh 2>/dev/null || true

# Setup cron job for automatic refresh
if command -v crontab &> /dev/null; then
DOMAIN_ARGS="${DYNAMIC_DOMAINS[@]}"
(crontab -l 2>/dev/null || true; echo "*/5 * * * * /usr/local/bin/refresh-dynamic-domains.sh $DOMAIN_ARGS") | \
grep -v refresh-dynamic-domains | \
(cat; echo "*/5 * * * * /usr/local/bin/refresh-dynamic-domains.sh $DOMAIN_ARGS") | \
crontab - 2>/dev/null || true
fi

echo "Firewall configuration complete"
echo "Verifying firewall rules..."
Expand Down