The default when proxying is $proxy_host. Proxied apps must used X-Forwarded-Host header to determine the intended target. I think we overwrote the Host header back in the day because apps had varied support for this. Ideally, it can be removed across all our configurations.
358 lines
12 KiB
Plaintext
358 lines
12 KiB
Plaintext
# http://nginx.org/en/docs/http/websocket.html
|
|
map $http_upgrade $connection_upgrade {
|
|
default upgrade;
|
|
'' close;
|
|
}
|
|
|
|
# Allow apps to override this https://stackoverflow.com/questions/13583501/nginx-how-to-add-header-if-it-is-not-set
|
|
map $upstream_http_referrer_policy $hrp {
|
|
default $upstream_http_referrer_policy;
|
|
"" "same-origin";
|
|
}
|
|
|
|
# http server
|
|
server {
|
|
# note listen [::]:80 only listens on ipv6 since ipv6only=on since nginx 1.3.4. listen 80 listens on ipv4 only
|
|
<% if (endpoint === 'ip' || endpoint === 'setup') { -%>
|
|
listen 80 default_server;
|
|
server_name _;
|
|
<% if (hasIPv6) { -%>
|
|
listen [::]:80 default_server;
|
|
<% } -%>
|
|
<% } else { -%>
|
|
listen 80;
|
|
server_name <%= vhost %>;
|
|
<% if (hasIPv6) { -%>
|
|
listen [::]:80;
|
|
<% } -%>
|
|
<% } -%>
|
|
|
|
server_tokens off; # hide version
|
|
|
|
# acme challenges
|
|
location /.well-known/acme-challenge/ {
|
|
default_type text/plain;
|
|
alias /home/yellowtent/platformdata/acme/;
|
|
}
|
|
|
|
location /notfound.html {
|
|
root <%= sourceDir %>/dashboard/dist;
|
|
try_files /notfound.html =404;
|
|
internal;
|
|
}
|
|
|
|
# for default server, serve the notfound page. for other endpoints, redirect to HTTPS
|
|
location / {
|
|
<% if ( endpoint === 'dashboard' || endpoint === 'setup' ) { %>
|
|
return 301 https://$host$request_uri;
|
|
<% } else if ( endpoint === 'app' || endpoint === 'external' ) { %>
|
|
return 301 https://$host$request_uri;
|
|
<% } else if ( endpoint === 'redirect' ) { %>
|
|
return 301 https://<%= redirectTo %>$request_uri;
|
|
<% } else if ( endpoint === 'ip' ) { %>
|
|
root /home/yellowtent/boxdata;
|
|
try_files /custom_pages/notfound.html /notfound.html;
|
|
<% } %>
|
|
}
|
|
}
|
|
|
|
# https server
|
|
server {
|
|
<% if (endpoint === 'ip' || endpoint === 'setup') { -%>
|
|
listen 443 ssl http2 default_server;
|
|
server_name _;
|
|
<% if (hasIPv6) { -%>
|
|
listen [::]:443 ssl http2 default_server;
|
|
<% } -%>
|
|
<% } else { -%>
|
|
listen 443 ssl http2;
|
|
server_name <%= vhost %>;
|
|
<% if (hasIPv6) { -%>
|
|
listen [::]:443 ssl http2;
|
|
<% } -%>
|
|
<% } -%>
|
|
|
|
server_tokens off; # hide version
|
|
|
|
# paths are relative to prefix and not to this file
|
|
ssl_certificate <%= certFilePath %>;
|
|
ssl_certificate_key <%= keyFilePath %>;
|
|
ssl_session_timeout 5m;
|
|
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
|
|
ssl_session_tickets off;
|
|
|
|
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
|
# https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#25-use-forward-secrecy
|
|
# ciphers according to https://ssl-config.mozilla.org/#server=nginx&version=1.14.0&config=intermediate&openssl=1.1.1&guideline=5.4
|
|
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:ECDHE-RSA-AES128-SHA256;
|
|
ssl_prefer_server_ciphers off;
|
|
|
|
# some apps have underscores in headers. this is apparently disabled by default because of some legacy CGI compat
|
|
underscores_in_headers on;
|
|
|
|
<% if (endpoint !== 'ip' && endpoint !== 'setup') { -%>
|
|
# dhparams is generated only after dns setup
|
|
ssl_dhparam /home/yellowtent/platformdata/dhparams.pem;
|
|
<% } -%>
|
|
add_header Strict-Transport-Security "max-age=63072000";
|
|
|
|
<% if ( ocsp ) { -%>
|
|
# OCSP. LE certs are generated with must-staple flag so clients can enforce OCSP
|
|
ssl_stapling on;
|
|
ssl_stapling_verify on;
|
|
<% } %>
|
|
|
|
# https://github.com/twitter/secureheaders
|
|
# https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Compatibility_Matrix
|
|
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
proxy_hide_header X-XSS-Protection;
|
|
add_header X-Download-Options "noopen";
|
|
proxy_hide_header X-Download-Options;
|
|
add_header X-Content-Type-Options "nosniff";
|
|
proxy_hide_header X-Content-Type-Options;
|
|
add_header X-Permitted-Cross-Domain-Policies "none";
|
|
proxy_hide_header X-Permitted-Cross-Domain-Policies;
|
|
|
|
# See header handling from upstream on top of this file
|
|
add_header Referrer-Policy $hrp;
|
|
proxy_hide_header Referrer-Policy;
|
|
|
|
# gzip responses that are > 50k and not images
|
|
gzip on;
|
|
gzip_min_length 18k;
|
|
gzip_types text/css text/javascript text/xml text/plain application/javascript application/x-javascript application/json;
|
|
|
|
# enable for proxied requests as well
|
|
gzip_proxied any;
|
|
|
|
<% if ( endpoint === 'dashboard' || endpoint === 'ip' || endpoint === 'setup' ) { -%>
|
|
# CSP headers for the dashboard resources
|
|
add_header Content-Security-Policy "default-src 'none'; frame-src 'self' cloudron.io *.cloudron.io; connect-src wss: https: 'self' *.cloudron.io; script-src https: 'self' 'unsafe-inline' 'unsafe-eval'; img-src * data:; style-src https: 'unsafe-inline'; object-src 'none'; font-src https: 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self';";
|
|
<% } else { %>
|
|
<% if (cspQuoted) { %>
|
|
add_header Content-Security-Policy <%- cspQuoted %>;
|
|
<% } %>
|
|
|
|
<% for (var i = 0; i < hideHeaders.length; i++) { -%>
|
|
proxy_hide_header <%- hideHeaders[i] %>;
|
|
<% } %>
|
|
<% } -%>
|
|
|
|
proxy_http_version 1.1;
|
|
# intercept errors (>= 400) and use the error_page handler
|
|
proxy_intercept_errors on;
|
|
# nginx will return 504 on connect/timeout errors
|
|
proxy_read_timeout 3500;
|
|
proxy_connect_timeout 3250;
|
|
|
|
<% if ( endpoint !== 'external' ) { %>
|
|
proxy_set_header Host $host;
|
|
<% } %>
|
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
proxy_set_header X-Forwarded-Port $server_port;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
proxy_set_header X-Forwarded-Ssl on;
|
|
|
|
# upgrade is a hop-by-hop header (http://nginx.org/en/docs/http/websocket.html)
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection $connection_upgrade;
|
|
|
|
# only serve up the status page if we get proxy gateway errors
|
|
root <%= sourceDir %>/dashboard/dist;
|
|
# some apps use 503 to indicate updating or maintenance
|
|
error_page 502 504 /app_error_page;
|
|
location /app_error_page {
|
|
root /home/yellowtent/boxdata;
|
|
# the first argument looks for file under the root
|
|
try_files /custom_pages/$request_uri /custom_pages/app_not_responding.html /appstatus.html;
|
|
# internal means this is for internal routing and cannot be accessed as URL from browser
|
|
internal;
|
|
}
|
|
|
|
location @wellknown-upstream {
|
|
<% if ( endpoint === 'dashboard' ) { %>
|
|
proxy_pass http://127.0.0.1:3000;
|
|
<% } else if ( endpoint === 'app' ) { %>
|
|
proxy_pass http://<%= ip %>:<%= port %>;
|
|
<% } else if ( endpoint === 'external' ) { %>
|
|
# without a variable, nginx will not start if upstream is down or
|
|
resolver 127.0.0.1 valid=30s;
|
|
set $upstream <%= upstreamUri %>;
|
|
proxy_ssl_verify off;
|
|
proxy_pass $upstream;
|
|
<% } else if ( endpoint === 'redirect' ) { %>
|
|
return 302 https://<%= redirectTo %>$request_uri;
|
|
<% } %>
|
|
}
|
|
|
|
# user defined .well-known resources
|
|
location /.well-known/ {
|
|
error_page 404 = @wellknown-upstream;
|
|
proxy_pass http://127.0.0.1:3000/well-known-handler/;
|
|
}
|
|
|
|
# increase the proxy buffer sizes to not run into buffer issues (http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffers)
|
|
proxy_buffer_size 128k;
|
|
proxy_buffers 4 256k;
|
|
proxy_busy_buffers_size 256k;
|
|
|
|
# No buffering to temp files, it fails for large downloads
|
|
proxy_max_temp_file_size 0;
|
|
|
|
# Disable check to allow unlimited body sizes. this allows apps to accept whatever size they want
|
|
client_max_body_size 0;
|
|
|
|
<% if (robotsTxtQuoted) { %>
|
|
location = /robots.txt {
|
|
return 200 <%- robotsTxtQuoted %>;
|
|
}
|
|
<% } %>
|
|
|
|
<% if ( endpoint === 'dashboard' || endpoint === 'setup' ) { %>
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
client_max_body_size 2m;
|
|
}
|
|
|
|
location ~ ^/api/v1/cloudron/login$ {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
client_max_body_size 1m;
|
|
limit_req zone=admin_login burst=5;
|
|
}
|
|
|
|
# the read timeout is between successive reads and not the whole connection
|
|
location ~ ^/api/v1/apps/.*/exec/.*/start$ {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
proxy_read_timeout 30m;
|
|
}
|
|
|
|
location ~ ^/api/v1/apps/.*/upload$ {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
client_max_body_size 0;
|
|
}
|
|
|
|
location ~ ^/api/v1/apps/.*/files/ {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
client_max_body_size 0;
|
|
}
|
|
|
|
location ~ ^/api/v1/volumes/.*/files/ {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
client_max_body_size 0;
|
|
}
|
|
|
|
location ~ ^/api/v1/profile/backgroundImage {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
client_max_body_size 0;
|
|
}
|
|
|
|
# graphite paths (uncomment block below and visit /graphite-web/)
|
|
# remember to comment out the CSP policy as well to access the graphite dashboard
|
|
# location ~ ^/graphite-web/ {
|
|
# proxy_pass http://172.18.0.6:8000;
|
|
# client_max_body_size 1m;
|
|
# }
|
|
|
|
location / {
|
|
root <%= sourceDir %>/dashboard/dist;
|
|
index index.html index.htm;
|
|
}
|
|
<% } else if ( endpoint === 'app' ) { %>
|
|
location = /appstatus.html {
|
|
root /home/yellowtent/box/dashboard/dist;
|
|
}
|
|
|
|
<% if (proxyAuth.enabled) { %>
|
|
location = /proxy-auth {
|
|
internal;
|
|
proxy_pass http://127.0.0.1:3001/auth;
|
|
proxy_pass_request_body off;
|
|
# repeat proxy headers since we addded proxy_set_header at this location level
|
|
proxy_set_header X-App-ID "<%= proxyAuth.id %>";
|
|
proxy_set_header Content-Length "";
|
|
}
|
|
|
|
location ~ ^/(login|logout)$ {
|
|
proxy_pass http://127.0.0.1:3001;
|
|
}
|
|
|
|
location @proxy-auth-login {
|
|
if ($http_user_agent ~* "docker") {
|
|
return 401;
|
|
}
|
|
if ($http_user_agent ~* "container") {
|
|
return 401;
|
|
}
|
|
return 302 /login?redirect=$request_uri;
|
|
}
|
|
|
|
location <%= proxyAuth.location %> {
|
|
auth_request /proxy-auth;
|
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
|
|
|
auth_request_set $user $upstream_http_x_remote_user;
|
|
auth_request_set $email $upstream_http_x_remote_email;
|
|
auth_request_set $name $upstream_http_x_remote_name;
|
|
|
|
error_page 401 = @proxy-auth-login;
|
|
|
|
proxy_pass http://<%= ip %>:<%= port %>;
|
|
}
|
|
|
|
<% if (proxyAuth.location !== '/') { %>
|
|
location / {
|
|
proxy_pass http://<%= ip %>:<%= port %>;
|
|
}
|
|
<% } %>
|
|
|
|
<% } else { %>
|
|
location / {
|
|
proxy_pass http://<%= ip %>:<%= port %>;
|
|
}
|
|
<% } %>
|
|
|
|
<% if (proxyAuth.enabled) { %>
|
|
# workaround caching issue after /logout. if max-age is set, browser uses cache and user thinks they have not logged out
|
|
# IMPORTANT: have to keep all the add_headers at top level here to avoid repeating all the add_headers and proxy_set_headers in location block
|
|
proxy_hide_header Cache-Control;
|
|
add_header Cache-Control no-cache;
|
|
add_header Set-Cookie $auth_cookie;
|
|
|
|
# To prevent header spoofing from a client, these variables must always be set (or removed with '') for all proxyAuth routes
|
|
proxy_set_header X-App-ID "<%= proxyAuth.id %>";
|
|
proxy_set_header X-Remote-User $user;
|
|
proxy_set_header X-Remote-Email $email;
|
|
proxy_set_header X-Remote-Name $name;
|
|
<% } %>
|
|
|
|
<% } else if ( endpoint === 'redirect' ) { %>
|
|
location / {
|
|
# redirect everything to the app. this is temporary because there is no way
|
|
# to clear a permanent redirect on the browser
|
|
return 302 https://<%= redirectTo %>$request_uri;
|
|
}
|
|
<% } else if ( endpoint === 'external' ) { %>
|
|
location / {
|
|
# without a variable, nginx will not start if upstream is down or unavailable
|
|
resolver 127.0.0.1 valid=30s;
|
|
set $upstream <%= upstreamUri %>;
|
|
proxy_ssl_verify off;
|
|
proxy_pass $upstream;
|
|
}
|
|
<% } else if ( endpoint === 'ip' ) { %>
|
|
location /notfound.html {
|
|
root <%= sourceDir %>/dashboard/dist;
|
|
try_files /notfound.html =404;
|
|
internal;
|
|
}
|
|
|
|
location / {
|
|
root /home/yellowtent/boxdata;
|
|
try_files /custom_pages/notfound.html /notfound.html;
|
|
}
|
|
<% } %>
|
|
}
|