-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
Summary
The current agent container architecture adds and drops CAP_NET_ADMIN capability within the same entrypoint script. While functionally secure, this pattern could be improved by separating privileged setup from unprivileged command execution at the Docker layer level.
Current Architecture
The current flow in containers/agent/entrypoint.sh:
- Container starts with
NET_ADMINcapability entrypoint.shruns as root and sets up iptables rules (line 115)entrypoint.shdropsCAP_NET_ADMINusingcapsh --drop=cap_net_admin(line 144)gosu awfuserswitches to unprivileged user- User command executes
# Current: Single script handles both privileged setup AND privilege drop
exec capsh --drop=cap_net_admin -- -c "exec gosu awfuser $(printf '%q ' "$@")"Concerns:
- Same container layer handles both privilege escalation (iptables) and de-escalation (capability drop)
- Relies on runtime tooling (
capsh,gosu) to enforce security rather than Docker's built-in mechanisms - A bug or misconfiguration in the entrypoint could potentially skip the privilege drop
Proposed Solution: Init Container Pattern
Separate the privileged iptables setup into a dedicated init container, allowing the agent container to run without ever having NET_ADMIN capability.
Architecture
┌──────────────────────────────────────────────────────────────────────┐
│ awf-net (shared network namespace: 172.30.0.0/24) │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ awf-iptables-setup │ │ awf-agent │ │
│ │ (init container) │ │ │ │
│ │ │ │ │ │
│ │ - NET_ADMIN cap │ │ - NO capabilities │ │
│ │ - Runs as root │ │ - USER awfuser (Dockerfile) │ │
│ │ - Sets up iptables │ │ - Runs user command directly │ │
│ │ - Exits on success │ │ │ │
│ └─────────┬───────────┘ └─────────────────────────────────┘ │
│ │ ↑ │
│ │ depends_on: │ │
│ │ service_completed_successfully │
│ └──────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Docker Compose Changes
services:
awf-iptables-setup:
image: ghcr.io/githubnext/gh-aw-firewall/agent-setup:latest
container_name: awf-iptables-setup
network_mode: "service:awf-agent" # Share network namespace
cap_add:
- NET_ADMIN
environment:
- SQUID_IP=172.30.0.10
- SQUID_PORT=3128
command: ["/usr/local/bin/setup-iptables.sh"]
# Container exits after setup completes
awf-agent:
image: ghcr.io/githubnext/gh-aw-firewall/agent:latest
container_name: awf-agent
# NO cap_add - container never has NET_ADMIN
user: "awfuser" # Or via USER directive in Dockerfile
depends_on:
awf-iptables-setup:
condition: service_completed_successfully
command: ["user-command-here"]Dockerfile Changes
# containers/agent/Dockerfile
FROM ubuntu:22.04
# ... existing package installation ...
# Create non-root user
RUN groupadd -g 1000 awfuser && \
useradd -u 1000 -g 1000 -m -s /bin/bash awfuser
# ... existing setup ...
# Set user at Docker layer - container NEVER runs as root
USER awfuser
# Simple entrypoint - just run the command
ENTRYPOINT ["/bin/bash", "-c"]Benefits
- Defense in depth: Agent container never has
NET_ADMINcapability, even at startup - Explicit security boundary: Docker layer (
USER awfuser) enforces user, not runtime script - Auditable: Security posture visible in Dockerfile and docker-compose.yml, not hidden in entrypoint logic
- Reduced attack surface: No
capsh,gosuin the command execution path - Simpler agent container: Entrypoint becomes trivial - just execute the user's command
Challenges to Address
- Network namespace sharing: Docker Compose's
network_mode: "service:X"requires careful ordering - UID/GID runtime adjustment: Currently handled in entrypoint.sh; may need alternative approach
- Two container images: Need to publish and version both
agent-setupandagentimages - Backward compatibility: Existing users may have scripts expecting single-container behavior
Alternative: Simpler Two-Stage Entrypoint
If the init container pattern is too complex, a simpler improvement would be:
# Dockerfile
USER awfuser
# Entrypoint wrapper that:
# 1. Uses sudo/setpriv for iptables (not shell script dropping caps)
# 2. Then exec's to user command
ENTRYPOINT ["/usr/local/bin/entrypoint-wrapper"]This keeps single-container simplicity while making the USER directive explicit.
Acceptance Criteria
- Agent container does not have
NET_ADMINcapability during user command execution - Security posture is visible at Docker layer (USER directive or no cap_add)
- iptables rules are still correctly applied before user command runs
- UID/GID matching with host user still works
- All existing tests pass
- Documentation updated
References
- Security review finding: "It would be better to switch users using a Docker layer instead of relying entirely on entrypoint.sh"
- Current entrypoint.sh:
containers/agent/entrypoint.sh - Current Dockerfile:
containers/agent/Dockerfile
Reactions are currently unavailable