Skip to content

Commit

Permalink
Merge #2717
Browse files Browse the repository at this point in the history
2717: nginx: Allow HTTP and/or TCP ports to accept the PROXY protocol r=mergify[bot] a=OdyX

This is a feature proposal, as a followup to close #2300, with a cleaner split proposal.

Co-authored-by: Didier 'OdyX' Raboud <odyx@raksha.ch>
Co-authored-by: Dimitri Huisman <diman@huisman.xyz>
Co-authored-by: Didier Raboud <odyx@debian.org>
  • Loading branch information
4 people committed Mar 28, 2023
2 parents e00c3b6 + dcd3a45 commit 83c4474
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 23 deletions.
2 changes: 1 addition & 1 deletion core/nginx/Dockerfile
Expand Up @@ -28,7 +28,7 @@ RUN echo $VERSION >/version

EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp
# EXPOSE 10025/tcp 10143/tcp 14190/tcp
HEALTHCHECK --start-period=60s CMD curl -skfLo /dev/null http://localhost/health
HEALTHCHECK --start-period=60s CMD curl -skfLo /dev/null http://127.0.0.1:10204/health

VOLUME ["/certs", "/overrides"]

Expand Down
54 changes: 34 additions & 20 deletions core/nginx/conf/nginx.conf
Expand Up @@ -23,6 +23,8 @@ http {

{% if REAL_IP_HEADER %}
real_ip_header {{ REAL_IP_HEADER }};
{% elif PROXY_PROTOCOL in ['all', 'http'] %}
real_ip_header proxy_protocol;
{% endif %}

{% if REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %}
Expand Down Expand Up @@ -58,9 +60,9 @@ http {
#
server {
# Listen over HTTP
listen 80;
listen 80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:80;
listen [::]:80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
{% endif %}
{% if TLS_FLAVOR == 'letsencrypt' %}
location ^~ /.well-known/acme-challenge/ {
Expand Down Expand Up @@ -92,17 +94,17 @@ http {

# Listen on HTTP only in kubernetes or behind reverse proxy
{% if KUBERNETES_INGRESS or TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %}
listen 80;
listen 80{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:80;
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;
listen 443 ssl http2{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:443 ssl http2;
listen [::]:443 ssl http2{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
{% endif %}

include /etc/nginx/tls.conf;
Expand Down Expand Up @@ -289,6 +291,14 @@ http {
}
}

# Healthcheck over localhost, for docker
server {
listen 127.0.0.1:10204;
location /health {
return 204;
}
}

include /etc/nginx/conf.d/*.conf;
}

Expand Down Expand Up @@ -323,6 +333,10 @@ mail {
ssl_session_cache shared:SSLMAIL:3m;
{% endif %}

{% if PROXY_PROTOCOL in ['all', '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;
pop3_capabilities TOP UIDL RESP-CODES PIPELINING AUTH-RESP-CODE USER;
Expand All @@ -348,9 +362,9 @@ mail {

# SMTP is always enabled, to avoid losing emails when TLS is failing
server {
listen 25;
listen 25{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:25;
listen [::]:25{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
{% if TLS and not TLS_ERROR %}
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
Expand All @@ -372,9 +386,9 @@ mail {
# All other protocols are disabled if TLS is failing
{% if not TLS_ERROR %}
server {
listen 143;
listen 143{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:143;
listen [::]:143{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
{% if TLS %}
starttls only;
Expand All @@ -387,9 +401,9 @@ mail {
}

server {
listen 110;
listen 110{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:110;
listen [::]:110{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
{% if TLS %}
starttls only;
Expand All @@ -402,9 +416,9 @@ mail {
}

server {
listen 587;
listen 587{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:587;
listen [::]:587{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
{% if TLS %}
starttls only;
Expand All @@ -416,19 +430,19 @@ mail {

{% if TLS %}
server {
listen 465 ssl;
listen 465 ssl{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:465 ssl;
listen [::]:465 ssl{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
protocol smtp;
smtp_auth plain login;
auth_http_header Auth-Port 465;
}

server {
listen 993 ssl;
listen 993 ssl{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:993 ssl;
listen [::]:993 ssl{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
protocol imap;
imap_auth plain;
Expand All @@ -438,9 +452,9 @@ mail {
}

server {
listen 995 ssl;
listen 995 ssl{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% if SUBNET6 %}
listen [::]:995 ssl;
listen [::]:995 ssl{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
{% endif %}
protocol pop3;
pop3_auth plain;
Expand Down
2 changes: 1 addition & 1 deletion core/nginx/conf/proxy.conf
Expand Up @@ -5,7 +5,7 @@ proxy_hide_header True-Client-IP;
proxy_hide_header CF-Connecting-IP;

proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
{% if REAL_IP_HEADER and REAL_IP_FROM %}
{% if (REAL_IP_HEADER or (PROXY_PROTOCOL in ['http', 'all'])) and REAL_IP_FROM %}
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-By $realip_remote_addr;
{% else %}
Expand Down
20 changes: 19 additions & 1 deletion docs/configuration.rst
Expand Up @@ -247,12 +247,30 @@ controls whether HTTP headers such as ``X-Forwarded-For`` or ``X-Real-IP`` shoul
The former should be the name of the HTTP header to extract the client IP address from and the
later a comma separated list of IP addresses designating which proxies to trust.
If you are using Mailu behind a reverse proxy, you should set both. Setting the former without
the later introduces a security vulnerability allowing a potential attacker to spoof his source address.
the latter introduces a security vulnerability allowing a potential attacker to spoof their source address.

The ``TZ`` sets the timezone Mailu will use. The timezone naming convention usually uses a ``Region/City`` format. See `TZ database name`_ for a list of valid timezones This defaults to ``Etc/UTC``. Warning: if you are observing different timestamps in your log files you should change your hosts timezone to UTC instead of changing TZ to your local timezone. Using UTC allows easy log correlation with remote MTAs.

.. _`TZ database name`: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

The ``PROXY_PROTOCOL`` (default: unset) allows the the front container to receive TCP and HTTP connections with
the `PROXY protocol`_ (originally introduced in HAProxy, now also configurable in other proxy servers).
It can be set to:

* ``http`` to accept the ``PROXY`` protocol on nginx's HTTP proxy ports
* ``mail`` to accept the ``PROXY`` protocol on nginx's mail proxy ports
* ``all`` to accept the ``PROXY`` protocol on all nginx's HTTP and mail proxy ports

.. _`PROXY protocol`: https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt

This requires to have a valid ``REAL_IP_FROM`` (default: unset). Setting ``PROXY_PROTOCOL`` without setting
``REAL_IP_FROM`` *will not work*. The ``REAL_IP_HEADER`` **must be unset**. Otherwise Mailu will not accept
the IP address from the remote client specified by the proxy. This results in the proxy being rate limited
or even banned (when fail2ban is used).
Make sure to set a ``REAL_IP_FROM`` only pointing to IP addresses or networks
that you trust; accepting the ``PROXY`` protocol from untrusted sources is a serious security vulnerability,
allowing a potential attacker to spoof their source address.

Antivirus settings
------------------

Expand Down
1 change: 1 addition & 0 deletions towncrier/newsfragments/2717.feature
@@ -0,0 +1 @@
Allow inbound to http and mail ports to accept the PROXY protocol

0 comments on commit 83c4474

Please sign in to comment.