- [docker](src/docker/bin/docker-config.sh) Generalized `dc_cond_chown()`.

- [kopano](src/kopano) Updated documentation and bug fix of support for secure IMAPS, POP3S and ICALS.
master
mlan 2020-11-15 20:27:46 +01:00
parent 4b8bc2b445
commit 362f4e3865
6 changed files with 77 additions and 21 deletions

View File

@ -8,6 +8,8 @@
- [test](test/Makefile) Arrange build tests. - [test](test/Makefile) Arrange build tests.
- [test](test/Makefile) Add SSL build tests. - [test](test/Makefile) Add SSL build tests.
- [travis-ci](.travis.yml) Travis CI now run test-all. - [travis-ci](.travis.yml) Travis CI now run test-all.
- [docker](src/docker/bin/docker-config.sh) Generalized `dc_cond_chown()`.
- [kopano](src/kopano) Updated documentation and bug fix of support for secure IMAPS, POP3S and ICALS.
# 1.2.2 # 1.2.2

View File

@ -319,9 +319,36 @@ To enable secure access we need to explicitly define their listening ports. This
If any of `IMAPS_LISTEN`, `POP3S_LISTEN` and `ICALS_LISTEN` are explicitly defined but there are no certificate files defined, a self-signed certificate will be generated when the container is created. If any of `IMAPS_LISTEN`, `POP3S_LISTEN` and `ICALS_LISTEN` are explicitly defined but there are no certificate files defined, a self-signed certificate will be generated when the container is created.
### SSL/LTS certificate and private key
For most deployments a trusted SSL/TLS certificate is desired. During startup the `mlan/kopano` looks for [RSA](https://en.wikipedia.org/wiki/RSA_(cryptosystem)) [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) certificate and private key with these specific names: `/etc/kopano/ssl/cert.pem` and `/etc/kopano/ssl/priv_key.pem`. If found they will used by the secure protocols IMAPS, POP3S and ICALS. Moreover the file ownership will be changed if needed to make them readable by the Kopano services.
#### `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE` #### `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE`
For most deployments a trusted TLS certificate is needed. When such are available, copy the [RSA](https://en.wikipedia.org/wiki/RSA_(cryptosystem)) [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) certificate and private key files to the container and use the envvars `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE` to let the kopano-gateway and kopano-ical services find them. For example `SSL_CERTIFICATE_FILE=/etc/kopano/ssl/cert.pem` and `SSL_PRIVATE_KEY_FILE=/etc/kopano/ssl/priv_key.pem`. Note that these files need to be readable by the `kopano` user. If you use other file names or directories, you let the Kopano services know by setting the variables `SSL_CERTIFICATE_FILE=/etc/kopano/ssl/cert.pem` and `SSL_PRIVATE_KEY_FILE=/etc/kopano/ssl/priv_key.pem` on the `docker run` command line or in the `docker-compose.yml` file.
For testing purposes you can create a self-signed certificate using the `openssl` utility, see below. Note that this is not necessary since, when secure protocols are defined, a self-signed certificate and private key will be automatically be created during container startup if they are not found.
```bash
openssl genrsa -out ssl/priv_key.pem
openssl req -x509 -utf8 -new -batch -subj "/CN=app" -key ssl/priv_key.pem -out ssl/cert.pem
```
One way to allow the container to read the certificate and private key is to bind mount the host directory holding the files to the container:
```bash
docker run -d -name app -v $pwd/ssl:/etc/kopano/ssl mlan/kopano
```
A other way is to copy them to the container:
```bash
docker create -name app mlan/kopano
docker cp ssl/. app:/etc/kopano/ssl
docker start app
```
If you copy the files to a running container you need to make sure that the user `kopano` can read them.
### Lets Encrypt LTS certificates using [Traefik](https://docs.traefik.io/) ### Lets Encrypt LTS certificates using [Traefik](https://docs.traefik.io/)
@ -329,19 +356,20 @@ For most deployments a trusted TLS certificate is needed. When such are availabl
#### `ACME_FILE`, `ACME_POSTHOOK` #### `ACME_FILE`, `ACME_POSTHOOK`
The `mlan/kopano` image looks for a file `ACME_FILE=/acme/acme.json` at container startup and every time this file changes certificates within this file are extracted. If the host or domain name of one of those certificates matches `HOSTNAME=$(hostname)` or `DOMAIN=${HOSTNAME#*.}` it will be used for TLS support. The `mlan/kopano` image looks for a file `ACME_FILE=/acme/acme.json` at container startup and every time this file changes certificates within this file are extracted. If the host or domain name of one of those certificates matches `HOSTNAME=$(hostname)` or `DOMAIN=${HOSTNAME#*.}` it will be used by the secure protocols.
Once the certificates and keys have been updated, we run the command in the environment variable `ACME_POSTHOOK="sv restart kopano-gateway kopano-ical"`. Kopano services needs to be restarted to update the LTS parameters. If such automatic reloading is not desired, set `ACME_POSTHOOK=` to empty. Once the certificates and keys have been updated, we run the command in the environment variable `ACME_POSTHOOK="sv restart kopano-gateway kopano-ical"`. Kopano services needs to be restarted to update the LTS parameters. If such automatic reloading is not desired, set `ACME_POSTHOOK=` to empty.
So reusing certificates from Traefik will work out of the box if the `/acme` directory in the Traefik container is also mounted in the `mlan/kopano` container. So reusing certificates from Traefik will work out of the box if the `/acme` directory in the Traefik container is also mounted in the `mlan/kopano` container.
```bash ```bash
docker run -d -name app -v proxy-acme:/acme:ro mlan/postfix docker run -d -name proxy -v proxy-acme:/acme traefik
docker run -d -name app -v proxy-acme:/acme:ro mlan/kopano
``` ```
Note, if the target certificate Common Name (CN) or Subject Alternate Name (SAN) is changed the container needs to be restarted. Note, if the target certificate Common Name (CN) or Subject Alternate Name (SAN) is changed the container needs to be restarted.
Moreover, do not set any of `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE` when using `ACME_FILE`. Moreover, do not set any of `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE` when using `ACME_FILE`.
## Logging `SYSLOG_LEVEL`, `LOG_LEVEL` ## Logging `SYSLOG_LEVEL`, `LOG_LEVEL`
@ -408,7 +436,9 @@ Here some topics relevant for arranging a mail server are presented.
## Kopano WebApp HTTP access ## Kopano WebApp HTTP access
The distribution installation of `kopano-webapp` only allow HTTPS access. The `mlan/kopano` image updates the configuration to `define("SECURE_COOKIES", false);` in `/etc/kopano/webapp/config.php` also allowing HTTP access. This can be useful when arranging the `mlan/kopano` container behind a reverse proxy, like [traefik](https://doc.traefik.io/traefik/), which then does the enforcement of HTTPS. The distribution installation of `kopano-webapp` only allow HTTPS access. The `mlan/kopano` image updates the configuration to [`define("SECURE_COOKIES", false);`](https://documentation.kopano.io/webapp_admin_manual/config.html#secure-cookies) in `/etc/kopano/webapp/config.php` also allowing HTTP access. This can be useful when arranging the `mlan/kopano` container behind a reverse proxy, like [Traefik](https://doc.traefik.io/traefik/), which then does the enforcement of HTTPS.
Note that prior to Kopano WebApp version 5.0.0 the corresponding parameter was `define("INSECURE_COOKIES", true);`, which provide the same functionality but with inverse logic.
## Mail client configuration ## Mail client configuration

View File

@ -141,14 +141,34 @@ dc_persist_mvdirs() {
# #
# Conditionally change owner of files. # Conditionally change owner of files.
# -a all
# -r readable
# -w writable
# -x executable
# #
dc_chowncond() { dc_cond_chown() {
dc_log 7 "Called with args: $@"
OPTIND=1
local find_opts="! -perm -404"
while getopts ":arwx" opts; do
case "${opts}" in
a) find_opts="";;
r) find_opts="! -perm -404";;
w) find_opts="! -perm -606";;
x) find_opts="! -perm -505";;
esac
done
shift $((OPTIND -1))
local user=$1 local user=$1
local dir=$2 shift
if id $user > /dev/null 2>&1; then if id $user > /dev/null 2>&1; then
if [ -n "$(find $dir ! -user $user -print -exec chown -h $user: {} \;)" ]; then for dir in $@; do
dc_log 5 "Changed owner to $user for some files in $dir" if [ -n "$(find $dir ! -user $user $find_opts -print -exec chown -h $user: {} \;)" ]; then
fi dc_log 5 "Changed owner to $user for some files in $dir"
fi
done
else
dc_log 3 "User $user is unknown."
fi fi
} }

View File

@ -62,6 +62,7 @@ init_service() {
local sourcefile= local sourcefile=
local sv_name cmd runsv_dir svlog_dir sv_log sv_down sv_force options local sv_name cmd runsv_dir svlog_dir sv_log sv_down sv_force options
dc_log 7 "Called with args $@" dc_log 7 "Called with args $@"
OPTIND=1
while getopts ":dfhln:s:q" opts; do while getopts ":dfhln:s:q" opts; do
case "${opts}" in case "${opts}" in
d) sv_down="down"; add_opt "down";; d) sv_down="down"; add_opt "down";;

View File

@ -5,6 +5,9 @@
# Kopano now installs without any cfg files, so we just write custom values # Kopano now installs without any cfg files, so we just write custom values
# into their target cfg file. # into their target cfg file.
# #
# Variables defined in Dockerfile
# DOCKER_CONF_DIR1 DOCKER_CONF_DIR2 DOCKER_ACME_SSL_DIR DOCKER_APPL_SSL_DIR
#
# #
# Configuration # Configuration
@ -34,7 +37,7 @@ dagent_env_vars="LMTP_LISTEN SPAM_HEADER_NAME SPAM_HEADER_VALUE LOG_LEVEL"
gateway_env_vars="BYPASS_AUTH DISABLE_PLAINTEXT_AUTH HTML_SAFETY_FILTER IMAP_CAPABILITY_IDLE IMAP_EXPUNGE_ON_DELETE IMAP_IGNORE_COMMAND_IDLE IMAP_LISTEN IMAP_MAX_FAIL_COMMANDS IMAP_MAX_MESSAGESIZE IMAP_ONLY_MAILFOLDERS IMAP_PUBLIC_FOLDERS IMAPS_LISTEN POP3_LISTEN POP3S_LISTEN DISABLE_PLAINTEXT_AUTH LOG_LEVEL" gateway_env_vars="BYPASS_AUTH DISABLE_PLAINTEXT_AUTH HTML_SAFETY_FILTER IMAP_CAPABILITY_IDLE IMAP_EXPUNGE_ON_DELETE IMAP_IGNORE_COMMAND_IDLE IMAP_LISTEN IMAP_MAX_FAIL_COMMANDS IMAP_MAX_MESSAGESIZE IMAP_ONLY_MAILFOLDERS IMAP_PUBLIC_FOLDERS IMAPS_LISTEN POP3_LISTEN POP3S_LISTEN DISABLE_PLAINTEXT_AUTH LOG_LEVEL"
ical_env_vars="ICAL_LISTEN ICALS_LISTEN ENABLE_ICAL_GET LOG_LEVEL" ical_env_vars="ICAL_LISTEN ICALS_LISTEN ENABLE_ICAL_GET LOG_LEVEL"
ldap_env_vars="LDAP_URI LDAP_STARTTLS LDAP_BIND_USER LDAP_BIND_PASSWD LDAP_SEARCH_BASE LDAP_USER_TYPE_ATTRIBUTE_VALUE LDAP_GROUP_TYPE_ATTRIBUTE_VALUE LDAP_USER_SEARCH_FILTER" ldap_env_vars="LDAP_URI LDAP_STARTTLS LDAP_BIND_USER LDAP_BIND_PASSWD LDAP_SEARCH_BASE LDAP_USER_TYPE_ATTRIBUTE_VALUE LDAP_GROUP_TYPE_ATTRIBUTE_VALUE LDAP_USER_SEARCH_FILTER"
search_env_vars="INDEX_PROCESSES INDEX_DRAFTS INDEX_JUNK SUGGESTIONS INDEX_ATTACHMENTS INDEX_ATTACHMENT_MAX_SIZE LOG_LEVEL LOG_METHOD" search_env_vars="INDEX_PROCESSES INDEX_DRAFTS INDEX_JUNK SUGGESTIONS INDEX_ATTACHMENTS INDEX_ATTACHMENT_MAX_SIZE LOG_LEVEL"
server_env_vars="MYSQL_HOST MYSQL_PORT MYSQL_DATABASE MYSQL_USER MYSQL_PASSWORD DISABLED_FEATURES USER_PLUGIN SEARCH_TIMEOUT LOG_LEVEL" server_env_vars="MYSQL_HOST MYSQL_PORT MYSQL_DATABASE MYSQL_USER MYSQL_PASSWORD DISABLED_FEATURES USER_PLUGIN SEARCH_TIMEOUT LOG_LEVEL"
spamd_env_vars="SPAM_DIR SPAM_DB HEADER_TAG LEARN_HAM HAM_DIR SA_GROUP LOG_LEVEL LOG_METHOD" spamd_env_vars="SPAM_DIR SPAM_DB HEADER_TAG LEARN_HAM HAM_DIR SA_GROUP LOG_LEVEL LOG_METHOD"
spooler_env_vars="SMTP_SERVER SMTP_PORT LOG_LEVEL" spooler_env_vars="SMTP_SERVER SMTP_PORT LOG_LEVEL"
@ -100,6 +103,9 @@ kopano_export_tls_cert() {
export SSL_CERTIFICATE_FILE=${SSL_CERTIFICATE_FILE-$DOCKER_APPL_SSL_CERT} export SSL_CERTIFICATE_FILE=${SSL_CERTIFICATE_FILE-$DOCKER_APPL_SSL_CERT}
export SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE-$DOCKER_APPL_SSL_KEY} export SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE-$DOCKER_APPL_SSL_KEY}
fi fi
if ([ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]); then
dc_cond_chown $DOCKER_APPL_RUNAS $SSL_CERTIFICATE_FILE $SSL_PRIVATE_KEY_FILE
fi
} }
# #
@ -116,6 +122,5 @@ kopano_generate_tls_cert() {
if ([ -z "$SSL_CERTIFICATE_FILE" ] && [ -n "$secure" ] && dc_is_installed openssl); then if ([ -z "$SSL_CERTIFICATE_FILE" ] && [ -n "$secure" ] && dc_is_installed openssl); then
dc_log 4 "$secure, but no certs given, so generating self-signed cert for host $HOSTNAME" dc_log 4 "$secure, but no certs given, so generating self-signed cert for host $HOSTNAME"
dc_tls_setup_selfsigned_cert $DOCKER_APPL_SSL_CERT $DOCKER_APPL_SSL_KEY dc_tls_setup_selfsigned_cert $DOCKER_APPL_SSL_CERT $DOCKER_APPL_SSL_KEY
dc_chowncond $DOCKER_APPL_RUNAS $DOCKER_APPL_SSL_DIR
fi fi
} }

View File

@ -10,7 +10,7 @@ _ip = $(shell docker inspect -f \
CURL_DBG ?= CURL_DBG ?=
TST_NAME ?= test TST_NAME ?= test
SSL_KEYF ?= priv-key.pem SSL_KEYF ?= priv_key.pem
SSL_CRTF ?= cert.pem SSL_CRTF ?= cert.pem
SSL_CRTD ?= 30 SSL_CRTD ?= 30
SSL_ACMF ?= acme.json SSL_ACMF ?= acme.json
@ -70,10 +70,8 @@ APPS_ENV ?= $(APP_ENV) \
-e POP3S_LISTEN=*:995 \ -e POP3S_LISTEN=*:995 \
-e ICALS_LISTEN=*:8443 -e ICALS_LISTEN=*:8443
APPF_ENV ?= $(APPS_ENV) \ APPF_ENV ?= $(APPS_ENV) \
-e SSL_CERTIFICATE_FILE=$(APP_SSLD)/$(SSL_CRTF) \ -v $(shell pwd)/$(TST_SSLD):/$(APP_SSLD)
-e SSL_PRIVATE_KEY_FILE=$(APP_SSLD)/$(SSL_KEYF)
APPA_ENV ?= $(APPS_ENV) \ APPA_ENV ?= $(APPS_ENV) \
-e ACME_FILE=/$(TST_ACME) \
-v $(shell pwd)/$(TST_ACMD):/$(TST_ACMD) -v $(shell pwd)/$(TST_ACMD):/$(TST_ACMD)
DB_NAME ?= db DB_NAME ?= db
@ -160,8 +158,6 @@ test-up_2: test-up-net test-up-auth_2 $(TST_CERT)
# #
docker run -d $(DB_ENV) $(DB_VOL) $(DB_IMG) docker run -d $(DB_ENV) $(DB_VOL) $(DB_IMG)
docker run -d $(APPF_ENV) $(APP_VOL) $(IMG_REPO):$(call _ver,$(IMG_VER),core) docker run -d $(APPF_ENV) $(APP_VOL) $(IMG_REPO):$(call _ver,$(IMG_VER),core)
docker cp $(TST_SSLD)/. $(APP_NAME):$(APP_SSLD)
docker exec -i $(APP_NAME) chown -R kopano: $(APP_SSLD)
test-up_3: test-up-net test-up-auth_3 $(TST_ACME) test-up_3: test-up-net test-up-auth_3 $(TST_ACME)
# #
@ -171,6 +167,7 @@ test-up_3: test-up-net test-up-auth_3 $(TST_ACME)
# send: curl lmtp://app -> srv mysql://db & srv ldap://auth # send: curl lmtp://app -> srv mysql://db & srv ldap://auth
# recv: curl pop3s://app # recv: curl pop3s://app
# recv: curl imaps://app # recv: curl imaps://app
# web: curl http://app
# #
# #
docker run -d $(DB_ENV) $(DB_VOL) $(DB_IMG) docker run -d $(DB_ENV) $(DB_VOL) $(DB_IMG)
@ -271,10 +268,11 @@ test-htop: test-debugtools
test-tls: #--starttls imap test-tls: #--starttls imap
docker run --rm -it $(NET_ENV) drwetter/testssl.sh $(APP_NAME):993 || true docker run --rm -it $(NET_ENV) drwetter/testssl.sh $(APP_NAME):993 || true
test-cert-gen: $(TST_ACME) test-ssl-gen: $(TST_ACME)
test-cert-rm: test-ssl-rm:
rm $(TST_KEY) $(TST_CRT) $(TST_ACME) rm -rf $(TST_SSLD) $(TST_ACMD)
# rm $(TST_KEY) $(TST_CRT) $(TST_ACME)
$(TST_ACME): $(TST_ACMD) $(TST_CERT) $(TST_ACME): $(TST_ACMD) $(TST_CERT)
bin/gen-acme-json.sh $(MAIL_US1)@$(MAIL_DOM) $(APP_FQDN) $(TST_KEY) $(TST_CERT) > $(TST_ACME) bin/gen-acme-json.sh $(MAIL_US1)@$(MAIL_DOM) $(APP_FQDN) $(TST_KEY) $(TST_CERT) > $(TST_ACME)