- [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) Add SSL build tests.
- [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

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.
### 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`
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/)
@ -329,19 +356,20 @@ For most deployments a trusted TLS certificate is needed. When such are availabl
#### `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.
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
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.
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`
@ -408,7 +436,9 @@ Here some topics relevant for arranging a mail server are presented.
## 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

View File

@ -141,14 +141,34 @@ dc_persist_mvdirs() {
#
# 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 dir=$2
shift
if id $user > /dev/null 2>&1; then
if [ -n "$(find $dir ! -user $user -print -exec chown -h $user: {} \;)" ]; then
dc_log 5 "Changed owner to $user for some files in $dir"
fi
for dir in $@; do
if [ -n "$(find $dir ! -user $user $find_opts -print -exec chown -h $user: {} \;)" ]; then
dc_log 5 "Changed owner to $user for some files in $dir"
fi
done
else
dc_log 3 "User $user is unknown."
fi
}

View File

@ -62,6 +62,7 @@ init_service() {
local sourcefile=
local sv_name cmd runsv_dir svlog_dir sv_log sv_down sv_force options
dc_log 7 "Called with args $@"
OPTIND=1
while getopts ":dfhln:s:q" opts; do
case "${opts}" in
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
# into their target cfg file.
#
# Variables defined in Dockerfile
# DOCKER_CONF_DIR1 DOCKER_CONF_DIR2 DOCKER_ACME_SSL_DIR DOCKER_APPL_SSL_DIR
#
#
# 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"
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"
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"
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"
@ -100,6 +103,9 @@ kopano_export_tls_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}
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
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_chowncond $DOCKER_APPL_RUNAS $DOCKER_APPL_SSL_DIR
fi
}

View File

@ -10,7 +10,7 @@ _ip = $(shell docker inspect -f \
CURL_DBG ?=
TST_NAME ?= test
SSL_KEYF ?= priv-key.pem
SSL_KEYF ?= priv_key.pem
SSL_CRTF ?= cert.pem
SSL_CRTD ?= 30
SSL_ACMF ?= acme.json
@ -70,10 +70,8 @@ APPS_ENV ?= $(APP_ENV) \
-e POP3S_LISTEN=*:995 \
-e ICALS_LISTEN=*:8443
APPF_ENV ?= $(APPS_ENV) \
-e SSL_CERTIFICATE_FILE=$(APP_SSLD)/$(SSL_CRTF) \
-e SSL_PRIVATE_KEY_FILE=$(APP_SSLD)/$(SSL_KEYF)
-v $(shell pwd)/$(TST_SSLD):/$(APP_SSLD)
APPA_ENV ?= $(APPS_ENV) \
-e ACME_FILE=/$(TST_ACME) \
-v $(shell pwd)/$(TST_ACMD):/$(TST_ACMD)
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 $(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)
#
@ -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
# recv: curl pop3s://app
# recv: curl imaps://app
# web: curl http://app
#
#
docker run -d $(DB_ENV) $(DB_VOL) $(DB_IMG)
@ -271,10 +268,11 @@ test-htop: test-debugtools
test-tls: #--starttls imap
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:
rm $(TST_KEY) $(TST_CRT) $(TST_ACME)
test-ssl-rm:
rm -rf $(TST_SSLD) $(TST_ACMD)
# rm $(TST_KEY) $(TST_CRT) $(TST_ACME)
$(TST_ACME): $(TST_ACMD) $(TST_CERT)
bin/gen-acme-json.sh $(MAIL_US1)@$(MAIL_DOM) $(APP_FQDN) $(TST_KEY) $(TST_CERT) > $(TST_ACME)