Skip to content
Merged
Show file tree
Hide file tree
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
21 changes: 20 additions & 1 deletion bin/ci/setup-arch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ set -euo pipefail
echo "=== [Arch] Installing base CI deps ==="
pacman -Sy --noconfirm --needed git jq sudo 2>&1 | tail -3

echo "=== [Arch] Ensuring iptables backend works ==="
if iptables -w -L OUTPUT -n >/dev/null 2>&1; then
echo " iptables backend OK"
else
IPTABLES_NFT_BIN="$(command -v iptables-nft 2>/dev/null || true)"
if [ -n "$IPTABLES_NFT_BIN" ] && "$IPTABLES_NFT_BIN" -w -L OUTPUT -n >/dev/null 2>&1; then
# Some Arch images ship iptables defaulting to legacy backend without
# ip_tables support. Force nft backend so setup-firewall.sh can apply rules.
ln -sf "$IPTABLES_NFT_BIN" /usr/local/sbin/iptables
ln -sf "$IPTABLES_NFT_BIN" /usr/local/bin/iptables
echo " iptables legacy unavailable; forced iptables-nft shim"
else
echo "❌ No working iptables backend found" >&2
exit 1
fi
fi

echo "=== Preparing source ==="
useradd -m -s /bin/bash baudbot_admin
mkdir -p /home/baudbot_admin/baudbot
Expand All @@ -29,8 +46,10 @@ BAUDBOT_BOOTSTRAP_TARGET="/usr/local/bin/baudbot" \
# Prompts: admin user, LLM choice(1=Anthropic), Anthropic key,
# Slack mode(2=advanced), Slack bot, Slack app, Slack users,
# Browser?(n), Sentry?(n), launch(n)
# Arch CI droplets frequently lack netfilter modules required by setup-firewall;
# skip firewall bootstrap here to keep install/integration coverage stable.
printf 'baudbot_admin\n1\nsk-ant-testkey\n2\nxoxb-test\nxapp-test\nU01TEST\nn\nn\nn\n' \
| BAUDBOT_INSTALL_SCRIPT_URL="file:///home/baudbot_admin/baudbot/install.sh" baudbot install
| BAUDBOT_SKIP_FIREWALL=1 BAUDBOT_INSTALL_SCRIPT_URL="file:///home/baudbot_admin/baudbot/install.sh" baudbot install

echo "=== Verifying install ==="
# .env exists with correct permissions
Expand Down
95 changes: 55 additions & 40 deletions bin/setup-firewall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,37 @@ if [ -z "$UID_BAUDBOT" ]; then
fi

CHAIN="BAUDBOT_OUTPUT"
IPTABLES_BIN="iptables"
if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -w -L OUTPUT -n >/dev/null 2>&1; then
IPTABLES_BIN="iptables-nft"
fi

fw() {
"$IPTABLES_BIN" -w "$@"
}

add_optional_rule() {
if ! fw "$@"; then
echo "⚠️ Optional firewall rule unsupported by kernel, skipping: $IPTABLES_BIN -w $*" >&2
fi
}

echo "🔒 Setting up firewall rules for $BAUDBOT_AGENT_USER (uid $UID_BAUDBOT)..."
echo " backend: $IPTABLES_BIN"

# Clean up any existing rules first
iptables -w -D OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" 2>/dev/null || true
iptables -w -F "$CHAIN" 2>/dev/null || true
iptables -w -X "$CHAIN" 2>/dev/null || true
fw -D OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" 2>/dev/null || true
fw -F "$CHAIN" 2>/dev/null || true
fw -X "$CHAIN" 2>/dev/null || true

# Create a dedicated chain for baudbot_agent
iptables -w -N "$CHAIN"
fw -N "$CHAIN"

# ── Logging (SYN + DNS only — low volume) ────────────────────────────────────
# Log all new outbound connections (SYN packets only to avoid flooding)
iptables -w -A "$CHAIN" -p tcp --syn -j LOG --log-prefix "baudbot-out: " --log-level info
# Log DNS queries
iptables -w -A "$CHAIN" -p udp --dport 53 -j LOG --log-prefix "baudbot-dns: " --log-level info
# Some kernels (notably certain cloud Arch images) lack optional LOG/tcp xtables
# modules. Treat logging rules as best-effort; the allow/drop policy is mandatory.
add_optional_rule -A "$CHAIN" -p tcp --syn -j LOG --log-prefix "baudbot-out: " --log-level info
add_optional_rule -A "$CHAIN" -p udp --dport 53 -j LOG --log-prefix "baudbot-dns: " --log-level info

# ── Localhost: allow only specific services ──────────────────────────────────

Expand All @@ -57,76 +72,76 @@ iptables -w -A "$CHAIN" -p udp --dport 53 -j LOG --log-prefix "baudbot-dns: " --
# 4000-4999: Astro (4321), Remix (4200), Nuxt, etc.
# 5000-5999: Vite (5173), Flask, generic dev servers
# 6000-6099: Storybook (6006), Expo (6100 range is X11 — skip 6063+)
iptables -w -A "$CHAIN" -o lo -p tcp --dport 3000:5999 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 6006 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 3000:5999 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 6006 -j ACCEPT

# ── Databases ────────────────────────────────────────────────────────────
# 5432: PostgreSQL (native)
# 6379: Redis
# 27017: MongoDB
# 54322: PostgreSQL (Docker-mapped)
iptables -w -A "$CHAIN" -o lo -p tcp --dport 5432 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 6379 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 27017 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 54322 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 5432 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 6379 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 27017 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 54322 -j ACCEPT

# ── Infrastructure ───────────────────────────────────────────────────────
# 7890: Slack bridge
# 8000-9999: Wrangler (8787), Django/FastAPI (8000), inspector (9229+), MinIO (9000)
# 11434: Ollama
# 24678: Vite HMR websocket
iptables -w -A "$CHAIN" -o lo -p tcp --dport 7890 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 8000:9999 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 11434 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 24678 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 7890 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 8000:9999 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 11434 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 24678 -j ACCEPT

# Allow DNS on localhost
iptables -w -A "$CHAIN" -o lo -p udp --dport 53 -j ACCEPT
iptables -w -A "$CHAIN" -o lo -p tcp --dport 53 -j ACCEPT
fw -A "$CHAIN" -o lo -p udp --dport 53 -j ACCEPT
fw -A "$CHAIN" -o lo -p tcp --dport 53 -j ACCEPT

# Allow localhost responses (established connections back to us)
iptables -w -A "$CHAIN" -o lo -m state --state ESTABLISHED,RELATED -j ACCEPT
fw -A "$CHAIN" -o lo -m state --state ESTABLISHED,RELATED -j ACCEPT

# Block everything else on localhost
iptables -w -A "$CHAIN" -o lo -j LOG --log-prefix "BAUDBOT_LOCAL_BLOCKED: " --log-level 4
iptables -w -A "$CHAIN" -o lo -j DROP
add_optional_rule -A "$CHAIN" -o lo -j LOG --log-prefix "BAUDBOT_LOCAL_BLOCKED: " --log-level 4
fw -A "$CHAIN" -o lo -j DROP

# ── Internet: allow standard + dev ports ─────────────────────────────────────

# DNS (UDP + TCP)
iptables -w -A "$CHAIN" -p udp --dport 53 -j ACCEPT
iptables -w -A "$CHAIN" -p tcp --dport 53 -j ACCEPT
fw -A "$CHAIN" -p udp --dport 53 -j ACCEPT
fw -A "$CHAIN" -p tcp --dport 53 -j ACCEPT

# HTTP/HTTPS (web, APIs, cloud services)
iptables -w -A "$CHAIN" -p tcp --dport 80 -j ACCEPT
iptables -w -A "$CHAIN" -p tcp --dport 443 -j ACCEPT
fw -A "$CHAIN" -p tcp --dport 80 -j ACCEPT
fw -A "$CHAIN" -p tcp --dport 443 -j ACCEPT

# SSH (git push/pull)
iptables -w -A "$CHAIN" -p tcp --dport 22 -j ACCEPT
fw -A "$CHAIN" -p tcp --dport 22 -j ACCEPT

# Cloud databases (Neon, Supabase, RDS, PlanetScale, Atlas, Upstash, etc.)
iptables -w -A "$CHAIN" -p tcp --dport 3306 -j ACCEPT # MySQL / PlanetScale
iptables -w -A "$CHAIN" -p tcp --dport 5432:5433 -j ACCEPT # PostgreSQL / Neon
iptables -w -A "$CHAIN" -p tcp --dport 6543 -j ACCEPT # Supabase pooler
iptables -w -A "$CHAIN" -p tcp --dport 6379 -j ACCEPT # Redis Cloud / Upstash
iptables -w -A "$CHAIN" -p tcp --dport 27017 -j ACCEPT # MongoDB Atlas
fw -A "$CHAIN" -p tcp --dport 3306 -j ACCEPT # MySQL / PlanetScale
fw -A "$CHAIN" -p tcp --dport 5432:5433 -j ACCEPT # PostgreSQL / Neon
fw -A "$CHAIN" -p tcp --dport 6543 -j ACCEPT # Supabase pooler
fw -A "$CHAIN" -p tcp --dport 6379 -j ACCEPT # Redis Cloud / Upstash
fw -A "$CHAIN" -p tcp --dport 27017 -j ACCEPT # MongoDB Atlas

# Observability (OpenTelemetry OTLP)
iptables -w -A "$CHAIN" -p tcp --dport 4317:4318 -j ACCEPT
fw -A "$CHAIN" -p tcp --dport 4317:4318 -j ACCEPT

# Allow established/related (responses to allowed outbound)
iptables -w -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT
fw -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT

# Log and drop everything else
iptables -w -A "$CHAIN" -j LOG --log-prefix "BAUDBOT_BLOCKED: " --log-level 4
iptables -w -A "$CHAIN" -j DROP
add_optional_rule -A "$CHAIN" -j LOG --log-prefix "BAUDBOT_BLOCKED: " --log-level 4
fw -A "$CHAIN" -j DROP

# Jump to our chain for all baudbot_agent traffic
iptables -w -A OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN"
fw -A OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN"

echo "✅ Firewall active. Rules:"
echo ""
iptables -w -L "$CHAIN" -n -v --line-numbers
fw -L "$CHAIN" -n -v --line-numbers
echo ""
echo "Localhost allowed: 3000-5999 (dev servers), 5432 (pg), 6006 (storybook),"
echo " 6379 (redis), 7890 (bridge), 8000-9999 (wrangler/inspector),"
Expand All @@ -137,6 +152,6 @@ echo " 3306 (mysql), 4317-4318 (otlp), 5432-5433 (pg),"
echo " 6379 (redis), 6543 (supabase), 27017 (mongo)"
echo "Everything else: BLOCKED + LOGGED"
echo ""
echo "To remove: sudo iptables -w -D OUTPUT -m owner --uid-owner $UID_BAUDBOT -j $CHAIN && sudo iptables -w -F $CHAIN && sudo iptables -w -X $CHAIN"
echo "To remove: sudo $IPTABLES_BIN -w -D OUTPUT -m owner --uid-owner $UID_BAUDBOT -j $CHAIN && sudo $IPTABLES_BIN -w -F $CHAIN && sudo $IPTABLES_BIN -w -X $CHAIN"
echo ""
echo "Persistence: baudbot-firewall.service (systemd)"
60 changes: 52 additions & 8 deletions bin/update-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,53 @@ source "$SCRIPT_DIR/lib/release-runtime-common.sh"
# shellcheck source=bin/lib/json-common.sh
source "$SCRIPT_DIR/lib/json-common.sh"

# ---------------------------------------------------------------------------
# Resolve the full path to npm. This script runs as root (sudo) where the
# embedded Node runtime is *not* on PATH. Resolution order:
# 1. Agent user's embedded runtime (/home/baudbot_agent/opt/node/bin/npm)
# 2. SUDO_USER's home (admin may have mise/nvm/etc.)
# 3. Standard PATH lookup (last resort)
# ---------------------------------------------------------------------------
resolve_npm_bin() {
local candidate=""

# 1) Agent's embedded runtime
local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}"
candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)"
if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then
echo "$candidate/npm"
return 0
fi

# 2) SUDO_USER's home (admin's local node install — mise, nvm, etc.)
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
local sudo_home=""
sudo_home="$(bb_resolve_user_home "$SUDO_USER" 2>/dev/null || true)"
if [ -n "$sudo_home" ]; then
local dir=""
for dir in \
"$sudo_home/.local/share/mise/shims" \
"$sudo_home/.nvm/versions/node"/*/bin \
"$sudo_home/.local/bin"; do
# Skip unexpanded globs.
case "$dir" in *\**) continue ;; esac
if [ -x "$dir/npm" ]; then
echo "$dir/npm"
return 0
fi
done
fi
fi

# 3) PATH (may work in non-sudo environments or CI)
if command -v npm >/dev/null 2>&1; then
command -v npm
return 0
fi

return 1
}

cleanup() {
if [ -n "$CHECKOUT_DIR" ] && [ -d "$CHECKOUT_DIR" ]; then
rm -rf "$CHECKOUT_DIR"
Expand Down Expand Up @@ -221,14 +268,11 @@ install_release_bridge_dependencies() {
log "installing production Slack bridge dependencies in release"
rm -rf "$bridge_dir/node_modules"

# Resolve npm from the agent's embedded node runtime, falling back to PATH.
local npm_bin="npm"
local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}"
local node_bin_dir=""
node_bin_dir="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)"
if [ -n "$node_bin_dir" ] && [ -x "$node_bin_dir/npm" ]; then
npm_bin="$node_bin_dir/npm"
fi
# Resolve npm via the embedded Node runtime. update-release runs as root
# (sudo) so bare `npm` / `node` will not be on PATH — we must resolve the
# full path. Try: agent home → SUDO_USER home → PATH (last resort).
local npm_bin=""
npm_bin="$(resolve_npm_bin)" || die "cannot find npm; install Node for the agent (see setup.sh) or ensure npm is on PATH"

if [ -f "$bridge_dir/package-lock.json" ]; then
(cd "$bridge_dir" && "$npm_bin" ci --omit=dev)
Expand Down
Loading