┌─────────────────────────────────────────────────────────────────┐
│ AWS VPC (10.0.0.0/16) │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Public Subnet │ │ Private Subnet │ │
│ │ (10.0.0.0/24) │ │ (10.0.1.0/24) │ │
│ │ │ │ │ │
│ │ ┌───────────────┐ │ │ ┌─────────────────────────────┐│ │
│ │ │ Web Proxy │ │ │ │ OpenHands Server ││ │
│ │ │ - Nginx │ │ │ │ - OpenHands App ││ │
│ │ │ - SSL Term │◄─┼────┼──┤ - Docker ││ │
│ │ │ - Basic Auth │ │ │ │ - Python Environment ││ │
│ │ └───────────────┘ │ │ └─────────────────────────────┘│ │
│ │ │ │ │ │ │ │
│ └─────────┼────────────┘ └──────────────┼──────────────────┘ │
│ │ │ │
│ ┌─────────▼────────────┐ ┌──────────────▼──────────────────┐ │
│ │ Internet Gateway │ │ NAT Gateway │ │
│ └──────────────────────┘ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Internet │ │ LLM Endpoints │
│ (Users) │ │ (OpenAI, etc.) │
└───────────────┘ └─────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Security Architecture │
├─────────────────────────────────────────────────────────────────┤
│ Layer 1: AWS Security Groups (Application Firewall) │
│ Layer 2: Network ACLs (Subnet-level Controls) │
│ Layer 3: Host-level iptables (Packet Filtering) │
│ Layer 4: Application Authentication (Basic Auth + SSL) │
│ Layer 5: System-level Access Controls (Linux Users) │
└─────────────────────────────────────────────────────────────────┘
| Component | Specification | Purpose |
|---|---|---|
| VPC | 10.0.0.0/16 | Network isolation |
| Public Subnet | 10.0.0.0/24 | Web proxy hosting |
| Private Subnet | 10.0.1.0/24 | OpenHands server hosting |
| Internet Gateway | Standard | Public internet access |
| NAT Gateway | Standard | Controlled outbound access |
| Web Proxy | t3.small, Ubuntu 22.04 | Reverse proxy, SSL termination |
| OpenHands Server | t3.large, Ubuntu 22.04 | AI assistant hosting |
Inbound Rules:
Port 22 (SSH) ← Your IP only
Port 80 (HTTP) ← Your IP (or 0.0.0.0/0 for broader access)
Port 443 (HTTPS) ← Your IP (or 0.0.0.0/0 for broader access)
Outbound Rules:
Port 22 → 10.0.1.0/24 (SSH to OpenHands server)
Port 3000 → 10.0.1.0/24 (OpenHands web interface)
Port 443 → 0.0.0.0/0 (SSL certificates, updates)
Inbound Rules:
Port 22 ← openhands-web-proxy-sg (SSH from proxy)
Port 3000 ← openhands-web-proxy-sg (Web interface)
Outbound Rules:
Port 443 → 0.0.0.0/0 (HTTPS - restricted by iptables)
Port 53 → 10.0.0.0/16 (DNS to VPC resolver)
Port 123 → 0.0.0.0/0 (NTP time sync)
# AWS Console: VPC → Create VPC
Name: openhands-isolated-vpc
IPv4 CIDR: 10.0.0.0/16
IPv6 CIDR: No IPv6 CIDR block
Tenancy: Default# Public Subnet
Name: openhands-public-subnet
VPC: openhands-isolated-vpc
Availability Zone: us-west-2a (or your preferred AZ)
IPv4 CIDR: 10.0.0.0/24
# Private Subnet
Name: openhands-private-subnet
VPC: openhands-isolated-vpc
Availability Zone: us-west-2a
IPv4 CIDR: 10.0.1.0/24# AWS Console: VPC → Internet Gateways
Name: openhands-igw
Attach to VPC: openhands-isolated-vpc# AWS Console: VPC → NAT Gateways
Name: openhands-nat-gw
Subnet: openhands-public-subnet
Connectivity type: Public
Elastic IP allocation: Create new EIP# Public Route Table
Name: openhands-public-rt
Routes:
- 10.0.0.0/16 → Local
- 0.0.0.0/0 → openhands-igw
Associate with: openhands-public-subnet
# Private Route Table
Name: openhands-private-rt
Routes:
- 10.0.0.0/16 → Local
- 0.0.0.0/0 → openhands-nat-gw
Associate with: openhands-private-subnet# AWS Console: EC2 → Security Groups → Create
Name: openhands-web-proxy-sg
Description: Security group for OpenHands web proxy
VPC: openhands-isolated-vpc
# Inbound Rules
Type: SSH, Protocol: TCP, Port: 22, Source: My IP
Type: HTTP, Protocol: TCP, Port: 80, Source: My IP
Type: HTTPS, Protocol: TCP, Port: 443, Source: My IP
# Outbound Rules (remove default, add these)
Type: SSH, Protocol: TCP, Port: 22, Destination: 10.0.1.0/24
Type: Custom TCP, Protocol: TCP, Port: 3000, Destination: 10.0.1.0/24
Type: HTTPS, Protocol: TCP, Port: 443, Destination: 0.0.0.0/0# AWS Console: EC2 → Security Groups → Create
Name: openhands-server-sg
Description: Security group for isolated OpenHands server
VPC: openhands-isolated-vpc
# Inbound Rules
Type: SSH, Protocol: TCP, Port: 22, Source: openhands-web-proxy-sg
Type: Custom TCP, Protocol: TCP, Port: 3000, Source: openhands-web-proxy-sg
# Outbound Rules (remove default, add these)
Type: HTTPS, Protocol: TCP, Port: 443, Destination: 0.0.0.0/0
Type: DNS (UDP), Protocol: UDP, Port: 53, Destination: 10.0.0.0/16
Type: NTP, Protocol: UDP, Port: 123, Destination: 0.0.0.0/0# AWS Console: EC2 → Key Pairs → Create
Name: openhands-keypair
Key pair type: RSA
Private key file format: .pem
# Download and secure the .pem fileInstance Configuration:
Name: openhands-web-proxy
AMI: Ubuntu Server 22.04 LTS (ami-0c2d3e23b7e8a4b8d)
Instance type: t3.small
Key pair: openhands-keypair
VPC: openhands-isolated-vpc
Subnet: openhands-public-subnet
Auto-assign public IP: Enable
Security groups: openhands-web-proxy-sgUser Data Script:
#!/bin/bash
apt-get update && apt-get upgrade -y
# Install packages
apt-get install -y nginx certbot python3-certbot-nginx fail2ban apache2-utils ufw
# Configure firewall
ufw allow 22 && ufw allow 80 && ufw allow 443 && ufw --force enable
# Create user
useradd -m -s /bin/bash openhands
echo "openhands:$(openssl rand -base64 32)" | chpasswd
echo "openhands:$(openssl rand -base64 32)" | tee -a /var/log/setup.log
# Create htpasswd
htpasswd -cb /etc/nginx/.htpasswd openhands $(openssl rand -base64 32)
# Nginx configuration
cat > /etc/nginx/sites-available/openhands << 'EOF'
server {
listen 80;
server_name _;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name _;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
ssl_protocols TLSv1.2 TLSv1.3;
auth_basic "OpenHands Access";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://OPENHANDS_PRIVATE_IP:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
EOF
# Create SSL certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
# Update script
cat > /root/update_openhands_ip.sh << 'EOF'
#!/bin/bash
if [ "$1" ]; then
sed -i "s/OPENHANDS_PRIVATE_IP/$1/g" /etc/nginx/sites-available/openhands
ln -sf /etc/nginx/sites-available/openhands /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
echo "Updated OpenHands IP to $1"
fi
EOF
chmod +x /root/update_openhands_ip.sh
# Configure fail2ban
systemctl enable fail2ban nginx
systemctl start fail2ban nginxInstance Configuration:
Name: openhands-server
AMI: Ubuntu Server 22.04 LTS
Instance type: t3.large
Key pair: openhands-keypair
VPC: openhands-isolated-vpc
Subnet: openhands-private-subnet
Auto-assign public IP: Disable
Security groups: openhands-server-sgUser Data Script:
#!/bin/bash
set -e
# Update system
apt-get update && apt-get upgrade -y
# Install packages
apt-get install -y python3 python3-pip python3-venv nodejs npm docker.io \
docker-compose git curl wget ufw iptables-persistent dnsutils fail2ban
# Create user
useradd -m -s /bin/bash openhands
echo "openhands:$(openssl rand -base64 32)" | chpasswd
usermod -aG docker,sudo openhands
# Configure firewall
ufw --force reset
ufw default deny incoming && ufw default deny outgoing
ufw allow from 10.0.0.0/24 to any port 22
ufw allow from 10.0.0.0/24 to any port 3000
ufw allow out 443 && ufw allow out 80
ufw allow out to 10.0.0.2 port 53
ufw allow out 123
ufw enable
# Start Docker
systemctl enable docker && systemctl start docker
# Install OpenHands
cd /home/openhands
git clone https://github.com/All-Hands-AI/OpenHands.git
chown -R openhands:openhands OpenHands
# Setup as openhands user
sudo -u openhands bash << 'EOSU'
cd /home/openhands/OpenHands
python3 -m venv venv
source venv/bin/activate
pip install -e .
mkdir -p /home/openhands/.openhands
cat > /home/openhands/.openhands/config.toml << 'EOF'
[core]
workspace_base = "/home/openhands/workspace"
persist_sandbox = false
[llm]
model = "gpt-4"
api_key = "your-api-key-here"
base_url = "https://api.openai.com/v1"
[agent]
memory_enabled = true
[security]
security_analyzer = "default"
allow_file_uploads = false
restrict_file_types = true
[server]
host = "0.0.0.0"
port = 3000
EOF
mkdir -p /home/openhands/workspace
EOSU
# Create startup script
cat > /home/openhands/start_openhands.sh << 'EOF'
#!/bin/bash
cd /home/openhands/OpenHands
source venv/bin/activate
if [ -z "$OPENAI_API_KEY" ]; then
echo "Error: OPENAI_API_KEY not set"
exit 1
fi
python -m openhands.server.listen --host 0.0.0.0 --port 3000
EOF
chmod +x /home/openhands/start_openhands.sh
chown openhands:openhands /home/openhands/start_openhands.sh
# Create systemd service
cat > /etc/systemd/system/openhands.service << 'EOF'
[Unit]
Description=OpenHands AI Assistant
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
User=openhands
Group=openhands
WorkingDirectory=/home/openhands/OpenHands
Environment=OPENAI_API_KEY=your-api-key-here
ExecStart=/home/openhands/start_openhands.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
# Network restriction script
cat > /home/openhands/restrict_network.sh << 'EOF'
#!/bin/bash
# Restrict to LLM endpoints only
OPENAI_IPS=$(dig +short api.openai.com | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
sudo iptables -F OUTPUT
sudo iptables -P OUTPUT DROP
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -d 10.0.0.2 -p udp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p udp --dport 123 -j ACCEPT
for ip in $OPENAI_IPS; do
sudo iptables -A OUTPUT -d $ip -p tcp --dport 443 -j ACCEPT
done
sudo iptables-save > /etc/iptables/rules.v4
echo "Network restrictions applied"
EOF
chmod +x /home/openhands/restrict_network.sh
chown openhands:openhands /home/openhands/restrict_network.sh
echo "Setup complete - configure API key and start service"# SSH to web proxy
ssh -i openhands-keypair.pem ubuntu@<WEB_PROXY_PUBLIC_IP>
# Update with OpenHands server private IP
sudo /root/update_openhands_ip.sh <OPENHANDS_PRIVATE_IP># SSH to OpenHands server via web proxy
ssh openhands@<OPENHANDS_PRIVATE_IP>
# Set API key
export OPENAI_API_KEY="your-actual-api-key"
sudo sed -i "s/your-api-key-here/$OPENAI_API_KEY/" /etc/systemd/system/openhands.service
sudo systemctl daemon-reload
# Start service
sudo systemctl start openhands
sudo systemctl enable openhands
# Apply network restrictions
./restrict_network.sh# Test web interface
# Browser: https://<WEB_PROXY_PUBLIC_IP>
# Credentials: openhands / <password from setup log>
# Test network isolation
curl -m 10 https://google.com # Should FAIL
curl -m 10 https://api.openai.com # Should WORK# From OpenHands Server - These should WORK:
curl -m 10 https://api.openai.com
dig api.openai.com
ntpdate -q pool.ntp.org# From OpenHands Server - These should FAIL:
curl -m 10 https://google.com
curl -m 10 https://github.com
ping 8.8.8.8
wget http://example.com# System logs
/var/log/syslog
/var/log/auth.log
/var/log/nginx/access.log
/var/log/nginx/error.log
# Application logs
/var/log/openhands-audit.log
/home/openhands/.openhands/logs/
# Setup logs
/var/log/setup.log# Service status
systemctl status openhands
systemctl status nginx
# Network connectivity
netstat -tlnp | grep :3000
netstat -tlnp | grep :443
# Firewall status
ufw status verbose
iptables -L OUTPUT -v# Verify isolation
ss -tuln # Check listening ports
netstat -rn # Check routing table
iptables -L -v # Check firewall rulesThis technical implementation provides complete network isolation while maintaining secure web access to the OpenHands interface through a properly configured reverse proxy with authentication.