From 8f579d18077bc16526da882ecf219bb5efc1dbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ku=C5=9B?= Date: Sat, 31 May 2025 19:33:17 +0200 Subject: [PATCH] removing overrides --- mailu/docker-compose.yml | 3 +- mailu/overrides/nginx.conf | 358 ++++++++++++++++++++++++++++++++++++- 2 files changed, 358 insertions(+), 3 deletions(-) diff --git a/mailu/docker-compose.yml b/mailu/docker-compose.yml index f3538ca..41a8c28 100644 --- a/mailu/docker-compose.yml +++ b/mailu/docker-compose.yml @@ -22,8 +22,7 @@ services: - "993:993" volumes: - ./data/certs:/certs/letsencrypt - - ./data/letsencrypt:/etc/letsencrypt - - ./overrides/nginx.conf:/overrides/nginx.conf:ro + - ./data/letsencrypt:/etc/letsencrypt imap: image: ghcr.io/mailu/dovecot:master diff --git a/mailu/overrides/nginx.conf b/mailu/overrides/nginx.conf index 1becba2..32d1f35 100644 --- a/mailu/overrides/nginx.conf +++ b/mailu/overrides/nginx.conf @@ -1 +1,357 @@ -404: Not Found \ No newline at end of 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_80 or PROXY_PROTOCOL_443) and REAL_IP_FROM %} + real_ip_header proxy_protocol; + {% endif %} + + {% if REAL_IP_FROM %} + real_ip_recursive on; + {% 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 PORT_80 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_80 %} proxy_protocol{% endif %}; +{% if SUBNET6 %} + listen [::]:80{% if PROXY_PROTOCOL_80 %} proxy_protocol{% endif %}; +{% endif %} + {% if TLS_FLAVOR in ['letsencrypt', 'mail-letsencrypt'] %} + location ^~ /.well-known/acme-challenge/testing { + return 204; + } + 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 }}; + http2 on; + + # Listen on HTTP only in kubernetes or behind reverse proxy + {% if TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %} + listen 80{% if PROXY_PROTOCOL_80 %} proxy_protocol{% endif %}; +{% if SUBNET6 %} + listen [::]:80{% if PROXY_PROTOCOL_80 %} proxy_protocol{% endif %}; +{% endif %} + {% endif %} + + # Only enable HTTPS if TLS is enabled with no error + {% if TLS_443 and not TLS_ERROR %} + listen 443 ssl{% if PROXY_PROTOCOL_443 %} proxy_protocol{% endif %}; +{% if SUBNET6 %} + listen [::]:443 ssl{% if PROXY_PROTOCOL_443 %} 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/testing { + return 204; + } + 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 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_25 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_25 %} proxy_protocol{% endif %}; +{% if SUBNET6 %} + listen [::]:25{% if PROXY_PROTOCOL_25 %} proxy_protocol{% endif %}; +{% endif %} + {% if TLS and not TLS_ERROR %} + {% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %} + ssl_certificate /certs/letsencrypt/live/mailu/DANE-chain.pem; + ssl_certificate /certs/letsencrypt/live/mailu-ecdsa/DANE-chain.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; + } +} \ No newline at end of file