Install Prosody XMPP Server¶
Installation¶
Install the repository:
sudo -s
echo 'deb https://packages.prosody.im/debian focal main' | tee /etc/apt/sources.list.d/prosody.list
wget https://prosody.im/files/prosody-debian-packages.key -O- | apt-key add -
Update the apt cache and install Prosody:
apt update && apt install prosody lua-sec
Start and enable the Prosody service
systemctl start prosody
systemctl enable prosody
Open Firewall Ports¶
Add the following rules in the relevant sections of IPTables:
-A INPUT ! -i lo -p tcp -m conntrack --ctstate NEW -m tcp --dport 5222 -j ACCEPT -m comment --comment "Prosody C2S"
-A INPUT ! -i lo -p tcp -m conntrack --ctstate NEW -m tcp --dport 5269 -j ACCEPT -m comment --comment "Prosody S2S"
-A INPUT ! -i lo -p tcp -m conntrack --ctstate NEW -m tcp --dport 5050 -j ACCEPT -m comment --comment "Prosody Proxy"
-A OUTPUT ! -o lo -p tcp -m conntrack --ctstate NEW -m tcp --dport 5269 -j ACCEPT -m comment --comment "Prosody S2S"
DNS Records¶
Set DNS A records in your DNS management console for the various components we'll use:
example.com
conference.example.com
proxy.example.com
pubsub.example.com
upload.example.com
vjud.example.com
Set the following SRV records:
_xmpp-client._tcp 3600 IN SRV 0 5 5222 example.com.
_xmpp-server._tcp 3600 IN SRV 0 5 5269 example.com.
_xmpp-server._tcp.conference 3600 IN SRV 0 5 5269 example.com.
_xmpp-server._tcp.proxy 3600 IN SRV 0 5 5050 example.com.
_xmpp-server._tcp.pubsub 3600 IN SRV 0 5 5269 example.com.
_xmpp-server._tcp.vjud 3600 IN SRV 0 5 5269 example.com.
Set the following TXT record:
_xmppconnect 3600 IN TXT "_xmpp-client-xbosh=https://example.com:443/http-bind"
Create the Certificates¶
Issue an RSA certificate and install to a custom location
~/.acme.sh/acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com -d conference.example.com -d proxy.example.com -d pubsub.example.com -d upload.example.com -d vjud.example.com --keylength 4096 --key-file /etc/letsencrypt/rsa-certs/example.com/privkey.pem --ca-file /etc/letsencrypt/rsa-certs/example.com/chain.pem --cert-file /etc/letsencrypt/rsa-certs/example.com/cert.pem --fullchain-file /etc/letsencrypt/rsa-certs/example.com/fullchain.pem --pre-hook "mkdir -p /etc/letsencrypt/rsa-certs/example.com" --post-hook "find /etc/letsencrypt/rsa-certs/example.com/ -name '*.pem' -type f -exec chmod 640 {} \; -exec chown root:prosody {} \; -exec service nginx reload \; -exec service prosody reload \;" --renew-hook "find /etc/letsencrypt/rsa-certs/example.com/ -name '*.pem' -type f -exec chmod 640 {} \; -exec chown root:prosody {} \; -exec service nginx reload \; -exec service prosody reload \;" --dnssleep 60
and issue an ECC certificate
~/.acme.sh/acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com -d conference.example.com -d proxy.example.com -d pubsub.example.com -d upload.example.com -d vjud.example.com --keylength ec-384 --key-file /etc/letsencrypt/ecc-certs/example.com/privkey.pem --ca-file /etc/letsencrypt/ecc-certs/example.com/chain.pem --cert-file /etc/letsencrypt/ecc-certs/example.com/cert.pem --fullchain-file /etc/letsencrypt/ecc-certs/example.com/fullchain.pem --pre-hook "mkdir -p /etc/letsencrypt/ecc-certs/example.com" --post-hook "find /etc/letsencrypt/ecc-certs/example.com/ -name '*.pem' -type f -exec chmod 640 {} \; -exec chown root:prosody {} \; -exec service nginx reload \; -exec service prosody reload \;" --renew-hook "find /etc/letsencrypt/ecc-certs/example.com/ -name '*.pem' -type f -exec chmod 640 {} \; -exec chown root:prosody {} \; -exec service nginx reload \; -exec service prosody reload \;" --dnssleep 60
Installing Extra Modules¶
First, we'll install mercurial:
apt update && apt install mercurial
Change to the /opt
directory and download the modules:
cd /opt
hg clone https://hg.prosody.im/prosody-modules/ prosody-modules
Change the default ownership:
chown -R root:prosody /opt/prosody-modules
chmod g+s /opt/prosody-modules
find /opt/prosody-modules -type d -exec chmod g+s {} \;
Whenever we need to update, witch to the prosody-modules directory and run:
hg pull --update
Configuring Nginx for BOSH¶
Assuming that your XMPP domain is example.com, edit the Nginx server block:
nano /etc/nginx/sites-available/example.com
and add the following location block:
location /http-bind {
proxy_pass http://localhost:5280/http-bind;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_buffering off;
tcp_nodelay on;
}
Setting up http_upload_external¶
Now we'll install an HTTP service for dealing with file attachments. This will be used in conjunction with the http_upload_external Prosody mod which we'll configure later:
Installation and initial configuration¶
apt update && apt install git uwsgi uwsgi-plugin-python3 python3-flask
cd /opt
git clone https://github.com/horazont/xmpp-http-upload
and we'll create an extra uwsgi ini file:
nano /opt/xmpp-http-upload/xmpp-http-upload.ini
Enter the following:
[uwsgi]
socket = 0.0.0.0:9002
processes = 5
threads = 1
auto-procname = true
procname-prefix-spaced = [xmpp-http-upload]
uid = nginx
gid = nginx
need-plugin = python3
chdir = /opt/xmpp-http-upload/
pythonpath = /opt/xmpp-http-upload/
wsgi-file = /opt/xmpp-http-upload/xhu.py
enable-threads = true
offload-threads = 10
env = XMPP_HTTP_UPLOAD_CONFIG=/opt/xmpp-http-upload/config.py
Now we'll create and edit the config file:
cp /opt/xmpp-http-upload/config.example.py /opt/xmpp-http-upload/config.py
nano /opt/xmpp-http-upload/config.py
Make sure the following are set and enter a secret key:
SECRET_KEY = b'your-secret-key'
DATA_ROOT = "/var/lib/xmpp-http-upload"
ENABLE_CORS = False
NON_ATTACHMENT_MIME_TYPES = [
"image/*",
"video/*",
"audio/*",
"text/plain",
]
Change ownership and permissions of the files:
chown -R nginx:prosody /opt/xmpp-http-upload
find /opt/xmpp-http-upload -type d -exec chmod g+s {} \;
find /opt/xmpp-http-upload -type d -exec chmod 750 {} \;
find /opt/xmpp-http-upload -type f -exec chmod 640 {} \;
Create an upload directory:
mkdir /var/lib/xmpp-http-upload
chown -R nginx:prosody /var/lib/xmpp-http-upload
chmod g+s /var/lib/xmpp-http-upload
Creating the service¶
Create a service file:
nano /etc/systemd/system/uwsgi-http-upload.service
Enter the following:
[Unit]
Description=uWSGI XMPP HTTP Upload
After=syslog.target
[Service]
ExecStart=/usr/bin/uwsgi --ini /opt/xmpp-http-upload/xmpp-http-upload.ini
# Requires systemd version 211 or newer
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
Enable and start the service:
systemctl daemon-reload
systemctl enable uwsgi-http-upload.service
systemctl start uwsgi-http-upload.service
Configuring Nginx¶
We'll create a server block now for nginx:
nano /etc/nginx/sites-available/upload.example.com
and enter the following:
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name upload.example.com;
ssl_certificate /etc/letsencrypt/rsa-certs/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/rsa-certs/example.com/privkey.pem;
ssl_certificate /etc/letsencrypt/ecc-certs/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/ecc-certs/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/ecc-certs/example.com/chain.pem;
include /etc/nginx/custom-config/ssl.conf;
include /etc/nginx/custom-config/header.conf;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
include /etc/nginx/bots.d/blockbots.conf;
include /etc/nginx/bots.d/ddos.conf;
if ($allowed_country = no) {
return 403;
}
if ( $bad_querystring !~* "[OK]" ) {
set $blockreason $bad_querystring;
return 403;
}
if ( $bad_request !~* "[OK]" ) {
set $blockreason $bad_request;
return 403;
}
if ( $bad_request_method !~* "[OK]" ) {
set $blockreason $bad_request_method;
return 403;
}
location /upload/ {
rewrite ^/upload/(.*) /$1 break;
include uwsgi_params;
uwsgi_pass uwsgi://localhost:9002;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 50M;
modsecurity_rules '
SecRuleRemoveById 911100
SecRuleRemoveById 920420
';
}
}
Enable the site and reload Nginx:
ln -s /etc/nginx/sites-available/upload.example.com /etc/nginx/sites-enabled/
service nginx reload
Creating an apparmor profile¶
Now we'll create an apparmor profile for uwsgi:
nano /etc/apparmor.d/usr.bin.uwsgi-core
#include <tunables/global>
/usr/bin/uwsgi-core {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/python>
capability setgid,
capability setuid,
/etc/mime.types r,
/opt/xmpp-http-upload/ r,
/opt/xmpp-http-upload/config.py r,
/opt/xmpp-http-upload/xhu.py r,
/proc/*/fd/ r,
/proc/sys/net/core/somaxconn r,
/run/systemd/notify w,
/usr/bin/uname mrix,
/usr/bin/uwsgi-core mr,
owner /opt/xmpp-http-upload/xmpp-http-upload.ini r,
owner /proc/sys/kernel/random/boot_id r,
owner /var/lib/xmpp-http-upload/** rw,
}
Reload apparmor:
service apparmor reload
Check the profile is enforced with aa-status
Finally, we'll create a cron job to purge uploaded files older than 6 months:
crontab -e
Add the following line:
@daily find /var/lib/xmpp-http-upload/ -mindepth 1 -type d -mtime +180 -print0 | xargs -0 -- rm -rf
The Prosody Config File¶
Config file:
-- Prosody XMPP Server Configuration
-- Server settings
admins = {"user@example.com" }
allow_registration = false
c2s_require_encryption = true
s2s_require_encryption = true
s2s_secure_auth = true
pidfile = "/run/prosody/prosody.pid"
daemonize = false
authentication = "internal_hashed"
storage = "internal"
-- SSL
https_ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
checkcerts_notify = 7
certificates = "certs"
hsts_header = "max-age=31556952"
-- BOSH configuration
consider_bosh_secure = true;
cross_domain_bosh = true;
-- Plugin Paths
plugin_paths = { "/opt/prosody-modules" }
-- Modules Enabled
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended ;)
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"dialback"; -- s2s dialback support
"disco"; -- Service discovery
-- Custom
"carbons"; -- Keep multiple clients in sync
"carbons_copies"; -- carbons for legacy clients
"pep"; -- Enables users to publish their mood, activity, playing music and more
"private"; -- Private XML storage (for room bookmarks, etc.)
"blocklist"; -- Allow users to block communications with other users
"vcard4"; -- Allow users to set vCards
"default_bookmarks"; -- Default bookmarks for all users
"privacy_lists";
"bookmarks"; -- Bookmarks for xmpp channels
"http_avatar"; -- serves avatars from local users
"strict_https"; -- force https on web
"offline"; -- Store offline messages
"idlecompat"; -- This module adds XEP-0319 idle tags to presence stanzas
"bidi"; -- This module implements XEP-0288: Bidirectional Server-to-Server Connections
"addressing"; -- This module is a partial implementation of XEP-0033: Extended Stanza Addressing
"webpresence"; -- Allows you to publish your Jabber status to your blog or website
"watchuntrusted"; -- notify on unencrypted s2s
"version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server
"ping"; -- Replies to XMPP pings with pongs
"mam"; -- Store messages in an archive and allow users to access it
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
"websocket"; -- XMPP over WebSockets
"server_contact_info"; -- Publish contact information for this service
"announce"; -- Send announcement to all online users
"watchregistrations"; -- Alert admins of registrations
"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
"smacks";
"net_multiplex";
"presence_cache"; -- stores a timestamp of the latest presence received from users contacts
"idlecompat";
"uptime_presence";
"checkcerts"; -- Inform admins before certificates expire
"csi";
"csi_battery_saver"; -- Use less battery on mobile phones
"cloud_notify";
"server_contact_info";
"vcard_legacy";
"log_auth";
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
}
modules_disabled = {
"http_upload";
}
-- Disco settings
disco_items = {
{ "example.com", "Example.com XMPP Server" };
{ "conference.example.com", "Example.com Chatrooms" };
{ "proxy.example.com", "Example.com SOCKS5 service" };
{ "pubsub.example.com", "Example.com Publish/Subscribe service" };
{ "upload.example.com", "Example.com File Uploads" };
{ "vjud.example.com", "Example.com User Directory" };
}
-- Contact settings
contact_info = {
abuse = { "mailto:admin@example.com", "xmpp:user@example.com" };
admin = { "mailto:admin@example.com", "xmpp:user@example.com" };
security = { "mailto:admin@example.com", "xmpp:user@example.com" };
support = { "mailto:admin@example.com", "xmpp:user@example.com" };
feedback = { "mailto:admin@example.com", "xmpp:user@example.com" };
}
-- Archive settings
archive_expires_after = "2m"; -- Remove archived messages after 2 months
max_archive_query_results = 30;
default_archive_policy = "roster"; -- archive only messages from users who are in your roster
mam_smart_enable = true
-- Log settings
log = {
info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging
error = "/var/log/prosody/prosody.err";
}
-- Watchers and notifications
registration_watchers = { "user@example.com" } -- mod_watchregistrations will use this list of users instead of the admin list
registration_notification = "$username registered on $host"
untrusted_fail_watchers = { "user@example.com" }
untrusted_fail_notification = "Establishing a secure connection from $from_host to $to_host failed. Certificate hash: $sha1. $errors"
-- Default bookmarks for new users
default_bookmarks = {
{ jid = "support@conference.example.com", name = "Support Room" };
}
-- Cloud_notify settings
push_notification_with_body = false -- Whether or not to send the message body to remote pubsub node
push_notification_with_sender = true -- Whether or not to send the message sender to remote pubsub node
push_max_errors = 16 -- persistent push errors are tolerated before notifications for the identifier in question are disabled
push_max_devices = 5 -- number of allowed devices per user
-- Proxy settings
proxy65_ports = { 5050 }
-- Virtualhosts
VirtualHost "example.com"
name = "Example.com XMPP Server"
enabled = true
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
Component "conference.example.com" "muc"
name = "Example.com chatrooms";
restrict_room_creation = "local";
muc_room_default_members_only = true;
muc_room_default_public_jids = true;
muc_log_by_default = true;
muc_log_true_rooms = false;
muc_log_all_rooms = false;
max_history_messages = 30;
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
modules_enabled = {
"muc_mam"; -- message archive in muc
"muc_mam_hints";
"muc_limits";
"vcard_muc"; -- This module adds the ability to set vCard for MUC rooms.
}
Component "proxy.example.com" "proxy65"
proxy65_acl = { "example.com" }
proxy65_address = "proxy.example.com"
name = "SOCKS5 Bytestreams Service"
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
Component "pubsub.example.com" "pubsub"
pubsub_max_items = 10000
modules_enabled = { "pubsub_feeds", "pubsub_text_interface" }
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
Component "vjud.example.com" "vjud"
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
Component "upload.example.com" "http_upload_external"
http_upload_external_base_url = "https://upload.example.com/upload/"
http_upload_external_secret = "ExtraSpamFightingTips"
http_upload_external_file_size_limit = 50000000 -- 50 MB
ssl = {
key = "/etc/letsencrypt/rsa-certs/example.com/privkey.pem";
certificate = "/etc/letsencrypt/rsa-certs/example.com/fullchain.pem";
}
Restart Prosody after config changes:
service prosody restart
Creating an Apparmor profile for Prosody:¶
Create the profile:
nano /etc/apparmor.d/usr.bin.prosody
Enter the following:
#include <tunables/global>
/usr/bin/prosody flags=(complain) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
#include <abstractions/ssl_keys>
capability dac_override,
capability dac_read_search,
/etc/prosody/** r,
/opt/prosody-modules/** r,
/usr/bin/lua5.2 mrix,
/usr/bin/prosody r,
/usr/lib/prosody/** m,
/usr/share/lua/** r,
/var/lib/prosody/** rw,
/var/log/prosody/* rw,
/var/lib/xmpp-http-upload/** r,
@{run}/prosody/* rwk,
owner @{run}/prosody/prosody.pid rwk,
}
Use aa-logprof
to monitor and once happy, enforce with aa-enforce
Useful Links
https://github.com/ThomasLeister/prosody-filer https://community.jitsi.org/t/error-on-prosody-without-any-reasons-no-key-present-in-ssl-tls-configuration-for-https-port-5281/17124/24 https://prosody.im/doc/setting_up_bosh https://community.hetzner.com/tutorials/prosody-debian9#optional-advanced-features https://groups.google.com/forum/#!topic/prosody-users/lPjWXYbYfeI https://blog.wirelessmoves.com/2019/05/configuring-prosody-for-ios-and-chatsecure-push.html https://github.com/ThomasLeister/prosody-config/blob/master/prosody.cfg.lua