Jitsi on AWS (with Terraform)

Posted on Apr 19, 2020

Jitsi Logo

Even before COVID-19, most of us were in need for some kind of conference solution. Some use proprietary ones like Google Hangouts, Zoom or one of the other popular ones out there.

But if you are in tightly-regulated industry or just want to have full control over your data and like to use open-source, then chances are high you already stumbled upon Jitsi Meet.

In this post I describe the process to setup a fully-functional Jitsi-Meet instance on AWS (+ Terraform code!)

Features

  • Jitsi Meet (Ubuntu 18.04)
  • Terraform code available (>= 0.12 / HCL2)
  • Authentication (Users need to be authenticated to create new conferences) + Guest access (can only join existing conferences)
  • LetsEncrypt certificate for HTTPS
  • Collaborative working on a shared document during Jitsi conference (etherpad-lite)
  • SQL Database for Jitsi authorized accounts
  • Aurora Serverless (MySQL)
  • Scale down to 0 to reduce costs
  • AutoScalingGroup
  • ASG notifications (+ SNS Topic)
  • CloudWatch Logs (+ CloudWatch Agent)
  • Route53 Public & Private records
  • OPTIONAL: Cross-Account for Public & Private records
  • Allow SSH by workstation IPv4 (can be disabled)
  • Add other allowed IPv4 CIDRs for SSH
  • Restrict Jitsi access CIDRs (Default: not restricted)

Infrastructure as Code

Terraform: GitHub Logogithub.com/hajowieland/terraform-aws-jitsi

Quickstart (Terraform)

  1. Clone the git repository git clone https://github.com/hajowieland/terraform-aws-jitsi.git
  2. Create a terraform.tfvars and fill in the below variables as key = value one per line
  3. If your Route53 Public & Private Hosted Zones are in the same AWS Account as where you want to deploy Jitsi, it’s just this:
TF VariableDescriptionTypeDefaultExample
aws_regionAWS Regionstringeu-central-1 (Frankfurt)eu-west-1 (Ireland)
domainJitsi Domainstringexample.comnapo.io
letsencrypt_emailLetsEncrypt E-Mailstringmail@example.commyemail@domain.com
public_subnet_idsPublic Subnet IDslist(string)"subnet-id-1", "subnet-id-2", "subnet-id-3"]["subnet-9ab8765", "subnet-1ab2c345", "subnet-01234567890ab01cd"]
public_zone_idPublic Zone IDstringZ0123publiczoneZC1BDEFGH2I3J
private_zone_idPrivate Zone IDstringZ456privatezoneZ01234567A89BC0D123E4
vpc_idVPC IDstringvpc-123vpc-1a2b3456

Terraform module

Of course you can use it as Terraform module, too:

module "jitsi" {
  source  = "hajowieland/jitsi/aws"
  version = "1.0.0"

  aws_region = "eu-central-1"

  name   = "jitsi-meet"
  host   = "meet"
  domain = "example.com" # should match public and private hosted zone
  # will result in FQDN => meet.example.com

  ec2_instance_type = "t3a.large"
  vpc_id            = "vpc-123"
  public_subnet_ids = ["subnet-id-1", "subnet-id-2", "subnet-id-3"]

  # If the Route53 zones are in a different AWS Account:
  enable_cross_account = "1"
  arn_role             = "arn:aws:iam::other-account-id:role/route53-jitsi-other-account"

  public_zone_id  = "Z0123publiczone"
  private_zone_id = "Z456privatezone

  letsencrypt_email = "mail@example.com"

  # If you want to allow other SSH IPv4 CIDRs (in addition to your workstation's IPV4 address):
  ssh_cidrs = {
    "127.0.0.1/32"  = "first-ip-to-allow",
    "127.0.0.2/32"  = "second-ip-to-allow"
  }
}

Manual setup of simplified Jitsi-Meet on AWS (not recommended)

Here I show you the process of manually setting up Jitsi-Meet on AWS on a single EC2 instance. For a production-ready solution, please use the Terraform code above !)

It shows how the important steps of the EC2 Userdata in the Terraform code work.

Prerequisites

  1. EC2 Instance with Ubuntu 18.04
  2. You can connect to the instance with SSH
  3. MySQL / PostgreSQL database

Raise system limits

Jitsi needs increased files and process limits, so we set them in systemd:

echo "DefaultLimitNOFILE=65000" >> /etc/systemd/system.conf
echo "DefaultLimitNPROC=65000" >> /etc/systemd/system.conf
echo "DefaultTasksMax=65000" >> /etc/systemd/system.conf
systemctl daemon-reload

Update System, install Jitsi

echo 'deb https://download.jitsi.org stable/' >> /etc/apt/sources.list.d/jitsi-stable.list
wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | apt-key add -
apt-get update
apt-get upgrade -y -q

# MySQL:
apt-get install -y -q jitsi-meet lua-dbi-mysql

# PostgreSQL
apt-get install -y -q jitsi-meet lua-dbi-postgresql

During install you get ask these two questions:

  • Hostname: Set to the desired FQDN for your Jitsi instance (e.g.: meet.example.com)
  • Certificate: Choose Generate a new self-signed certificate (we will get a LetsEncrypt certificate later)

Now export the Hostname as environment variable which we will use in the next steps (replace with the FQDN you configured during install):

export HOSTNAME=meet.example.com

Etherpad

Now we are going to install etherpad-lite, which is integrated in Jitsi-Meet and allows us to collaboratively work together on a shared document during conferences.

Install etherpad-lite

Download node, clone etherpad-lite git repository and add a etherpad-lite system user:

curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
apt install -y nodejs
cd /opt/ || exit
adduser --system --home /opt/etherpad --group etherpad-lite
cd /opt/etherpad || exit
git clone --branch master https://github.com/ether/etherpad-lite.git
chown -R etherpad-lite:etherpad-lite /opt/etherpad/etherpad-lite

etherpad-lite systemd

To enable etherpad-lite at startup and be controlled by systemd, create a systemd unit file and enable the service:

cat << 'EOF' > /etc/systemd/system/etherpad-lite.service
[Unit]
Description=Etherpad-lite, the collaborative editor.
After=syslog.target network.target

[Service]
Type=simple
User=etherpad-lite
Group=etherpad-lite
WorkingDirectory=/opt/etherpad/etherpad-lite
Environment=NODE_ENV=production
ExecStart=/bin/sh /opt/etherpad/etherpad-lite/bin/run.sh
Restart=always

[Install]
WantedBy=multi-user.target
EOF


systemctl daemon-reload
systemctl enable etherpad-lite
systemctl start etherpad-lite

You can check if everything is working with:

systemctl status ethterpad-lite
journalctl -u ethterpad-lite -f

Enable ethterpad in Meet

The Meet component needs to know that it can now use Ethterpad:

sed -i "/makeJsonParserHappy.*/i\    etherpad_base: 'https://$HOSTNAME/etherpad/p/'", /etc/jitsi/meet/$HOSTNAME-config.js

Prosody SQL

To allow users to be created and stored in an SQL database, configure Prosody (the XMPP component of Jitsi) to use the database instead of local filestore:

sed -i "s/--storage = \"sql\".*/storage = \"sql\"/g" /etc/prosody/prosody.cfg.lua

Now fill in the MySQL / PostgreSQL credentials of your database (database name, user, password and host)

For MySQL:

sed -i "s/--sql = { driver = \"MySQL\".*/sql = { driver = \"MySQL\", database = \"${db_name}\", username = \"${db_user}\", password = \"${db_password}\", host = \"${db_host}\" }/g" /etc/prosody/prosody.cfg.lua

For PostgreSQL:

sed -i "s/--sql = { driver = \"PostgreSQL\".*/sql = { driver = \"PostgreSQL\", database = \"${db_name}\", username = \"${db_user}\", password = \"${db_password}\", host = \"${db_host}\" }/g" /etc/prosody/prosody.cfg.lua

Prosody initially used to local filestore for the focus and jvb users, we need them to be converted to our SQL backend.

We can use Prosody Migrator for this task. First we createa Migrator config (fill in your Database data like in the previous step):

For MySQL:

cat <<EOT >> /tmp/migrator.cfg.lua
filestore {
         type = "prosody_files";
         path = "/var/lib/prosody";
}
database {
         type = "prosody_sql";
         driver = "MySQL";
         database = "${db_name}";
         username = "${db_user}";
         password = "${db_password}";
         host = "${db_host}";
}
EOT

For PostgreSQL:

cat <<EOT >> /tmp/migrator.cfg.lua
filestore {
         type = "prosody_files";
         path = "/var/lib/prosody";
}
database {
         type = "prosody_sql";
         driver = "PostgreSQL";
         database = "${db_name}";
         username = "${db_user}";
         password = "${db_password}";
         host = "${db_host}";
}
EOT

And then we migrate the local filestore to the SQL database:

prosody-migrator filestore database --config=/tmp/migrator.cfg.lua

Configure Authentication

By default, no authentication is set which means every (unauthenticated!) user can create new conferences (and if you have a public Jitsi instance, this means everyone).

This exposes Jitsi to various spam/flood attacks and the Jitsi instance may be used for unintended purposes.

So we definitely need to set up authentication. In the previous step we configured the database backend for the internal Jitsi users and our authenticated users we can add later.

In Jitsi Meet and Prosody we set up a guest domain:

sed -i "s|// anonymousdomain:.*|anonymousdomain: 'guest.$HOSTNAME',|g" /etc/jitsi/meet/$HOSTNAME-config.js

cat <<EOT >> /etc/prosody/conf.avail/$HOSTNAME.cfg.lua
VirtualHost "guest.$HOSTNAME"
    authentication = "anonymous"
    c2s_require_encryption = false
EOT

In Jicofo (the focus component), we configure the XMPP auth url so it connects to XMPP (= Prosody) for authentication:

echo "org.jitsi.jicofo.auth.URL=XMPP:$HOSTNAME" >> /etc/jitsi/jicofo/sip-communicator.properties

nginx configuration

Finally we configure nginx for etherpad-lite and for a seperate interface configuration.

The latter allows us for example to modify the visible Toolbar Buttons (TOOLBAR_BUTTONS) in Jitsi.

We use a seperate config file for this so it does not get overwritten by a Jitsi update.

# interface config
cp /usr/share/jitsi-meet/interface_config.js /etc/jitsi/meet/$HOSTNAME-interface_config.js
sed -i "s|^}|\    location = /interface_config.js {\n        alias /etc/jitsi/meet/$HOSTNAME-interface_config.js;\n    }\n}|g" /etc/nginx/sites-enabled/$HOSTNAME.conf

# ethterpad-lite
sed -i "s|^}|\    location ^~ /etherpad/ {\n        proxy_pass http://localhost:9001/;\n        proxy_set_header X-Forwarded-For \$remote_addr;\n        proxy_buffering off;\n        proxy_set_header       Host \$host;\n    }\n}|g" /etc/nginx/sites-enabled/$HOSTNAME.conf

LetsEncrypt

As a last step we request a LetsEncrypt certificate. Luckily, Jitsi already provides us with a handy script:

/usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh

(You get asked for an email adress).

All necessary configuration and auto-renewal (cronjob) will be configured by the script 👍

Restart services

Now restart all services and then you are ready to use your shiny new Jitsi-Meet instance 👏

systemctl restart nginx
systemctl restart prosody
systemctl restart jicofo
systemctl restart jitsi-videobridge2

Add authenticated users

If you visit Jitsi-Meet with your browser and create a new conference, you have to authenticate as host:

Jitsi Meet - Hosted Authentication Screenshot

Login with prosody credentials

New users can be created in Prosody via prosodyctl:

# Create a new user test
prosodyctl adduser test@$HOSTNAME
# Asks for password

Now try right away to log in with this new user and voilâ –> Now you can invite guests to your conference (they do not have to authenticate - but can only conferences with are created by authenticated users).

To collaborate together on a shared document:

Jitsi with etherpad-lite

Shared document by etherpad-lite

Final Words

If you encounter any problems or have some ideas on how to enhance the IaC code ➡️ please let me know!

I would be very happy to see some Pull Requests on GitHub for the Terraform code of this blog post: