From 21fb3f3c86391f079afb7fb59260b64019fa2e51 Mon Sep 17 00:00:00 2001 From: alinmear Date: Mon, 3 Jul 2017 13:16:16 +0200 Subject: [PATCH] Fix ldap related critical Problems (#644) * Fix Dovecot Ldap Problems * Fix typo within DEFAULT_VARS Definitions * Fix wrong ldap hosts value within the bats test * Fix override_config for strings containing & * Fix erroneous removal of an conditional within the postfix override function * Renamed Test 129, to be clear that this belongs to ldap * Fix mail_with_ldap setting dn pass explicit * Add 3 env variables for ldap: LDAP_QUERY_FILTER_{USER,GROUP,ALIAS} * Update README.md --- Makefile | 6 ++ README.md | 117 +++++++++++++++++++++++++++++++------ target/start-mailserver.sh | 110 +++++++++++++++++++--------------- test/tests.bats | 37 +++++++----- 4 files changed, 190 insertions(+), 80 deletions(-) diff --git a/Makefile b/Makefile index c43fd70b..e0695097 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,12 @@ run: -e LDAP_SERVER_HOST=ldap \ -e LDAP_SEARCH_BASE=ou=people,dc=localhost,dc=localdomain \ -e LDAP_BIND_DN=cn=admin,dc=localhost,dc=localdomain \ + -e LDAP_BIND_PW=admin \ + -e LDAP_QUERY_FILTER_USER="(&(mail=%s)(mailEnabled=TRUE))" \ + -e LDAP_QUERY_FILTER_GROUP="(&(mailGroupMember=%s)(mailEnabled=TRUE))" \ + -e LDAP_QUERY_FILTER_ALIAS="(&(mailAlias=%s)(mailEnabled=TRUE))" \ + -e DOVECOT_PASS_FILTER="(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))" \ + -e DOVECOT_USER_FILTER="(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))" \ -e ENABLE_SASLAUTHD=1 \ -e SASLAUTHD_MECHANISMS=ldap \ -e SASLAUTHD_LDAP_SERVER=ldap \ diff --git a/README.md b/README.md index 438af213..18c0f2ed 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Your configs must be mounted in `/tmp/docker-mailserver/`. To understand how thi `restart: always` ensures that the mail server container (and ELK container when using the mail server together with ELK stack) is automatically restarted by Docker in cases like a Docker service or host restart or container exit. -```yaml +```yaml version: '2' services: @@ -79,6 +79,60 @@ volumes: driver: local ``` +__for ldap setup__: + +```yaml +version: '2' + +services: + mail: + image: tvial/docker-mailserver:latest + hostname: mail + domainname: domain.com + container_name: mail + ports: + - "25:25" + - "143:143" + - "587:587" + - "993:993" + volumes: + - maildata:/var/mail + - mailstate:/var/mail-state + - ./config/:/tmp/docker-mailserver/ + environment: + - ENABLE_SPAMASSASSIN=1 + - ENABLE_CLAMAV=1 + - ENABLE_FAIL2BAN=1 + - ENABLE_POSTGREY=1 + - ONE_DIR=1 + - DMS_DEBUG=0 + - ENABLE_LDAP=1 + - LDAP_SERVER_HOST=ldap # your ldap container/IP/ServerName + - LDAP_SEARCH_BASE=ou=people,dc=localhost,dc=localdomain + - LDAP_BIND_DN=cn=admin,dc=localhost,dc=localdomain + - LDAP_BIND_PW=admin + - LDAP_QUERY_FILTER_USER="(&(mail=%s)(mailEnabled=TRUE))" + - LDAP_QUERY_FILTER_GROUP="(&(mailGroupMember=%s)(mailEnabled=TRUE))" + - LDAP_QUERY_FILTER_ALIAS="(&(mailAlias=%s)(mailEnabled=TRUE))" + - DOVECOT_PASS_FILTER="(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))" + - DOVECOT_USER_FILTER="(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))" + - ENABLE_SASLAUTHD=1 + - SASLAUTHD_MECHANISMS=ldap + - SASLAUTHD_LDAP_SERVER=ldap + - SASLAUTHD_LDAP_BIND_DN=cn=admin,dc=localhost,dc=localdomain + - SASLAUTHD_LDAP_PASSWORD=admin + - SASLAUTHD_LDAP_SEARCH_BASE=ou=people,dc=localhost,dc=localdomain + - POSTMASTER_ADDRESS=postmaster@localhost.localdomain + cap_add: + - NET_ADMIN + +volumes: + maildata: + driver: local + mailstate: + driver: local +``` + #### Create your mail accounts Don't forget to adapt MAIL_USER and MAIL_PASS to your needs @@ -212,6 +266,29 @@ Otherwise, `iptables` won't be able to ban IPs. - **empty** => admin - => Specify the password to bind against ldap +##### LDAP_QUERY_FILTER_USER + + - e.g. `"(&(mail=%s)(mailEnabled=TRUE))"` + - => Specify how ldap should be asked for users + +##### LDAP_QUERY_FILTER_GROUP + + - e.g. `"(&(mailGroupMember=%s)(mailEnabled=TRUE))"` + - => Specify how ldap should be asked for groups + +##### LDAP_QUERY_FILTER_ALIAS + + - e.g. `"(&(mailAlias=%s)(mailEnabled=TRUE))"` + - => Specify how ldap should be asked for aliases + +##### DOVECOT_USER_FILTER + + - e.g. `"(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))"` + +##### DOVECOT_PASS_FILTER + + - e.g. `"(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))"` + ##### OVERRIDE_HOSTNAME - **empty** => uses the `hostname` command to get the mail server's canonical hostname @@ -234,13 +311,13 @@ Otherwise, `iptables` won't be able to ban IPs. Note: This postgrey setting needs `ENABLE_POSTGREY=1` ##### POSTGREY_MAX_AGE - + - **35** => delete entries older than N days since the last time that they have been seen Note: This postgrey setting needs `ENABLE_POSTGREY=1` ##### POSTGREY_TEXT - + - **Delayed by postgrey** => response when a mail is greylisted Note: This postgrey setting needs `ENABLE_POSTGREY=1` @@ -253,10 +330,10 @@ Note: This postgrey setting needs `ENABLE_POSTGREY=1` ##### SASLAUTHD_MECHANISMS - empty => pam - - ldap => authenticate against ldap server - - shadow => authenticate against local user db - - mysql => authenticate against mysql db - - rimap => authenticate against imap server + - `ldap` => authenticate against ldap server + - `shadow` => authenticate against local user db + - `mysql` => authenticate against mysql db + - `rimap` => authenticate against imap server - NOTE: can be a list of mechanisms like pam ldap shadow ##### SASLAUTHD_MECH_OPTIONS @@ -270,8 +347,8 @@ Note: This postgrey setting needs `ENABLE_POSTGREY=1` ##### SASLAUTHD_LDAP_SSL - - empty or 0 => ldap:// will be used - - 1 => ldaps:// will be used + - empty or 0 => `ldap://` will be used + - 1 => `ldaps://` will be used ##### SASLAUTHD_LDAP_BIND_DN @@ -291,9 +368,9 @@ Note: This postgrey setting needs `ENABLE_POSTGREY=1` ##### SASLAUTHD_LDAP_FILTER - - empty => default filter (&(uniqueIdentifier=%u)(mailEnabled=TRUE)) - - e.g. for active directory: (&(sAMAccountName=%U)(objectClass=person)) - - e.g. for openldap: (&(uid=%U)(objectClass=person)) + - empty => default filter `(&(uniqueIdentifier=%u)(mailEnabled=TRUE))` + - e.g. for active directory: `(&(sAMAccountName=%U)(objectClass=person))` + - e.g. for openldap: `(&(uid=%U)(objectClass=person))` ##### SASL_PASSWD @@ -331,14 +408,16 @@ Set how many days a virusmail will stay on the server before being deleted ##### ENABLE_POSTFIX_VIRTUAL_TRANSPORT This Option is activating the Usage of POSTFIX_DAGENT to specify a ltmp client different from default dovecot socket. - - **empty** => disabled - - 1 => enabled + +- **empty** => disabled +- 1 => enabled ##### POSTFIX_DAGENT Enabled by ENABLE_POSTFIX_VIRTUAL_TRANSPORT. Specify the final delivery of postfix - - **empty**: fail - - lmtp:unix:private/dovecot-lmtp (use socket) - - lmtps:inet:: (secure lmtp with starttls, take a look at https://sys4.de/en/blog/2014/11/17/sicheres-lmtp-mit-starttls-in-dovecot/) - - lmtp::2003 (use kopano as mailstore) - - etc. + +- **empty**: fail +- `lmtp:unix:private/dovecot-lmtp` (use socket) +- `lmtps:inet::` (secure lmtp with starttls, take a look at https://sys4.de/en/blog/2014/11/17/sicheres-lmtp-mit-starttls-in-dovecot/) +- `lmtp::2003` (use kopano as mailstore) +- etc. diff --git a/target/start-mailserver.sh b/target/start-mailserver.sh index c26d8366..1f5438d0 100644 --- a/target/start-mailserver.sh +++ b/target/start-mailserver.sh @@ -317,47 +317,47 @@ function display_startup_daemon() { } function override_config() { - notify "task" "Starting do do overrides" + notify "task" "Starting do do overrides" - declare -A config_overrides + declare -A config_overrides - _env_variable_prefix=$1 - [ -z ${_env_variable_prefix} ] && return 1 + _env_variable_prefix=$1 + [ -z ${_env_variable_prefix} ] && return 1 - - IFS=" " read -r -a _config_files <<< $2 - # dispatch env variables - for env_variable in $(printenv | grep $_env_variable_prefix);do - # get key - # IFS not working because values like ldap_query_filter or search base consists of several '=' - # IFS="=" read -r -a __values <<< $env_variable - # key="${__values[0]}" - # value="${__values[1]}" - key=$(echo $env_variable | cut -d "=" -f1) - key=${key#"${_env_variable_prefix}"} - # make key lowercase - key=${key,,} - # get value - value=$(echo $env_variable | cut -d "=" -f2-) + IFS=" " read -r -a _config_files <<< $2 - config_overrides[$key]=$value - done + # dispatch env variables + for env_variable in $(printenv | grep $_env_variable_prefix);do + # get key + # IFS not working because values like ldap_query_filter or search base consists of several '=' + # IFS="=" read -r -a __values <<< $env_variable + # key="${__values[0]}" + # value="${__values[1]}" + key=$(echo $env_variable | cut -d "=" -f1) + key=${key#"${_env_variable_prefix}"} + # make key lowercase + key=${key,,} + # get value + value=$(echo $env_variable | cut -d "=" -f2-) - for f in "${_config_files[@]}" - do - if [ ! -f "${f}" ];then - echo "Can not find ${f}. Skipping override" - else - for key in ${!config_overrides[@]} - do - [ -z $key ] && echo -e "\t no key provided" && return 1 - - sed -i -e "s|^${key}[[:space:]]\+.*|${key} = "${config_overrides[$key]}'|g' \ - ${f} - done - fi - done + config_overrides[$key]=$value + done + + for f in "${_config_files[@]}" + do + if [ ! -f "${f}" ];then + echo "Can not find ${f}. Skipping override" + else + for key in ${!config_overrides[@]} + do + [ -z $key ] && echo -e "\t no key provided" && return 1 + + sed -i -e "s|^${key}[[:space:]]\+.*|${key} = ${config_overrides[$key]//&/\\&}|g" \ + ${f} + done + fi + done } # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -569,21 +569,37 @@ function _setup_ldap() { for i in 'users' 'groups' 'aliases'; do fpath="/tmp/docker-mailserver/ldap-${i}.cf" if [ -f $fpath ]; then - cp ${fpath} /etc/postfix/ldap-${i}.cf + cp ${fpath} /etc/postfix/ldap-${i}.cf fi done notify 'inf' 'Starting to override configs' - override_config "LDAP_" "/etc/postfix/ldap-users.cf /etc/postfix/ldap-groups.cf /etc/postfix/ldap-aliases.cf" + for f in /etc/postfix/ldap-users.cf /etc/postfix/ldap-groups.cf /etc/postfix/ldap-aliases.cf + do + [[ $f =~ ldap-user ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_USER}" + [[ $f =~ ldap-group ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_GROUP}" + [[ $f =~ ldap-aliases ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_ALIAS}" + override_config "LDAP_" "${f}" + done + + notify 'inf' "Configuring dovecot LDAP" + + declare -A _dovecot_ldap_mapping + + _dovecot_ldap_mapping["DOVECOT_BASE"]="${DOVECOT_BASE:="${LDAP_SEARCH_BASE}"}" + _dovecot_ldap_mapping["DOVECOT_DN"]="${DOVECOT_DN:="${LDAP_BIND_DN}"}" + _dovecot_ldap_mapping["DOVECOT_DNPASS"]="${DOVECOT_DNPASS:="${LDAP_BIND_PW}"}" + _dovecot_ldap_mapping["DOVECOT_HOSTS"]="${DOVECOT_HOSTS:="${LDAP_SERVER_HOST}"}" + # Not sure whether this can be the same or not + # _dovecot_ldap_mapping["DOVECOT_PASS_FILTER"]="${DOVECOT_PASS_FILTER:="${LDAP_QUERY_FILTER_USER}"}" + # _dovecot_ldap_mapping["DOVECOT_USER_FILTER"]="${DOVECOT_USER_FILTER:="${LDAP_QUERY_FILTER_USER}"}" + + for var in ${!_dovecot_ldap_mapping[@]}; do + export $var=${_dovecot_ldap_mapping[$var]} + done + + override_config "DOVECOT_" "/etc/dovecot/dovecot-ldap.conf.ext" - # @TODO: Environment Variables for DOVECOT ldap integration to configure for better control - notify 'inf' "Configuring dovecot LDAP authentification" - sed -i -e 's|^hosts.*|hosts = '${LDAP_SERVER_HOST:="mail.domain.com"}'|g' \ - -e 's|^base.*|base = '${LDAP_SEARCH_BASE:="ou=people,dc=domain,dc=com"}'|g' \ - -e 's|^dn\s*=.*|dn = '${LDAP_BIND_DN:="cn=admin,dc=domain,dc=com"}'|g' \ - -e 's|^dnpass\s*=.*|dnpass = '${LDAP_BIND_PW:="admin"}'|g' \ - /etc/dovecot/dovecot-ldap.conf.ext - # Add domainname to vhost. echo $DOMAINNAME >> /tmp/vhost.tmp @@ -629,7 +645,7 @@ EOF # cyrus sasl or dovecot sasl if [[ ${ENABLE_SASLAUTHD} == 1 ]] || [[ ${SMTP_ONLY} == 0 ]];then sed -i -e 's|^smtpd_sasl_auth_enable[[:space:]]\+.*|smtpd_sasl_auth_enable = yes|g' /etc/postfix/main.cf - else + else sed -i -e 's|^smtpd_sasl_auth_enable[[:space:]]\+.*|smtpd_sasl_auth_enable = no|g' /etc/postfix/main.cf fi @@ -667,7 +683,7 @@ EOF sed -i \ -e "/^[^#].*smtpd_sasl_type.*/s/^/#/g" \ -e "/^[^#].*smtpd_sasl_path.*/s/^/#/g" \ - etc/postfix/master.cf + /etc/postfix/master.cf sed -i \ -e "s|^START=.*|START=yes|g" \ diff --git a/test/tests.bats b/test/tests.bats index c27872b0..e6d6db7c 100644 --- a/test/tests.bats +++ b/test/tests.bats @@ -341,7 +341,7 @@ load 'test_helper/bats-assert/load' run docker exec mail_smtponly /bin/sh -c 'grep -cE "to=.*status\=sent" /var/log/mail/mail.log' [ "$status" -ge 0 ] } - + # @@ -1051,34 +1051,34 @@ load 'test_helper/bats-assert/load' } @test "checking postfix: ldap custom config files copied" { - run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-users.cf" + run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-users.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-groups.cf" + run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-groups.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-aliases.cf" + run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-aliases.cf" assert_success } @test "checking postfix: ldap config overwrites success" { - run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-users.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-users.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-users.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-users.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-users.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-users.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-groups.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-groups.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-groups.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-groups.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-groups.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-groups.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-aliases.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-aliases.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-aliases.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-aliases.cf" assert_success - run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-aliases.cf" + run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-aliases.cf" assert_success } @@ -1088,7 +1088,7 @@ load 'test_helper/bats-assert/load' assert_success } -@test "checking dovecot: mail delivery works" { +@test "checking dovecot: ldap mail delivery works" { run docker exec mail_with_ldap /bin/sh -c "sendmail -f user@external.tld some.user@localhost.localdomain < /tmp/docker-mailserver-test/email-templates/test-email.txt" sleep 10 run docker exec mail_with_ldap /bin/sh -c "ls -A /var/mail/localhost.localdomain/some.user/new | wc -l" @@ -1096,6 +1096,15 @@ load 'test_helper/bats-assert/load' assert_output 1 } +@test "checking dovecot: ldap config overwrites success" { + run docker exec mail_with_ldap /bin/sh -c "grep 'hosts = ldap' /etc/dovecot/dovecot-ldap.conf.ext" + assert_success + run docker exec mail_with_ldap /bin/sh -c "grep 'base = ou=people,dc=localhost,dc=localdomain' /etc/dovecot/dovecot-ldap.conf.ext" + assert_success + run docker exec mail_with_ldap /bin/sh -c "grep 'dn = cn=admin,dc=localhost,dc=localdomain' /etc/dovecot/dovecot-ldap.conf.ext" + assert_success +} + # saslauthd @test "checking saslauthd: sasl ldap authentication works" { run docker exec mail_with_ldap bash -c "testsaslauthd -u some.user -p secret"