openvidu/openvidu-server/deployments/external-turn/aws/CF-External-Turn.yml

376 lines
12 KiB
YAML

AWSTemplateFormatVersion: 2010-09-09
Description: External TURN server for OpenVidu Server.
Parameters:
PublicElasticIP:
Description: "Previously created AWS Elastic IP to associate it to the EC2 instance. Example 13.33.145.23."
Type: String
AllowedPattern: ^([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])$
ConstraintDescription: The public Elastic IP does not have a valid IPv4 format and it is mandatory
MyDomainName:
Description: "Valid domain name pointing to previous IP. Example: openvidu.company.com"
Type: String
AllowedPattern: ^$|^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
ConstraintDescription: The domain name does not have a valid domain name format
LetsEncryptEmail:
Description: "This email will be used for Let's Encrypt notifications"
AllowedPattern: ^(.+)@(.+)$
Type: String
TurnStaticAuthSecret:
Description: "Shared secret for the TURN server to generate credentials for clients"
Type: String
NoEcho: true
AllowedPattern: .+$
ConstraintDescription: Turn secret is mandatory.
InstanceType:
Description: "Specifies the EC2 instance type for your TURN instance"
Type: String
Default: c5.xlarge
AllowedValues:
- t2.large
- t2.xlarge
- t2.2xlarge
- t3.large
- t3.xlarge
- t3.2xlarge
- m4.large
- m4.xlarge
- m4.2xlarge
- m4.4xlarge
- m4.10xlarge
- m4.16xlarge
- m5.large
- m5.xlarge
- m5.2xlarge
- m5.4xlarge
- m5.8xlarge
- m5.12xlarge
- m5.16xlarge
- m5.24xlarge
- c4.large
- c4.xlarge
- c4.2xlarge
- c4.4xlarge
- c4.8xlarge
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.12xlarge
- c5.18xlarge
- c5.24xlarge
- c6a.large
- c6a.xlarge
- c6a.2xlarge
- c6a.4xlarge
- c6a.8xlarge
- c6a.12xlarge
- c6a.16xlarge
- c6a.24xlarge
- c6a.32xlarge
- c6a.48xlarge
- c6a.metal
ConstraintDescription: "Must be a valid EC2 instance type"
KeyName:
Description: "Name of an existing EC2 KeyPair to enable SSH access to the instance. It is mandatory to perform some administrative tasks to the TURN server."
Type: 'AWS::EC2::KeyPair::KeyName'
ConstraintDescription: "must be the name of an existing EC2 KeyPair"
Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: Domain and SSL certificate configuration
Parameters:
- PublicElasticIP
- MyDomainName
- LetsEncryptEmail
- Label:
default: Turn server configuration
Parameters:
- TurnStaticAuthSecret
- Label:
default: EC2 Instance configuration
Parameters:
- InstanceType
- KeyName
ParameterLabels:
# SSL certificate configuration
PublicElasticIP:
default: "AWS Elastic IP (EIP)"
MyDomainName:
default: "Domain Name pointing to Elastic IP"
LetsEncryptEmail:
default: "Email for Let's Encrypt (letsencrypt)"
# OpenVidu configuration
TurnStaticAuthSecret:
default: >
Turn shared secret. This is used to generate credentials and should be in OpenVidu Server configuration.
# EC2 Instance configuration
InstanceType:
default: "Instance type"
KeyName:
default: "SSH Key"
# Other configuration
Resources:
DescribeImagesRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: DescribeImages
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action: ec2:DescribeImages
Effect: Allow
Resource: "*"
GetLatestUbuntuAmi:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !Sub ${DescribeImagesRole.Arn}
Timeout: 60
Runtime: python3.11
Code:
ZipFile: |
import boto3
import cfnresponse
import json
import traceback
def handler(event, context):
try:
response = boto3.client('ec2').describe_images(Filters=[
{'Name': 'name', 'Values': [event['ResourceProperties']['Name']]},
{'Name': 'owner-alias', 'Values': ['amazon']}
])
amis = sorted(response['Images'], key=lambda x: x['CreationDate'], reverse=True)
id = amis[0]['ImageId']
cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, id)
except:
traceback.print_last()
cfnresponse.send(event, context, cfnresponse.FAIL, {}, "ok")
UbuntuAmi:
Type: Custom::FindAMI
Properties:
ServiceToken: !Sub ${GetLatestUbuntuAmi.Arn}
Name: "*ubuntu/images/hvm-ssd/ubuntu-noble-24.04-amd64-server-*"
TurnServerInstance:
Type: 'AWS::EC2::Instance'
Metadata:
Comment: 'Install External TURN server for OpenVidu Server'
AWS::CloudFormation::Init:
config:
files:
'/usr/local/bin/install_docker.sh':
content: |
#!/bin/bash
# Check if docker is already installed
if ! command -v docker &> /dev/null; then
# Install docker
apt-get -y update
apt-get -y install \
ca-certificates \
curl \
gnupg
mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get -y update
apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
fi
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/coturn.sh':
content: !Sub |
#!/bin/bash -x
WORKIND_DIR=/opt/coturn
cd /opt
# Check if directory /opt/coturn exists
# If it does not exist, it means it is the first time we run this script
# and we need to install coturn and fill the .env file
if [ ! -d "$WORKIND_DIR" ]; then
# This means it is the first time we run this script
curl https://s3.eu-west-1.amazonaws.com/aws.openvidu.io/external-turn/4.6.2/install_openvidu_external_coturn.sh | bash
cd "$WORKIND_DIR"
# Replace environment variables
sed -i "s|TURN_DOMAIN_NAME=.*|TURN_DOMAIN_NAME=${MyDomainName}|" .env
sed -i "s|LETSENCRYPT_EMAIL=.*|LETSENCRYPT_EMAIL=${LetsEncryptEmail}|" .env
sed -i "s|TURN_STATIC_AUTH_SECRET=.*|TURN_STATIC_AUTH_SECRET=${TurnStaticAuthSecret}|" .env
fi
cd "$WORKIND_DIR"
docker compose down
docker compose up -d
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/wait_for_coturn.sh':
content: !Sub |
#!/bin/bash -x
# Configuration
SERVER="${MyDomainName}"
PORT="443"
TIMEOUT=600 # 10 minutes in seconds
INTERVAL=10 # Time interval between attempts in seconds
TARGET_EXIT_CODE=0
start_time=$(date +%s)
while true; do
current_time=$(date +%s)
elapsed_time=$((current_time - start_time))
if [ $elapsed_time -ge $TIMEOUT ]; then
echo "Time limit reached: $TIMEOUT seconds"
exit 1
fi
# Run the command and get the exit code
docker exec coturn turnutils_stunclient -p $PORT $SERVER
exit_code=$?
if [ $exit_code -eq $TARGET_EXIT_CODE ]; then
echo "Coturn is ready"
exit 0
else
echo "Coturn is not ready yet. Waiting $INTERVAL seconds before the next attempt..."
sleep $INTERVAL
fi
done
mode: "000755"
owner: "root"
group: "root"
Properties:
ImageId: !Ref UbuntuAmi
InstanceType: !Ref InstanceType
SecurityGroups:
- !Ref WebServerSecurityGroup
KeyName: !Ref KeyName
Tags:
- Key: Name
Value: !Ref 'AWS::StackName'
UserData:
Fn::Base64: !Sub |
#!/bin/bash -x
set -eu -o pipefail
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
pip3 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource TurnServerInstance
/usr/local/bin/install_docker.sh
export HOME="/root"
# Check if crontab has already been configured
if ! crontab -l | grep -q "coturn.sh"; then
# Configure crontab
echo "@reboot /usr/local/bin/coturn.sh" | crontab
fi
# Run coturn
/usr/local/bin/coturn.sh
# Wait for coturn to be ready
/usr/local/bin/wait_for_coturn.sh
# sending the finish call
/usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: gp3
DeleteOnTermination: true
VolumeSize: 25
MyEIP:
Type: 'AWS::EC2::EIPAssociation'
Properties:
InstanceId: !Ref TurnServerInstance
EIP: !Ref PublicElasticIP
WaitCondition:
Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy:
ResourceSignal:
Timeout: PT30M
Count: '1'
WebServerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: SSH, Proxy and Turn necessaty ports
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 443
ToPort: 443
CidrIpv6: ::/0
Outputs:
TurnServerURI:
Description: Use this URL to connect the TURN Server
Value: !Sub 'turns://${MyDomainName}:443?transport=tcp'