Setting up ejabberd with LDAP
Learn how to set up ejabberd securely, link it up to your LDAP trust and configure the necessary DNS records.
I'm really not sure how many more witty intros I can do on these articles. Think I'll give it a pass today. Let's get into this!
I'm setting this up on FreeBSD, so let's install all the relevant packages. Let's install ejabberd
:
root@sin:~ # pkg install ejabberd
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
All repositories are up to date.
The following 14 package(s) will be affected (of 0 checked):
New packages to be INSTALLED:
ejabberd: 20.12
erlang: 21.3.8.18_1,4
erlang-man: 21.3_2
[...]
[14/14] Extracting ejabberd-20.12: 100%
We'll want to create some DNS records to tell people where to look for our XMPP service. Let's get those out of the way now so they have time to propagate.
I'll be adding these to point to sin.zm.is
.
_jabber._tcp.zm.is 86400 IN SRV 0 0 5269 sin.zm.is
_xmpp-client._tcp.zm.is 86400 IN SRV 0 0 5222 sin.zm.is
_xmpp-server._tcp.zm.is 86400 IN SRV 0 0 5269 sin.zm.is
Let's test one of these out:
omega# dig srv _xmpp-server._tcp.zm.is +short
0 0 5269 sin.zm.is.
Let's move onto configuring ejabberd
.
After issuing certificates with acme.sh
and copying them into my FreeBSD jail every day with CRON (out of scope of this article), let's update the config with the LDAP definition:
host_config:
zm.is: # replace with your domain
auth_method: ldap
ldap_encrypt: tls
ldap_tls_cacertfile: "/etc/ssl/ldapcert.pem" # replace with your cert path
ldap_servers:
- xf # this is your LDAP server
ldap_uids:
- uid # which attribute in LDAP is the username
ldap_rootdn: "cn=passdn,ou=admin,dc=xf" # what DN to bind as
ldap_password: "hunter2" # password for said DN
ldap_base: "ou=users,dc=xf" # where to search
ldap_filter: "(memberOf=cn=xmpp,ou=groups,dc=xf)" # check group membership (optional)
We're going to harden TLS a bit with the following lines in each listen stanza (don't copy this into any configs, see below for full config):
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
The protocol options are industry standard, and the cipher selected is either CHACHA20-POLY1305
or AES256-SHA384
- arguably the best options out there.
Here's the full file:
###
### ejabberd configuration file
###
### The parameters used in this configuration file are explained at
###
### https://docs.ejabberd.im/admin/configuration
###
### The configuration file is written in YAML.
### *******************************************************
### ******* !!! WARNING !!! *******
### ******* YAML IS INDENTATION SENSITIVE *******
### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY *******
### *******************************************************
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
###
hosts:
- zm.is
host_config:
zm.is:
auth_method: ldap
ldap_encrypt: tls
ldap_tls_cacertfile: "/etc/ssl/ldapcert.pem"
ldap_servers:
- zm
ldap_uids:
- uid
ldap_rootdn: "cn=passdn,ou=admin,dc=xf"
ldap_password: "hunter2"
ldap_base: "ou=users,dc=xf"
ldap_filter: "(memberOf=cn=xmpp,ou=groups,dc=xf)"
loglevel: 4
log_rotate_size: 10485760
log_rotate_date: ""
log_rotate_count: 1
log_rate_limit: 100
## If you already have certificates, list them here
certfiles:
- /etc/ssl/private/fullchain
- /etc/ssl/private/privkey
listen:
-
port: 5222
ip: "10.1.0.141"
module: ejabberd_c2s
max_stanza_size: 262144
shaper: c2s_shaper
access: c2s
starttls_required: true
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
-
port: 5222
ip: "fd20:cead:faff::141"
module: ejabberd_c2s
max_stanza_size: 262144
shaper: c2s_shaper
access: c2s
starttls_required: true
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
-
port: 5269
ip: "10.1.0.141"
module: ejabberd_s2s_in
max_stanza_size: 524288
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
-
port: 5269
ip: "fd20:cead:faff::141"
module: ejabberd_s2s_in
max_stanza_size: 524288
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
-
port: 5443
ip: "10.1.0.141"
module: ejabberd_http
tls: true
request_handlers:
/admin: ejabberd_web_admin
/api: mod_http_api
/bosh: mod_bosh
# /captcha: ejabberd_captcha
/upload: mod_http_upload
/ws: ejabberd_http_ws
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
-
port: 5443
ip: "fd20:cead:faff::141"
module: ejabberd_http
tls: true
request_handlers:
/admin: ejabberd_web_admin
/api: mod_http_api
/bosh: mod_bosh
# /captcha: ejabberd_captcha
/upload: mod_http_upload
/ws: ejabberd_http_ws
protocol_options:
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
ciphers: "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384"
# -
# port: 5280
# ip: "::"
# module: ejabberd_http
# request_handlers:
# /admin: ejabberd_web_admin
# /.well-known/acme-challenge: ejabberd_acme
-
port: 1883
ip: "10.1.0.141"
module: mod_mqtt
backlog: 1000
-
port: 1883
ip: "fd20:cead:faff::141"
module: mod_mqtt
backlog: 1000
s2s_use_starttls: optional
acme:
auto: false
acl:
local:
user_regexp: ""
loopback:
ip:
- 127.0.0.0/8
- ::1/128
admin:
user:
- "m@zm.is"
access_rules:
local:
allow: local
c2s:
deny: blocked
allow: all
announce:
allow: admin
configure:
allow: admin
muc_create:
allow: local
pubsub_createnode:
allow: local
trusted_network:
allow: loopback
api_permissions:
"console commands":
from:
- ejabberd_ctl
who: all
what: "*"
"admin access":
who:
access:
allow:
acl: loopback
acl: admin
oauth:
scope: "ejabberd:admin"
access:
allow:
acl: loopback
acl: admin
what:
- "*"
- "!stop"
- "!start"
"public commands":
who:
ip: 127.0.0.1/8
what:
- status
- connected_users_number
shaper:
normal: 1000
fast: 50000
shaper_rules:
max_user_sessions: 10
max_user_offline_messages:
5000: admin
100: all
c2s_shaper:
none: admin
normal: all
s2s_shaper: fast
modules:
mod_adhoc: {}
mod_admin_extra: {}
mod_announce:
access: announce
# mod_avatar: {}
mod_blocking: {}
mod_bosh: {}
mod_caps: {}
mod_carboncopy: {}
mod_client_state: {}
mod_configure: {}
mod_disco: {}
mod_fail2ban: {}
mod_http_api: {}
mod_http_upload:
put_url: https://@HOST@:5443/upload
mod_last: {}
mod_mam:
## Mnesia is limited to 2GB, better to use an SQL backend
## For small servers SQLite is a good fit and is very easy
## to configure. Uncomment this when you have SQL configured:
## db_type: sql
assume_mam_usage: true
default: always
mod_mqtt: {}
mod_muc:
access:
- allow
access_admin:
- allow: admin
access_create: muc_create
access_persistent: muc_create
# access_mam:
# - allow
# default_room_options:
# mam: true
mod_muc_admin: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
mod_privacy: {}
mod_private: {}
mod_proxy65:
access: local
max_connections: 5
mod_pubsub:
access_createnode: pubsub_createnode
plugins:
- flat
- pep
force_node_config:
## Avoid buggy clients to make their bookmarks public
storage:bookmarks:
access_model: whitelist
mod_push: {}
mod_push_keepalive: {}
mod_register:
## Only accept registration requests from the "trusted"
## network (see access_rules section above).
## Think twice before enabling registration from any
## address. See the Jabber SPAM Manifesto for details:
## https://github.com/ge0rg/jabber-spam-fighting-manifesto
ip_access: trusted_network
mod_roster:
versioning: true
mod_s2s_dialback: {}
mod_shared_roster: {}
mod_stream_mgmt:
resend_on_timeout: if_offline
mod_vcard: {}
mod_vcard_xupdate: {}
mod_version:
show_os: false
### Local Variables:
### mode: yaml
### End:
### vim: set filetype=yaml tabstop=8
You'll want to change all the IP addresses and the admin JID - m@zm.is
(message me!).
Now, let's open 5222
, 5269
, 5443
and 1883
in our firewall in order to let it talk out.
I've done it like this:
xmpp="10.1.0.141"
xmpp_v6="fd20:cead:faff::141"
ports_xmpp="{ 5222, 5269, 5443, 1883 }"
rdr pass on $ext_if inet proto tcp from !<sshguard> to $ext_if port $ports_xmpp -> $xmpp
rdr pass on $ext_if inet6 proto tcp from !<sshguard> to $ext_if_v6 port $ports_xmpp -> $xmpp_v6
rdr pass on $br_if inet proto tcp from $net_v to 10.1.0.4 port $ports_xmpp -> $xmpp
rdr pass on $br_if inet6 proto tcp from $net_v_v6 to fd20:cead:faff::4 port $ports_xmpp -> $xmpp_v6
I've also got the bonus (last 2 lines) of being able to reach my XMPP server from within my VPN, since I've got split-horizon DNS:
x@qi ~ ยป host sin.zm.is
sin.zm.is has address 10.1.0.4
sin.zm.is has IPv6 address fd20:cead:faff::4
If you don't have a fancy network setup like mine you can do a simple pass
rule:
ports_xmpp="{ 5222, 5269, 5443, 1883 }"
pass in on $ext_if inet proto tcp from any to any port $ports_xmpp keep state
I'd recommend using Gajim to connect to it, for it's fantastic OMEMO support. Adding an account is rather simple, and thanks to the SRV
accounts we set up earlier, set-up will be automatic, with no need to put in the custom server we've set up for our domain. If you are actually running your XMPP server on the same domain, they aren't technically needed but are required if you want to adhere to the standard. So put them in.