1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00

docker: Add Dockerfile for running chasquid+dovecot+letsencrypt

This patch adds a new docker directory, which contains a Dockerfile plus
some additional configuration for creating a container that runs
chasquid+dovecot+letsencrypt.

It also updates the gitlab CI pipeline to automatically build and
publish an image on each commit.

This is experimental and likely to break.
This commit is contained in:
Alberto Bertogli
2018-06-04 23:33:49 +01:00
parent a7d49792f5
commit e98464c424
9 changed files with 490 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
stages: stages:
- test - test
- docker_image
integration_test: integration_test:
stage: test stage: test
@@ -12,3 +13,14 @@ integration_test:
- docker build -t chasquid-test -f test/Dockerfile . - docker build -t chasquid-test -f test/Dockerfile .
- docker run chasquid-test env - docker run chasquid-test env
- docker run chasquid-test make test - docker run chasquid-test make test
image_build:
stage: docker_image
image: docker:stable
services:
- docker:dind
script:
- docker info
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME -f docker/Dockerfile .
- docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME

View File

@@ -25,6 +25,7 @@ nav:
- hooks.md - hooks.md
- dovecot.md - dovecot.md
- dkim.md - dkim.md
- docker.md
- flow.md - flow.md
- monitoring.md - monitoring.md
- sec-levels.md - sec-levels.md

84
docker/Dockerfile Normal file
View File

@@ -0,0 +1,84 @@
# Docker file for creating a container that will run chasquid and Dovecot.
#
# THIS IS EXPERIMENTAL AND LIKELY TO CHANGE.
#
# This is not recommended for serious installations, you're probably better
# off following the documentation and setting the server up manually.
#
# See the README.md file for more details.
# Build the binaries.
FROM golang:latest as build
WORKDIR /go/src/blitiri.com.ar/go/chasquid
COPY . .
RUN go get -d ./...
RUN go install ./...
# Create the image.
FROM debian:stable
# Make debconf/frontend non-interactive, to avoid distracting output about the
# lack of $TERM.
ENV DEBIAN_FRONTEND noninteractive
# Install the packages we need.
# This includes chasquid, which sets up good defaults.
RUN apt-get update -q
RUN apt-get install -y -q \
chasquid \
dovecot-lmtpd dovecot-imapd dovecot-pop3d \
dovecot-sieve dovecot-managesieved \
acl sudo certbot
# Copy the binaries. This overrides the debian packages with the ones we just
# built above.
COPY --from=build /go/bin/chasquid /usr/bin/
COPY --from=build /go/bin/chasquid-util /usr/bin/
COPY --from=build /go/bin/smtp-check /usr/bin/
COPY --from=build /go/bin/mda-lmtp /usr/bin/
# Let chasquid bind privileged ports, so we can run it as its own user.
RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/chasquid
# Copy docker-specific configurations.
COPY docker/dovecot.conf /etc/dovecot/dovecot.conf
COPY docker/chasquid.conf /etc/chasquid/chasquid.conf
# Copy utility scripts.
COPY docker/add-user.sh /
COPY docker/entrypoint.sh /
# chasquid: SMTP, submission, submission+tls.
EXPOSE 25 465 587
# dovecot: POP3s, IMAPs, managesieve.
EXPOSE 993 995 4190
# http for letsencrypt/certbot.
EXPOSE 80 443
# Store emails and chasquid databases in an external volume, to be mounted at
# /data, so they're independent from the image itself.
VOLUME /data
# Put some directories where we expect persistent user data into /data.
RUN rmdir /etc/chasquid/domains/
RUN ln -sf /data/chasquid/domains/ /etc/chasquid/domains
RUN rm -rf /etc/letsencrypt/
RUN ln -sf /data/letsencrypt/ /etc/letsencrypt
# Give the chasquid user access to the necessary configuration.
RUN setfacl -R -m u:chasquid:rX /etc/chasquid/
RUN mv /etc/chasquid/certs/ /etc/chasquid/certs-orig
RUN ln -s /etc/letsencrypt/live/ /etc/chasquid/certs
# NOTE: Set AUTO_CERTS="example.com example.org" to automatically obtain and
# renew certificates upon startup, via Letsencrypt. You're agreeing to their
# ToS by setting this variable, so please review them carefully.
# CERTS_EMAIL should be set to your email address so letsencrypt can send you
# critical notifications.
# Custom entry point that does some configuration checks and ensures
# letsencrypt is properly set up.
ENTRYPOINT ["/entrypoint.sh"]

84
docker/README.md Normal file
View File

@@ -0,0 +1,84 @@
# Docker
chasquid comes with a Dockerfile to create a container running [chasquid],
[dovecot], and managed certificates with [Let's Encrypt].
**IT IS EXPERIMENTAL AND LIKELY TO BREAK**
The more traditional setup is **highly recommended**, see the
[how-to](howto.md) documentation for more details.
[chasquid]: https://blitiri.com.ar/p/chasquid
[dovecot]: https://dovecot.org
[Let's Encrypt]: https://letsencrypt.org
## Images
There are [pre-built images at gitlab
registry](https://gitlab.com/albertito/chasquid/container_registry). They are
automatically built, and tagged with the corresponding branch name. Use the
*master* tag for a stable version.
If, instead, you want to build the image yourself, just run:
```sh
$ docker build -t chasquid -f docker/Dockerfile .
```
## Running
First, pull the image into your target machine:
```sh
$ docker pull registry.gitlab.com/albertito/chasquid:master
```
You will need a data volume to store persistent data, outside the image. This
will contain the mailboxes, user databases, etc.
```sh
$ docker volume create chasquid-data
```
To add your first user to the image:
```
$ docker run \
--mount source=chasquid-data,target=/data \
-it --entrypoint=/add-user.sh \
registry.gitlab.com/albertito/chasquid:master
Email (full user@domain format): pepe@example.com
Password:
pepe@example.com added to /data/dovecot/users
```
Upon startup, the image will obtain a TLS certificate for you using [Let's
Encrypt](https://letsencrypt.com/). You need to tell it the domain(s) to get a
certificate from by setting the `AUTO_CERTS` variable.
Because certificates expire, you should restart the container every week or
so. Certificates will be renewed automatically upon startup if needed.
In order for chasquid to get access to the source IP address, you will need to
use host networking, or create a custom docker network that does IP forwarding
and not proxying.
Finally, start the container:
```sh
$ docker run -e AUTO_CERTS=mail.yourdomain.com \
--mount source=chasquid-data,target=/data \
--network host \
registry.gitlab.com/albertito/chasquid:master
```
## Debugging
To get a shell on the running container for debugging, you can use `docker ps`
to find the container ID, and then `docker exec -it CONTAINERID /bin/bash` to
open a shell on the running container.

43
docker/add-user.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
#
# Creates a user. If it exists, updates the password.
#
# Note this is not robust, it's only for convenience on extremely simple
# setups.
set -e
read -p "Email (full user@domain format): " EMAIL
if ! echo "${EMAIL}" | grep -q @; then
echo "Error: email should have '@'."
exit 1
fi
read -p "Password: " -s PASSWORD
echo
DOMAIN=$(echo echo "${EMAIL}" | cut -d '@' -f 2)
# If the domain doesn't exist in chasquid's config, create it.
mkdir -p "/data/chasquid/domains/${DOMAIN}/"
# Encrypt password.
ENCPASS=$(doveadm pw -u "${EMAIL}" -p "${PASSWORD}")
# Edit dovecot users: remove user if it exits.
mkdir -p /data/dovecot
if grep -q "^${EMAIL}:" /data/dovecot/users; then
cp /data/dovecot/users /data/dovecot/users.old
cat /data/dovecot/users.old | grep -v "^${EMAIL}:" \
> /data/dovecot/users
fi
# Edit dovecot users: add user.
echo "${EMAIL}:${ENCPASS}::::" >> /data/dovecot/users
echo "${EMAIL} added to /data/dovecot/users"

26
docker/chasquid.conf Normal file
View File

@@ -0,0 +1,26 @@
# Listening addresses.
smtp_address: ":25"
submission_address: ":587"
submission_over_tls_address: ":465"
# Monitoring HTTP server only bound to localhost, just in case.
monitoring_address: "127.0.0.1:1099"
# Auth against dovecot.
dovecot_auth: true
# Use mda-lmtp to talk to dovecot.
mail_delivery_agent_bin: "/usr/bin/mda-lmtp"
mail_delivery_agent_args: "--addr"
mail_delivery_agent_args: "/run/dovecot/lmtp"
mail_delivery_agent_args: "-f"
mail_delivery_agent_args: "%from%"
mail_delivery_agent_args: "-d"
mail_delivery_agent_args: "%to%"
# Store data in the container volume.
data_dir: "/data/chasquid/data"
# Mail log to the container volume.
mail_log_path: "/data/chasquid/mail.log"

134
docker/dovecot.conf Normal file
View File

@@ -0,0 +1,134 @@
#
# Logging
#
log_path = /data/dovecot/dovecot.log
#
# Email storage
#
# Store emails in /data/mail/home/domain/user, which will be inside the
# container's volume.
mail_home = /data/mail/home/%d/%n
# Use Dovecot's native format.
mail_location = mdbox:~/mdbox
# User and group used to store and access mailboxes.
mail_uid = dovecot
mail_gid = dovecot
# As we're using virtual mailboxes, the system user will be "dovecot", which
# has uid in the 100-500 range. By default using uids <500 is blocked, so we
# need to explicitly lower the value to allow storage of mail as "dovecot".
first_valid_uid = 100
first_valid_gid = 100
#
# Authentication
#
# Static file, in /data/dovecot/users.
auth_mechanisms = plain
passdb {
driver = passwd-file
args = scheme=CRYPT username_format=%u /data/dovecot/users
}
userdb {
driver = passwd-file
args = /data/dovecot/users
}
#
# TLS
#
# TLS is mandatory.
# The entrypoint generates auto-ssl.conf, with all the certificates.
ssl = required
!include_try /etc/dovecot/auto-ssl.conf
# Only allow TLS 1.2 and up.
ssl_min_protocol = TLSv1.2
#
# Protocols
#
protocols = lmtp imap pop3 sieve
#
# IMAP
#
service imap-login {
inet_listener imap {
# Disable plain text IMAP, just in case.
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
}
service imap {
}
#
# POP3
#
service pop3-login {
inet_listener pop3 {
# Disable plain text POP3, just in case.
port = 0
}
inet_listener pop3s {
port = 995
ssl = yes
}
}
service pop3 {
}
#
# Sieve/managesieve
#
service managesieve-login {
}
service managesieve {
}
protocol sieve {
}
plugin {
sieve = file:~/sieve;active=~/.dovecot.sieve
}
#
# Internal services
#
service auth {
unix_listener auth-userdb {
}
# Grant chasquid access to request user authentication.
unix_listener auth-chasquid-userdb {
mode = 0660
user = chasquid
}
unix_listener auth-chasquid-client {
mode = 0660
user = chasquid
}
}
service auth-worker {
}
dict {
}
service lmtp {
# This is used by mda-lmtp.
unix_listener lmtp {
}
}

105
docker/entrypoint.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/bin/bash
#
# Script that is used as a Docker entrypoint.
#
set -e
if ! grep -q data /proc/mounts; then
echo "/data is not mounted."
echo "Check that the /data volume is set up correctly."
exit 1
fi
# Create the directory structure if it's not there.
# Some of these directories are symlink targets, see the Dockerfile.
mkdir -p /data/chasquid
mkdir -p /data/letsencrypt
mkdir -p /data/chasquid
mkdir -p /data/chasquid/domains
mkdir -p /data/dovecot
# Set up the certificates for the requested domains.
if [ "$AUTO_CERTS" != "" ]; then
# If we were given an email to use for letsencrypt, use it. Otherwise
# continue without one.
MAIL_OPTS="--register-unsafely-without-email"
if [ "$CERTS_MAIL" != "" ]; then
MAIL_OPTS="-m $CERTS_MAIL"
fi
for DOMAIN in $(echo $AUTO_CERTS); do
# If it has never been set up, then do so.
if ! [ -e /etc/letsencrypt/live/$DOMAIN/fullchain.pem ]; then
certbot certonly \
--non-interactive \
--standalone \
--agree-tos \
$MAIL_OPTS \
-d $DOMAIN
else
echo "$DOMAIN certificate already set up."
fi
done
# Renew on startup, since the container won't have cron facilities.
# Note this requires you to restart every week or so, to make sure
# your certificate does not expire.
certbot renew
fi
CERT_DOMAINS=""
for i in $(ls /etc/letsencrypt/live/); do
if [ -e "/etc/letsencrypt/live/$i/fullchain.pem" ]; then
CERT_DOMAINS="$CERT_DOMAINS $i"
fi
done
# We need one domain to use as a default - pick the last one.
ONE_DOMAIN=$i
# Check that there's at least once certificate at this point.
if [ "$CERT_DOMAINS" == "" ]; then
echo "No certificates found."
echo
echo "Set AUTO_CERTS='example.com' to automatically get one."
exit 1
fi
# Give chasquid access to the certificates.
# Dovecot does not need this as it reads them as root.
setfacl -R -m u:chasquid:rX /etc/letsencrypt/{live,archive}
# Give chasquid access to the data directory.
mkdir -p /data/chasquid/data
chown -R chasquid /data/chasquid/
# Give dovecot access to the mailbox home.
mkdir -p /data/mail/
chown dovecot:dovecot /data/mail/
# Generate the dovecot ssl configuration based on all the certificates we have.
# The default goes first because dovecot complains otherwise.
echo "# Autogenerated by entrypoint.sh" > /etc/dovecot/auto-ssl.conf
cat >> /etc/dovecot/auto-ssl.conf <<EOF
ssl_cert = </etc/letsencrypt/live/$ONE_DOMAIN/fullchain.pem
ssl_key = </etc/letsencrypt/live/$ONE_DOMAIN/privkey.pem
EOF
for DOMAIN in $CERT_DOMAINS; do
echo "local_name $DOMAIN {"
echo " ssl_cert = </etc/letsencrypt/live/$DOMAIN/fullchain.pem"
echo " ssl_key = </etc/letsencrypt/live/$DOMAIN/privkey.pem"
echo "}"
done >> /etc/dovecot/auto-ssl.conf
# Pick the default domain as default hostname for chasquid. This is only used
# in plain text sessions and on very rare cases, and it's mostly for aesthetic
# purposes.
echo "hostname: '$ONE_DOMAIN'" >> /etc/chasquid/chasquid.conf
# Start the services: dovecot in background, chasquid in foreground.
start-stop-daemon --start --quiet --pidfile /run/dovecot.pid \
--exec /usr/sbin/dovecot -- -c /etc/dovecot/dovecot.conf
sudo -u chasquid -g chasquid /usr/bin/chasquid $CHASQUID_FLAGS

1
docs/docker.md Symbolic link
View File

@@ -0,0 +1 @@
../docker/README.md