mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
The backend only sees the bundled nginx on loopback, so the allowlist could only work by trusting 127.0.0.1, which let any client that reached nginx log in as anyone by sending the identity headers. nginx now passes its own TCP peer (before real_ip rewriting) in X-Termix-Proxy-Peer, and loopback connections are judged by that address instead.
485 lines
19 KiB
Plaintext
485 lines
19 KiB
Plaintext
worker_processes 1;
|
|
master_process off;
|
|
pid /tmp/nginx/nginx.pid;
|
|
error_log /tmp/nginx/error.log warn;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
server_tokens off;
|
|
|
|
access_log /tmp/nginx/access.log;
|
|
|
|
client_body_temp_path /tmp/nginx/client_body;
|
|
proxy_temp_path /tmp/nginx/proxy_temp;
|
|
fastcgi_temp_path /tmp/nginx/fastcgi_temp;
|
|
uwsgi_temp_path /tmp/nginx/uwsgi_temp;
|
|
scgi_temp_path /tmp/nginx/scgi_temp;
|
|
|
|
sendfile on;
|
|
keepalive_timeout 65;
|
|
|
|
# Static assets only. API responses arrive already gzipped from the node
|
|
# backend, and gzip_proxied would otherwise have nginx decompress and
|
|
# recompress them for nothing.
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 2048;
|
|
gzip_comp_level 5;
|
|
gzip_types
|
|
text/plain
|
|
text/css
|
|
text/javascript
|
|
application/javascript
|
|
application/json
|
|
application/wasm
|
|
image/svg+xml;
|
|
client_header_timeout 300s;
|
|
|
|
# BEGIN TRUSTED_PROXIES
|
|
set_real_ip_from 127.0.0.1;
|
|
set_real_ip_from 10.0.0.0/8;
|
|
set_real_ip_from 172.16.0.0/12;
|
|
set_real_ip_from 192.168.0.0/16;
|
|
# END TRUSTED_PROXIES
|
|
real_ip_header X-Forwarded-For;
|
|
real_ip_recursive on;
|
|
|
|
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
|
default $http_x_forwarded_proto;
|
|
'' $scheme;
|
|
}
|
|
|
|
map $http_x_forwarded_host $proxy_x_forwarded_host {
|
|
default $http_x_forwarded_host;
|
|
'' $http_host;
|
|
}
|
|
|
|
map $http_x_forwarded_port $proxy_x_forwarded_port {
|
|
default $http_x_forwarded_port;
|
|
'' '';
|
|
}
|
|
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
|
|
ssl_prefer_server_ciphers off;
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
|
|
# BEGIN HTTP_REDIRECT_SERVER
|
|
server {
|
|
listen ${PORT};
|
|
server_name _;
|
|
|
|
# ACME http-01 checks arrive on plain HTTP and must not be redirected.
|
|
location ^~ /.well-known/acme-challenge/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
location / {
|
|
return 301 https://$host:${SSL_PORT}$request_uri;
|
|
}
|
|
}
|
|
# END HTTP_REDIRECT_SERVER
|
|
|
|
server {
|
|
listen ${SSL_PORT} ssl;
|
|
server_name _;
|
|
client_max_body_size 50m;
|
|
|
|
absolute_redirect off;
|
|
|
|
ssl_certificate ${SSL_CERT_PATH};
|
|
ssl_certificate_key ${SSL_KEY_PATH};
|
|
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
|
|
location ^~ /.well-known/acme-challenge/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
location = /sw.js {
|
|
root /app/html;
|
|
expires off;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location = /manifest.json {
|
|
root /app/html;
|
|
expires off;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ^~ /assets/ {
|
|
root /app/html;
|
|
expires 1y;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ^~ /fonts/ {
|
|
root /app/html;
|
|
expires 1y;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ^~ /icons/ {
|
|
root /app/html;
|
|
expires 30d;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "public, max-age=2592000" always;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ~* ^/[^/]+\.(js|css|png|jpe?g|gif|ico|svg|webp|woff2?|ttf|eot)$ {
|
|
root /app/html;
|
|
expires 30d;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "public, max-age=2592000" always;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ~* \.map$ {
|
|
access_log off;
|
|
log_not_found off;
|
|
return 404;
|
|
}
|
|
|
|
location / {
|
|
root /app/html;
|
|
index index.html index.htm;
|
|
expires off;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-0FPou4vGHwupDie6Xais6VntpJ4yaK9E6TyhXLwZe8o='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
|
|
add_header X-Frame-Options "DENY" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
location ~ ^/users/sessions(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/users(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
# The TCP peer before set_real_ip_from rewrites $remote_addr from
|
|
# X-Forwarded-For; trusted proxy login checks this address.
|
|
proxy_set_header X-Termix-Proxy-Peer $realip_remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
|
}
|
|
|
|
location ~ ^/version(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/releases(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/rbac(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/credentials(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
|
|
proxy_connect_timeout 60s;
|
|
proxy_send_timeout 300s;
|
|
proxy_read_timeout 300s;
|
|
}
|
|
|
|
location ~ ^/ssh-auth(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/sync(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/audit-logs(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/plugins(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
# Plugin frontend bundles, stylesheets and locales. Public like
|
|
# /assets; the backend picks the cache policy from the ?v= query.
|
|
location ^~ /plugin-assets/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
# Every plugin's HTTP routes. One block, no per-plugin entries: a
|
|
# plugin serves at /plugin-api/<id>/ on the main backend and never
|
|
# gets a port or a block of its own. Buffering is off so SSE streams
|
|
# (AI, Proxmox discovery) reach the browser as they are produced, and
|
|
# request buffering is off so large uploads stream through.
|
|
location ^~ /plugin-api/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
proxy_connect_timeout 10s;
|
|
|
|
proxy_buffering off;
|
|
proxy_request_buffering off;
|
|
|
|
client_max_body_size 0;
|
|
}
|
|
|
|
# Every plugin's WebSockets: terminal, Guacamole display, Docker
|
|
# console. Served at /plugin-ws/<id>/<path> from the same backend.
|
|
location ^~ /plugin-ws/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
|
proxy_cache_bypass $http_upgrade;
|
|
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
proxy_connect_timeout 10s;
|
|
|
|
proxy_buffering off;
|
|
proxy_request_buffering off;
|
|
|
|
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
|
}
|
|
|
|
location ~ ^/open-tabs(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/user-preferences(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/host-sidebar(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/credential-sidebar(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/ui-preferences(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location ~ ^/database(/.*)?$ {
|
|
client_max_body_size 5G;
|
|
client_body_timeout 300s;
|
|
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
|
|
proxy_connect_timeout 60s;
|
|
proxy_send_timeout 300s;
|
|
proxy_read_timeout 300s;
|
|
|
|
proxy_request_buffering off;
|
|
proxy_buffering off;
|
|
}
|
|
|
|
location ~ ^/encryption(/.*)?$ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
location /host/quick-connect {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_set_header Host $http_host;
|
|
proxy_cache_bypass $http_upgrade;
|
|
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 $proxy_x_forwarded_proto;
|
|
# A terminal is quiet most of the time - waiting on a prompt, reading
|
|
# output, running something long. Every console plugin dials this route
|
|
# (terminal, serial, remote desktop), and nginx's default
|
|
# proxy_read_timeout of 60s closed the socket about a minute into any
|
|
# pause, which reaches the user as the SSH server disconnecting. The
|
|
# plugin WebSocket routes below already ask for 86400s; this is the same
|
|
# class of socket, so it asks for the same.
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
proxy_buffering off;
|
|
}
|
|
|
|
location /host/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
# File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved
|
|
# to the file-manager plugin, served under /plugin-api/file-manager/.
|
|
# Session logs moved to the session-recording plugin, served under
|
|
# /plugin-api/session-recording/.
|
|
|
|
location /health {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
# Service links and the homepage widget canvas moved to the homepage
|
|
# plugin, served under /plugin-api/homepage/.
|
|
|
|
location /dashboard/ {
|
|
proxy_pass http://127.0.0.1:30001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_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 $proxy_x_forwarded_proto;
|
|
}
|
|
|
|
error_page 500 502 503 504 /50x.html;
|
|
location = /50x.html {
|
|
root /app/html;
|
|
internal;
|
|
}
|
|
}
|
|
}
|