feat: Add Alateya, Clan, Eonarch agents + fix gateway-router connection
## Agents Added - Alateya: R&D, biotech, innovations - Clan (Spirit): Community spirit agent - Eonarch: Consciousness evolution agent ## Changes - docker-compose.node1.yml: Added tokens for all 3 new agents - gateway-bot/http_api.py: Added configs and webhook endpoints - gateway-bot/clan_prompt.txt: New prompt file - gateway-bot/eonarch_prompt.txt: New prompt file ## Fixes - Fixed ROUTER_URL from :9102 to :8000 (internal container port) - All 9 Telegram agents now working ## Documentation - Created PROJECT-MASTER-INDEX.md - single entry point - Added various status documents and scripts Tokens configured: - Helion, NUTRA, Agromatrix (existing) - Alateya, Clan, Eonarch (new) - Druid, GreenFood, DAARWIZZ (configured)
This commit is contained in:
180
ops/nginx/node1-api.conf
Normal file
180
ops/nginx/node1-api.conf
Normal file
@@ -0,0 +1,180 @@
|
||||
#
|
||||
# NODE1 API Gateway - Nginx Configuration
|
||||
# Version: 1.0
|
||||
# Last Updated: 2026-01-26
|
||||
#
|
||||
# Features:
|
||||
# - Rate limiting per IP (10 req/s, burst 20)
|
||||
# - Connection limiting (20 concurrent per IP)
|
||||
# - Security headers
|
||||
# - Upstream keepalive
|
||||
# - Heavy endpoint separate limits
|
||||
#
|
||||
|
||||
# === Rate Limit Zones ===
|
||||
# Standard API: 10 req/s per IP
|
||||
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=10r/s;
|
||||
# Heavy endpoints (RAG, image, search): 2 req/s per IP
|
||||
limit_req_zone $binary_remote_addr zone=heavy_per_ip:10m rate=2r/s;
|
||||
# Connection limit per IP
|
||||
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
|
||||
|
||||
# === Upstreams ===
|
||||
upstream gateway_upstream {
|
||||
server 127.0.0.1:9300;
|
||||
keepalive 64;
|
||||
}
|
||||
|
||||
upstream grafana_upstream {
|
||||
server 127.0.0.1:3030;
|
||||
keepalive 8;
|
||||
}
|
||||
|
||||
upstream prometheus_upstream {
|
||||
server 127.0.0.1:9090;
|
||||
keepalive 8;
|
||||
}
|
||||
|
||||
# === Main API Server ===
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.daarion.io _;
|
||||
|
||||
# Redirect to HTTPS (uncomment when SSL is configured)
|
||||
# return 301 https://$host$request_uri;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Rate limit status page (for debugging)
|
||||
location = /nginx-status {
|
||||
stub_status on;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Health check endpoint (no rate limit)
|
||||
location = /health {
|
||||
proxy_pass http://gateway_upstream/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# Heavy endpoints - stricter rate limit
|
||||
location ~ ^/(v1/rag|v1/image|v1/search|v1/embed) {
|
||||
limit_req zone=heavy_per_ip burst=5 nodelay;
|
||||
limit_conn conn_per_ip 10;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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;
|
||||
|
||||
# Longer timeouts for heavy operations
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_read_timeout 120s;
|
||||
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
# Webhook endpoints - higher burst for Telegram
|
||||
location ~ ^/(webhook|telegram) {
|
||||
limit_req zone=api_per_ip burst=50 nodelay;
|
||||
limit_conn conn_per_ip 30;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_connect_timeout 5s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# Default - standard rate limit
|
||||
location / {
|
||||
limit_req zone=api_per_ip burst=20 nodelay;
|
||||
limit_conn conn_per_ip 20;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_connect_timeout 5s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# Rate limit exceeded - custom error
|
||||
error_page 429 = @rate_limited;
|
||||
location @rate_limited {
|
||||
default_type application/json;
|
||||
return 429 '{"error": "rate_limit_exceeded", "message": "Too many requests. Please slow down.", "retry_after": 1}';
|
||||
}
|
||||
}
|
||||
|
||||
# === Admin Panel (Internal Only) ===
|
||||
# Access via SSH tunnel: ssh -L 3030:localhost:3030 root@node1
|
||||
# Or via allowlisted IPs
|
||||
server {
|
||||
listen 127.0.0.1:8080;
|
||||
server_name localhost;
|
||||
|
||||
# Grafana
|
||||
location /grafana/ {
|
||||
proxy_pass http://grafana_upstream/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Prometheus
|
||||
location /prometheus/ {
|
||||
proxy_pass http://prometheus_upstream/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
|
||||
# === HTTPS Server (uncomment after certbot) ===
|
||||
# server {
|
||||
# listen 443 ssl http2;
|
||||
# server_name api.daarion.io;
|
||||
#
|
||||
# ssl_certificate /etc/letsencrypt/live/api.daarion.io/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/api.daarion.io/privkey.pem;
|
||||
# ssl_session_timeout 1d;
|
||||
# ssl_session_cache shared:SSL:50m;
|
||||
# ssl_session_tickets off;
|
||||
#
|
||||
# # Modern SSL config
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
# ssl_prefer_server_ciphers off;
|
||||
#
|
||||
# # HSTS
|
||||
# add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
#
|
||||
# # (same location blocks as HTTP server above)
|
||||
# include /etc/nginx/conf.d/node1-api-locations.conf;
|
||||
# }
|
||||
284
ops/nginx/node1-hardened-v3.conf
Normal file
284
ops/nginx/node1-hardened-v3.conf
Normal file
@@ -0,0 +1,284 @@
|
||||
#
|
||||
# NODE1 Hardened Nginx Configuration v3
|
||||
# Version: 3.0
|
||||
# Last Updated: 2026-01-26
|
||||
#
|
||||
# v3 Features:
|
||||
# - /health protected (allowlist + token)
|
||||
# - Auth-gate for /v1/* endpoints
|
||||
# - Nginx metrics endpoint for Prometheus
|
||||
# - Enhanced WAF rules
|
||||
# - Fail2ban integration ready
|
||||
#
|
||||
|
||||
# === Rate Limit Zones ===
|
||||
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=heavy_per_ip:10m rate=2r/s;
|
||||
limit_req_zone $binary_remote_addr zone=webhook_per_ip:10m rate=50r/s;
|
||||
limit_req_zone $binary_remote_addr zone=auth_fail:10m rate=5r/s;
|
||||
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
|
||||
|
||||
# === Auth token (change this!) ===
|
||||
# Generate: openssl rand -hex 32
|
||||
map $http_x_health_token $health_token_valid {
|
||||
default 0;
|
||||
"dg-health-2026-secret-change-me" 1;
|
||||
}
|
||||
|
||||
# API Key validation map
|
||||
map $http_authorization $api_key_valid {
|
||||
default 0;
|
||||
"~^Bearer\s+sk-[a-zA-Z0-9]{32,}$" 1;
|
||||
}
|
||||
|
||||
map $http_x_api_key $x_api_key_valid {
|
||||
default 0;
|
||||
"~^sk-[a-zA-Z0-9]{32,}$" 1;
|
||||
}
|
||||
|
||||
# === Logging format (no auth headers, fail2ban ready) ===
|
||||
log_format api_safe '$remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'rt=$request_time';
|
||||
|
||||
log_format fail2ban '$remote_addr - [$time_local] "$request" $status';
|
||||
|
||||
# === Upstream ===
|
||||
upstream gateway_upstream {
|
||||
server 127.0.0.1:9300;
|
||||
keepalive 64;
|
||||
}
|
||||
|
||||
# === HTTP → HTTPS redirect ===
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name gateway.daarion.city api.daarion.io 144.76.224.179 _;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# === Main HTTPS Server ===
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name gateway.daarion.city api.daarion.io 144.76.224.179;
|
||||
|
||||
# === SSL Configuration ===
|
||||
ssl_certificate /etc/letsencrypt/live/gateway.daarion.city/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/gateway.daarion.city/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
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 off;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# === Security Headers ===
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' wss: https:;" always;
|
||||
|
||||
# === Logging ===
|
||||
access_log /var/log/nginx/api-access.log api_safe;
|
||||
access_log /var/log/nginx/fail2ban.log fail2ban;
|
||||
error_log /var/log/nginx/api-error.log warn;
|
||||
|
||||
# === WAF-lite: Block sensitive files ===
|
||||
location ~* \.(env|git|sql|bak|swp|old|backup|log|ini|conf|config|yml|yaml|json|xml|db|sqlite|pem|key)$ {
|
||||
access_log /var/log/nginx/waf-blocks.log fail2ban;
|
||||
return 444;
|
||||
}
|
||||
|
||||
location ~* ^/(\.git|\.svn|\.hg|\.env|wp-admin|wp-login|phpmyadmin|admin\.php|xmlrpc\.php|\.aws|\.docker) {
|
||||
access_log /var/log/nginx/waf-blocks.log fail2ban;
|
||||
return 444;
|
||||
}
|
||||
|
||||
# Block SQL injection attempts
|
||||
if ($query_string ~* "(union|select|insert|drop|delete|update|truncate|exec|script|alert|eval|base64)") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
# Block suspicious user agents
|
||||
if ($http_user_agent ~* (sqlmap|nikto|nmap|masscan|zgrab|python-requests/2\.[0-9]+\.[0-9]+$)) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
# === Nginx metrics (internal only) ===
|
||||
location = /nginx-status {
|
||||
stub_status on;
|
||||
allow 127.0.0.1;
|
||||
allow 10.42.0.0/16; # K8s pod network
|
||||
allow 10.43.0.0/16; # K8s service network
|
||||
deny all;
|
||||
}
|
||||
|
||||
# === Health check (protected) ===
|
||||
location = /health {
|
||||
# Allow localhost
|
||||
set $health_allowed 0;
|
||||
if ($remote_addr = "127.0.0.1") {
|
||||
set $health_allowed 1;
|
||||
}
|
||||
# Allow with valid token
|
||||
if ($health_token_valid = 1) {
|
||||
set $health_allowed 1;
|
||||
}
|
||||
# Allow internal networks (Prometheus, K8s)
|
||||
if ($remote_addr ~ "^(10\.(42|43)\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)") {
|
||||
set $health_allowed 1;
|
||||
}
|
||||
|
||||
if ($health_allowed = 0) {
|
||||
return 401;
|
||||
}
|
||||
|
||||
proxy_pass http://gateway_upstream/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# === Public health (limited info) ===
|
||||
location = /ping {
|
||||
default_type application/json;
|
||||
return 200 '{"status":"ok"}';
|
||||
}
|
||||
|
||||
# === Webhook endpoints (Telegram, etc.) - no auth ===
|
||||
location ~ ^/(webhook|telegram|bot[0-9]+) {
|
||||
limit_req zone=webhook_per_ip burst=100 nodelay;
|
||||
limit_conn conn_per_ip 50;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_connect_timeout 5s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# === API v1 endpoints (require auth) ===
|
||||
location ~ ^/v1/ {
|
||||
# Check for valid API key
|
||||
set $auth_valid 0;
|
||||
if ($api_key_valid = 1) {
|
||||
set $auth_valid 1;
|
||||
}
|
||||
if ($x_api_key_valid = 1) {
|
||||
set $auth_valid 1;
|
||||
}
|
||||
# Allow internal networks (service-to-service)
|
||||
if ($remote_addr ~ "^(127\.0\.0\.1|10\.(42|43)\.|172\.(1[6-9]|2[0-9]|3[01])\.)") {
|
||||
set $auth_valid 1;
|
||||
}
|
||||
|
||||
if ($auth_valid = 0) {
|
||||
access_log /var/log/nginx/auth-fails.log fail2ban;
|
||||
return 401 '{"error":"unauthorized","message":"Valid API key required"}';
|
||||
}
|
||||
|
||||
limit_req zone=heavy_per_ip burst=5 nodelay;
|
||||
limit_conn conn_per_ip 10;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_connect_timeout 10s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
client_max_body_size 100m;
|
||||
}
|
||||
|
||||
# === Root / docs / public (rate limited) ===
|
||||
location / {
|
||||
limit_req zone=api_per_ip burst=20 nodelay;
|
||||
limit_conn conn_per_ip 20;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $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_connect_timeout 5s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# === Error pages ===
|
||||
error_page 401 = @unauthorized;
|
||||
location @unauthorized {
|
||||
default_type application/json;
|
||||
return 401 '{"error":"unauthorized","message":"Authentication required"}';
|
||||
}
|
||||
|
||||
error_page 429 = @rate_limited;
|
||||
location @rate_limited {
|
||||
default_type application/json;
|
||||
return 429 '{"error":"rate_limit_exceeded","message":"Too many requests","retry_after":1}';
|
||||
}
|
||||
|
||||
error_page 403 = @forbidden;
|
||||
location @forbidden {
|
||||
default_type application/json;
|
||||
return 403 '{"error":"forbidden","message":"Access denied"}';
|
||||
}
|
||||
}
|
||||
|
||||
# === WebSocket upgrade mapping ===
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# === Admin Panel (localhost only) ===
|
||||
server {
|
||||
listen 127.0.0.1:8080;
|
||||
server_name localhost;
|
||||
|
||||
location /grafana/ {
|
||||
proxy_pass http://127.0.0.1:3030/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /prometheus/ {
|
||||
proxy_pass http://127.0.0.1:9090/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /nginx-metrics {
|
||||
stub_status on;
|
||||
}
|
||||
}
|
||||
224
ops/nginx/node1-hardened.conf
Normal file
224
ops/nginx/node1-hardened.conf
Normal file
@@ -0,0 +1,224 @@
|
||||
#
|
||||
# NODE1 Hardened Nginx Configuration
|
||||
# Version: 2.0
|
||||
# Last Updated: 2026-01-26
|
||||
#
|
||||
# Features:
|
||||
# - TLS 1.2/1.3 only with modern ciphers
|
||||
# - HSTS with preload
|
||||
# - Rate limiting (standard + heavy endpoints)
|
||||
# - WAF-lite rules (block scanners, sensitive files)
|
||||
# - Security headers (XSS, CSRF, clickjacking)
|
||||
# - Logging with privacy (no auth headers)
|
||||
#
|
||||
|
||||
# === Rate Limit Zones ===
|
||||
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=heavy_per_ip:10m rate=2r/s;
|
||||
limit_req_zone $binary_remote_addr zone=webhook_per_ip:10m rate=50r/s;
|
||||
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
|
||||
|
||||
# === Logging format (no Authorization header) ===
|
||||
log_format api_safe '$remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'rt=$request_time uct="$upstream_connect_time" '
|
||||
'uht="$upstream_header_time" urt="$upstream_response_time"';
|
||||
|
||||
# === Upstream ===
|
||||
upstream gateway_upstream {
|
||||
server 127.0.0.1:9300;
|
||||
keepalive 64;
|
||||
}
|
||||
|
||||
# === HTTP → HTTPS redirect ===
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name gateway.daarion.city api.daarion.io 144.76.224.179 _;
|
||||
|
||||
# Allow ACME challenge for certificate renewal
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
# Redirect everything else to HTTPS
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# === Main HTTPS Server ===
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name gateway.daarion.city api.daarion.io 144.76.224.179;
|
||||
|
||||
# === SSL Configuration ===
|
||||
ssl_certificate /etc/letsencrypt/live/gateway.daarion.city/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/gateway.daarion.city/privkey.pem;
|
||||
|
||||
# Modern SSL (TLS 1.2+ only)
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# SSL session
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# OCSP Stapling
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
resolver 8.8.8.8 8.8.4.4 valid=300s;
|
||||
resolver_timeout 5s;
|
||||
|
||||
# === Security Headers ===
|
||||
# HSTS (2 years, with preload)
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
# Prevent clickjacking
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
# Prevent MIME sniffing
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
# XSS Protection
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
# Referrer policy
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
# Content Security Policy (adjust as needed)
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' wss: https:;" always;
|
||||
|
||||
# === Logging ===
|
||||
access_log /var/log/nginx/api-access.log api_safe;
|
||||
error_log /var/log/nginx/api-error.log warn;
|
||||
|
||||
# === WAF-lite: Block sensitive files ===
|
||||
location ~* \.(env|git|sql|bak|swp|old|backup|log|ini|conf|config|yml|yaml|json|xml|db|sqlite)$ {
|
||||
return 444; # Close connection without response
|
||||
}
|
||||
|
||||
# Block common attack paths
|
||||
location ~* ^/(\.git|\.svn|\.hg|\.env|wp-admin|wp-login|phpmyadmin|admin\.php|xmlrpc\.php) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
# Block suspicious query strings
|
||||
if ($query_string ~* "(union|select|insert|drop|delete|update|truncate|exec|script|alert)") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
# === Rate limit status (internal only) ===
|
||||
location = /nginx-status {
|
||||
stub_status on;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
|
||||
# === Health check (no rate limit) ===
|
||||
location = /health {
|
||||
limit_req off;
|
||||
proxy_pass http://gateway_upstream/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# === Webhook endpoints (higher burst for Telegram) ===
|
||||
location ~ ^/(webhook|telegram|bot) {
|
||||
limit_req zone=webhook_per_ip burst=100 nodelay;
|
||||
limit_conn conn_per_ip 50;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_connect_timeout 5s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# === Heavy endpoints (stricter limit) ===
|
||||
location ~ ^/(v1/rag|v1/image|v1/search|v1/embed|v1/generate) {
|
||||
limit_req zone=heavy_per_ip burst=5 nodelay;
|
||||
limit_conn conn_per_ip 10;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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;
|
||||
|
||||
# Longer timeouts for heavy operations
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
client_max_body_size 100m;
|
||||
}
|
||||
|
||||
# === Default API (standard rate limit) ===
|
||||
location / {
|
||||
limit_req zone=api_per_ip burst=20 nodelay;
|
||||
limit_conn conn_per_ip 20;
|
||||
|
||||
proxy_pass http://gateway_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $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_connect_timeout 5s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# === Error pages ===
|
||||
error_page 429 = @rate_limited;
|
||||
location @rate_limited {
|
||||
default_type application/json;
|
||||
return 429 '{"error": "rate_limit_exceeded", "message": "Too many requests", "retry_after": 1}';
|
||||
}
|
||||
|
||||
error_page 403 = @forbidden;
|
||||
location @forbidden {
|
||||
default_type application/json;
|
||||
return 403 '{"error": "forbidden", "message": "Access denied"}';
|
||||
}
|
||||
}
|
||||
|
||||
# === WebSocket upgrade mapping ===
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# === Admin Panel (localhost only, via SSH tunnel) ===
|
||||
server {
|
||||
listen 127.0.0.1:8080;
|
||||
server_name localhost;
|
||||
|
||||
location /grafana/ {
|
||||
proxy_pass http://127.0.0.1:3030/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /prometheus/ {
|
||||
proxy_pass http://127.0.0.1:9090/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user