avasdream@home:~#

Infrastructure as Code: Deploy Jitsi Meet to AWS

Hello world! Since the global COVID-19 pandemic started a lot of businesses are now relying on video conference software like Zoom.In my humble opinion open source alternatives like Jitsi Meet are not getting enough attention. So today we are going to set up a Jitsi Meet Server on AWS. If you’ve got no clue why to use Terraform, read my short introduction or the official documentation.

Prerequisites

Before we start with the setup we need to organize a few things.

  1. Install Terraform on your local machine. Documentation
  2. Set up a fully-qualified domain name for your server instance. Without a domain we can’t register a TLS certificate via Let’s encrypt. I registered my domain with name.com. If you are currently enrolled as a student you can get a free domain for one year with the Github student developer pack.
  3. Create AWS access and secret keys for the Terraform provider to authenticate and authorize. Documentation
  4. Create and download SSH keys to connect to your instance. Documentation
  5. Allocate one Elastic IP for your Jitsi Meet instance. When you start a EC2 instance you get a dynamic IPv4 address, but since we need a domain for the SSL/TLS encryption, a static IPv4 is required. Documentation
  6. Change your DNS settings so that your domain name is pointing to your Elastic IP. Be aware that the propagation of DNS entries can take up to 48 Hours. Here you can verify that the entry is correct.

Please note the price model for elastic IPs. When the IP is associated with a running instance, you do not pay for it. If the IP is allocated and not connected to an instance, you have to pay 0.005 USD per hour. For more details see the EC2 pricing page.

Setup

  1. Clone the Github repository. AvasDream/terraform_aws_jitsi_meet
  2. Copy the template variables.tf file to the root of the repository.

    variable "aws_access_key" {
      description = "Access key from AWS"
      default = "D0NTPV5HCR3DST0G1THVB"
    }
    variable "aws_secret_key" {
      description = "Secret key from AWS"
      default = "D0NTPV5HCR3DST0G1THVB"
    }
    variable "aws_region" {
      description = "Region where the instance should be located"
      default = "eu-central-1"
    }
    variable "ssh_key_path" {
      description = "Path to the AWS SSH key"
      default = "C:/Users/...."
    }
    variable "instance_type" {
      description = "Instance type to launch"
      default = "t2.large"
    }
    variable "ssh_key_name" {
      description = "Name of the SSH key"
      default = "terraform-key"
    }
    variable "ip_whitelist" {
      description = "All allowed ingress IPs"
      default = ["1.3.3.7/32"]
    }
    variable "eip" {
      description = "Elastic IP associated with the instance"
      default     = "1.3.3.7"
    }
    variable "email_address" {
      description = "Email to use for the certificate generation"
      default     = "user@domain.de"
    }
    variable "domain_name" {
      description = "Domain of the Jitsi Server"
      default     = "jitsi.example.com"
    }
    
  3. Fill in your data in the variables.tf file.
  4. Execute terraform init to initialize the project and download all providers.
  5. Execute terraform apply to apply the changes to AWS.

The setup can take some time depending on your EC2 instance type. You can now connect to your instance with SSH and check the /debug.txt file. When the setup is finished, there should be a success message from the Let’s encrypt init script. One thing to be aware of is the rate limit on certificates from Let’s encrypt (50 certifiactes per week for one domain).

Terraform Configurations

Next I will try to explain what every configuration file is responsible for.

resource "aws_eip_association" "eip_assoc" {
  instance_id   = "${aws_instance.jitsi-meet-server.id}"
  allocation_id = "${data.aws_eip.jitsi-eip.id}"
}

associate_eip.tf: The Elastic IP is associated with the created EC2 instance. Documentation

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }
  #/ubuntu-disco-19.04-amd64-server-
  #/ubuntu-bionic-18.04-amd64-server-
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

get_ami.tf: The Amazon machine image created by Cannonical is searched from the AWS Marketplace and filtered by name.

data "aws_eip" "jitsi-eip" {
  public_ip = "${var.eip}"
}

get_eip.tf: The allocated Elastic IP is retrieved from the AWS account by IPv4 address.

data "http" "external_ip" {
  url = "http://ipv4.icanhazip.com/"
}

get_external_ip.tf: The WAN IP of your local machine is retrieved from the icanhazip API. This is used in the security policy, so only your address has SSH access.

#!/bin/bash
set -e
export HOSTNAME="${domain_name}"
export EMAIL="${email_address}"

echo -e "nameserver 8.8.8.8\nnameserver 8.8.4.4" >> /etc/resolv.conf
# disable ipv6
sysctl -w net.ipv6.conf.all.disable_ipv6=1
sysctl -w net.ipv6.conf.default.disable_ipv6=1
# set hostname
hostnamectl set-hostname $HOSTNAME
echo -e "127.0.0.1 localhost $HOSTNAME" >> /etc/hosts
apt update
# install Java
apt install -y openjdk-8-jre-headless
echo "JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:bin/java::")" | sudo tee -a /etc/profile
source /etc/profile
# install NGINX
apt install -y nginx
systemctl start nginx.service
systemctl enable nginx.service
# add Jitsi to sources
wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | sudo apt-key add -
sh -c "echo 'deb https://download.jitsi.org stable/' > /etc/apt/sources.list.d/jitsi-stable.list"
apt update
echo -e "DefaultLimitNOFILE=65000\nDefaultLimitNPROC=65000\nDefaultTasksMax=65000" >> /etc/systemd/system.conf
systemctl daemon-reload
# Configure Jits install
debconf-set-selections <<< $(echo 'jitsi-videobridge jitsi-videobridge/jvb-hostname string '$HOSTNAME)
debconf-set-selections <<< 'jitsi-meet-web-config   jitsi-meet/cert-choice  select  "Generate a new self-signed certificate"';

# Debug
echo $EMAIL >> /debug.txt
echo $HOSTNAME >> /debug.txt
cat /etc/resolv.conf >> /debug.txt
whoami >> /debug.txt
cat /etc/hosts >> /debug.txt
# Install Jitsi
apt install -y jitsi-meet &>> /debug.txt
# letsencrypt
echo $EMAIL | /usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh &>> /debug.txt

install_jitsi.tpl: This template file is used by cloud-init and executed while booting the instance. String interpolation evaluates all variables (${var.name}) and inserts the defined value. After the interpolation this file is a valid bash script. Most of the used commands are defined in the Jitsi Meet install guide.

data "template_file" "install_script" {
  template = "${file("install_jitsi.tpl")}"
  vars = {
    email_address = "${var.email_address}"
    domain_name   = "${var.domain_name}"
  }
}
resource "aws_instance" "jitsi-meet-server" {
  ami                    = "${data.aws_ami.ubuntu.id}"
  instance_type          = "${var.instance_type}"
  vpc_security_group_ids = ["${aws_security_group.allow_connections_jitsi-meet.id}"]
  key_name               = "${var.ssh_key_name}"
  user_data              = "${data.template_file.install_script.rendered}"
  tags = {
    Name = "jitsi-meet-server"
  }
}

create_instance.tf: Here the main instance is configured and the template file above is inserted as user_data. In the vars section of the template_file data source, the defined variables email_address and domain_name from variables.tf are inserted/interpolated in the bash script.

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region     = "${var.aws_region}"
  version    = "~> 2.7"
}

aws_provider.tf: The AWS provider version, region and authentication keys are defined here.

resource "aws_security_group" "allow_connections_jitsi-meet" {
  name        = "allow_connections_jitsi-meet"
  description = "Allow TLS inbound traffic for ssh but only for the host PCs external IP."

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = concat(formatlist("%s/32", list(chomp(data.http.external_ip.body))), var.ip_whitelist)
  }
  ingress {
    from_port   = 10000
    to_port     = 10000
    protocol    = "udp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 80
    to_port     = 80
    protocol    = "6"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "6"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 53
    to_port     = 53
    protocol    = "udp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "allow_connections_jitsi-meet"
  }
}

security_groups.tf: In this file the network access ingress and egress for the instance is defined. The following table shows all ports, their protocol and service.

Port Protocol Direction Used by
53 UDP egress DNS
443 TCP egress HTTPS
80 TCP egress HTTP
10000 UDP ingress Jitsi Videobridge
443 TCP ingress HTTPS
80 TCP ingress HTTP
22 TCP ingress SSH

If there are any open questions just ask me on Twitter or check the references.

Stay healthy!

References

Github: Jitsi Meet

Documentation: Terraform

Book: Terraform up and running, First Edition

Electronic frontier foundation: Zoom privacy concerns

CVE Details: Zoom