From 507c999d126b28ce8f5ef3c43fecaeea14681031 Mon Sep 17 00:00:00 2001 From: mrveiss Date: Thu, 19 Mar 2026 13:25:28 +0200 Subject: [PATCH 1/2] feat(docker): add optional TLS/HTTPS configuration (#1896) - Add self-signed cert generation script at docker/certs/ - Add nginx-ssl.conf with HTTPS server block, HTTP->HTTPS redirect, TLS 1.2/1.3, and security headers (HSTS, X-Frame-Options, etc.) - Add TLS volume mounts to frontend service in docker-compose.yml - Add TLS env vars to .env.docker (commented out by default) - TLS is opt-in: default remains HTTP-only --- docker-compose.yml | 7 ++ docker/.env.docker | 5 + docker/certs/.gitignore | 5 + docker/certs/generate-self-signed.sh | 33 +++++++ docker/nginx/nginx-ssl.conf | 134 +++++++++++++++++++++++++++ 5 files changed, 184 insertions(+) create mode 100644 docker/certs/.gitignore create mode 100644 docker/certs/generate-self-signed.sh create mode 100644 docker/nginx/nginx-ssl.conf diff --git a/docker-compose.yml b/docker-compose.yml index b272e3117..82d11e642 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -198,6 +198,13 @@ services: ports: - "80:80" - "443:443" + volumes: + # Override nginx config at runtime (default: HTTP-only) + # For TLS: set AUTOBOT_NGINX_CONF=./docker/nginx/nginx-ssl.conf in .env + - ${AUTOBOT_NGINX_CONF:-./docker/nginx/nginx.conf}:/etc/nginx/nginx.conf:ro + # TLS certificates directory (mounted read-only) + # Generate dev certs: bash docker/certs/generate-self-signed.sh + - ${AUTOBOT_TLS_CERT_DIR:-./docker/certs}:/etc/nginx/certs:ro depends_on: - autobot-backend - autobot-slm diff --git a/docker/.env.docker b/docker/.env.docker index e7839e107..c369ba492 100644 --- a/docker/.env.docker +++ b/docker/.env.docker @@ -86,5 +86,10 @@ AUTOBOT_DB_USER=autobot AUTOBOT_DB_PASSWORD=autobot AUTOBOT_DB_NAME=autobot_slm +# --- TLS (optional) --- +# To enable HTTPS, uncomment and run: bash docker/certs/generate-self-signed.sh +# AUTOBOT_NGINX_CONF=./docker/nginx/nginx-ssl.conf +# AUTOBOT_TLS_CERT_DIR=./docker/certs + # --- Logging --- AUTOBOT_LOG_LEVEL=INFO diff --git a/docker/certs/.gitignore b/docker/certs/.gitignore new file mode 100644 index 000000000..11611c97e --- /dev/null +++ b/docker/certs/.gitignore @@ -0,0 +1,5 @@ +# Generated TLS certificates — do not commit +*.crt +*.key +*.pem +!.gitignore diff --git a/docker/certs/generate-self-signed.sh b/docker/certs/generate-self-signed.sh new file mode 100644 index 000000000..20a38541b --- /dev/null +++ b/docker/certs/generate-self-signed.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# AutoBot - Self-Signed TLS Certificate Generator (#1896) +# For development/testing only. Use proper CA certs in production. +# +# AutoBot - AI-Powered Automation Platform +# Copyright (c) 2025 mrveiss +# Author: mrveiss + +set -e + +CERT_DIR="$(cd "$(dirname "$0")" && pwd)" +CERT_FILE="$CERT_DIR/autobot.crt" +KEY_FILE="$CERT_DIR/autobot.key" + +if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then + echo "Certificates already exist at $CERT_DIR" + echo " Delete them first to regenerate." + exit 0 +fi + +echo "Generating self-signed TLS certificate..." +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout "$KEY_FILE" \ + -out "$CERT_FILE" \ + -subj "/C=US/ST=Local/L=Local/O=AutoBot/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:autobot,IP:127.0.0.1" + +chmod 600 "$KEY_FILE" +chmod 644 "$CERT_FILE" + +echo "Certificate generated:" +echo " Cert: $CERT_FILE" +echo " Key: $KEY_FILE" diff --git a/docker/nginx/nginx-ssl.conf b/docker/nginx/nginx-ssl.conf new file mode 100644 index 000000000..03a4a7816 --- /dev/null +++ b/docker/nginx/nginx-ssl.conf @@ -0,0 +1,134 @@ +# AutoBot nginx Configuration — TLS/HTTPS (#1896) +# Use with: AUTOBOT_NGINX_CONF=./docker/nginx/nginx-ssl.conf +# +# AutoBot - AI-Powered Automation Platform +# Copyright (c) 2025 mrveiss +# Author: mrveiss + +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +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"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 100M; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css application/json application/javascript + text/xml application/xml application/xml+rss text/javascript + image/svg+xml; + + # Upstream services + upstream backend { + server autobot-backend:8000; + } + + upstream slm { + server autobot-slm:8000; + } + + # HTTP -> HTTPS redirect + server { + listen 80; + server_name _; + return 301 https://$host$request_uri; + } + + # HTTPS server + server { + listen 443 ssl; + server_name _; + + ssl_certificate /etc/nginx/certs/autobot.crt; + ssl_certificate_key /etc/nginx/certs/autobot.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Security headers + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options SAMEORIGIN always; + add_header X-XSS-Protection "1; mode=block" always; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend/api/; + 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 300s; + proxy_send_timeout 300s; + } + + location /ws { + proxy_pass http://backend/ws; + 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_read_timeout 86400s; + } + + location /slm/ { + proxy_pass http://slm/; + 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; + } + + location /slm/api/ { + proxy_pass http://slm/api/; + 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 300s; + } + + location /health { + access_log off; + return 200 '{"status":"ok"}'; + add_header Content-Type application/json; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + } +} From 7d0337b3c9fae221ab03ac792a7cfccde87f1abc Mon Sep 17 00:00:00 2001 From: mrveiss Date: Fri, 20 Mar 2026 15:57:32 +0200 Subject: [PATCH 2/2] fix(docker): address TLS code review findings (#1896) - Tighten cipher suite to ECDHE-only AEAD ciphers (removes 3DES, CBC) - Enable HTTP/2 (listen 443 ssl http2) - Fix security header inheritance: repeat critical headers in child location blocks (/health, static assets) - Add Referrer-Policy and Permissions-Policy headers - Replace deprecated X-XSS-Protection with modern headers - Add X-Forwarded-Proto to WebSocket location - Add commented OCSP stapling scaffold for CA-signed certs --- docker/nginx/nginx-ssl.conf | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docker/nginx/nginx-ssl.conf b/docker/nginx/nginx-ssl.conf index 03a4a7816..b4ceda047 100644 --- a/docker/nginx/nginx-ssl.conf +++ b/docker/nginx/nginx-ssl.conf @@ -57,22 +57,28 @@ http { # HTTPS server server { - listen 443 ssl; + listen 443 ssl http2; server_name _; ssl_certificate /etc/nginx/certs/autobot.crt; ssl_certificate_key /etc/nginx/certs/autobot.key; ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; + # Enable for CA-signed certificates: + # ssl_stapling on; + # ssl_stapling_verify on; + # resolver 8.8.8.8 8.8.4.4 valid=300s; + # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; - add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; root /usr/share/nginx/html; index index.html; @@ -99,6 +105,7 @@ http { 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 86400s; } @@ -122,12 +129,15 @@ http { location /health { access_log off; return 200 '{"status":"ok"}'; - add_header Content-Type application/json; + add_header Content-Type application/json always; + add_header X-Content-Type-Options nosniff always; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; - add_header Cache-Control "public, immutable"; + add_header Cache-Control "public, immutable" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options nosniff always; try_files $uri =404; } }