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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ A network firewall for agentic workflows with domain whitelisting. This tool pro
- **L7 Domain Whitelisting**: Control HTTP/HTTPS traffic at the application layer
- **Host-Level Enforcement**: Uses iptables DOCKER-USER chain to enforce firewall on ALL containers
- **Chroot Mode**: Transparent access to host binaries (Python, Node.js, Go) while maintaining network isolation
- **API Proxy Sidecar**: Optional Node.js-based proxy for secure LLM API credential management (OpenAI Codex, Anthropic Claude) that routes through Squid

## Requirements

Expand All @@ -33,6 +34,7 @@ The `--` separator divides firewall options from the command to run.
- [Quick start](docs/quickstart.md) — install, verify, and run your first command
- [Usage guide](docs/usage.md) — CLI flags, domain allowlists, examples
- [Chroot mode](docs/chroot-mode.md) — use host binaries with network isolation
- [API proxy sidecar](docs/api-proxy-sidecar.md) — secure credential management for LLM APIs
- [SSL Bump](docs/ssl-bump.md) — HTTPS content inspection for URL path filtering
- [GitHub Actions](docs/github_actions.md) — CI/CD integration and MCP server setup
- [Environment variables](docs/environment.md) — passing environment variables to containers
Expand Down
32 changes: 32 additions & 0 deletions containers/api-proxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Node.js API proxy for credential management
# Routes through Squid to respect domain whitelisting
FROM node:22-alpine

# Install curl for healthchecks
RUN apk add --no-cache curl

# Create app directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application files
COPY server.js ./

# Create non-root user
RUN addgroup -S apiproxy && adduser -S apiproxy -G apiproxy

# Switch to non-root user
USER apiproxy

# Expose ports
# 10000 - OpenAI API proxy
# 10001 - Anthropic API proxy
EXPOSE 10000 10001

# Start the proxy server
CMD ["node", "server.js"]
77 changes: 77 additions & 0 deletions containers/api-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# AWF API Proxy Sidecar

Node.js-based API proxy that keeps LLM API credentials isolated from the agent container while routing all traffic through Squid to respect domain whitelisting.

## Architecture

```
Agent Container (172.30.0.20)
↓ HTTP request to api-proxy:10000
API Proxy Sidecar (172.30.0.30)
↓ Injects Authorization header
↓ Routes via HTTP_PROXY (172.30.0.10:3128)
Squid Proxy (172.30.0.10)
↓ Domain whitelist enforcement
↓ TLS connection
api.openai.com or api.anthropic.com
```

## Features

- **Credential Isolation**: API keys held only in sidecar, never exposed to agent
- **Squid Routing**: All traffic routes through Squid via HTTP_PROXY/HTTPS_PROXY
- **Domain Whitelisting**: Squid enforces ACL filtering on all egress traffic
- **Header Injection**: Automatically adds Authorization and x-api-key headers
- **Health Checks**: /health endpoint on both ports

## Ports

- **10000**: OpenAI API proxy (api.openai.com)
- **10001**: Anthropic API proxy (api.anthropic.com)

## Environment Variables

Required (at least one):
- `OPENAI_API_KEY` - OpenAI API key for authentication
- `ANTHROPIC_API_KEY` - Anthropic API key for authentication

Set by AWF:
- `HTTP_PROXY` - Squid proxy URL (http://172.30.0.10:3128)
- `HTTPS_PROXY` - Squid proxy URL (http://172.30.0.10:3128)

## Security

- Runs as non-root user (apiproxy)
- All capabilities dropped (cap_drop: ALL)
- Memory limits (512MB)
- Process limits (100 PIDs)
- no-new-privileges security option

## Building

```bash
cd containers/api-proxy
docker build -t awf-api-proxy .
```

## Testing

```bash
# Start proxy with test key
docker run -p 10000:10000 \
-e OPENAI_API_KEY=sk-test123 \
-e HTTP_PROXY=http://squid:3128 \
-e HTTPS_PROXY=http://squid:3128 \
awf-api-proxy

# Test health endpoint
curl http://localhost:10000/health
```

## Implementation Details

- Built on Node.js 22 Alpine Linux
- Uses Express for HTTP server
- Uses http-proxy-middleware for proxying
- Naturally respects HTTP_PROXY/HTTPS_PROXY environment variables
- Simpler and more maintainable than Envoy configuration
16 changes: 16 additions & 0 deletions containers/api-proxy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "awf-api-proxy",
"version": "1.0.0",
"description": "API proxy sidecar for AWF - routes LLM API requests through Squid while injecting authentication headers",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"http-proxy-middleware": "^2.0.6"
},
"engines": {
"node": ">=18.0.0"
}
}
112 changes: 112 additions & 0 deletions containers/api-proxy/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env node

/**
* AWF API Proxy Sidecar
*
* Node.js-based proxy that:
* 1. Keeps LLM API credentials isolated from agent container
* 2. Routes all traffic through Squid via HTTP_PROXY/HTTPS_PROXY
* 3. Injects authentication headers (Authorization, x-api-key)
* 4. Respects domain whitelisting enforced by Squid
*/

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

// Read API keys from environment (set by docker-compose)
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;

// Squid proxy configuration (set via HTTP_PROXY/HTTPS_PROXY in docker-compose)
const HTTP_PROXY = process.env.HTTP_PROXY;
const HTTPS_PROXY = process.env.HTTPS_PROXY;

console.log('[API Proxy] Starting AWF API proxy sidecar...');
console.log(`[API Proxy] HTTP_PROXY: ${HTTP_PROXY}`);
console.log(`[API Proxy] HTTPS_PROXY: ${HTTPS_PROXY}`);
if (OPENAI_API_KEY) {
console.log('[API Proxy] OpenAI API key configured');
}
if (ANTHROPIC_API_KEY) {
console.log('[API Proxy] Anthropic API key configured');
}

// Create Express app
const app = express();

// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
service: 'awf-api-proxy',
squid_proxy: HTTP_PROXY || 'not configured',
providers: {
openai: !!OPENAI_API_KEY,
anthropic: !!ANTHROPIC_API_KEY
}
});
});

// OpenAI API proxy (port 10000)
if (OPENAI_API_KEY) {
app.use(createProxyMiddleware({
target: 'https://api.openai.com',
changeOrigin: true,
secure: true,
onProxyReq: (proxyReq, req, res) => {
// Inject Authorization header
proxyReq.setHeader('Authorization', `Bearer ${OPENAI_API_KEY}`);
console.log(`[OpenAI Proxy] ${req.method} ${req.url}`);
},
onError: (err, req, res) => {
console.error(`[OpenAI Proxy] Error: ${err.message}`);
res.status(502).json({ error: 'Proxy error', message: err.message });
}
}));

app.listen(10000, '0.0.0.0', () => {
console.log('[API Proxy] OpenAI proxy listening on port 10000');
console.log('[API Proxy] Routing through Squid to api.openai.com');
});
}

// Anthropic API proxy (port 10001)
if (ANTHROPIC_API_KEY) {
const anthropicApp = express();

anthropicApp.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy', service: 'anthropic-proxy' });
});

anthropicApp.use(createProxyMiddleware({
target: 'https://api.anthropic.com',
changeOrigin: true,
secure: true,
onProxyReq: (proxyReq, req, res) => {
// Inject Anthropic authentication headers
proxyReq.setHeader('x-api-key', ANTHROPIC_API_KEY);
proxyReq.setHeader('anthropic-version', '2023-06-01');
console.log(`[Anthropic Proxy] ${req.method} ${req.url}`);
},
onError: (err, req, res) => {
console.error(`[Anthropic Proxy] Error: ${err.message}`);
res.status(502).json({ error: 'Proxy error', message: err.message });
}
}));

anthropicApp.listen(10001, '0.0.0.0', () => {
console.log('[API Proxy] Anthropic proxy listening on port 10001');
console.log('[API Proxy] Routing through Squid to api.anthropic.com');
});
}

// Graceful shutdown
process.on('SIGTERM', () => {
console.log('[API Proxy] Received SIGTERM, shutting down gracefully...');
process.exit(0);
});

process.on('SIGINT', () => {
console.log('[API Proxy] Received SIGINT, shutting down gracefully...');
process.exit(0);
});
Loading
Loading