Enable dynamic resolution of hostnames

main
Florent Daigniere 2 years ago
parent 1a67921b7c
commit 4e3874b0c1

@ -1,7 +1,6 @@
import os import os
from datetime import timedelta from datetime import timedelta
from socrate import system
import ipaddress import ipaddress
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
@ -83,17 +82,6 @@ DEFAULT_CONFIG = {
'PROXY_AUTH_WHITELIST': '', 'PROXY_AUTH_WHITELIST': '',
'PROXY_AUTH_HEADER': 'X-Auth-Email', 'PROXY_AUTH_HEADER': 'X-Auth-Email',
'PROXY_AUTH_CREATE': False, 'PROXY_AUTH_CREATE': False,
# Host settings
'HOST_IMAP': 'imap',
'HOST_LMTP': 'imap:2525',
'HOST_POP3': 'imap',
'HOST_SMTP': 'smtp',
'HOST_AUTHSMTP': 'smtp',
'HOST_ADMIN': 'admin',
'HOST_WEBMAIL': 'webmail',
'HOST_WEBDAV': 'webdav:5232',
'HOST_REDIS': 'redis',
'HOST_FRONT': 'front',
'SUBNET': '192.168.203.0/24', 'SUBNET': '192.168.203.0/24',
'SUBNET6': None 'SUBNET6': None
} }
@ -111,19 +99,6 @@ class ConfigManager:
def __init__(self): def __init__(self):
self.config = dict() self.config = dict()
def get_host_address(self, name):
# if MYSERVICE_ADDRESS is defined, use this
if f'{name}_ADDRESS' in os.environ:
return os.environ.get(f'{name}_ADDRESS')
# otherwise use the host name and resolve it
return system.resolve_address(self.config[f'HOST_{name}'])
def resolve_hosts(self):
for key in ['IMAP', 'POP3', 'AUTHSMTP', 'SMTP', 'REDIS']:
self.config[f'{key}_ADDRESS'] = self.get_host_address(key)
if self.config['WEBMAIL'] != 'none':
self.config['WEBMAIL_ADDRESS'] = self.get_host_address('WEBMAIL')
def __get_env(self, key, value): def __get_env(self, key, value):
key_file = key + "_FILE" key_file = key + "_FILE"
if key_file in os.environ: if key_file in os.environ:
@ -144,11 +119,14 @@ class ConfigManager:
# get current app config # get current app config
self.config.update(app.config) self.config.update(app.config)
# get environment variables # get environment variables
for key in os.environ:
if key.endswith('_ADDRESS'):
self.config[key] = os.environ[key]
self.config.update({ self.config.update({
key: self.__coerce_value(self.__get_env(key, value)) key: self.__coerce_value(self.__get_env(key, value))
for key, value in DEFAULT_CONFIG.items() for key, value in DEFAULT_CONFIG.items()
}) })
self.resolve_hosts()
# automatically set the sqlalchemy string # automatically set the sqlalchemy string
if self.config['DB_FLAVOR']: if self.config['DB_FLAVOR']:

@ -2,7 +2,6 @@ from mailu import models, utils
from flask import current_app as app from flask import current_app as app
from socrate import system from socrate import system
import re
import urllib import urllib
import ipaddress import ipaddress
import sqlalchemy.exc import sqlalchemy.exc
@ -128,20 +127,16 @@ def get_status(protocol, status):
status, codes = STATUSES[status] status, codes = STATUSES[status]
return status, codes[protocol] return status, codes[protocol]
def extract_host_port(host_and_port, default_port):
host, _, port = re.match('^(.*?)(:([0-9]*))?$', host_and_port).groups()
return host, int(port) if port else default_port
def get_server(protocol, authenticated=False): def get_server(protocol, authenticated=False):
if protocol == "imap": if protocol == "imap":
hostname, port = extract_host_port(app.config['IMAP_ADDRESS'], 143) hostname, port = app.config['IMAP_ADDRESS'], 143
elif protocol == "pop3": elif protocol == "pop3":
hostname, port = extract_host_port(app.config['POP3_ADDRESS'], 110) hostname, port = app.config['IMAP_ADDRESS'], 110
elif protocol == "smtp": elif protocol == "smtp":
if authenticated: if authenticated:
hostname, port = extract_host_port(app.config['AUTHSMTP_ADDRESS'], 10025) hostname, port = app.config['SMTP_ADDRESS'], 10025
else: else:
hostname, port = extract_host_port(app.config['SMTP_ADDRESS'], 25) hostname, port = app.config['SMTP_ADDRESS'], 25
try: try:
# test if hostname is already resolved to an ip address # test if hostname is already resolved to an ip address
ipaddress.ip_address(hostname) ipaddress.ip_address(hostname)

@ -421,8 +421,7 @@ class Email(object):
""" send an email to the address """ """ send an email to the address """
try: try:
f_addr = f'{app.config["POSTMASTER"]}@{idna.encode(app.config["DOMAIN"]).decode("ascii")}' f_addr = f'{app.config["POSTMASTER"]}@{idna.encode(app.config["DOMAIN"]).decode("ascii")}'
ip, port = app.config['HOST_LMTP'].rsplit(':') with smtplib.LMTP(ip=app.config['IMAP_ADDRESS'], port=2525) as lmtp:
with smtplib.LMTP(ip, port=port) as lmtp:
to_address = f'{self.localpart}@{idna.encode(self.domain_name).decode("ascii")}' to_address = f'{self.localpart}@{idna.encode(self.domain_name).decode("ascii")}'
msg = text.MIMEText(body) msg = text.MIMEText(body)
msg['Subject'] = subject msg['Subject'] = subject

@ -21,7 +21,7 @@
</tr> </tr>
<tr> <tr>
<th>{% trans %}Server name{% endtrans %}</th> <th>{% trans %}Server name{% endtrans %}</th>
<td><pre class="pre-config border bg-light">{{ config["HOSTNAMES"] }}</pre></td> <td><pre class="pre-config border bg-light">{{ config["HOSTNAME"] }}</pre></td>
</tr> </tr>
<tr> <tr>
<th>{% trans %}Username{% endtrans %}</th> <th>{% trans %}Username{% endtrans %}</th>
@ -46,7 +46,7 @@
</tr> </tr>
<tr> <tr>
<th>{% trans %}Server name{% endtrans %}</th> <th>{% trans %}Server name{% endtrans %}</th>
<td><pre class="pre-config border bg-light">{{ config["HOSTNAMES"] }}</pre></td> <td><pre class="pre-config border bg-light">{{ config["HOSTNAME"] }}</pre></td>
</tr> </tr>
<tr> <tr>
<th>{% trans %}Username{% endtrans %}</th> <th>{% trans %}Username{% endtrans %}</th>

@ -75,12 +75,15 @@ ENV \
DEBUG_ASSETS="/app/static" \ DEBUG_ASSETS="/app/static" \
DEBUG_TB_INTERCEPT_REDIRECTS=False \ DEBUG_TB_INTERCEPT_REDIRECTS=False \
\ \
IMAP_ADDRESS="127.0.0.1" \ ADMIN_ADDRESS="127.0.0.1" \
POP3_ADDRESS="127.0.0.1" \ FRONT_ADDRESS="127.0.0.1" \
AUTHSMTP_ADDRESS="127.0.0.1" \
SMTP_ADDRESS="127.0.0.1" \ SMTP_ADDRESS="127.0.0.1" \
IMAP_ADDRESS="127.0.0.1" \
REDIS_ADDRESS="127.0.0.1" \ REDIS_ADDRESS="127.0.0.1" \
WEBMAIL_ADDRESS="127.0.0.1" ANTIVIRUS_ADDRESS="127.0.0.1" \
ANTISPAM_ADDRESS="127.0.0.1" \
WEBMAIL_ADDRESS="127.0.0.1" \
WEBDAV_ADDRESS="127.0.0.1"
CMD ["/bin/bash", "-c", "flask db upgrade &>/dev/null && flask mailu admin '${DEV_ADMIN/@*}' '${DEV_ADMIN#*@}' '${DEV_PASSWORD}' --mode ifmissing >/dev/null; flask --debug run --host=0.0.0.0 --port=8080"] CMD ["/bin/bash", "-c", "flask db upgrade &>/dev/null && flask mailu admin '${DEV_ADMIN/@*}' '${DEV_ADMIN#*@}' '${DEV_PASSWORD}' --mode ifmissing >/dev/null; flask --debug run --host=0.0.0.0 --port=8080"]
EOF EOF

@ -4,6 +4,7 @@ import os
import logging as log import logging as log
from pwd import getpwnam from pwd import getpwnam
import sys import sys
from socrate import system
os.system("chown mailu:mailu -R /dkim") os.system("chown mailu:mailu -R /dkim")
os.system("find /data | grep -v /fetchmail | xargs -n1 chown mailu:mailu") os.system("find /data | grep -v /fetchmail | xargs -n1 chown mailu:mailu")
@ -12,6 +13,7 @@ os.setgid(mailu_id.pw_gid)
os.setuid(mailu_id.pw_uid) os.setuid(mailu_id.pw_uid)
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "INFO")) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "INFO"))
system.set_env(['SECRET'])
os.system("flask mailu advertise") os.system("flask mailu advertise")
os.system("flask db upgrade") os.system("flask db upgrade")

@ -17,11 +17,21 @@ RUN set -euxo pipefail \
; ! [[ "${machine}" == x86_64 ]] \ ; ! [[ "${machine}" == x86_64 ]] \
|| apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc==11-r0 || apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc==11-r0
ENV LD_PRELOAD=/usr/lib/libhardened_malloc.so ENV \
ENV CXXFLAGS="-g -O2 -fdebug-prefix-map=/app=. -fstack-protector-strong -Wformat -Werror=format-security -fstack-clash-protection -fexceptions" LD_PRELOAD="/usr/lib/libhardened_malloc.so" \
ENV CFLAGS="-g -O2 -fdebug-prefix-map=/app=. -fstack-protector-strong -Wformat -Werror=format-security -fstack-clash-protection -fexceptions" CXXFLAGS="-g -O2 -fdebug-prefix-map=/app=. -fstack-protector-strong -Wformat -Werror=format-security -fstack-clash-protection -fexceptions" \
ENV CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2" CFLAGS="-g -O2 -fdebug-prefix-map=/app=. -fstack-protector-strong -Wformat -Werror=format-security -fstack-clash-protection -fexceptions" \
ENV LDFLAGS="-Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2" \
LDFLAGS="-Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" \
ADMIN_ADDRESS="admin" \
FRONT_ADDRESS="front" \
SMTP_ADDRESS="smtp" \
IMAP_ADDRESS="imap" \
REDIS_ADDRESS="redis" \
ANTIVIRUS_ADDRESS="antivirus" \
ANTISPAM_ADDRESS="antispam" \
WEBMAIL_ADDRESS="webmail" \
WEBDAV_ADDRESS="webdav"
WORKDIR /app WORKDIR /app

@ -1,7 +1,8 @@
import hmac
import logging as log
import os
import socket import socket
import tenacity import tenacity
from os import environ
import logging as log
@tenacity.retry(stop=tenacity.stop_after_attempt(100), @tenacity.retry(stop=tenacity.stop_after_attempt(100),
wait=tenacity.wait_random(min=2, max=5)) wait=tenacity.wait_random(min=2, max=5))
@ -14,25 +15,20 @@ def resolve_hostname(hostname):
except Exception as e: except Exception as e:
log.warn("Unable to lookup '%s': %s",hostname,e) log.warn("Unable to lookup '%s': %s",hostname,e)
raise e raise e
def set_env(required_secrets=[]):
""" This will set all the environment variables and retains only the secrets we need """
secret_key = os.environ.get('SECRET_KEY')
if not secret_key:
try:
secret_key = open(env.get("SECRET_KEY_FILE"), "r").read().strip()
except Exception as exc:
log.error(f"Can't read SECRET_KEY from file: {exc}")
raise exc
clean_env()
# derive the keys we need
for secret in required_secrets:
os.environ[f'{secret}_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray(secret, 'utf-8'), 'sha256').hexdigest()
def clean_env():
def resolve_address(address): """ remove all secret keys """
""" This function is identical to ``resolve_hostname`` but also supports [os.environ.pop(key, None) for key in os.environ.keys() if key.endswith("_KEY")]
resolving an address, i.e. including a port.
"""
hostname, *rest = address.rsplit(":", 1)
ip_address = resolve_hostname(hostname)
if ":" in ip_address:
ip_address = "[{}]".format(ip_address)
return ip_address + "".join(":" + port for port in rest)
def get_host_address_from_environment(name, default):
""" This function looks up an envionment variable ``{{ name }}_ADDRESS``.
If it's defined, it is returned unmodified. If it's undefined, an environment
variable ``HOST_{{ name }}`` is looked up and resolved to an ip address.
If this is also not defined, the default is resolved to an ip address.
"""
if "{}_ADDRESS".format(name) in environ:
return environ.get("{}_ADDRESS".format(name))
return resolve_address(environ.get("HOST_{}".format(name), default))

@ -1,9 +1,8 @@
#!/bin/bash #!/bin/bash
{% set hostname,port = ANTISPAM_WEBUI_ADDRESS.split(':') %} RSPAMD_HOST="$(getent hosts {{ ANTISPAM_ADDRESS }}|cut -d\ -f1):11334"
RSPAMD_HOST="$(getent hosts {{ hostname }}|cut -d\ -f1):{{ port }}"
if [[ $? -ne 0 ]] if [[ $? -ne 0 ]]
then then
echo "Failed to lookup {{ ANTISPAM_WEBUI_ADDRESS }}" >&2 echo "Failed to lookup {{ ANTISPAM_ADDRESS }}" >&2
exit 1 exit 1
fi fi

@ -1,9 +1,8 @@
#!/bin/bash #!/bin/bash
{% set hostname,port = ANTISPAM_WEBUI_ADDRESS.split(':') %} RSPAMD_HOST="$(getent hosts {{ ANTISPAM_ADDRESS }}|cut -d\ -f1):11334"
RSPAMD_HOST="$(getent hosts {{ hostname }}|cut -d\ -f1):{{ port }}"
if [[ $? -ne 0 ]] if [[ $? -ne 0 ]]
then then
echo "Failed to lookup {{ ANTISPAM_WEBUI_ADDRESS }}" >&2 echo "Failed to lookup {{ ANTISPAM_ADDRESS }}" >&2
exit 1 exit 1
fi fi

@ -11,6 +11,7 @@ from podop import run_server
from socrate import system, conf from socrate import system, conf
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
system.set_env()
def start_podop(): def start_podop():
id_mail = getpwnam('mail') id_mail = getpwnam('mail')
@ -24,10 +25,6 @@ def start_podop():
]) ])
# Actual startup script # Actual startup script
os.environ["FRONT_ADDRESS"] = system.get_host_address_from_environment("FRONT", "front")
os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
os.environ["ANTISPAM_WEBUI_ADDRESS"] = system.get_host_address_from_environment("ANTISPAM_WEBUI", "antispam:11334")
for dovecot_file in glob.glob("/conf/*.conf"): for dovecot_file in glob.glob("/conf/*.conf"):
conf.jinja(dovecot_file, os.environ, os.path.join("/etc/dovecot", os.path.basename(dovecot_file))) conf.jinja(dovecot_file, os.environ, os.path.join("/etc/dovecot", os.path.basename(dovecot_file)))

@ -77,12 +77,12 @@ http {
root /static; root /static;
# Variables for proxifying # Variables for proxifying
set $admin {{ ADMIN_ADDRESS }}; set $admin {{ ADMIN_ADDRESS }};
set $antispam {{ ANTISPAM_WEBUI_ADDRESS }}; set $antispam {{ ANTISPAM_ADDRESS }}:11334;
{% if WEBMAIL_ADDRESS %} {% if WEBMAIL_ADDRESS %}
set $webmail {{ WEBMAIL_ADDRESS }}; set $webmail {{ WEBMAIL_ADDRESS }};
{% endif %} {% endif %}
{% if WEBDAV_ADDRESS %} {% if WEBDAV_ADDRESS %}
set $webdav {{ WEBDAV_ADDRESS }}; set $webdav {{ WEBDAV_ADDRESS }}:5232;
{% endif %} {% endif %}
client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }}; client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }};

@ -5,8 +5,8 @@ import logging as log
import sys import sys
from socrate import system, conf from socrate import system, conf
system.set_env()
args = os.environ.copy() args = os.environ.copy()
log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING")) log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING"))
args['TLS_PERMISSIVE'] = str(args.get('TLS_PERMISSIVE')).lower() not in ('false', 'no') args['TLS_PERMISSIVE'] = str(args.get('TLS_PERMISSIVE')).lower() not in ('false', 'no')
@ -17,13 +17,6 @@ with open("/etc/resolv.conf") as handle:
resolver = content[content.index("nameserver") + 1] resolver = content[content.index("nameserver") + 1]
args["RESOLVER"] = f"[{resolver}]" if ":" in resolver else resolver args["RESOLVER"] = f"[{resolver}]" if ":" in resolver else resolver
args["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
args["ANTISPAM_WEBUI_ADDRESS"] = system.get_host_address_from_environment("ANTISPAM_WEBUI", "antispam:11334")
if args["WEBMAIL"] != "none":
args["WEBMAIL_ADDRESS"] = system.get_host_address_from_environment("WEBMAIL", "webmail")
if args["WEBDAV"] != "none":
args["WEBDAV_ADDRESS"] = system.get_host_address_from_environment("WEBDAV", "webdav:5232")
# TLS configuration # TLS configuration
cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem") cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem")
keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem") keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem")

@ -81,7 +81,7 @@ virtual_mailbox_maps = ${podop}mailbox
# Mails are transported if required, then forwarded to Dovecot for delivery # Mails are transported if required, then forwarded to Dovecot for delivery
relay_domains = ${podop}transport relay_domains = ${podop}transport
transport_maps = lmdb:/etc/postfix/transport.map, ${podop}transport transport_maps = lmdb:/etc/postfix/transport.map, ${podop}transport
virtual_transport = lmtp:inet:{{ LMTP_ADDRESS }} virtual_transport = lmtp:inet:{{ IMAP_ADDRESS }}:2525
# Sender and recipient canonical maps, mostly for SRS # Sender and recipient canonical maps, mostly for SRS
sender_canonical_maps = ${podop}sendermap sender_canonical_maps = ${podop}sendermap
@ -126,7 +126,7 @@ unverified_recipient_reject_reason = Address lookup failure
# Milter # Milter
############### ###############
smtpd_milters = inet:{{ ANTISPAM_MILTER_ADDRESS }} smtpd_milters = inet:{{ ANTISPAM_ADDRESS }}:11332
milter_protocol = 6 milter_protocol = 6
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
milter_default_action = tempfail milter_default_action = tempfail

@ -13,6 +13,7 @@ from pwd import getpwnam
from socrate import system, conf from socrate import system, conf
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
system.set_env()
os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid") os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid")
@ -45,10 +46,6 @@ def is_valid_postconf_line(line):
# Actual startup script # Actual startup script
os.environ['DEFER_ON_TLS_ERROR'] = os.environ['DEFER_ON_TLS_ERROR'] if 'DEFER_ON_TLS_ERROR' in os.environ else 'True' os.environ['DEFER_ON_TLS_ERROR'] = os.environ['DEFER_ON_TLS_ERROR'] if 'DEFER_ON_TLS_ERROR' in os.environ else 'True'
os.environ["FRONT_ADDRESS"] = system.get_host_address_from_environment("FRONT", "front")
os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
os.environ["ANTISPAM_MILTER_ADDRESS"] = system.get_host_address_from_environment("ANTISPAM_MILTER", "antispam:11332")
os.environ["LMTP_ADDRESS"] = system.get_host_address_from_environment("LMTP", "imap:2525")
os.environ["POSTFIX_LOG_SYSLOG"] = os.environ.get("POSTFIX_LOG_SYSLOG","local") os.environ["POSTFIX_LOG_SYSLOG"] = os.environ.get("POSTFIX_LOG_SYSLOG","local")
os.environ["POSTFIX_LOG_FILE"] = os.environ.get("POSTFIX_LOG_FILE", "") os.environ["POSTFIX_LOG_FILE"] = os.environ.get("POSTFIX_LOG_FILE", "")

@ -3,7 +3,7 @@ clamav {
scan_mime_parts = true; scan_mime_parts = true;
symbol = "CLAM_VIRUS"; symbol = "CLAM_VIRUS";
type = "clamav"; type = "clamav";
servers = "{{ ANTIVIRUS_ADDRESS }}"; servers = "{{ ANTIVIRUS_ADDRESS }}:3310";
{% if ANTIVIRUS_ACTION|default('discard') == 'reject' %} {% if ANTIVIRUS_ACTION|default('discard') == 'reject' %}
action = "reject" action = "reject"
{% endif %} {% endif %}

@ -6,18 +6,13 @@ import logging as log
import requests import requests
import sys import sys
import time import time
from socrate import system, conf from socrate import system,conf
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
system.set_env()
# Actual startup script # Actual startup script
os.environ["REDIS_ADDRESS"] = system.get_host_address_from_environment("REDIS", "redis")
os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
if os.environ.get("ANTIVIRUS") == 'clamav':
os.environ["ANTIVIRUS_ADDRESS"] = system.get_host_address_from_environment("ANTIVIRUS", "antivirus:3310")
for rspamd_file in glob.glob("/conf/*"): for rspamd_file in glob.glob("/conf/*"):
conf.jinja(rspamd_file, os.environ, os.path.join("/etc/rspamd/local.d", os.path.basename(rspamd_file))) conf.jinja(rspamd_file, os.environ, os.path.join("/etc/rspamd/local.d", os.path.basename(rspamd_file)))

@ -249,32 +249,22 @@ virus mails during SMTP dialogue, so the sender will receive a reject message.
Infrastructure settings Infrastructure settings
----------------------- -----------------------
Various environment variables ``HOST_*`` can be used to run Mailu containers Various environment variables ``*_ADDRESS`` can be used to run Mailu containers
separately from a supported orchestrator. It is used by the various components separately from a supported orchestrator. It is used by the various components
to find the location of the other containers it depends on. They can contain an to find the location of the other containers it depends on. Those variables are:
optional port number. Those variables are:
- ``HOST_IMAP``: the container that is running the IMAP server (default: ``imap``, port 143) - ``ADMIN_ADDRESS``
- ``HOST_LMTP``: the container that is running the LMTP server (default: ``imap:2525``) - ``ANTISPAM_ADDRESS``
- ``HOST_HOSTIMAP``: the container that is running the IMAP server for the webmail (default: ``imap``, port 10143) - ``ANTIVIRUS_ADDRESS``
- ``HOST_POP3``: the container that is running the POP3 server (default: ``imap``, port 110) - ``FRONT_ADDRESS``
- ``HOST_SMTP``: the container that is running the SMTP server (default: ``smtp``, port 25) - ``IMAP_ADDRESS``
- ``HOST_AUTHSMTP``: the container that is running the authenticated SMTP server for the webnmail (default: ``smtp``, port 10025) - ``REDIS_ADDRESS``
- ``HOST_ADMIN``: the container that is running the admin interface (default: ``admin``) - ``SMTP_ADDRESS``
- ``HOST_ANTISPAM_MILTER``: the container that is running the antispam milter service (default: ``antispam:11332``) - ``WEBDAV_ADDRESS``
- ``HOST_ANTISPAM_WEBUI``: the container that is running the antispam webui service (default: ``antispam:11334``) - ``WEBMAIL_ADDRESS``
- ``HOST_ANTIVIRUS``: the container that is running the antivirus service (default: ``antivirus:3310``)
- ``HOST_WEBMAIL``: the container that is running the webmail (default: ``webmail``)
- ``HOST_WEBDAV``: the container that is running the webdav server (default: ``webdav:5232``)
- ``HOST_REDIS``: the container that is running the redis daemon (default: ``redis``)
- ``HOST_WEBMAIL``: the container that is running the webmail (default: ``webmail``)
The startup scripts will resolve ``HOST_*`` to their IP addresses and store the result in ``*_ADDRESS`` for further use. These are used for DNS based service discovery with possibly changing services IP addresses.
``*_ADDRESS`` values must be fully qualified domain names without port numbers.
Alternatively, ``*_ADDRESS`` can directly be set. In this case, the values of ``*_ADDRESS`` is kept and not
resolved. This can be used to rely on DNS based service discovery with changing services IP addresses.
When using ``*_ADDRESS``, the hostnames must be full-qualified hostnames. Otherwise nginx will not be able to
resolve the hostnames.
.. _db_settings: .. _db_settings:

@ -7,7 +7,6 @@ from pwd import getpwnam
import tempfile import tempfile
import shlex import shlex
import subprocess import subprocess
import re
import requests import requests
from socrate import system from socrate import system
import sys import sys
@ -34,11 +33,6 @@ poll "{host}" proto {protocol} port {port}
""" """
def extract_host_port(host_and_port, default_port):
host, _, port = re.match('^(.*?)(:([0-9]*))?$', host_and_port).groups()
return host, int(port) if port else default_port
def escape_rc_string(arg): def escape_rc_string(arg):
return "".join("\\x%2x" % ord(char) for char in arg) return "".join("\\x%2x" % ord(char) for char in arg)
@ -54,20 +48,7 @@ def fetchmail(fetchmailrc):
def run(debug): def run(debug):
try: try:
os.environ["SMTP_ADDRESS"] = system.get_host_address_from_environment("SMTP", "smtp")
os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
fetches = requests.get(f"http://{os.environ['ADMIN_ADDRESS']}/internal/fetch").json() fetches = requests.get(f"http://{os.environ['ADMIN_ADDRESS']}/internal/fetch").json()
smtphost, smtpport = extract_host_port(os.environ["SMTP_ADDRESS"], None)
if smtpport is None:
smtphostport = smtphost
else:
smtphostport = "%s/%d" % (smtphost, smtpport)
os.environ["LMTP_ADDRESS"] = system.get_host_address_from_environment("LMTP", "imap:2525")
lmtphost, lmtpport = extract_host_port(os.environ["LMTP_ADDRESS"], None)
if lmtpport is None:
lmtphostport = lmtphost
else:
lmtphostport = "%s/%d" % (lmtphost, lmtpport)
for fetch in fetches: for fetch in fetches:
fetchmailrc = "" fetchmailrc = ""
options = "options antispam 501, 504, 550, 553, 554" options = "options antispam 501, 504, 550, 553, 554"
@ -79,7 +60,7 @@ def run(debug):
protocol=fetch["protocol"], protocol=fetch["protocol"],
host=escape_rc_string(fetch["host"]), host=escape_rc_string(fetch["host"]),
port=fetch["port"], port=fetch["port"],
smtphost=smtphostport if fetch['scan'] else lmtphostport, smtphost=f'{os.environ["SMTP_ADDRESS"]}' if fetch['scan'] else f'{os.environ["IMAP_ADDRESS"]}/2525',
username=escape_rc_string(fetch["username"]), username=escape_rc_string(fetch["username"]),
password=escape_rc_string(fetch["password"]), password=escape_rc_string(fetch["password"]),
options=options, options=options,
@ -118,6 +99,7 @@ if __name__ == "__main__":
os.chmod("/data/fetchids", 0o700) os.chmod("/data/fetchids", 0o700)
os.setgid(id_fetchmail.pw_gid) os.setgid(id_fetchmail.pw_gid)
os.setuid(id_fetchmail.pw_uid) os.setuid(id_fetchmail.pw_uid)
system.set_env()
while True: while True:
delay = int(os.environ.get("FETCHMAIL_DELAY", 60)) delay = int(os.environ.get("FETCHMAIL_DELAY", 60))
print("Sleeping for {} seconds".format(delay)) print("Sleeping for {} seconds".format(delay))

@ -3,9 +3,10 @@
import os import os
import logging as log import logging as log
import sys import sys
from socrate import conf from socrate import conf, system
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
system.set_env()
conf.jinja("/unbound.conf", os.environ, "/etc/unbound/unbound.conf") conf.jinja("/unbound.conf", os.environ, "/etc/unbound/unbound.conf")

@ -113,6 +113,9 @@ services:
- "{{ root }}/overrides/rspamd:/etc/rspamd/override.d:ro" - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d:ro"
depends_on: depends_on:
- front - front
{% if antivirus_enabled %}
- antivirus
{% endif %}
{% if resolver_enabled %} {% if resolver_enabled %}
- resolver - resolver
dns: dns:

@ -0,0 +1,4 @@
Remove HOST_* variables, use *_ADDRESS everywhere instead. Please note that those should only contain a FQDN (no port number).
Derive a different key for admin/SECRET_KEY; this will invalidate existing sessions
Ensure that rspamd starts after clamav
Only display a single HOSTNAME on the client configuration page

@ -13,14 +13,13 @@ from socrate import conf, system
env = os.environ env = os.environ
logging.basicConfig(stream=sys.stderr, level=env.get("LOG_LEVEL", "WARNING")) logging.basicConfig(stream=sys.stderr, level=env.get("LOG_LEVEL", "WARNING"))
system.set_env(['ROUNDCUBE','SNUFFLEUPAGUS'])
# jinja context # jinja context
context = {} context = {}
context.update(env) context.update(env)
context["MAX_FILESIZE"] = str(int(int(env.get("MESSAGE_SIZE_LIMIT", "50000000")) * 0.66 / 1048576)) context["MAX_FILESIZE"] = str(int(int(env.get("MESSAGE_SIZE_LIMIT", "50000000")) * 0.66 / 1048576))
context["FRONT_ADDRESS"] = system.get_host_address_from_environment("FRONT", "front")
context["IMAP_ADDRESS"] = system.get_host_address_from_environment("IMAP", "imap")
db_flavor = env.get("ROUNDCUBE_DB_FLAVOR", "sqlite") db_flavor = env.get("ROUNDCUBE_DB_FLAVOR", "sqlite")
if db_flavor == "sqlite": if db_flavor == "sqlite":
@ -43,17 +42,6 @@ else:
print(f"Unknown ROUNDCUBE_DB_FLAVOR: {db_flavor}", file=sys.stderr) print(f"Unknown ROUNDCUBE_DB_FLAVOR: {db_flavor}", file=sys.stderr)
exit(1) exit(1)
# derive roundcube secret key
secret_key = env.get("SECRET_KEY")
if not secret_key:
try:
secret_key = open(env.get("SECRET_KEY_FILE"), "r").read().strip()
except Exception as exc:
print(f"Can't read SECRET_KEY from file: {exc}", file=sys.stderr)
exit(2)
context['ROUNDCUBE_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray('ROUNDCUBE_KEY', 'utf-8'), 'sha256').hexdigest()
context['SNUFFLEUPAGUS_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray('SNUFFLEUPAGUS_KEY', 'utf-8'), 'sha256').hexdigest()
conf.jinja("/etc/snuffleupagus.rules.tpl", context, "/etc/snuffleupagus.rules") conf.jinja("/etc/snuffleupagus.rules.tpl", context, "/etc/snuffleupagus.rules")
# roundcube plugins # roundcube plugins
@ -127,8 +115,7 @@ conf.jinja("/conf/nginx-webmail.conf", context, "/etc/nginx/http.d/webmail.conf"
if os.path.exists("/var/run/nginx.pid"): if os.path.exists("/var/run/nginx.pid"):
os.system("nginx -s reload") os.system("nginx -s reload")
# clean env system.clean_env()
[env.pop(key, None) for key in env.keys() if key == "SECRET_KEY" or key.endswith("_KEY")]
# run nginx # run nginx
os.system("php-fpm81") os.system("php-fpm81")

Loading…
Cancel
Save