# Dockerized ejabberd XMPP server with Biboumi IRC gateway ## Introduction This setup packs a [ejabberd](https://docs.ejabberd.im/) XMPP Server with a [Biboumi](https://biboumi.louiz.org/) IRC Gateway in a `docker-compose.yml` file. The whole service lives in a single folder to ease backup and migration. All persistent data is stored in `./data/`. All configurations are made in `ejabberd.yml` and in a `.env` file. For consistent backups, a cronjob one-liner, running in the ejabberd container, dumps daily the ejabberd DB into `./data/backup/`. It is assumed that on the docker host we're already running 1. a [jwilder/nginx-proxy](https://github.com/jwilder/nginx-proxy) reverse proxy 2. together with [jrcs/letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion) to obtain letsencrypt TLS certificates, 3. as well as [watchtower](https://github.com/v2tec/watchtower) to keep images up-to-date. ## Design desitions: 1. Simple configuration – only need to set IP and hostname in .env 2. Compliance – with the [Conversations](https://conversations.im/) feature set 3. External acme client – for more flexibility when serving other containers on the same host. Port 80 can be bound only once, so there can be obly one acme client per host. 4. No cluster setup ### Used docker images - This setup uses [ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/) from the ProcessOne ejabberd developers. The underlying [Dockerfile](https://github.com/processone/docker-ejabberd) builds the server from source and installs it into a Alpine image. The server runs as unprivileged user (uid 9000). This image doesn't come with many parameters or helper scripts, so all configurations are made in the ejabberd.yml configuration file. [rroemhild/docker-ejabberd](https://github.com/rroemhild/docker-ejabberd) was not used as it didn't run reliably for me in July 2018. Still it is worth to take a look at, as it uses parameters and helper scripts to learn from. - Biboumi runs from [louiz/biboumi](https://hub.docker.com/r/louiz/biboumi/). The image is provided by the Biboumi developer himself and only contains the binary as it is based on `FROM scratch`. All parameters are set using environment variables defined in `docker-compose.yml`. The server runs under a unprivileged user (uid 1000). ### Open Ports Marked [x] ports will be opened on the Docker host, so they should not be used by another service on the same host. - [x] `5222:5222` Client 2 Server — bare minimum to speak with clients - [x] `5223:5223` XMPP over TLS is actually deprecated, instead STARTTLS on port 5222 should be used. It is active for backward compatibility or could be moved to port 443 to ease usage behind firewalls. - [x] `5269:5269` Server 2 Server — only needed when we want to speak with users from other servers - [x] `5280:5280` Web admin (disabled), BOSH and WebSocket - [x] `5443:5443` HTTP Upload - [x] `113:8113` Identd so IRC servers can differentiate between users - [x] `443!8080` Integrated webserver to serve static content, such as a javascript xmpp client. The port is exposed via the reverse proxy. - [ ] `5347:5347` Only used for internal unencrypted communication with the Biboumi (IRC gateway) component - [ ] `----:4560` XMLRPC — API, disabled *BOSH (previously known as 'HTTP binding' or "http-bind") is a technology to use XMPP over HTTP. This allows XMPP applications to run in web pages, but also any other HTTP-only environment such as behind restrictive firewalls.* ### Used volumes ejabber runs under user `ejabberd` with uid/guid `9000`. All data is stored in `/home/ejabberd/` within the container. - `./data/conf/ejabberd.yml` — main configuration file - `.data/database/` — Erlang DB used to store all application data - `.data/backup/` — place to to store DB dumps - `.data/uploads/` — files transferred between users - `.data/cron/backup.sh` — job to daily dump the DB - `./opt/docker/proxy/data/certs/im.example.net/` — certificates and key material (to be changed as per the local docker host setup) - `.data/www/` — static web content - `./data/biboumi/database/` — sqlite DB for Biboumi. The Biboumi configuration is defined via environment variables in `docker-compose.yml`. ### DNS Setup To allow service discovery, following DNS records have to be created. ``` im 3600 IN A 1.2.3.4 *.im 3600 IN CNAME in #_service._proto.name TTL class SRV priority weight port target _xmpp-client._tcp.im.example.net. 3600 IN SRV 5 0 5222 im.example.net. _xmpp-clients._tcp.im.example.net. 3600 IN SRV 10 0 5223 im.example.net. _xmpp-server._tcp.im.example.net. 3600 IN SRV 5 0 5269 im.example.net. ``` To check the records: https://kingant.net/check_xmpp_dns/ ## Installation & Configuration 1. Prepare data folders with proper permissions. ``` inst_dir=/opt/docker/ejabberd mkdir -p $inst_dir/data/conf \ $inst_dir/data/database \ $inst_dir/data/backup \ $inst_dir/data/upload \ $inst_dir/data/cron \ $inst_dir/data/www \ && chown -R 9000:9000 $inst_dir/data/ mkdir -p $inst_dir/data/biboumi/database chown -R 1000:1000 $inst_dir/data/biboumi/ ``` 2. Obtain a vanilla `ejabberd.yml` configuration file. We'll briefly spawn a volatile container and copy the file to the host. ``` docker run -d --rm --name ejabberd ejabberd/ecs docker cp ejabberd:/home/ejabberd/conf/ejabberd.yml $inst_dir/data/conf/ docker kill ejabberd ``` 3. Configure the server / tune `ejabberd.yml` ``` hostname=im.example.com config_file=$inst_dir/data/conf/ejabberd.yml # Set the hostname in .env to be used in docker-compose.yml echo "HOSTNAME=$hostname" > $inst_dir/.env # Set hostname and disable localhost configuration as we have no certificate for it sed -i \ -e "s/^hosts:/hosts:\\n - \"$hostname\"/" \ -e '/^ - "localhost"/s/^/# /' \ $config_file ## Disable web admin, api and oauth as not used and to reduce attack surface sed -i \ -e '/web_admin: true/s/^/# /' \ -e '/\/api/s/^/# /' \ -e '/\/oauth/s/^/# /' \ $config_file ## Enable TLS for BOSH and WebSockets sed -i \ -e '/\/bosh/a\ tls: true' \ $config_file ## Logs: Rotate at 1MB, don't keep old versions # The server is started in foreground and therefore logs are collected by docker, # so we actually don't need logs at all and can rotate rapidly without keeping old version sed -i \ -e '/log_rotate_size/s/10485760/1048576/' \ -e '/log_rotate_count/s/1/0/' \ $config_file ## Point to letsencrypt certificates from jrcs/letsencrypt-nginx-proxy-companion, ## for details, see: https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion sed -i \ -e '/^certfiles/a\ - \"/home/ejabberd/ssl/*.pem\"' \ -e '/server.pem/s/^/# /' \ $config_file ## Enforce TLS connection for c2s sed -i \ -e '/ ## starttls_required: true/a\ starttls_required: true' \ $config_file ## Enable XEP-0313 Message Archive Management, ## for details, see: https://xmpp.org/extensions/xep-0313.html) sed -i \ -e '/ ## mod_mam/a\ mod_mam: {}' \ $config_file ## Enable OMEMO (XEP-0384), ## for details, see: https://github.com/processone/ejabberd/issues/2425 sed -i \ -e '/OMEMO/,+3 s/^/#/' \ $config_file ## Enable Roster Versioning (XEP-0237) sed -i \ -e '/^ mod_roster: {}/a\ mod_roster:\n versioning: true' \ -e '/^ mod_roster: {}/s/^/#/' \ $config_file ## Enable SOCKS5 Bytestreams Proxy (XEP-0065) sed -i -e '/mod_proxy65/s/## //' $config_file ## Enable legacy XMPP via TLS (XEP-0368) heredoc=`tempfile` cat > $heredoc <<'EOF' - port: 5223 ip: "::" module: ejabberd_c2s tls: true max_stanza_size: 65536 shaper: c2s_shaper access: c2s EOF sed -i "/^listen:$/r $heredoc" $config_file rm $heredoc ## Enable HTTP File Upload Listener(XEP-0363) heredoc=`tempfile` cat > $heredoc <<'EOF' - port: 5443 ip: "::" module: ejabberd_http request_handlers: "": mod_http_upload tls: true EOF sed -i "/^listen:$/r $heredoc" $config_file rm $heredoc ## Enable HTTP File Upload Module ## with quota the keep files max 3 days and max size of 5MB per file heredoc=`tempfile` cat > $heredoc <<'EOF' mod_http_upload: put_url: "https://@HOST@:5443/upload" thumbnail: false # otherwise needs ejabberd to be compiled with libgd support max_size: 524288 # 5MB mod_http_upload_quota: max_days: 3 EOF sed -i "/^modules:$/r $heredoc" $config_file rm $heredoc ## Add component for the IRC gateway heredoc=`tempfile` cat > $heredoc < $heredoc <<'EOF' - port: 8080 ip: "::" module: ejabberd_http request_handlers: "/": mod_http_fileserver EOF sed -i "/^listen:$/r $heredoc" $config_file rm $heredocmkdir -p $inst_dir/data/biboumi/database chown -R 1000:1000 $inst_dir/data/biboumi/ sed -i \ -e '/## mod_http_fileserver:/s/## //' \ -e '/mod_http_fileserver:/a\ default_content_type: "text/html"' \ -e '/mod_http_fileserver:/a\ docroot: "/var/www/"' \ -e '/mod_http_fileserver:/a\ directory_indices:\n - "index.html"' \ $config_file ## Disable legacy SSL protocols and ciphers sed -i \ -e '/c2s_protocol_options:/ac2s_protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n - "no_tlsv1"\n - "no_tlsv1_1"\n' \ \ -e '/s2s_protocol_options:/as2s_protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n' \ \ -e '/## s2s_ciphers:/as2s_ciphers: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"\n' \ \ -e '/## c2s_ciphers:/ac2s_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"\n' \ \ -e '/^ port: 5443/a\ protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n - "no_tlsv1"\n - "no_tlsv1_1"\n ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"\n' \ \ -e '/^ port: 5280/a\ protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n - "no_tlsv1"\n - "no_tlsv1_1"\n ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"\n' \ $config_file ``` 4. Setup the cronjob for dumping daily the DB ``` cat >$inst_dir/data/cron/dumb_db.sh <<'EOF' #!/bin/sh /home/ejabberd/bin/ejabberdctl backup /home/ejabberd/backup/ejabberd.backup EOF chmod a+x $inst_dir/data/cron/dumb_db.sh ``` 5. Start the container. During the first startup the databases will be created. ``` docker-compose up -d ``` 6. Create users ejabberd comes by default with no users. Privileges and ACLs for users are set in `ejabberd.yml`. In the default configuration file `admin@localhost` is set as privileged user already. To create new users, run from the docker host: `docker exec -it ejabberd /home/ejabberd/bin/ejabberdctl register ` We'll need to create at least a user for the IRC gateway. ``` user=ircadmin pass=`apg -q -n1 -m12` docker exec -it ejabberd /home/ejabberd/bin/ejabberdctl register $user $hostname $pass Further self-registration or LDAP authentication can be enabled in `ejabberd.yml`. ``` 7. (Optional) Run the XMPP compliance tester ``` jar_file=ComplianceTester-0.2.3.jar dl_url=https://gultsch.de/files/$jar_file domain=$hostname docker run --rm -it --name=xmpptest openjdk:alpine \ /bin/sh -c "wget $dl_url ; java -jar $jar_file $user@$domain $pass" \ | tee $domain.txt ``` 8. (Optional) Setup a static website `data/www/` can be used to serve a static site, e.g. for presenting compliance test results, a web xmpp client or client setup instructions ``` cd $inst_dir/data/www git clone --depth=1 https://github.com/conversejs/converse.js.git mv converse.js webchat cp webchat/fullscreen.html webchat/index.html sed -i \ -e '/analytics/d' \ -e '/piwik/d' \ -e "/bosh_service_url/s/conversejs.org/$hostname:5280/" \ -e "/bosh_service_url/s/http-bind/bosh/" \ webchat/index.html ``` ## Maintenance ##### Get a shell in the container `docker exec -it ejabberd sh` ##### Control the server `docker exec -it ejabberd /home/ejabberd/bin/ejabberdctl \` - `registered_users ` List all registered users in HOST - `unregister ` List all registered users in HOST - `modules_available` List the contributed modules available to install - `modules_installed` List the contributed modules already installed - `mnesia` Get details about the database - `reload_config` Reload ejabberd configuration file into memory (this will not start new servers) - `connected_users` list connected users with their resources - `backup /home/ejabberd/backup/ejabberd.backup` Backup database - `install_fallback /home/ejabberd/backup/ejabberd.backup` restores the db and makes it active after the next restart - `help` lists [available commands](https://docs.ejabberd.im/admin/guide/managing/#list-of-ejabberd-commands) ##### Biboumi [Biboumi documentation](https://github.com/louiz/biboumi/blob/master/doc/biboumi.1.rst) ##### Check if the IRC server certificate is valid firefox https://www.sslshopper.com/ssl-checker.html#hostname=irc.example.net:6697 #### Whislist - Jingle? Jingle Video? WebRTC? STUN/TURN server to find ports for video calls, e.g. for Jingle ICE? - Jabber transport for Skype, e.g. [spectrum](https://spectrum.im/) - mirror vhost using mod_echo (this requires moving general configs in a vhost) #### Notes and pitfalls ##### The Node and DB name changes with the hostname Docker's randomly generated hostnames causes ejabberd to [calculate](https://docs.ejabberd.im/admin/guide/managing/#change-computer-hostname) different unique node and DB name. To prevent a new DB beeing created with each container restart, use `docker run --hostname ` or `hostname:` and `domainname:` in the `docker-compose.yml`. `/opt/docker/proxy/data/certs/` should be readable by `ejabberd` user. #### Debugging Inspect the vanilla container: `docker run --rm -it --entrypoint /bin/sh ejabberd/ecs`