mirror of
https://github.com/Mailu/Mailu.git
synced 2025-01-20 03:29:50 +02:00
585549ce92
2924: Remove the usage of capabilities, use port 8080 for admin r=nextgens a=nextgens ## What type of PR? bug-fix ## What does this PR do? In the real world users can't get them to work... I wonder if they use patched-up kernels or if xattrs are lost somehow... in any case, we can do without capabilities so let's do that. Ensure that dovecot doesn't attempt to bind a v6 socket if SUBNET6 is not configured Also, document that systemd-resolve may cause trouble with DNSSEC. ### Related issue(s) - closes #2906 - closes #2913 ## Prerequisites Before we can consider review and merge, please make sure the following list is done and checked. If an entry in not applicable, you can check it or remove it from the list. - [x] In case of feature or enhancement: documentation updated accordingly - [x] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Florent Daigniere <nextgens@freenetproject.org>
349 lines
11 KiB
Nginx Configuration File
349 lines
11 KiB
Nginx Configuration File
# Basic configuration
|
|
user nginx;
|
|
worker_processes auto;
|
|
pcre_jit on;
|
|
error_log /dev/stderr notice;
|
|
pid /var/run/nginx.pid;
|
|
load_module "modules/ngx_mail_module.so";
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
# Standard HTTP configuration with slight hardening
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
sendfile on;
|
|
keepalive_timeout 65;
|
|
server_tokens off;
|
|
absolute_redirect off;
|
|
resolver {{ RESOLVER }} valid=30s;
|
|
|
|
{% if REAL_IP_HEADER %}
|
|
real_ip_header {{ REAL_IP_HEADER }};
|
|
{% elif PROXY_PROTOCOL in ['all', 'all-but-http', 'http'] %}
|
|
real_ip_header proxy_protocol;
|
|
{% endif %}
|
|
|
|
{% if REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %}
|
|
set_real_ip_from {{ from_ip }};
|
|
{% endfor %}{% endif %}
|
|
|
|
# Header maps
|
|
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
|
default $http_x_forwarded_proto;
|
|
'' $scheme;
|
|
}
|
|
map $uri $expires {
|
|
default off;
|
|
~*\.(ico|css|js|gif|jpeg|jpg|png|woff2?|ttf|otf|svg|tiff|eot|webp)$ 97d;
|
|
}
|
|
|
|
map $request_uri $loggable {
|
|
/health 0;
|
|
/auth/email 0;
|
|
default 1;
|
|
}
|
|
access_log /dev/stdout combined if=$loggable;
|
|
|
|
# compression
|
|
gzip on;
|
|
gzip_static on;
|
|
gzip_types text/plain text/css application/xml application/javascript
|
|
gzip_min_length 1024;
|
|
# TODO: figure out how to server pre-compressed assets from admin container
|
|
|
|
{% if not KUBERNETES_INGRESS and TLS_FLAVOR in [ 'letsencrypt', 'cert' ] %}
|
|
# Enable the proxy for certbot if the flavor is letsencrypt and not on kubernetes
|
|
#
|
|
server {
|
|
# Listen over HTTP
|
|
listen 80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
|
|
{% if SUBNET6 %}
|
|
listen [::]:80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
|
|
{% endif %}
|
|
{% if TLS_FLAVOR in ['letsencrypt', 'mail-letsencrypt'] %}
|
|
location ^~ /.well-known/acme-challenge/ {
|
|
proxy_pass http://127.0.0.1:8008;
|
|
}
|
|
{% endif %}
|
|
# redirect to https
|
|
location / {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
location /health {
|
|
return 204;
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
# Main HTTP server
|
|
server {
|
|
# Favicon stuff
|
|
root /static;
|
|
# Variables for proxifying
|
|
set $admin {{ ADMIN_ADDRESS }}:8080;
|
|
set $antispam {{ ANTISPAM_ADDRESS }}:11334;
|
|
{% if WEBMAIL_ADDRESS %}
|
|
set $webmail {{ WEBMAIL_ADDRESS }};
|
|
{% endif %}
|
|
{% if WEBDAV_ADDRESS %}
|
|
set $webdav {{ WEBDAV_ADDRESS }}:5232;
|
|
{% endif %}
|
|
client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }};
|
|
|
|
# Listen on HTTP only in kubernetes or behind reverse proxy
|
|
{% if KUBERNETES_INGRESS or TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %}
|
|
listen 80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
|
|
{% if SUBNET6 %}
|
|
listen [::]:80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
# Only enable HTTPS if TLS is enabled with no error and not on kubernetes
|
|
{% if not KUBERNETES_INGRESS and TLS and not TLS_ERROR %}
|
|
listen 443 ssl http2{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'http'] %} proxy_protocol{% endif %};
|
|
{% if SUBNET6 %}
|
|
listen [::]:443 ssl http2{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'http'] %} proxy_protocol{% endif %};
|
|
{% endif %}
|
|
|
|
include /etc/nginx/tls.conf;
|
|
ssl_stapling on;
|
|
ssl_stapling_verify on;
|
|
ssl_session_cache shared:SSLHTTP:3m;
|
|
add_header Strict-Transport-Security 'max-age=31536000';
|
|
|
|
{% if not TLS_FLAVOR in [ 'mail', 'mail-letsencrypt' ] %}
|
|
if ($proxy_x_forwarded_proto = http) {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
# Remove headers to prevent duplication and information disclosure
|
|
proxy_hide_header X-XSS-Protection;
|
|
proxy_hide_header X-Powered-By;
|
|
|
|
add_header X-Frame-Options 'SAMEORIGIN';
|
|
add_header X-Content-Type-Options 'nosniff';
|
|
add_header X-Permitted-Cross-Domain-Policies 'none';
|
|
add_header Referrer-Policy 'same-origin';
|
|
|
|
# mozilla autoconfiguration
|
|
location ~ ^/(\.well\-known/autoconfig/)?mail/config\-v1\.1\.xml {
|
|
rewrite ^ /internal/autoconfig/mozilla break;
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
}
|
|
# microsoft autoconfiguration
|
|
location ~* ^/Autodiscover/Autodiscover.json {
|
|
rewrite ^ /internal/autoconfig/microsoft.json break;
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
}
|
|
location ~* ^/Autodiscover/Autodiscover.xml {
|
|
rewrite ^ /internal/autoconfig/microsoft break;
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
}
|
|
# apple mobileconfig
|
|
location ~ ^/(apple\.)?mobileconfig {
|
|
rewrite ^ /internal/autoconfig/apple break;
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
}
|
|
|
|
{% if TLS_FLAVOR in ['letsencrypt', 'mail-letsencrypt'] %}
|
|
location ^~ /.well-known/acme-challenge/ {
|
|
proxy_pass http://127.0.0.1:8008;
|
|
}
|
|
{% endif %}
|
|
|
|
# If TLS is failing, prevent access to anything except certbot
|
|
{% if not KUBERNETES_INGRESS and TLS_ERROR and not (TLS_FLAVOR in [ 'mail-letsencrypt', 'mail' ]) %}
|
|
location / {
|
|
return 403;
|
|
}
|
|
{% else %}
|
|
include /overrides/*.conf;
|
|
|
|
# Actual logic
|
|
{% if ADMIN or WEBMAIL != 'none' %}
|
|
location ~ ^/(sso|static)/ {
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
}
|
|
{% endif %}
|
|
|
|
location @sso_login {
|
|
return 302 /sso/login?url=$request_uri;
|
|
}
|
|
|
|
{% if WEB_WEBMAIL != '/' and WEBROOT_REDIRECT != 'none' %}
|
|
location / {
|
|
expires $expires;
|
|
{% if WEBROOT_REDIRECT %}
|
|
try_files $uri {{ WEBROOT_REDIRECT }}?homepage;
|
|
{% else %}
|
|
try_files $uri =404;
|
|
{% endif %}
|
|
}
|
|
{% endif %}
|
|
|
|
{% if WEBMAIL != 'none' %}
|
|
location {{ WEB_WEBMAIL }} {
|
|
{% if WEB_WEBMAIL != '/' %}
|
|
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
|
|
rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break;
|
|
{% endif %}
|
|
include /etc/nginx/proxy.conf;
|
|
auth_request /internal/auth/user;
|
|
error_page 403 @sso_login;
|
|
proxy_pass http://$webmail;
|
|
}
|
|
|
|
{% if WEB_WEBMAIL == '/' %}
|
|
location /sso.php {
|
|
{% else %}
|
|
location {{ WEB_WEBMAIL }}/sso.php {
|
|
{% endif %}
|
|
{% if WEB_WEBMAIL != '/' %}
|
|
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
|
|
rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break;
|
|
{% endif %}
|
|
include /etc/nginx/proxy.conf;
|
|
auth_request /internal/auth/user;
|
|
auth_request_set $user $upstream_http_x_user;
|
|
auth_request_set $token $upstream_http_x_user_token;
|
|
proxy_set_header X-Remote-User $user;
|
|
proxy_set_header X-Remote-User-Token $token;
|
|
error_page 403 @sso_login;
|
|
proxy_pass http://$webmail;
|
|
}
|
|
{% endif %}
|
|
{% if ADMIN %}
|
|
location {{ WEB_ADMIN }} {
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
expires $expires;
|
|
}
|
|
|
|
location {{ WEB_ADMIN }}/antispam {
|
|
rewrite ^{{ WEB_ADMIN }}/antispam/(.*) /$1 break;
|
|
auth_request /internal/auth/admin;
|
|
proxy_set_header X-Real-IP "";
|
|
proxy_set_header X-Forwarded-For "";
|
|
proxy_set_header X-Forwarded-By: "";
|
|
proxy_pass http://$antispam;
|
|
error_page 403 @sso_login;
|
|
}
|
|
{% endif %}
|
|
|
|
{% if WEBDAV != 'none' %}
|
|
location /webdav {
|
|
rewrite ^/webdav/(.*) /$1 break;
|
|
auth_request /internal/auth/basic;
|
|
auth_request_set $user $upstream_http_x_user;
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_set_header X-Remote-User $user;
|
|
proxy_set_header X-Script-Name /webdav;
|
|
proxy_pass http://$webdav;
|
|
}
|
|
|
|
location ~ ^/.well-known/(carddav|caldav) {
|
|
return 301 /webdav/;
|
|
}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{% if API %}
|
|
location ~ {{ WEB_API or '/api' }} {
|
|
include /etc/nginx/proxy.conf;
|
|
proxy_pass http://$admin;
|
|
}
|
|
{% endif %}
|
|
|
|
location /internal {
|
|
internal;
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Authorization $http_authorization;
|
|
proxy_pass_header Authorization;
|
|
proxy_pass http://$admin;
|
|
proxy_pass_request_body off;
|
|
proxy_set_header Content-Length "";
|
|
}
|
|
location /health {
|
|
return 204;
|
|
}
|
|
}
|
|
|
|
# Forwarding authentication server
|
|
server {
|
|
# Variables for proxifying
|
|
set $admin {{ ADMIN_ADDRESS }}:8080;
|
|
|
|
listen 127.0.0.1:8000;
|
|
|
|
location / {
|
|
proxy_pass http://$admin/internal$request_uri;
|
|
}
|
|
}
|
|
|
|
# Healthcheck over localhost, for docker
|
|
server {
|
|
listen 127.0.0.1:10204;
|
|
location /health {
|
|
return 204;
|
|
}
|
|
}
|
|
|
|
include /etc/nginx/conf.d/*.conf;
|
|
}
|
|
|
|
mail {
|
|
server_name {{ HOSTNAMES.split(",")[0] }};
|
|
auth_http http://127.0.0.1:8000/auth/email;
|
|
proxy_pass_error_message on;
|
|
resolver {{ RESOLVER }} valid=30s;
|
|
error_log /dev/stderr info;
|
|
|
|
{% if TLS and not TLS_ERROR %}
|
|
include /etc/nginx/tls.conf;
|
|
ssl_session_cache shared:SSLMAIL:3m;
|
|
{% endif %}
|
|
|
|
{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] and REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %}
|
|
set_real_ip_from {{ from_ip }};
|
|
{% endfor %}{% endif %}
|
|
|
|
# Advertise real capabilities of backends (postfix/dovecot)
|
|
smtp_capabilities PIPELINING "SIZE {{ MESSAGE_SIZE_LIMIT }}" ETRN ENHANCEDSTATUSCODES 8BITMIME DSN;
|
|
|
|
# SMTP is always enabled, to avoid losing emails when TLS is failing
|
|
server {
|
|
listen 25{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %} proxy_protocol{% endif %};
|
|
{% if SUBNET6 %}
|
|
listen [::]:25{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %} proxy_protocol{% endif %};
|
|
{% endif %}
|
|
{% if TLS and not TLS_ERROR %}
|
|
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
|
|
ssl_certificate /certs/letsencrypt/live/mailu/fullchain.pem;
|
|
ssl_certificate /certs/letsencrypt/live/mailu-ecdsa/fullchain.pem;
|
|
{% endif %}
|
|
{% if TLS_PERMISSIVE %}
|
|
ssl_protocols TLSv1 TLSv1.1 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:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA;
|
|
ssl_prefer_server_ciphers on;
|
|
{% endif %}
|
|
starttls on;
|
|
{% endif %}
|
|
protocol smtp;
|
|
smtp_auth none;
|
|
auth_http_header Auth-Port 25;
|
|
auth_http_header Client-Port $remote_port;
|
|
}
|
|
}
|