diff --git a/README.md b/README.md index 5a2b8c57..d5348a53 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docker-assets/generate-certs.sh b/docker-assets/generate-certs.sh new file mode 100755 index 00000000..b7750245 --- /dev/null +++ b/docker-assets/generate-certs.sh @@ -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/emailAddress=noreply@sigmaprime.io" + +# Set proper permissions +chmod 600 /etc/nginx/certs/nginx.key +chmod 644 /etc/nginx/certs/nginx.crt + +echo "SSL certificates generated successfully" diff --git a/docker-assets/nginx_proxy.conf.template b/docker-assets/nginx_proxy.conf.template new file mode 100644 index 00000000..24a178f7 --- /dev/null +++ b/docker-assets/nginx_proxy.conf.template @@ -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 + } +} diff --git a/docker-assets/siren-https.conf b/docker-assets/siren-https.conf index 8ce89848..39f332a9 100644 --- a/docker-assets/siren-https.conf +++ b/docker-assets/siren-https.conf @@ -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; diff --git a/docker-compose.yml b/docker-compose.yml index b978beca..4d00cf53 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 \ No newline at end of file