Skip to content

Restrict internet access from siren container #286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: unstable
Choose a base branch
from
Open
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
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,44 @@ Advanced users can mount their own certificate (the config expects 3 files: `/ce

### Docker

The docker image can be built with the following command:
#### Starting Siren with Docker Compose

1. Copy the environment file:
```bash
cp .env.example .env
```

2. Edit `.env` with your configuration (at minimum set `BEACON_URL`, `VALIDATOR_URL`, and `API_TOKEN`)

3. Start the services:
```bash
docker compose up -d
```
The `-d` flag runs the containers in the background. Remove it if you want to see the logs directly in your terminal.

4. Access Siren at https://localhost:4443

Note: When you first visit the site, your browser will show a security warning because Siren uses a self-signed SSL certificate. This is expected and safe for local development. You can proceed by:
- In Chrome: Click "Advanced" and then "Proceed to localhost (unsafe)"
- In Firefox: Click "Advanced..." and then "Accept the Risk and Continue"
- In Safari: Click "Show Details" and then "visit this website"

#### Stopping Siren

To stop the services:
```bash
docker compose down
```

To stop and remove all data (including certificates):
```bash
docker compose down -v
```

#### Alternative Docker Run Method

You can also run Siren using the docker run command:

`docker build -f Dockerfile -t siren .`

### Building locally
Expand Down
17 changes: 17 additions & 0 deletions docker-assets/generate-certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
set -e

# Create directory for certificates if it doesn't exist
mkdir -p /etc/nginx/certs

# Generate self-signed certificate and private key
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
-keyout /etc/nginx/certs/nginx.key \
-out /etc/nginx/certs/nginx.crt \
-subj "/C=AU/CN=siren/[email protected]"

# Set proper permissions
chmod 600 /etc/nginx/certs/nginx.key
chmod 644 /etc/nginx/certs/nginx.crt

echo "SSL certificates generated successfully"
93 changes: 93 additions & 0 deletions docker-assets/nginx_proxy.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
user nginx;
worker_processes 8;

error_log /dev/stdout warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

# HTTP configuration for Siren web interface
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;
access_log /dev/stdout main;

sendfile on;
keepalive_timeout 65;

# Resolve DNS for upstreams
resolver 127.0.0.11 ipv6=off;

# Siren web interface proxy
server {
listen 4080;
server_name localhost;

location / {
set $upstream_siren siren;
proxy_pass http://$upstream_siren:80;
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_read_timeout 300;
proxy_connect_timeout 300;
}
}
}

# TCP proxy configuration for beacon/validator
stream {
log_format main '$remote_addr - [$time_local] ';
access_log /var/log/nginx/access.log main;
access_log /dev/stdout main;

# Split BN_TARGET into host and port using shell parameter expansion
upstream beacon_node {
server ${BN_TARGET};
}

# Split VC_TARGET into host and port using shell parameter expansion
upstream validator_client {
server ${VC_TARGET};
}

# Beacon Node proxy
server {
listen 9001;
# Resolve DNS for upstreams
resolver 127.0.0.11;

# Proxy pass to the beacon node
proxy_pass beacon_node;
}

# Validator Client proxy
server {
listen 9002;
# Resolve DNS for upstreams
resolver 127.0.0.11;

# Proxy pass to the validator client
proxy_pass validator_client;
}

# Block all other outbound connections
server {
listen 80;
return 444; # Connection closed without response
}

server {
listen 443;
return 444; # Connection closed without response
}
}
1 change: 1 addition & 0 deletions docker-assets/siren-https.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ server {
listen 443 ssl;
ssl_certificate /certs/cert.pem;
ssl_certificate_key /certs/key.pem;
ssl_password_file /certs/key.pass;
ssl_protocols TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

Expand Down
82 changes: 76 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,81 @@
services:
cert-init:
container_name: cert-init
image: alpine/openssl:3.3.2
command: /usr/local/bin/generate-certs.sh
volumes:
- ./docker-assets/generate-certs.sh:/usr/local/bin/generate-certs.sh
- nginx-certs:/etc/nginx/certs
entrypoint: >
/bin/sh -c "
# Make script executable
chmod +x /usr/local/bin/generate-certs.sh &&
# Run script
/usr/local/bin/generate-certs.sh
"

nginx-proxy:
container_name: nginx-proxy
image: nginx:alpine
networks:
- internet
- no_internet
ports:
- "4080:4080" # For Siren web interface HTTP
- "4443:4443" # For Siren web interface HTTPS (fixed port mapping)
volumes:
- ./docker-assets/nginx_proxy.conf.template:/etc/nginx/nginx.conf.template:ro
- nginx-certs:/etc/nginx/certs:ro
command: /bin/sh -c "export BN_TARGET=$(echo $BEACON_URL | awk -F'[/:]' '{print $4 \":\" $5}') && export VC_TARGET=$(echo $VALIDATOR_URL | awk -F'[/:]' '{print $4 \":\" $5}') && envsubst '$$BN_TARGET $$VC_TARGET $$SIREN_HOST' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"
env_file:
- .env
depends_on:
cert-init:
condition: service_completed_successfully
restart: unless-stopped # Don't restart if manually stopped

siren:
container_name: siren
image: sigp/siren
ports:
- '4443:443' # comment this line when using `SSL_ENABLED=false`
# - "4080:80" # uncomment this line when using `SSL_ENABLED=false`
networks:
- no_internet # Only on internal network
env_file:
- .env
# uncomment these 2 lines if you use docker on linux and want to use the special host `host.docker.internal` as your BN/VC address
# extra_hosts:
# - "host.docker.internal:host-gateway"
tmpfs:
- /tmp:noexec,nosuid # Mount tmp with restricted permissions
- /run:noexec,nosuid # Required for nginx pid file
environment:
- NODE_ENV=production
- BEACON_URL=http://nginx-proxy:9001
- VALIDATOR_URL=http://nginx-proxy:9002
- SSL_ENABLED=true
- PORT=3000
- BACKEND_URL=http://127.0.0.1:3001
# Security enhancements
security_opt:
- no-new-privileges:true # Prevent privilege escalation
depends_on:
- nginx-proxy
healthcheck: # Add health monitoring
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped # Don't restart if manually stopped

networks:
internet:
name: internet
driver: bridge
no_internet:
name: no_internet
driver: bridge
internal: true # Prevents direct internet access
# Enable network encryption
driver_opts:
encrypted: "true"

volumes:
nginx-certs:
driver: local