diff --git a/src/claude-code/NOTES.md b/src/claude-code/NOTES.md index 2f6b791..b8fbe88 100644 --- a/src/claude-code/NOTES.md +++ b/src/claude-code/NOTES.md @@ -35,37 +35,3 @@ If your container already has Node.js installed (for example, a container based When using with containers that have nvm pre-installed, you can use the Claude Code feature directly, and it will use the existing Node.js installation. -## Optional Network Firewall - -This feature includes a network firewall script that you can optionally enable to restrict outbound traffic to only essential services (GitHub, npm registry, Anthropic API, etc.). This improves security by limiting the container's network access. - -The firewall script is installed but not enabled by default. To enable the firewall, add these to your devcontainer.json: - -```json -"runArgs": [ - "--cap-add=NET_ADMIN", - "--cap-add=NET_RAW" -], -"postCreateCommand": "sudo /usr/local/bin/init-firewall.sh" -``` - -The firewall will be initialized when the container starts, blocking all outbound connections except to essential services. The allowed services include: - -- GitHub API, Git, and Web services -- npm registry -- Anthropic API -- Sentry.io -- Statsig services - -All other outbound connections will be blocked, providing an additional layer of security for your development environment. - -### How the Firewall Works - -The firewall uses iptables and ipset to: - -1. Create a whitelist of allowed domains and IP addresses -2. Allow all established connections and responses -3. Allow outbound DNS and SSH -4. Block all other outbound connections - -The script automatically resolves and adds the IP addresses for essential services to the whitelist. If you need to add additional domains to the allowed list, you can modify the firewall script at `/usr/local/bin/init-firewall.sh`. diff --git a/src/claude-code/init-firewall.sh b/src/claude-code/init-firewall.sh deleted file mode 100755 index e45908c..0000000 --- a/src/claude-code/init-firewall.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/bin/bash -set -euo pipefail # Exit on error, undefined vars, and pipeline failures -IFS=$'\n\t' # Stricter word splitting - -# Flush existing rules and delete existing ipsets -iptables -F -iptables -X -iptables -t nat -F -iptables -t nat -X -iptables -t mangle -F -iptables -t mangle -X -ipset destroy allowed-domains 2>/dev/null || true - -# First allow DNS and localhost before any restrictions -# Allow outbound DNS -iptables -A OUTPUT -p udp --dport 53 -j ACCEPT -# Allow inbound DNS responses -iptables -A INPUT -p udp --sport 53 -j ACCEPT -# Allow outbound SSH -iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT -# Allow inbound SSH responses -iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT -# Allow localhost -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 - -# Fetch GitHub meta information and aggregate + add their IP ranges -echo "Fetching GitHub IP ranges..." -gh_ranges=$(curl -s https://api.github.com/meta) -if [ -z "$gh_ranges" ]; then - echo "ERROR: Failed to fetch GitHub IP ranges" - exit 1 -fi - -if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then - echo "ERROR: GitHub API response missing required fields" - exit 1 -fi - -echo "Processing GitHub IPs..." -while read -r cidr; do - if [[ ! "$cidr" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then - 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 - echo "Resolving $domain..." - ips=$(dig +short A "$domain") - if [ -z "$ips" ]; then - echo "ERROR: Failed to resolve $domain" - exit 1 - fi - - while read -r ip; do - if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - echo "ERROR: Invalid IP from DNS for $domain: $ip" - exit 1 - fi - echo "Adding $ip for $domain" - ipset add allowed-domains "$ip" - done < <(echo "$ips") -done - -# Get host IP from default route -HOST_IP=$(ip route | grep default | cut -d" " -f3) -if [ -z "$HOST_IP" ]; then - echo "ERROR: Failed to detect host IP" - exit 1 -fi - -HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/") -echo "Host network detected as: $HOST_NETWORK" - -# Set up remaining iptables rules -iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT -iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT - -# Set default policies to DROP first -# Set default policies to DROP first -iptables -P INPUT DROP -iptables -P FORWARD DROP -iptables -P OUTPUT DROP - -# First allow established connections for already approved traffic -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 - -echo "Firewall configuration complete" -echo "Verifying firewall rules..." -if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then - echo "ERROR: Firewall verification failed - was able to reach https://example.com" - exit 1 -else - echo "Firewall verification passed - unable to reach https://example.com as expected" -fi - -# Verify GitHub API access -if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then - echo "ERROR: Firewall verification failed - unable to reach https://api.github.com" - exit 1 -else - echo "Firewall verification passed - able to reach https://api.github.com as expected" -fi diff --git a/src/claude-code/install.sh b/src/claude-code/install.sh index 314a4f9..b115c9e 100755 --- a/src/claude-code/install.sh +++ b/src/claude-code/install.sh @@ -87,61 +87,6 @@ install_nodejs() { fi } -# Map of package manager to required firewall packages -get_firewall_packages() { - local pkg_manager="$1" - - case "$pkg_manager" in - apt) echo "iptables ipset dnsutils jq curl aggregate" ;; - apk) echo "iptables ipset bind-tools jq curl aggregate" ;; - dnf|yum) echo "iptables ipset bind-utils jq curl aggregate" ;; - *) echo "" ;; - esac -} - -# Function to install firewall packages -install_firewall_packages() { - local pkg_manager="$1" - local packages=$(get_firewall_packages "$pkg_manager") - - if [ -z "$packages" ]; then - echo "WARNING: Could not determine firewall packages for this system type" - return 1 - fi - - echo "Installing firewall packages: $packages" - install_packages "$pkg_manager" $packages -} - -# Function to set up firewall script -setup_firewall_script() { - local script_path="/usr/local/bin/init-firewall.sh" - - echo "Setting up firewall initialization script..." - - # Create destination directory and copy the script - mkdir -p /usr/local/bin - cp "$(dirname "$0")/init-firewall.sh" "$script_path" - chmod +x "$script_path" - - cat </dev/null || ! command -v npm >/dev/null; then echo "Node.js or npm not found, attempting to install automatically..." @@ -193,9 +134,6 @@ main() { # Install Claude Code CLI install_claude_code || exit 1 - - # Always set up the firewall script, but don't execute it - setup_firewall_script } # Execute main function diff --git a/test/claude-code/scenarios.json b/test/claude-code/scenarios.json index 86213bf..0c894f0 100644 --- a/test/claude-code/scenarios.json +++ b/test/claude-code/scenarios.json @@ -5,17 +5,5 @@ "ghcr.io/devcontainers/features/node:1": {}, "claude-code": {} } - }, - "with-firewall": { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "ghcr.io/devcontainers/features/node:1": {}, - "claude-code": {} - }, - "runArgs": [ - "--cap-add=NET_ADMIN", - "--cap-add=NET_RAW" - ], - "postCreateCommand": "sudo /usr/local/bin/init-firewall.sh" } } \ No newline at end of file diff --git a/test/claude-code/test.sh b/test/claude-code/test.sh index 10e07de..f546f57 100755 --- a/test/claude-code/test.sh +++ b/test/claude-code/test.sh @@ -11,9 +11,5 @@ check "npm version" npm --version check "claude cli installed" command -v claude check "claude version" claude --version -# The firewall script should always exist now -check "firewall script exists" test -f /usr/local/bin/init-firewall.sh -check "firewall script is executable" test -x /usr/local/bin/init-firewall.sh - # Report results reportResults \ No newline at end of file diff --git a/test/claude-code/with-firewall.sh b/test/claude-code/with-firewall.sh deleted file mode 100755 index 08f9473..0000000 --- a/test/claude-code/with-firewall.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -set -e - -# Optional: Import test library -source dev-container-features-test-lib - -# Verify firewall script exists and is executable -if [ ! -f /usr/local/bin/init-firewall.sh ]; then - echo "ERROR: Firewall script not found at /usr/local/bin/init-firewall.sh" - check "firewall script exists" false - reportResults - exit 1 -fi - -if [ ! -x /usr/local/bin/init-firewall.sh ]; then - echo "ERROR: Firewall script is not executable" - check "firewall script is executable" false - reportResults - exit 1 -fi - -# Check if we can reach GitHub API (should be allowed) -if curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then - check "can reach GitHub API" true -else - echo "ERROR: Cannot reach GitHub API, which should be allowed" - check "can reach GitHub API" false -fi - -# Check if we can reach example.com (should be blocked) -if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then - echo "ERROR: Can reach example.com, which should be blocked" - check "example.com is blocked" false -else - check "example.com is blocked" true -fi - -# Report results -reportResults