Skip to content

Commit d5737be

Browse files
CopilotMossakaclaude
authored
fix(docker): drop NET_ADMIN capability after iptables setup (#133)
* Initial plan * fix(security): drop NET_ADMIN capability after iptables setup Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * ci: disable Docker cache for agent image builds Ensure security-critical packages like libcap2-bin (required for CAP_NET_ADMIN dropping via capsh) are always freshly installed during release builds. This prevents stale cached layers from potentially missing security dependencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> Co-authored-by: Jiaxiao (mossaka) Zhou <duibao55328@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 212c938 commit d5737be

8 files changed

Lines changed: 33 additions & 15 deletions

File tree

.github/workflows/release.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ jobs:
9999
tags: |
100100
ghcr.io/${{ github.repository }}/agent:${{ steps.version_early.outputs.version_number }}
101101
ghcr.io/${{ github.repository }}/agent:latest
102-
cache-from: type=gha
103-
cache-to: type=gha,mode=max
102+
# Disable cache for agent image to ensure security-critical packages
103+
# (like libcap2-bin for capability dropping) are always freshly installed
104+
no-cache: true
104105

105106
- name: Sign Agent image with cosign
106107
run: |

AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,11 @@ The codebase follows a modular architecture with clear separation of concerns:
208208
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm, docker-cli
209209
- Mounts entire host filesystem at `/host` and user home directory for full access
210210
- Mounts Docker socket (`/var/run/docker.sock`) for docker-in-docker support
211-
- `NET_ADMIN` capability required for iptables manipulation
211+
- `NET_ADMIN` capability required for iptables setup during initialization
212+
- **Security:** `NET_ADMIN` is dropped via `capsh --drop=cap_net_admin` before executing user commands, preventing malicious code from modifying iptables rules
212213
- Two-stage entrypoint:
213214
1. `setup-iptables.sh`: Configures iptables NAT rules to redirect HTTP/HTTPS traffic to Squid (agent container only)
214-
2. `entrypoint.sh`: Tests connectivity, then executes user command
215+
2. `entrypoint.sh`: Drops NET_ADMIN capability, then executes user command as non-root user
215216
- **Docker Wrapper** (`docker-wrapper.sh`): Intercepts `docker run` commands to inject network and proxy configuration
216217
- Symlinked at `/usr/bin/docker` (real docker at `/usr/bin/docker-real`)
217218
- Automatically injects `--network awf-net` to all spawned containers

CLAUDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,11 @@ The codebase follows a modular architecture with clear separation of concerns:
179179
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm, docker-cli
180180
- Mounts entire host filesystem at `/host` and user home directory for full access
181181
- Mounts Docker socket (`/var/run/docker.sock`) for docker-in-docker support
182-
- `NET_ADMIN` capability required for iptables manipulation
182+
- `NET_ADMIN` capability required for iptables setup during initialization
183+
- **Security:** `NET_ADMIN` is dropped via `capsh --drop=cap_net_admin` before executing user commands, preventing malicious code from modifying iptables rules
183184
- Two-stage entrypoint:
184185
1. `setup-iptables.sh`: Configures iptables NAT rules to redirect HTTP/HTTPS traffic to Squid (agent container only)
185-
2. `entrypoint.sh`: Tests connectivity, then executes user command
186+
2. `entrypoint.sh`: Drops NET_ADMIN capability, then executes user command as non-root user
186187
- **Docker Wrapper** (`docker-wrapper.sh`): Intercepts `docker run` commands to inject network and proxy configuration
187188
- Symlinked at `/usr/bin/docker` (real docker at `/usr/bin/docker-real`)
188189
- Automatically injects `--network awf-net` to all spawned containers

containers/agent/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ RUN apt-get update && \
1111
dnsutils \
1212
net-tools \
1313
netcat-openbsd \
14-
gosu && \
14+
gosu \
15+
libcap2-bin && \
1516
# Install Node.js 22 from NodeSource
1617
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
1718
apt-get install -y nodejs && \

containers/agent/entrypoint.sh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,16 @@ fi
149149
runuser -u awfuser -- git config --global --add safe.directory '*' 2>/dev/null || true
150150

151151
echo "[entrypoint] =================================="
152-
echo "[entrypoint] Dropping privileges to awfuser (UID: $(id -u awfuser), GID: $(id -g awfuser))"
152+
echo "[entrypoint] Dropping CAP_NET_ADMIN capability and privileges to awfuser (UID: $(id -u awfuser), GID: $(id -g awfuser))"
153153
echo "[entrypoint] Executing command: $@"
154154
echo ""
155155

156-
# Drop privileges and execute the provided command as awfuser
157-
# Using gosu instead of su/sudo for cleaner signal handling
158-
exec gosu awfuser "$@"
156+
# Drop CAP_NET_ADMIN capability and privileges, then execute the user command
157+
# This prevents malicious code from modifying iptables rules to bypass the firewall
158+
# Security note: capsh --drop removes the capability from the bounding set,
159+
# preventing any process (even if it escalates to root) from acquiring it
160+
# The order of operations:
161+
# 1. capsh drops CAP_NET_ADMIN from the bounding set (cannot be regained)
162+
# 2. gosu switches to awfuser (drops root privileges)
163+
# 3. exec replaces the current process with the user command
164+
exec capsh --drop=cap_net_admin -- -c "exec gosu awfuser $(printf '%q ' "$@")"

docs/architecture.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ The firewall uses a containerized architecture with Squid proxy for L7 (HTTP/HTT
7878
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm, docker-cli
7979
- Mounts entire host filesystem at `/host` and user home directory for full access
8080
- Mounts Docker socket (`/var/run/docker.sock`) for docker-in-docker support
81-
- `NET_ADMIN` capability required for iptables manipulation
81+
- `NET_ADMIN` capability required for iptables setup during initialization
82+
- **Security:** `NET_ADMIN` is dropped via `capsh --drop=cap_net_admin` before executing user commands, preventing malicious code from modifying iptables rules
8283
- Two-stage entrypoint:
8384
1. `setup-iptables.sh`: Configures iptables NAT rules to redirect HTTP/HTTPS traffic to Squid (agent container only)
84-
2. `entrypoint.sh`: Tests connectivity, then executes user command
85+
2. `entrypoint.sh`: Drops NET_ADMIN capability, then executes user command as non-root user
8586
- **Docker Wrapper** (`docker-wrapper.sh`): Intercepts `docker run` commands to inject network and proxy configuration
8687
- Symlinked at `/usr/bin/docker` (real docker at `/usr/bin/docker-real`)
8788
- Automatically injects `--network awf-net` to all spawned containers

src/docker-manager.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ describe('docker-manager', () => {
166166
expect(depends['squid-proxy'].condition).toBe('service_healthy');
167167
});
168168

169-
it('should add NET_ADMIN capability to agent', () => {
169+
it('should add NET_ADMIN capability to agent for iptables setup', () => {
170+
// NET_ADMIN is required at container start for setup-iptables.sh
171+
// The capability is dropped before user command execution via capsh
172+
// (see containers/agent/entrypoint.sh)
170173
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
171174
const agent = result.services.agent;
172175

src/docker-manager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,11 @@ export function generateDockerCompose(
304304
condition: 'service_healthy',
305305
},
306306
},
307-
cap_add: ['NET_ADMIN'], // Required for iptables
307+
// NET_ADMIN is required for iptables setup in entrypoint.sh.
308+
// Security: The capability is dropped before running user commands
309+
// via 'capsh --drop=cap_net_admin' in containers/agent/entrypoint.sh.
310+
// This prevents malicious code from modifying iptables rules.
311+
cap_add: ['NET_ADMIN'],
308312
// Drop capabilities to reduce attack surface (security hardening)
309313
cap_drop: [
310314
'NET_RAW', // Prevents raw socket creation (iptables bypass attempts)

0 commit comments

Comments
 (0)