From 98fa66b5ad13c8f203fcfe40d42240417951f218 Mon Sep 17 00:00:00 2001 From: Georg Date: Mon, 13 Sep 2021 09:40:35 +0200 Subject: Init MC update + Dovecot/SOGo LDAP configuration Signed-off-by: Georg --- mailcow/data/Dockerfiles/ldap/Dockerfile | 16 ++ mailcow/data/Dockerfiles/ldap/api.py | 69 ++++++++ mailcow/data/Dockerfiles/ldap/filedb.py | 52 ++++++ mailcow/data/Dockerfiles/ldap/syncer.py | 186 +++++++++++++++++++++ mailcow/data/Dockerfiles/ldap/syscid-ca.crt | 32 ++++ .../Dockerfiles/ldap/templates/dovecot/extra.conf | 4 + .../ldap/templates/dovecot/ldap/passdb.conf | 8 + .../Dockerfiles/ldap/templates/sogo/plist_ldap | 43 +++++ mailcow/data/conf/dovecot/extra.conf | 5 + mailcow/data/conf/dovecot/ldap/passdb.conf | 9 + mailcow/data/conf/sogo/plist_ldap | 44 +++++ mailcow/docker-compose.override.yml | 34 ++++ mailcow/docker-compose.yml | 81 +++------ mailcow/mailcow.conf | 13 +- 14 files changed, 524 insertions(+), 72 deletions(-) create mode 100644 mailcow/data/Dockerfiles/ldap/Dockerfile create mode 100644 mailcow/data/Dockerfiles/ldap/api.py create mode 100644 mailcow/data/Dockerfiles/ldap/filedb.py create mode 100644 mailcow/data/Dockerfiles/ldap/syncer.py create mode 100644 mailcow/data/Dockerfiles/ldap/syscid-ca.crt create mode 100644 mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf create mode 100644 mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf create mode 100644 mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap create mode 100644 mailcow/data/conf/dovecot/extra.conf create mode 100644 mailcow/data/conf/dovecot/ldap/passdb.conf create mode 100644 mailcow/data/conf/sogo/plist_ldap create mode 100644 mailcow/docker-compose.override.yml (limited to 'mailcow') diff --git a/mailcow/data/Dockerfiles/ldap/Dockerfile b/mailcow/data/Dockerfiles/ldap/Dockerfile new file mode 100644 index 0000000..9b7f507 --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3-alpine + +RUN apk --no-cache add build-base openldap-dev python2-dev python3-dev +RUN pip3 install python-ldap sqlalchemy requests + +COPY templates ./templates +COPY api.py filedb.py syncer.py ./ + +ADD syscid-ca.crt /usr/local/share/ca-certificates/syscid-ca.crt +RUN chmod 644 /usr/local/share/ca-certificates/syscid-ca.crt && update-ca-certificates + +VOLUME [ "/db" ] +VOLUME [ "/conf/dovecot" ] +VOLUME [ "/conf/sogo" ] + +ENTRYPOINT [ "python3", "syncer.py" ] diff --git a/mailcow/data/Dockerfiles/ldap/api.py b/mailcow/data/Dockerfiles/ldap/api.py new file mode 100644 index 0000000..de056d9 --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/api.py @@ -0,0 +1,69 @@ +import random, string, sys +import requests + +def __post_request(url, json_data): + api_url = f"{api_host}/{url}" + headers = {'X-API-Key': api_key, 'Content-type': 'application/json'} + + req = requests.post(api_url, headers=headers, json=json_data,verify=is_ssl_verify) + rsp = req.json() + req.close() + + if isinstance(rsp, list): + rsp = rsp[0] + + if not "type" in rsp or not "msg" in rsp: + sys.exit(f"API {url}: got response without type or msg from Mailcow API") + + if rsp['type'] != 'success': + sys.exit(f"API {url}: {rsp['type']} - {rsp['msg']}") + +def add_user(email, name, active): + password = ''.join(random.choices(string.ascii_letters + string.digits, k=20)) + json_data = { + 'local_part':email.split('@')[0], + 'domain':email.split('@')[1], + 'name':name, + 'password':password, + 'password2':password, + "active": 1 if active else 0 + } + + __post_request('api/v1/add/mailbox', json_data) + +def edit_user(email, active=None, name=None): + attr = {} + if (active is not None): + attr['active'] = 1 if active else 0 + if (name is not None): + attr['name'] = name + + json_data = { + 'items': [email], + 'attr': attr + } + + __post_request('api/v1/edit/mailbox', json_data) + +def __delete_user(email): + json_data = [email] + + __post_request('api/v1/delete/mailbox', json_data) + +def check_user(email): + url = f"{api_host}/api/v1/get/mailbox/{email}" + headers = {'X-API-Key': api_key, 'Content-type': 'application/json'} + req = requests.get(url, headers=headers,verify=is_ssl_verify) + rsp = req.json() + req.close() + + if not isinstance(rsp, dict): + sys.exit("API get/mailbox: got response of a wrong type") + + if (not rsp): + return (False, False, None) + + if 'active_int' not in rsp and rsp['type'] == 'error': + sys.exit(f"API {url}: {rsp['type']} - {rsp['msg']}") + + return (True, bool(rsp['active_int']), rsp['name']) diff --git a/mailcow/data/Dockerfiles/ldap/filedb.py b/mailcow/data/Dockerfiles/ldap/filedb.py new file mode 100644 index 0000000..10cadb9 --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/filedb.py @@ -0,0 +1,52 @@ +import datetime, os + +import logging +logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%d.%m.%y %H:%M:%S', level=logging.INFO) + +import sqlalchemy +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import create_engine, Column, String, Boolean, DateTime +from sqlalchemy.orm import sessionmaker + +db_file = 'db/ldap-mailcow.sqlite3' + +Base = declarative_base() + +class DbUser(Base): # type: ignore + __tablename__ = 'users' + email = Column(String, primary_key=True) + active = Column(Boolean, nullable=False) + last_seen = Column(DateTime, nullable=False) + +Session = sessionmaker() + +if not os.path.isfile(db_file): + logging.info (f"New database file created: {db_file}") + +db_engine = create_engine(f"sqlite:///{db_file}") # echo=True +Base.metadata.create_all(db_engine) +Session.configure(bind=db_engine) +session = Session() +session_time = datetime.datetime.now() + +def get_unchecked_active_users(): + query = session.query(DbUser.email).filter(DbUser.last_seen != session_time).filter(DbUser.active == True) + + return [x.email for x in query] + +def add_user(email, active=True): + session.add(DbUser(email=email, active=active, last_seen=session_time)) + session.commit() + +def check_user(email): + user = session.query(DbUser).filter_by(email=email).first() + if user is None: + return (False, False) + user.last_seen = session_time + session.commit() + return (True, user.active) + +def user_set_active_to(email, active): + user = session.query(DbUser).filter_by(email=email).first() + user.active = active + session.commit() \ No newline at end of file diff --git a/mailcow/data/Dockerfiles/ldap/syncer.py b/mailcow/data/Dockerfiles/ldap/syncer.py new file mode 100644 index 0000000..e9a3f02 --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/syncer.py @@ -0,0 +1,186 @@ +import sys, os, string, time, datetime +import ldap + +import filedb, api + +from string import Template +from pathlib import Path + +import logging +logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%d.%m.%y %H:%M:%S', level=logging.INFO) + +def main(): + global config + config = read_config() + + passdb_conf = read_dovecot_passdb_conf_template() + plist_ldap = read_sogo_plist_ldap_template() + extra_conf = read_dovecot_extra_conf() + + passdb_conf_changed = apply_config('conf/dovecot/ldap/passdb.conf', config_data = passdb_conf) + extra_conf_changed = apply_config('conf/dovecot/extra.conf', config_data = extra_conf) + plist_ldap_changed = apply_config('conf/sogo/plist_ldap', config_data = plist_ldap) + + if passdb_conf_changed or extra_conf_changed or plist_ldap_changed: + logging.info ("One or more config files have been changed, please make sure to restart dovecot-mailcow and sogo-mailcow!") + + api.api_host = config['API_HOST'] + api.api_key = config['API_KEY'] + api.is_ssl_verify = bool(int(config['API_SSL_VERIFY'])) + + while (True): + sync() + interval = int(config['SYNC_INTERVAL']) + logging.info(f"Sync finished, sleeping {interval} seconds before next cycle") + time.sleep(interval) + +def sync(): + ldap_connector = ldap.initialize(f"{config['LDAP_HOST']}") + ldap_connector.set_option(ldap.OPT_REFERRALS, 0) + ldap_connector.simple_bind_s(config['LDAP_BIND_DN'], config['LDAP_BIND_DN_PASSWORD']) + + #ldap_results = ldap_connector.search_s(config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE, + # '(&(objectClass=user)(objectCategory=person))', + # ['userPrincipalName', 'cn', 'userAccountControl']) + + ldap_results = ldap_connector.search_s(config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE, config['LDAP_FILTER'], + config['LDAP_FIELDS_MAIL'], config['LDAP_FIELDS_NAME']) + + ldap_results = map(lambda x: ( + [i.decode() for i in x[1][config['LDAP_FIELDS_MAIL']]], + x[1][config['LDAP_FIELDS_NAME']][0].decode(), + #False if int(x[1]['userAccountControl'][0].decode()) & 0b10 else True), ldap_results) + True), ldap_results) + + filedb.session_time = datetime.datetime.now() + + for (ldap_email, ldap_name, ldap_active) in ldap_results: + for email in ldap_email: + if email.split('@')[1] not in config['EMAIL_DOMAINS']: + continue + (db_user_exists, db_user_active) = filedb.check_user(email) + (api_user_exists, api_user_active, api_name) = api.check_user(email) + + unchanged = True + + if not db_user_exists: + filedb.add_user(email, ldap_active) + (db_user_exists, db_user_active) = (True, ldap_active) + logging.info (f"Added filedb user: {email} (Active: {ldap_active})") + unchanged = False + + if not api_user_exists: + api.add_user(email, ldap_name, ldap_active) + (api_user_exists, api_user_active, api_name) = (True, ldap_active, ldap_name) + logging.info (f"Added Mailcow user: {email} (Active: {ldap_active})") + unchanged = False + + if db_user_active != ldap_active: + filedb.user_set_active_to(email, ldap_active) + logging.info (f"{'Activated' if ldap_active else 'Deactived'} {email} in filedb") + unchanged = False + + if api_user_active != ldap_active: + api.edit_user(email, active=ldap_active) + logging.info (f"{'Activated' if ldap_active else 'Deactived'} {email} in Mailcow") + unchanged = False + + if api_name != ldap_name: + api.edit_user(email, name=ldap_name) + logging.info (f"Changed name of {email} in Mailcow to {ldap_name}") + unchanged = False + + if unchanged: + logging.info (f"Checked user {email}, unchanged") + + for email in filedb.get_unchecked_active_users(): + (api_user_exists, api_user_active, _) = api.check_user(email) + + if (api_user_active and api_user_active): + api.edit_user(email, active=False) + logging.info (f"Deactivated user {email} in Mailcow, not found in LDAP") + + filedb.user_set_active_to(email, False) + logging.info (f"Deactivated user {email} in filedb, not found in LDAP") + +def apply_config(config_file, config_data): + if os.path.isfile(config_file): + with open(config_file) as f: + old_data = f.read() + + if old_data.strip() == config_data.strip(): + logging.info(f"Config file {config_file} unchanged") + return False + + backup_index = 1 + backup_file = f"{config_file}.ldap_mailcow_bak" + while os.path.exists(backup_file): + backup_file = f"{config_file}.ldap_mailcow_bak.{backup_index}" + backup_index += 1 + + os.rename(config_file, backup_file) + logging.info(f"Backed up {config_file} to {backup_file}") + + Path(os.path.dirname(config_file)).mkdir(parents=True, exist_ok=True) + + print(config_data, file=open(config_file, 'w')) + + logging.info(f"Saved generated config file to {config_file}") + return True + +def read_config(): + required_config_keys = [ + 'LDAP-MAILCOW_LDAP_HOST', + 'LDAP-MAILCOW_LDAP_BASE_DN', + 'LDAP-MAILCOW_LDAP_BIND_DN', + 'LDAP-MAILCOW_LDAP_BIND_DN_PASSWORD', + 'LDAP-MAILCOW_LDAP_FILTER', + 'LDAP-MAILCOW_LDAP_FIELDS_MAIL', + 'LDAP-MAILCOW_LDAP_FIELDS_NAME', + 'LDAP-MAILCOW_API_HOST', + 'LDAP-MAILCOW_API_KEY', + 'LDAP-MAILCOW_API_SSL_VERIFY', + 'LDAP-MAILCOW_SYNC_INTERVAL', + 'LDAP-MAILCOW_EMAIL_DOMAINS' + ] + + config = {} + + for config_key in required_config_keys: + if config_key not in os.environ: + sys.exit (f"Required envrionment value {config_key} is not set") + + config[config_key.replace('LDAP-MAILCOW_', '')] = os.environ[config_key] + config['EMAIL_DOMAINS'] = config['EMAIL_DOMAINS'].split(',') + return config + +def read_dovecot_passdb_conf_template(): + with open('templates/dovecot/ldap/passdb.conf') as f: + data = Template(f.read()) + + return data.substitute( + ldap_host=config['LDAP_HOST'], + ldap_base_dn=config['LDAP_BASE_DN'], + ldap_bind_dn=config['LDAP_BIND_DN'], + ldap_bind_dn_password=config['LDAP_BIND_DN_PASSWORD'] + ) + +def read_sogo_plist_ldap_template(): + with open('templates/sogo/plist_ldap') as f: + data = Template(f.read()) + + return data.substitute( + ldap_host=config['LDAP_HOST'], + ldap_base_dn=config['LDAP_BASE_DN'], + ldap_bind_dn=config['LDAP_BIND_DN'], + ldap_bind_dn_password=config['LDAP_BIND_DN_PASSWORD'] + ) + +def read_dovecot_extra_conf(): + with open('templates/dovecot/extra.conf') as f: + data = f.read() + + return data + +if __name__ == '__main__': + main() diff --git a/mailcow/data/Dockerfiles/ldap/syscid-ca.crt b/mailcow/data/Dockerfiles/ldap/syscid-ca.crt new file mode 100644 index 0000000..3ac52fa --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/syscid-ca.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIUUtCa6jguJLt20qzq/MR82JX/6JQwDQYJKoZIhvcNAQEL +BQAwTzEUMBIGA1UECgwLTGliZXJ0YUNhc2ExEzARBgNVBAMMCnN5c2NpZC5jb20x +IjAgBgkqhkiG9w0BCQEWE3N5c3RlbUBseXNlcmdpYy5kZXYwHhcNMjEwODEwMDAy +MjM2WhcNMjIwODEwMDAyMjM2WjBPMRQwEgYDVQQKDAtMaWJlcnRhQ2FzYTETMBEG +A1UEAwwKc3lzY2lkLmNvbTEiMCAGCSqGSIb3DQEJARYTc3lzdGVtQGx5c2VyZ2lj +LmRldjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO0m7lpEZwGB1pxo +24RESADjbnk6iGmH783cuMo7jzio6P5vZk8FRD0/8Gmli/sOe8oZ2hGE5sNx4RKK +4g7kDVnYznHS6k5zpBzU4FP9wVMho/TcfaXlCdSwj6Ih0mLxDYzvX0l12Gi1K9gg +0HxdG2XPfslQbk6py1jQYVkRjwZIj2ya7t7/fNyn6S7flVUIvvcvZd3eNvAlg4ZU +wDV1H5mF3s42Iv5TOEYi88n7yXUex5I9xi5NqG/qOuYuC69yYobI/WjfId7bUDPT +UjZJFD5wHUHwtBmjp2bdyzdl9Z9iJp24jhR3Syi4h/BjYFwUG793PjP8DZBWtrOC +jHoOwkCyYFfOTa8n+Knb8i2FSuX4TgMZeFwcLpSsecBIjknKHPNYW2NTcP7S/Xbl +KpP2fpN7JBlR8WTi9+WQZVHuMfU1rjp3Kjwj9dmjcWsOMuMEqJUZMSsMpUBKULbq +6QFhMPJL+yYDHg1S0E3ymHRU94mlQ57mQwAg0AraCDtdPR3zw+gh2k1hUEkva26G +zHYigssbhCT81Dp4Ez65tKHZoKYKvgywJ6gb7PURS8Ued8PSDEhJd18WN22l4xQP +k08fg+mB5gHXNNGzYVETPalrWmk9IHczQnDITWM1hj14VhIcwa9oMNesIllviL7/ +BRjiz6jHZfc3Htp4NZ/5sBgFDUelAgMBAAGjUzBRMB0GA1UdDgQWBBSU/TIhYSYN +HQ3+7ueHMmkVD8BZRzAfBgNVHSMEGDAWgBSU/TIhYSYNHQ3+7ueHMmkVD8BZRzAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCLf0GU+mrZo4l7qMJz +WfNFxZv3E5GFRgl1NlUfJM2hnpgqrT7ukXcRnY4n9KtLx14QWHpPdX7KyLsSqx6X +UoPsJNywYQNVyAQ0qddY4glGV8u6+QE1zN1yUw6CMbsqWz50T76r1Y1CRZuMyffU +OeVhBM17sWibgDbev0SmG12uYTkq7qmbCKOWUhbaL1jCE1yvu9ZFFXCQ/OaAMyn9 +fYyyn48z7MHsyISuBdAcJkR2JkIgL4oZufw5hcecZ1wcnmYTRm6owuhUsZ2FYXkU +5o2Pn6nce1QEaUKsik8xBNA0/jIBCkiPDb5/eIA8Yys7pb/DeFEE/X0JM9rhoOk7 +tXOvxV2S7Y1xRqVwa3mrlp/0yXHuBb+u3/1+jsxkaeSPQ8FRejPIZzeOGhVHPaub +RdvzSO0TBK54vLA0CrkDRLgFdyuzsvm6VMVqGpcKn/aaju7pLI/knJzITaHHxzqV +wxmA0kf/68+wVvdICZt4R3iqSU9KVmCmh6owTixNTgh1wLmFIyMl2VhOwibFVg4L +600gIb59wEV4tWnYEX3Ugsw8g0ZXoqPDA27CPlpmgaVXwBv1qssRYnUreZEXxEN3 +4A1UOTdjMPn9v1wqmBwTCb9MULX60byX72YPMOnuSAQyptbXx8oMvInK91T5ivZ0 +JpmySX/Gfpgrr7HSI9+cD3eUcQ== +-----END CERTIFICATE----- diff --git a/mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf b/mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf new file mode 100644 index 0000000..7e97513 --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf @@ -0,0 +1,4 @@ +passdb { + args = /etc/dovecot/ldap/passdb.conf + driver = ldap +} diff --git a/mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf b/mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf new file mode 100644 index 0000000..f81a57a --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf @@ -0,0 +1,8 @@ +uris = $ldap_host +ldap_version = 3 +base = $ldap_base_dn +auth_bind = yes +dn = $ldap_bind_dn +dnpass = $ldap_bind_dn_password +pass_attrs = userPassword=password +pass_filter = (&(memberof=cn=syscid_email_mailcow,ou=syscid-groups,dc=syscid,dc=com)(|(uid=%n)(mail=%u))) diff --git a/mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap b/mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap new file mode 100644 index 0000000..87a0367 --- /dev/null +++ b/mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap @@ -0,0 +1,43 @@ + + + type + ldap + id + $${line}_ldap + + CNFieldName + cn + IDFieldName + uid + UIDFieldName + uid + + baseDN + $ldap_base_dn + + bindDN + $ldap_bind_dn + bindPassword + $ldap_bind_dn_password + bindFields + + uid + mail + + + bindAsCurrentUser + YES + + hostname + $ldap_host + canAuthenticate + YES + + isAddressBook + NO + displayName + LibertaCasa + + scope + SUB + diff --git a/mailcow/data/conf/dovecot/extra.conf b/mailcow/data/conf/dovecot/extra.conf new file mode 100644 index 0000000..2ec91d8 --- /dev/null +++ b/mailcow/data/conf/dovecot/extra.conf @@ -0,0 +1,5 @@ +passdb { + args = /etc/dovecot/ldap/passdb.conf + driver = ldap +} + diff --git a/mailcow/data/conf/dovecot/ldap/passdb.conf b/mailcow/data/conf/dovecot/ldap/passdb.conf new file mode 100644 index 0000000..28feedd --- /dev/null +++ b/mailcow/data/conf/dovecot/ldap/passdb.conf @@ -0,0 +1,9 @@ +uris = ldaps://orpheus.syscid.com +ldap_version = 3 +base = OU=syscid-users,DC=syscid,DC=com +auth_bind = yes +dn = $BINDDN +dnpass = $BINDSEC +pass_attrs = userPassword=password +pass_filter = (&(memberof=cn=syscid_email_mailcow,ou=syscid-groups,dc=syscid,dc=com)(|(uid=%n)(mail=%u))) + diff --git a/mailcow/data/conf/sogo/plist_ldap b/mailcow/data/conf/sogo/plist_ldap new file mode 100644 index 0000000..dfa203e --- /dev/null +++ b/mailcow/data/conf/sogo/plist_ldap @@ -0,0 +1,44 @@ + + + type + ldap + id + ${line}_ldap + + CNFieldName + cn + IDFieldName + uid + UIDFieldName + uid + + baseDN + OU=syscid-users,DC=syscid,DC=com + + bindDN + $BINDDN + bindPassword + $BINDSEC + bindFields + + uid + mail + + + bindAsCurrentUser + YES + + hostname + ldaps://orpheus.syscid.com + canAuthenticate + YES + + isAddressBook + NO + displayName + LibertaCasa + + scope + SUB + + diff --git a/mailcow/docker-compose.override.yml b/mailcow/docker-compose.override.yml new file mode 100644 index 0000000..be769e2 --- /dev/null +++ b/mailcow/docker-compose.override.yml @@ -0,0 +1,34 @@ +version: '2.1' +services: + + ldap-mailcow: + image: mailcow/ldap + network_mode: host + container_name: mailcowcustomized_ldap + depends_on: + - nginx-mailcow + volumes: + - ./data/ldap:/db:rw + - ./data/conf/dovecot:/conf/dovecot:rw + - ./data/conf/sogo:/conf/sogo:rw + environment: + - LDAP-MAILCOW_LDAP_HOST=ldaps://orpheus.syscid.com + - LDAP-MAILCOW_LDAP_BASE_DN=OU=syscid-users,DC=syscid,DC=com + - LDAP-MAILCOW_LDAP_BIND_DN=$BINDDN + - LDAP-MAILCOW_LDAP_BIND_DN_PASSWORD=$BINDSEC + - LDAP-MAILCOW_LDAP_FILTER=(objectClass=inetOrgPerson) + - LDAP-MAILCOW_LDAP_FIELDS_MAIL=mail + - LDAP-MAILCOW_LDAP_FIELDS_NAME=cn + - LDAP-MAILCOW_API_HOST=$MAILCOWHOST + - LDAP-MAILCOW_API_KEY=$MAILCOWAPI + - LDAP-MAILCOW_API_SSL_VERIFY=1 + - LDAP-MAILCOW_SYNC_INTERVAL=300 + - LDAP-MAILCOW_EMAIL_DOMAINS=syscid.com + + dovecot-mailcow: + extra_hosts: + - "orpheus.syscid.com:192.168.0.115" + + sogo-mailcow: + extra_hosts: + - "orpheus.syscid.com:192.168.0.115" diff --git a/mailcow/docker-compose.yml b/mailcow/docker-compose.yml index 3d747c5..b1396c0 100644 --- a/mailcow/docker-compose.yml +++ b/mailcow/docker-compose.yml @@ -2,7 +2,7 @@ version: '2.1' services: unbound-mailcow: - image: mailcow/unbound:1.13 + image: mailcow/unbound:1.14 environment: - TZ=${TZ} volumes: @@ -41,7 +41,7 @@ services: - mysql redis-mailcow: - image: redis:5-alpine + image: redis:6-alpine volumes: - redis-vol-1:/data/:Z restart: always @@ -49,6 +49,8 @@ services: - "${REDIS_PORT:-127.0.0.1:7654}:6379" environment: - TZ=${TZ} + sysctls: + - net.core.somaxconn=4096 networks: mailcow-network: ipv4_address: ${IPV4_NETWORK:-172.22.1}.249 @@ -56,7 +58,7 @@ services: - redis clamd-mailcow: - image: mailcow/clamd:1.40 + image: mailcow/clamd:1.41 restart: always dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -71,7 +73,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.76 + image: mailcow/rspamd:1.79 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -101,7 +103,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.75 + image: mailcow/phpfpm:1.76 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -122,7 +124,6 @@ services: - ./data/conf/dovecot/global_sieve_before:/global_sieve/before:z - ./data/conf/dovecot/global_sieve_after:/global_sieve/after:z - ./data/assets/templates:/tpls:z - - ./data/conf/ejabberd/autogen:/ejabberd/:z - ./data/conf/nginx/:/etc/nginx/conf.d/:z dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -146,8 +147,6 @@ services: - SUBMISSION_PORT=${SUBMISSION_PORT:-587} - SMTPS_PORT=${SMTPS_PORT:-465} - SMTP_PORT=${SMTP_PORT:-25} - - XMPP_C2S_PORT=${XMPP_C2S_PORT:-5222} - - XMPP_S2S_PORT=${XMPP_S2S_PORT:-5269} - API_KEY=${API_KEY:-invalid} - API_KEY_READ_ONLY=${API_KEY_READ_ONLY:-invalid} - API_ALLOW_FROM=${API_ALLOW_FROM:-invalid} @@ -164,7 +163,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.99 + image: mailcow/sogo:1.101 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -184,6 +183,7 @@ services: dns: - ${IPV4_NETWORK:-172.22.1}.254 volumes: + - ./data/hooks/sogo:/hooks:Z - ./data/conf/sogo/:/etc/sogo/:z - ./data/web/inc/init_db.inc.php:/init_db.inc.php:Z - ./data/conf/sogo/custom-favicon.ico:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico:z @@ -212,7 +212,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.145 + image: mailcow/dovecot:1.156 depends_on: - mysql-mailcow dns: @@ -244,7 +244,7 @@ services: - MAILCOW_PASS_SCHEME=${MAILCOW_PASS_SCHEME:-BLF-CRYPT} - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} - - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440} + - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-7200} - ACL_ANYONE=${ACL_ANYONE:-disallow} - SKIP_SOLR=${SKIP_SOLR:-y} - MAILDIR_SUB=${MAILDIR_SUB:-} @@ -292,7 +292,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.61 + image: mailcow/postfix:1.66 depends_on: - mysql-mailcow volumes: @@ -323,6 +323,7 @@ services: - ${IPV4_NETWORK:-172.22.1}.254 networks: mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.253 aliases: - postfix @@ -347,7 +348,6 @@ services: 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/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_proxy_auth.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 && @@ -356,7 +356,6 @@ services: 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 && - until ping ejabberd -c1 > /dev/null; do sleep 1; done && exec nginx -g 'daemon off;'" environment: - HTTPS_PORT=${HTTPS_PORT:-443} @@ -387,7 +386,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.79 + image: mailcow/acme:1.80 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: @@ -423,7 +422,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.43 + image: mailcow/netfilter:1.44 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -446,7 +445,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.91 + image: mailcow/watchdog:1.94 # Debug #command: /watchdog.sh dns: @@ -499,7 +498,6 @@ services: - RATELIMIT_THRESHOLD=${RATELIMIT_THRESHOLD:-1} - FAIL2BAN_THRESHOLD=${FAIL2BAN_THRESHOLD:-1} - ACME_THRESHOLD=${ACME_THRESHOLD:-1} - - IPV6NAT_THRESHOLD=${IPV6NAT_THRESHOLD:-1} - RSPAMD_THRESHOLD=${RSPAMD_THRESHOLD:-5} - OLEFY_THRESHOLD=${OLEFY_THRESHOLD:-5} - MAILQ_THRESHOLD=${MAILQ_THRESHOLD:-20} @@ -510,7 +508,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:1.38 + image: mailcow/dockerapi:1.39 security_opt: - label=disable restart: always @@ -544,7 +542,7 @@ services: - solr olefy-mailcow: - image: mailcow/olefy:1.7 + image: mailcow/olefy:1.8 restart: always environment: - TZ=${TZ} @@ -561,44 +559,6 @@ services: aliases: - olefy - ejabberd-mailcow: - image: mailcow/ejabberd:1.6 - volumes: - - ./data/conf/ejabberd/ejabberd.yml:/home/ejabberd/conf/ejabberd.yml:z - - xmpp-vol-1:/home/ejabberd/database:z - - xmpp-upload-vol-1:/var/www/upload:z - - ./data/assets/ejabberd/sqlite:/sqlite:z - - ./data/conf/ejabberd/autogen:/ejabberd/:z - - mysql-socket-vol-1:/var/run/mysqld/:z - - ./data/assets/ssl:/ssl:ro,z - restart: always - dns: - - ${IPV4_NETWORK:-172.22.1}.254 - hostname: ejabberd.mailcow.local - labels: - ofelia.enabled: "true" - ofelia.job-exec.ejabberd_certs.schedule: "@every 168h" - ofelia.job-exec.ejabberd_certs.command: "/sbin/su-exec ejabberd /home/ejabberd/bin/ejabberdctl --node ejabberd@$${MAILCOW_HOSTNAME} request-certificate all" - extra_hosts: - - "${MAILCOW_HOSTNAME}:127.0.0.1" - environment: - - TZ=${TZ} - - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - - MASTER=${MASTER:-y} - - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} - - XMPP_HTTPS_PORT=${XMPP_HTTPS_PORT:-5443} - - DBNAME=${DBNAME} - - DBUSER=${DBUSER} - - DBPASS=${DBPASS} - ports: - - "${XMPP_C2S_PORT:-5222}:5222" - - "${XMPP_S2S_PORT:-5269}:5269" - - "${XMPP_HTTPS_PORT:-5443}:5443" - networks: - mailcow-network: - aliases: - - ejabberd - ofelia-mailcow: image: mcuadros/ofelia:latest restart: always @@ -607,9 +567,10 @@ services: depends_on: - sogo-mailcow - dovecot-mailcow - - ejabberd-mailcow labels: ofelia.enabled: "true" + security_opt: + - label=disable volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: @@ -671,5 +632,3 @@ volumes: crypt-vol-1: sogo-web-vol-1: sogo-userdata-backup-vol-1: - xmpp-vol-1: - xmpp-upload-vol-1: diff --git a/mailcow/mailcow.conf b/mailcow/mailcow.conf index 2b1f13b..f3904ab 100644 --- a/mailcow/mailcow.conf +++ b/mailcow/mailcow.conf @@ -16,8 +16,8 @@ MAILCOW_PASS_SCHEME=BLF-CRYPT # SQL database configuration # ------------------------------ -DBNAME=mailcow -DBUSER=mailcow +DBNAME=$DBNAME +DBUSER=$DBUSER # Please use long, random alphanumeric strings (A-Za-z0-9) @@ -125,15 +125,6 @@ ENABLE_SSL_SNI=n # Skip IPv4 check in ACME container - y/n -SKIP_LETS_ENCRYPT=y - -# Create seperate certificates for all domains - y/n -# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames -# see https://wiki.dovecot.org/SSL/SNIClientSupport -ENABLE_SSL_SNI=n - -# Skip IPv4 check in ACME container - y/n - SKIP_IP_CHECK=n # Skip HTTP verification in ACME container - y/n -- cgit v1.2.3