diff --git a/data/Dockerfiles/nginx/Dockerfile b/data/Dockerfiles/nginx/Dockerfile new file mode 100644 index 000000000..7d2ce34f3 --- /dev/null +++ b/data/Dockerfiles/nginx/Dockerfile @@ -0,0 +1,18 @@ +FROM nginx:alpine +LABEL maintainer "The Infrastructure Company GmbH " + +ENV PIP_BREAK_SYSTEM_PACKAGES=1 + +RUN apk add --no-cache nginx \ + python3 \ + py3-pip && \ + pip install --upgrade pip && \ + pip install Jinja2 + +RUN mkdir -p /etc/nginx/includes + +COPY ./bootstrap.py / +COPY ./docker-entrypoint.sh / + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/data/Dockerfiles/nginx/bootstrap.py b/data/Dockerfiles/nginx/bootstrap.py new file mode 100644 index 000000000..0d24ae9b2 --- /dev/null +++ b/data/Dockerfiles/nginx/bootstrap.py @@ -0,0 +1,76 @@ +import os +import subprocess +from jinja2 import Environment, FileSystemLoader + + +def sites_default_conf(env, template_vars): + config_name = "sites-default.conf" + template = env.get_template(f"{config_name}.j2") + config = template.render(template_vars) + + with open(f"/etc/nginx/includes/{config_name}", "w") as f: + f.write(config) + +def nginx_conf(env, template_vars): + config_name = "nginx.conf" + template = env.get_template(f"{config_name}.j2") + config = template.render(template_vars) + + with open(f"/etc/nginx/{config_name}", "w") as f: + f.write(config) + +def prepare_template_vars(): + template_vars = { + 'IPV4_NETWORK': os.getenv("IPV4_NETWORK", "172.22.1"), + 'TRUSTED_NETWORK': os.getenv("TRUSTED_NETWORK", False), + 'SKIP_RSPAMD': os.getenv("SKIP_RSPAMD", "n").lower() in ("y", "yes"), + 'SKIP_SOGO': os.getenv("SKIP_SOGO", "n").lower() in ("y", "yes"), + 'NGINX_USE_PROXY_PROTOCOL': os.getenv("NGINX_USE_PROXY_PROTOCOL", "n").lower() in ("y", "yes"), + 'MAILCOW_HOSTNAME': os.getenv("MAILCOW_HOSTNAME", ""), + 'ADDITIONAL_SERVER_NAMES': os.getenv("ADDITIONAL_SERVER_NAMES", "").replace(',', ' '), + 'HTTP_PORT': os.getenv("HTTP_PORT", "80"), + 'HTTPS_PORT': os.getenv("HTTPS_PORT", "443"), + 'SOGOHOST': os.getenv("SOGOHOST", "sogo-mailcow"), + 'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"), + 'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"), + } + + ssl_dir = '/etc/ssl/mail/' + template_vars['valid_cert_dirs'] = [] + for d in os.listdir(ssl_dir): + full_path = os.path.join(ssl_dir, d) + if not os.path.isdir(full_path): + continue + + cert_path = os.path.join(full_path, 'cert.pem') + key_path = os.path.join(full_path, 'key.pem') + domains_path = os.path.join(full_path, 'domains') + + if os.path.isfile(cert_path) and os.path.isfile(key_path) and os.path.isfile(domains_path): + with open(domains_path, 'r') as file: + domains = file.read().strip() + domains_list = domains.split() + if domains_list and template_vars["MAILCOW_HOSTNAME"] not in domains_list: + template_vars['valid_cert_dirs'].append({ + 'cert_path': full_path + '/', + 'domains': domains + }) + + return template_vars + +def main(): + env = Environment(loader=FileSystemLoader('./etc/nginx/conf.d')) + + # Render config + print("Render config") + template_vars = prepare_template_vars() + sites_default_conf(env, template_vars) + nginx_conf(env, template_vars) + + # Validate config + print("Validate config") + subprocess.run(["nginx", "-qt"]) + + +if __name__ == "__main__": + main() diff --git a/data/Dockerfiles/nginx/docker-entrypoint.sh b/data/Dockerfiles/nginx/docker-entrypoint.sh new file mode 100755 index 000000000..beb0b4d9e --- /dev/null +++ b/data/Dockerfiles/nginx/docker-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +until ping ${REDISHOST} -c1 > /dev/null; do + echo "Waiting for Redis..." + sleep 1 +done +until ping ${PHPFPMHOST} -c1 > /dev/null; do + echo "Waiting for PHP..." + sleep 1 +done +if printf "%s\n" "${SKIP_SOGO}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then + until ping ${SOGOHOST} -c1 > /dev/null; do + echo "Waiting for SOGo..." + sleep 1 + done +fi +if printf "%s\n" "${SKIP_RSPAMD}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then + until ping ${RSPAMDHOST} -c1 > /dev/null; do + echo "Waiting for Rspamd..." + sleep 1 + done +fi + +python3 /bootstrap.py + +exec "$@" diff --git a/data/conf/nginx/000-map-size.conf b/data/conf/nginx/000-map-size.conf deleted file mode 100644 index a834306ed..000000000 --- a/data/conf/nginx/000-map-size.conf +++ /dev/null @@ -1,3 +0,0 @@ -map_hash_max_size 256; -map_hash_bucket_size 256; - diff --git a/data/conf/nginx/dynmaps.conf b/data/conf/nginx/dynmaps.conf deleted file mode 100644 index 99c0c6aaa..000000000 --- a/data/conf/nginx/dynmaps.conf +++ /dev/null @@ -1,19 +0,0 @@ -server { - listen 8081; - listen [::]:8081; - index index.php index.html; - server_name _; - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - root /dynmaps; - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9001; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } -} diff --git a/data/conf/nginx/includes/site-defaults.conf b/data/conf/nginx/includes/site-defaults.conf deleted file mode 100644 index 1d03e9398..000000000 --- a/data/conf/nginx/includes/site-defaults.conf +++ /dev/null @@ -1,242 +0,0 @@ - - include /etc/nginx/mime.types; - charset utf-8; - override_charset on; - - server_tokens off; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; - ssl_ecdh_curve X25519:X448:secp384r1:secp256k1; - ssl_session_cache shared:SSL:50m; - ssl_session_timeout 1d; - ssl_session_tickets off; - - add_header Strict-Transport-Security "max-age=15768000;"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies none; - add_header Referrer-Policy strict-origin; - - index index.php index.html; - - client_max_body_size 0; - - gzip on; - gzip_disable "msie6"; - - gzip_vary on; - gzip_proxied off; - gzip_comp_level 6; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_min_length 256; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; - - location ~ ^/(fonts|js|css|img)/ { - expires max; - add_header Cache-Control public; - } - - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - fastcgi_hide_header X-Powered-By; - absolute_redirect off; - root /web; - - location / { - try_files $uri $uri/ @strip-ext; - } - - location /qhandler { - rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; - } - - location /edit { - rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; - } - - location @strip-ext { - rewrite ^(.*)$ $1.php last; - } - - location ~ ^/api/v1/(.*)$ { - try_files $uri $uri/ /json_api.php?query=$1&$args; - } - - location ^~ /.well-known/acme-challenge/ { - allow all; - default_type "text/plain"; - } - - # If behind reverse proxy, forwards the correct IP - 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; - set_real_ip_from fc00::/7; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent; - rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; - - location ^~ /principals { - return 301 /SOGo/dav; - } - - location ^~ /inc/lib/ { - deny all; - return 403; - } - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - fastcgi_index index.php; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_read_timeout 3600; - fastcgi_send_timeout 3600; - } - - location /rspamd/ { - location /rspamd/auth { - # proxy_pass is not inherited - proxy_pass http://rspamd:11334/auth; - proxy_intercept_errors on; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_redirect off; - error_page 401 /_rspamderror.php; - } - proxy_pass http://rspamd:11334/; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_redirect off; - } - - location ~* ^/Autodiscover/Autodiscover.xml { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autodiscover.php =404; - } - - location ~* ^/Autodiscover/Autodiscover.json { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autodiscover-json.php =404; - } - - location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autoconfig.php =404; - } - - location /sogo-auth-verify { - internal; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Host $http_host; - proxy_set_header Content-Length ""; - proxy_pass http://127.0.0.1:65510/sogo-auth; - proxy_pass_request_body off; - } - - location ^~ /Microsoft-Server-ActiveSync { - include /etc/nginx/conf.d/includes/sogo_proxy_auth.conf; - include /etc/nginx/conf.d/sogo_eas.active; - proxy_connect_timeout 75; - proxy_send_timeout 3600; - proxy_read_timeout 3600; - proxy_buffer_size 128k; - proxy_buffers 64 512k; - proxy_busy_buffers_size 512k; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - client_body_buffer_size 512k; - client_max_body_size 0; - } - - location ^~ /SOGo { - location ~* ^/SOGo/so/.*\.(xml|js|html|xhtml)$ { - include /etc/nginx/conf.d/includes/sogo_proxy_auth.conf; - include /etc/nginx/conf.d/sogo.active; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - proxy_hide_header Content-Type; - add_header Content-Type text/plain; - break; - } - include /etc/nginx/conf.d/includes/sogo_proxy_auth.conf; - include /etc/nginx/conf.d/sogo.active; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - proxy_buffer_size 128k; - proxy_buffers 64 512k; - proxy_busy_buffers_size 512k; - proxy_send_timeout 3600; - proxy_read_timeout 3600; - client_body_buffer_size 128k; - client_max_body_size 0; - break; - } - - location ~* /sogo$ { - return 301 $client_req_scheme://$http_host/SOGo; - } - - location /SOGo.woa/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; - } - - location /.woa/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; - } - - location /SOGo/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; - } - - location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { - alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; - } - - include /etc/nginx/conf.d/site.*.custom; - - error_page 502 @awaitingupstream; - - location @awaitingupstream { - rewrite ^(.*)$ /_status.502.html break; - } - - location ~ ^/cache/(.*)$ { - try_files $uri $uri/ /resource.php?file=$1; - } diff --git a/data/conf/nginx/includes/sogo_proxy_auth.conf b/data/conf/nginx/includes/sogo_proxy_auth.conf deleted file mode 100644 index 045b98adc..000000000 --- a/data/conf/nginx/includes/sogo_proxy_auth.conf +++ /dev/null @@ -1,8 +0,0 @@ -auth_request /sogo-auth-verify; -auth_request_set $user $upstream_http_x_user; -auth_request_set $auth $upstream_http_x_auth; -auth_request_set $auth_type $upstream_http_x_auth_type; -proxy_set_header x-webobjects-remote-user "$user"; -proxy_set_header Authorization "$auth"; -proxy_set_header x-webobjects-auth-type "$auth_type"; - diff --git a/data/conf/nginx/meta_exporter.conf b/data/conf/nginx/meta_exporter.conf deleted file mode 100644 index 74291b147..000000000 --- a/data/conf/nginx/meta_exporter.conf +++ /dev/null @@ -1,19 +0,0 @@ -server { - listen 9081; - index index.php index.html; - server_name _; - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - root /meta_exporter; - client_max_body_size 10M; - location ~ \.php$ { - client_max_body_size 10M; - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9001; - fastcgi_index pipe.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } -} diff --git a/data/conf/nginx/nginx.conf.j2 b/data/conf/nginx/nginx.conf.j2 new file mode 100644 index 000000000..13444129f --- /dev/null +++ b/data/conf/nginx/nginx.conf.j2 @@ -0,0 +1,119 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + # map-size.conf: + map_hash_max_size 256; + map_hash_bucket_size 256; + + # site.conf: + proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; + server_names_hash_max_size 512; + server_names_hash_bucket_size 128; + + map $http_x_forwarded_proto $client_req_scheme { + default $scheme; + https https; + } + + # Default + server { + listen 127.0.0.1:65510; # sogo-auth verify internal + listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + http2 on; + + ssl_certificate /etc/ssl/mail/cert.pem; + ssl_certificate_key /etc/ssl/mail/key.pem; + + server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* {{ ADDITIONAL_SERVER_NAMES }}; + + include /etc/nginx/includes/sites-default.conf; + } + + # rspamd dynmaps: + server { + listen 8081; + listen [::]:8081; + index index.php index.html; + server_name _; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /dynmaps; + + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9001; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + } + + # rspamd meta_exporter: + server { + listen 9081; + index index.php index.html; + server_name _; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /meta_exporter; + client_max_body_size 10M; + location ~ \.php$ { + client_max_body_size 10M; + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9001; + fastcgi_index pipe.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + } + + {% for cert in valid_cert_dirs %} + server { + listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + http2 on; + + ssl_certificate {{ cert.cert_path }}cert.pem; + ssl_certificate_key {{ cert.cert_path }}key.pem; + + server_name {{ cert.domains }}; + + include /etc/nginx/includes/sites-default.conf; + } + {% endfor %} +} diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf deleted file mode 100644 index fb40de879..000000000 --- a/data/conf/nginx/site.conf +++ /dev/null @@ -1,10 +0,0 @@ -proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; -server_names_hash_max_size 512; -server_names_hash_bucket_size 128; - -map $http_x_forwarded_proto $client_req_scheme { - default $scheme; - https https; -} - -include /etc/nginx/conf.d/sites.active; diff --git a/data/conf/nginx/sites-default.conf.j2 b/data/conf/nginx/sites-default.conf.j2 new file mode 100644 index 000000000..783723bfc --- /dev/null +++ b/data/conf/nginx/sites-default.conf.j2 @@ -0,0 +1,276 @@ +include /etc/nginx/mime.types; +charset utf-8; +override_charset on; + +server_tokens off; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers on; +ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; +ssl_ecdh_curve X25519:X448:secp384r1:secp256k1; +ssl_session_cache shared:SSL:50m; +ssl_session_timeout 1d; +ssl_session_tickets off; + +add_header Strict-Transport-Security "max-age=15768000;"; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; +add_header X-Robots-Tag none; +add_header X-Download-Options noopen; +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Permitted-Cross-Domain-Policies none; +add_header Referrer-Policy strict-origin; + +index index.php index.html; + +client_max_body_size 0; + +gzip on; +gzip_disable "msie6"; + +gzip_vary on; +gzip_proxied off; +gzip_comp_level 6; +gzip_buffers 16 8k; +gzip_http_version 1.1; +gzip_min_length 256; +gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; + +location ~ ^/(fonts|js|css|img)/ { + expires max; + add_header Cache-Control public; +} + +error_log /var/log/nginx/error.log; +access_log /var/log/nginx/access.log; +fastcgi_hide_header X-Powered-By; +absolute_redirect off; +root /web; + +# If behind reverse proxy, forwards the correct IP +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; +set_real_ip_from fc00::/7; +{% if not TRUSTED_NETWORK %} +real_ip_header X-Forwarded-For; +{% else %} +set_real_ip_from {{ TRUSTED_NETWORK }}; +real_ip_header proxy_protocol; +{% endif %} +real_ip_recursive on; + + +location @strip-ext { + rewrite ^(.*)$ $1.php last; +} + +location ^~ /inc/lib/ { + deny all; + return 403; +} + +location ^~ /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; +} + +rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent; +rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; + + +location / { + try_files $uri $uri/ @strip-ext; +} + +location /qhandler { + rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; +} + +location /edit { + rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; +} + +location ~ ^/api/v1/(.*)$ { + try_files $uri $uri/ /json_api.php?query=$1&$args; +} + +location ~ ^/cache/(.*)$ { + try_files $uri $uri/ /resource.php?file=$1; +} + +location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + fastcgi_index index.php; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_read_timeout 3600; + fastcgi_send_timeout 3600; +} + +location ~* ^/Autodiscover/Autodiscover.xml { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + try_files /autodiscover.php =404; +} + +location ~* ^/Autodiscover/Autodiscover.json { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + try_files /autodiscover-json.php =404; +} + +location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + try_files /autoconfig.php =404; +} + +{% if not SKIP_RSPAMD %} +location /rspamd/ { + proxy_pass http://{{ RSPAMDHOST }}:11334/; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_redirect off; + proxy_intercept_errors on; + error_page 401 /_rspamderror.php; +} +{% endif %} + +{% if not SKIP_SOGO %} +location ^~ /principals { + return 301 /SOGo/dav; +} + +location /sogo-auth-verify { + internal; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $http_host; + proxy_set_header Content-Length ""; + proxy_pass http://127.0.0.1:65510/sogo-auth; + proxy_pass_request_body off; +} + +location ^~ /Microsoft-Server-ActiveSync { + auth_request /sogo-auth-verify; + auth_request_set $user $upstream_http_x_user; + auth_request_set $auth $upstream_http_x_auth; + auth_request_set $auth_type $upstream_http_x_auth_type; + proxy_set_header x-webobjects-remote-user "$user"; + proxy_set_header Authorization "$auth"; + proxy_set_header x-webobjects-auth-type "$auth_type"; + + proxy_pass http://{{ SOGOHOST }}:20000/SOGo/Microsoft-Server-ActiveSync; + + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_connect_timeout 75; + proxy_send_timeout 3600; + proxy_read_timeout 3600; + proxy_buffer_size 128k; + proxy_buffers 64 512k; + proxy_busy_buffers_size 512k; + proxy_set_header Host $http_host; + client_body_buffer_size 512k; + client_max_body_size 0; +} + +location ^~ /SOGo { + location ~* ^/SOGo/so/.*\.(xml|js|html|xhtml)$ { + auth_request /sogo-auth-verify; + auth_request_set $user $upstream_http_x_user; + auth_request_set $auth $upstream_http_x_auth; + auth_request_set $auth_type $upstream_http_x_auth_type; + proxy_set_header x-webobjects-remote-user "$user"; + proxy_set_header Authorization "$auth"; + proxy_set_header x-webobjects-auth-type "$auth_type"; + + proxy_pass http://{{ SOGOHOST }}:20000; + + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header Host $http_host; + proxy_set_header x-webobjects-server-protocol HTTP/1.0; + proxy_set_header x-webobjects-remote-host $remote_addr; + proxy_set_header x-webobjects-server-name $server_name; + proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; + proxy_set_header x-webobjects-server-port $server_port; + proxy_hide_header Content-Type; + add_header Content-Type text/plain; + break; + } + auth_request /sogo-auth-verify; + auth_request_set $user $upstream_http_x_user; + auth_request_set $auth $upstream_http_x_auth; + auth_request_set $auth_type $upstream_http_x_auth_type; + proxy_set_header x-webobjects-remote-user "$user"; + proxy_set_header Authorization "$auth"; + proxy_set_header x-webobjects-auth-type "$auth_type"; + + proxy_pass http://{{ SOGOHOST }}:20000; + + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header Host $http_host; + proxy_set_header x-webobjects-server-protocol HTTP/1.0; + proxy_set_header x-webobjects-remote-host $remote_addr; + proxy_set_header x-webobjects-server-name $server_name; + proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; + proxy_set_header x-webobjects-server-port $server_port; + proxy_buffer_size 128k; + proxy_buffers 64 512k; + proxy_busy_buffers_size 512k; + proxy_send_timeout 3600; + proxy_read_timeout 3600; + client_body_buffer_size 128k; + client_max_body_size 0; + break; +} + +location ~* /sogo$ { + return 301 $client_req_scheme://$http_host/SOGo; +} + +location /SOGo.woa/WebServerResources/ { + alias /usr/lib/GNUstep/SOGo/WebServerResources/; +} + +location /.woa/WebServerResources/ { + alias /usr/lib/GNUstep/SOGo/WebServerResources/; +} + +location /SOGo/WebServerResources/ { + alias /usr/lib/GNUstep/SOGo/WebServerResources/; +} + +location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { + alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; +} +{% endif %} + + +include /etc/nginx/conf.d/site.*.custom; + +error_page 502 @awaitingupstream; + +location @awaitingupstream { + rewrite ^(.*)$ /_status.502.html break; +} + +location ~* \.php$ { + return 404; +} +location ~* \.twig$ { + return 404; +} diff --git a/data/conf/nginx/templates/listen_plain.template b/data/conf/nginx/templates/listen_plain.template deleted file mode 100644 index a044b22f2..000000000 --- a/data/conf/nginx/templates/listen_plain.template +++ /dev/null @@ -1,2 +0,0 @@ -listen ${HTTP_PORT}; -listen [::]:${HTTP_PORT}; diff --git a/data/conf/nginx/templates/listen_ssl.template b/data/conf/nginx/templates/listen_ssl.template deleted file mode 100644 index 40c402d04..000000000 --- a/data/conf/nginx/templates/listen_ssl.template +++ /dev/null @@ -1,3 +0,0 @@ -listen ${HTTPS_PORT} ssl; -listen [::]:${HTTPS_PORT} ssl; -http2 on; diff --git a/data/conf/nginx/templates/server_name.template.sh b/data/conf/nginx/templates/server_name.template.sh deleted file mode 100755 index 291b378fb..000000000 --- a/data/conf/nginx/templates/server_name.template.sh +++ /dev/null @@ -1 +0,0 @@ -echo "server_name ${MAILCOW_HOSTNAME} autodiscover.* autoconfig.* $(echo ${ADDITIONAL_SERVER_NAMES} | tr ',' ' ');" diff --git a/data/conf/nginx/templates/sites.template.sh b/data/conf/nginx/templates/sites.template.sh deleted file mode 100644 index 782c8141b..000000000 --- a/data/conf/nginx/templates/sites.template.sh +++ /dev/null @@ -1,38 +0,0 @@ -echo ' -server { - listen 127.0.0.1:65510; - include /etc/nginx/conf.d/listen_plain.active; - include /etc/nginx/conf.d/listen_ssl.active; - - ssl_certificate /etc/ssl/mail/cert.pem; - ssl_certificate_key /etc/ssl/mail/key.pem; - - include /etc/nginx/conf.d/server_name.active; - - include /etc/nginx/conf.d/includes/site-defaults.conf; -} -'; -for cert_dir in /etc/ssl/mail/*/ ; do - if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then - continue - fi - # do not create vhost for default-certificate. the cert is already in the default server listen - domains="$(cat ${cert_dir}domains | sed -e 's/^[[:space:]]*//')" - case "${domains}" in - "") continue;; - "${MAILCOW_HOSTNAME}"*) continue;; - esac - echo -n ' -server { - include /etc/nginx/conf.d/listen_ssl.active; - - ssl_certificate '${cert_dir}'cert.pem; - ssl_certificate_key '${cert_dir}'key.pem; -'; - echo -n ' - server_name '${domains}'; - - include /etc/nginx/conf.d/includes/site-defaults.conf; -} -'; -done diff --git a/data/conf/nginx/templates/sogo.template b/data/conf/nginx/templates/sogo.template deleted file mode 100644 index 2c084389f..000000000 --- a/data/conf/nginx/templates/sogo.template +++ /dev/null @@ -1 +0,0 @@ -proxy_pass http://${IPV4_NETWORK}.248:20000; diff --git a/data/conf/nginx/templates/sogo_eas.template.sh b/data/conf/nginx/templates/sogo_eas.template.sh deleted file mode 100644 index b241ef0e7..000000000 --- a/data/conf/nginx/templates/sogo_eas.template.sh +++ /dev/null @@ -1,5 +0,0 @@ -if printf "%s\n" "${SKIP_SOGO}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then - echo "return 410;" -else - echo "proxy_pass http://${IPV4_NETWORK}.248:20000/SOGo/Microsoft-Server-ActiveSync;" -fi diff --git a/docker-compose.yml b/docker-compose.yml index b0324521a..31d6f56fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -359,33 +359,26 @@ services: nginx-mailcow: depends_on: - - sogo-mailcow - - php-fpm-mailcow - redis-mailcow - image: nginx:mainline-alpine + - php-fpm-mailcow + - sogo-mailcow + - rspamd-mailcow + image: mailcow/nginx:1.00 dns: - ${IPV4_NETWORK:-172.22.1}.254 - command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active && - envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && - envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && - . /etc/nginx/conf.d/templates/server_name.template.sh > /etc/nginx/conf.d/server_name.active && - . /etc/nginx/conf.d/templates/sites.template.sh > /etc/nginx/conf.d/sites.active && - . /etc/nginx/conf.d/templates/sogo_eas.template.sh > /etc/nginx/conf.d/sogo_eas.active && - nginx -qt && - until ping phpfpm -c1 > /dev/null; do sleep 1; done && - until ping sogo -c1 > /dev/null; do sleep 1; done && - until ping redis -c1 > /dev/null; do sleep 1; done && - until ping rspamd -c1 > /dev/null; do sleep 1; done && - exec nginx -g 'daemon off;'" environment: - HTTPS_PORT=${HTTPS_PORT:-443} - HTTP_PORT=${HTTP_PORT:-80} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - ADDITIONAL_SERVER_NAMES=${ADDITIONAL_SERVER_NAMES:-} - TZ=${TZ} - SKIP_SOGO=${SKIP_SOGO:-n} - - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} - - ADDITIONAL_SERVER_NAMES=${ADDITIONAL_SERVER_NAMES:-} + - SKIP_RSPAMD=${SKIP_RSPAMD:-n} + - PHPFPMHOST=${PHPFPMHOST:-php-fpm-mailcow} + - SOGOHOST=${SOGOHOST:-sogo-mailcow} + - RSPAMDHOST=${RSPAMDHOST:-rspamd-mailcow} + - REDISHOST=${REDISHOST:-redis-mailcow} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} volumes: - ./data/web:/web:ro,z - ./data/conf/rspamd/dynmaps:/dynmaps:ro,z