openvidu/openvidu-server/deployments/pro/aws/CF-OpenVidu-Pro.yaml.template

1159 lines
43 KiB
Plaintext

---
AWSTemplateFormatVersion: 2010-09-09
Description: OpenVidu Pro CloudFormation template
Parameters:
# Domain and SSL certificate configuration
WhichCert:
Description: >
[selfsigned] Self signed certificate. Not recommended for production use.
[owncert] Valid certificate purchased in a Internet services company.
[letsencrypt] Generate a new certificate using Let's Encrypt.
Type: String
AllowedValues:
- selfsigned
- owncert
- letsencrypt
Default: selfsigned
PublicElasticIP:
Description: "Previously created AWS Elastic IP to associate it to the OpenVidu EC2 instance. If certificate type is 'selfsigned' this value is optional. If certificate type is 'owncert' or 'letsencrypt' this value is mandatory. 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
MyDomainName:
Description: "Valid domain name pointing to previous IP. If certificate type is 'selfsigned' this value is optional. If certificate type is 'owncert' or 'letsencrypt' this value is mandatory. 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
OwnCertCRT:
Description: "If certificate type is 'owncert' this is the URL where CRT file will be downloaded"
Type: String
OwnCertKEY:
Description: "If certificate type is 'owncert' this is the URL where KEY file will be downloaded"
Type: String
LetsEncryptEmail:
Description: "If certificate type is 'letsencrypt', this email will be used for Let's Encrypt notifications"
Type: String
Recording:
Description: |
If 'disabled', recordings will not be active.
If 'local' recordings will be saved in EC2 instance locally.
If 's3', recordings will be stored in a S3 bucket"
Type: String
AllowedValues:
- disabled
- local
- s3
Default: local
S3RecordingsBucketName:
Description: "S3 Bucket Name"
Type: String
# OpenVidu Configuration
OpenViduLicense:
Description: "Visit https://openvidu.io/account"
Type: String
AllowedPattern: ^(?!\s*$).+$
NoEcho: true
ConstraintDescription: OpenVidu Pro License is mandatory
OpenViduEdition:
Description: "Visit https://docs.openvidu.io/en/stable/deployment/#openvidu-editions"
Type: String
AllowedValues:
- pro
- enterprise
Default: pro
OpenViduSecret:
Description: "Secret to connect to this OpenVidu Platform. Cannot be empty and must contain only alphanumeric characters [a-zA-Z0-9], hypens ('-') and underscores ('_')"
Type: String
AllowedPattern: ^[a-zA-Z0-9_-]+$
NoEcho: true
ConstraintDescription: "Cannot be empty and must contain only alphanumeric characters [a-zA-Z0-9], hypens ('-') and underscores ('_')"
MediaNodesStartNumber:
Description: "How many Media Nodes do you want on startup (EC2 instances will be launched)"
Type: Number
Default: 1
# Enable Elasticsearch and Kibana
ElasticsearchEnabled:
Description: "Choose if you want OpenVidu to use Elasticsearch."
Type: String
AllowedValues:
- true
- false
Default: true
# Elasticsearch configuration
ElasticsearchUser:
Description: "Username for Elasticsearch and Kibana. ('ElasticSearch Enabled' must be true)"
Type: String
AllowedPattern: ^$|^[^" ]+$
ConstraintDescription: Elasticsearch user is mandatory (no whitespaces or quotations allowed)
Default: elasticadmin
ElasticsearchPassword:
Description: "Password for Elasticsearch and Kibana ('ElasticSearch Enabled' must be true)"
Type: String
AllowedPattern: ^$|^[^" ]+$
NoEcho: true
ConstraintDescription: Elasticsearch password is mandatory and it should have at least 6 characters (no whitespaces or quotations allowed)
# Elasticsearch configuration
ElasticsearchUrl:
Description: "If you have an external Elasticsearch service running, put here the url to the service. If empty, an Elasticsearch service will be deployed next to OpenVidu. ('ElasticSearch Enabled' must be true)"
Type: String
AllowedPattern: (^(https?:\/\/)?([^:\/]+)(:([0-9]+))?(\/.*)?$|^$)
ConstraintDescription: "It is very important to specify the Elasticsearch URL with the port used by this service. For example: https://es-example"
KibanaUrl:
Description: "If you have an external Kibana service running, put here the url to the service. If empty, a Kibana service will be deployed next to OpenVidu. ('ElasticSearch Enabled' must be true)"
Type: String
AllowedPattern: (^(https?:\/\/)?([^:\/]+)(:([0-9]+))?(\/.*)?$|^$)
ConstraintDescription: "It is very important to specify the url with port used by this service. For example: https://kibana-example"
# EC2 Instance configuration
AwsInstanceTypeOV:
Description: "Specifies the EC2 instance type for your OpenVidu Server Pro Node"
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"
AwsInstanceTypeKMS:
Description: "Specifies the EC2 instance type for your Media Nodes"
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 of OpenVidu."
Type: 'AWS::EC2::KeyPair::KeyName'
ConstraintDescription: "must be the name of an existing EC2 KeyPair"
# Networking configuration
OpenViduVPC:
Description: "Dedicated VPC for OpenVidu cluster"
Type: AWS::EC2::VPC::Id
AllowedPattern: ^.+$
ConstraintDescription: You must specify a VPC ID
OpenViduSubnet:
Description: "Subnet for OpenVidu cluster"
Type: AWS::EC2::Subnet::Id
AllowedPattern: ^.+$
ConstraintDescription: You must specify a subnet ID
# Other configuration
WantToDeployDemos:
Description: "Choose if you want to deploy OpenVidu Call application alongside OpenVidu platform."
Type: String
AllowedValues:
- true
- false
Default: true
CoturnInMediaNodes:
Description: "If true, Coturn will be deployed on media nodes. Otherwise it will be deployed in master nodes."
Type: String
AllowedValues:
- true
- false
Default: false
#start_mappings
Mappings:
OVAMIMAP:
eu-west-1:
AMI: OV_AMI_ID
KMSAMIMAP:
eu-west-1:
AMI: KMS_AMI_ID
#end_mappings
Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: Domain and SSL certificate configuration
Parameters:
- WhichCert
- PublicElasticIP
- MyDomainName
- OwnCertCRT
- OwnCertKEY
- LetsEncryptEmail
- Label:
default: OpenVidu configuration
Parameters:
- OpenViduLicense
- OpenViduEdition
- OpenViduSecret
- MediaNodesStartNumber
- Label:
default: OpenVidu Recording Configuration
Parameters:
- Recording
- S3RecordingsBucketName
- Label:
default: Elasticsearch and Kibana configuration
Parameters:
- ElasticsearchEnabled
- ElasticsearchUrl
- KibanaUrl
- ElasticsearchUser
- ElasticsearchPassword
- Label:
default: EC2 Instance configuration
Parameters:
- AwsInstanceTypeOV
- AwsInstanceTypeKMS
- KeyName
- Label:
default: Networking configuration
Parameters:
- OpenViduVPC
- OpenViduSubnet
- Label:
default: Other configuration
Parameters:
- WantToDeployDemos
- CoturnInMediaNodes
ParameterLabels:
# SSL certificate configuration
WhichCert:
default: "Certificate Type"
PublicElasticIP:
default: "AWS Elastic IP (EIP)"
MyDomainName:
default: "Domain Name pointing to Elastic IP"
OwnCertCRT:
default: "URL to the CRT file (owncert)"
OwnCertKEY:
default: "URL to the key file (owncert)"
LetsEncryptEmail:
default: "Email for Let's Encrypt (letsencrypt)"
Recording:
default: "OpenVidu Recording"
S3RecordingsBucketName:
default: "S3 Bucket where recordings will be stored"
# OpenVidu configuration
OpenViduLicense:
default: "OpenVidu Pro License key"
OpenViduEdition:
default: "Which OpenVidu Edition you want to deploy"
MediaNodesStartNumber:
default: "Initial number of Media Node in your cluster"
OpenViduSecret:
default: "Openvidu Secret"
# Kibana configuration
ElasticsearchEnabled:
default: "Enable Elasticsearch and Kibana"
ElasticsearchUrl:
default: "Elasticsearch URL"
KibanaUrl:
default: "Kibana URL"
ElasticsearchUser:
default: "Elasticsearch and Kibana username"
ElasticsearchPassword:
default: "Elasticsearch and Kibana password"
# EC2 instance configuration
AwsInstanceTypeOV:
default: "Instance type for Openvidu Server Pro Node"
AwsInstanceTypeKMS:
default: "Instance type for Media Nodes"
KeyName:
default: "SSH Key"
# Networking configuration
OpenViduVPC:
default: "OpenVidu VPC"
OpenViduSubnet:
default: "OpenVidu Subnet"
# Other configuration
WantToDeployDemos:
default: "Deploy OpenVidu Call application"
CoturnInMediaNodes:
default: "Deploy Coturn in Media Nodes. (Experimental)"
Conditions:
WhichCertPresent: !Not [ !Equals [!Ref WhichCert, ''] ]
PublicElasticIPPresent: !Not [ !Equals [!Ref PublicElasticIP, ''] ]
RecordingStorageIsS3: !Equals [ !Ref Recording, 's3' ]
CreateS3Bucket: !And
- !Equals [!Ref Recording, 's3' ]
- !Equals [!Ref S3RecordingsBucketName, '']
Rules:
# Check recording
RecordingValidation:
RuleCondition: !Or [ !Equals [!Ref Recording, 'disabled' ], !Equals [!Ref Recording, 'local' ] ]
Assertions:
- AssertDescription: Parameter 'S3 Bucket where recordings will be stored' (S3RecordingsBucketName) is not needed when 'Recording' is 'disabled' or 'local'.
Assert: !Equals [ !Ref S3RecordingsBucketName, '' ]
# Check when Elasticsearch is enabled that all the parameters are present
ElasticsearchValidation:
RuleCondition: !Equals [ !Ref ElasticsearchEnabled, 'true' ]
Assertions:
- AssertDescription: Paramter 'Elasticsearch and Kibana username' (ElasticsearchUser) is needed when 'Enable Elasticsearch and Kibana' (ElasticsearchEnabled) is 'true'.
Assert: !Not [ !Equals [!Ref ElasticsearchUser, ''] ]
- AssertDescription: Parameter 'Elasticsearch and Kibana password' (ElasticsearchPassword) is needed when 'Enable Elasticsearch and Kibana' (ElasticsearchEnabled) is 'true'.
Assert: !Not [ !Equals [!Ref ElasticsearchPassword, ''] ]
# Check when Elasticsearch is disabled that any parameter of elasticsearch is not present
ElasticsearchDisabledValidation:
RuleCondition: !Equals [ !Ref ElasticsearchEnabled, 'false' ]
Assertions:
- AssertDescription: Parameter 'Elasticsearch URL' (ElasticsearchUrl) is not needed when 'Enable Elasticsearch and Kibana' (ElasticsearchEnabled) is 'false'.
Assert: !Equals [ !Ref ElasticsearchUrl, "" ]
- AssertDescription: Parameter 'Kibana URL' (KibanaUrl) is not needed when 'Enable Elasticsearch and Kibana' (ElasticsearchEnabled) is 'false'.
Assert: !Equals [ !Ref KibanaUrl, "" ]
- AssertDescription: Parameter 'Elasticsearch and Kibana username' (ElasticsearchUser) is not needed when 'Enable Elasticsearch and Kibana' (ElasticsearchEnabled) is 'false'.
Assert: !Equals [ !Ref ElasticsearchUser, "" ]
- AssertDescription: Parameter 'Elasticsearch and Kibana password' (ElasticsearchPassword) is not needed when 'Enable Elasticsearch and Kibana' (ElasticsearchEnabled) is 'false'.
Assert: !Equals [ !Ref ElasticsearchPassword, "" ]
# Check selfsigend parameters
SelfSignedValidation:
RuleCondition: !Equals [!Ref WhichCert, 'selfsigned' ]
Assertions:
- AssertDescription: Parameter 'URL to the CRT file' (OwnCertCRT) is not necessary when using 'selfsigned' as 'Certificate Type' (WhichCert).
Assert: !Equals [ !Ref OwnCertCRT, '' ]
- AssertDescription: Parameter 'URL to the key file' (OwnCertKEY) is not necessary when using 'selfsigned' as 'Certificate Type' (WhichCert).
Assert: !Equals [!Ref OwnCertKEY, '']
- AssertDescription: Parameter 'Email for Let's Encrypt' (LetsEncryptEmail) is not necessary when using 'selfsigned' as 'Certificate Type' (WhichCert).
Assert: !Equals [!Ref LetsEncryptEmail, '']
# Check Letsencrypt parameters
LetsEncryptValidation:
RuleCondition: !Equals [!Ref WhichCert, 'letsencrypt' ]
Assertions:
- AssertDescription: Parameter 'AWS Elastic IP' (PublicElasticIP) is needed when using 'letsencrypt' as 'Certificate Type' (WhichCert).
Assert: !Not [ !Equals [ !Ref PublicElasticIP, '' ] ]
- AssertDescription: Parameter 'Email for Let's Encrypt' (LetsEncryptEmail) is needed when using 'letsencrypt' as 'Certificate Type' (WhichCert).
Assert: !Not [ !Equals [!Ref LetsEncryptEmail, ''] ]
# Check OwnCertCRT and OwnCertKEY are defined if owncert is selected
OwnCertValidation:
RuleCondition: !Equals [ !Ref WhichCert, 'owncert' ]
Assertions:
- AssertDescription: Parameter 'AWS Elastic IP' (PublicElasticIP) is needed when using 'owncert' as 'Certificate Type' (WhichCert).
Assert: !Not [ !Equals [ !Ref PublicElasticIP, '' ] ]
- AssertDescription: Parameter 'URL to the CRT file' (OwnCertCRT) is needed when using 'owncert' as 'Certificate Type' (WhichCert).
Assert: !Not [ !Equals [!Ref OwnCertCRT, ''] ]
- AssertDescription: Parameter 'URL to the key file' (OwnCertKEY) is needed when using 'owncert' as 'Certificate Type' (WhichCert).
Assert: !Not [ !Equals [!Ref OwnCertKEY, ''] ]
Resources:
OpenViduManageEC2Role:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: OpenViduManageEC2Policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'ec2:DescribeInstances'
- 'ec2:RunInstances'
- 'ec2:TerminateInstances'
- 'ec2:CreateTags'
- 'ec2:DescribeSecurityGroups'
- 'ec2:DescribeSubnets'
- 'iam:PassRole'
- 'route53:ChangeResourceRecordSets'
- 'route53:ListHostedZones'
Resource: '*'
- Fn::If:
# Only apply this policy if S3 is configured
- RecordingStorageIsS3
- Effect: Allow
Action:
- 's3:DeleteObject'
- 's3:GetObject'
- 's3:PutObject'
Resource:
- Fn::If:
# Get bucket name depending if the user defines a bucket name or not
- CreateS3Bucket
### Unique bucket name using Stack ID
- !Join [ "", [ 'arn:aws:s3:::', 'openvidu-recordings-', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]], "/*" ]]
- !Join [ "", [ 'arn:aws:s3:::', !Ref S3RecordingsBucketName, '/*'] ]
- Ref: AWS::NoValue
- Fn::If:
# Only apply this policy if S3 is configured
- RecordingStorageIsS3
- Effect: Allow
Action:
- 's3:ListBucket'
- 's3:GetBucketLocation'
Resource:
- Fn::If:
# Get bucket name depending if the user defines a bucket name or not
- CreateS3Bucket
### Unique bucket name using Stack ID
- !Join [ "", [ 'arn:aws:s3:::', 'openvidu-recordings-', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
- !Join [ "", [ 'arn:aws:s3:::', !Ref S3RecordingsBucketName ] ]
- Ref: AWS::NoValue
- Fn::If:
# Only apply this policy if S3 is configured
- RecordingStorageIsS3
- Effect: Allow
Action:
- s3:ListAllMyBuckets
Resource: 'arn:aws:s3:::'
- Ref: AWS::NoValue
RoleName: !Join [ "-", [ OpenViduManageEC2Role, !Ref 'AWS::StackName', !Ref 'AWS::Region'] ]
OpenviduInstancesProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
InstanceProfileName: !Join [ "-", [ OpenViduInstanceProfile, !Ref 'AWS::StackName', !Ref 'AWS::Region'] ]
Path: /
Roles:
- !Join [ "-", [ OpenViduManageEC2Role, !Ref 'AWS::StackName', !Ref 'AWS::Region'] ]
DependsOn:
- OpenViduManageEC2Role
S3bucket:
Type: 'AWS::S3::Bucket'
Properties:
### Unique bucket name using Stack ID
BucketName: !Join ["-" , [ 'openvidu-recordings', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls : true
RestrictPublicBuckets: true
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Condition: CreateS3Bucket
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: IMDSV2
LaunchTemplateData:
MetadataOptions:
HttpEndpoint: enabled
HttpPutResponseHopLimit: 1
HttpTokens: required
OpenViduServer:
Type: AWS::EC2::Instance
Metadata:
Comment: OpenVidu Pro
AWS::CloudFormation::Init:
config:
files:
'/usr/local/bin/check_app_ready.sh':
content: |
#!/bin/bash
while true; do
HTTP_STATUS=$(curl -Ik http://localhost:5443/ | head -n1 | awk '{print $2}')
if [ $HTTP_STATUS == 200 ]; then
break
fi
sleep 5
done
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/feedGroupVars.sh':
content: !Sub
- |
#!/bin/bash -xe
WORKINGDIR=/opt/openvidu
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Pro License
sed -i "s/OPENVIDU_PRO_LICENSE=/OPENVIDU_PRO_LICENSE=${OpenViduLicense}/" $WORKINGDIR/.env
# OpenVidu Edition
sed -i "s/OPENVIDU_EDITION=pro/OPENVIDU_EDITION=${OpenViduEdition}/" $WORKINGDIR/.env
# Replace secret
sed -i "s/OPENVIDU_SECRET=/OPENVIDU_SECRET=${OpenViduSecret}/" $WORKINGDIR/.env
# Replace domain name
if [[ "${MyDomainName}" != '' && "${PublicElasticIP}" != '' ]]; then
sed -i "s/DOMAIN_OR_PUBLIC_IP=/DOMAIN_OR_PUBLIC_IP=${MyDomainName}/" $WORKINGDIR/.env
elif [[ "${MyDomainName}" == '' && "${PublicElasticIP}" != '' ]]; then
sed -i "s/DOMAIN_OR_PUBLIC_IP=/DOMAIN_OR_PUBLIC_IP=${PublicElasticIP}/" $WORKINGDIR/.env
else
[ ! -d "/usr/share/openvidu" ] && mkdir -p /usr/share/openvidu
PublicHostname=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-hostname)
sed -i "s/DOMAIN_OR_PUBLIC_IP=/DOMAIN_OR_PUBLIC_IP=$PublicHostname/" $WORKINGDIR/.env
echo $PublicHostname > /usr/share/openvidu/old-host-name
fi
# OpenVidu Pro mode
sed -i "s/OPENVIDU_PRO_CLUSTER_MODE=manual/OPENVIDU_PRO_CLUSTER_MODE=auto/" $WORKINGDIR/.env
# OpenVidu Pro Media Nodes
sed -i "s/#OPENVIDU_PRO_CLUSTER_MEDIA_NODES=/OPENVIDU_PRO_CLUSTER_MEDIA_NODES=${MediaNodesStartNumber}/" $WORKINGDIR/.env
# OpenVidu Pro enviroment
sed -i "s/OPENVIDU_PRO_CLUSTER_ENVIRONMENT=on_premise/OPENVIDU_PRO_CLUSTER_ENVIRONMENT=aws/" $WORKINGDIR/.env
# Replace certificated type
sed -i "s/CERTIFICATE_TYPE=selfsigned/CERTIFICATE_TYPE=${WhichCert}/" $WORKINGDIR/.env
sed -i "s/LETSENCRYPT_EMAIL=user@example.com/LETSENCRYPT_EMAIL=${LetsEncryptEmail}/" $WORKINGDIR/.env
# Replace Elastic Search Conf
if [[ "${ElasticsearchEnabled}" == "true" ]]; then
if [[ ! -z "${ElasticsearchUrl}" ]]; then
sed -i "s,#OPENVIDU_PRO_ELASTICSEARCH_HOST=,OPENVIDU_PRO_ELASTICSEARCH_HOST=${ElasticsearchUrl}," $WORKINGDIR/.env
fi
if [[ ! -z "${KibanaUrl}" ]]; then
sed -i "s,#OPENVIDU_PRO_KIBANA_HOST=,OPENVIDU_PRO_KIBANA_HOST=${KibanaUrl}," $WORKINGDIR/.env
fi
sed -i "s/ELASTICSEARCH_USERNAME=elasticadmin/ELASTICSEARCH_USERNAME=${ElasticsearchUser}/" $WORKINGDIR/.env
sed -i "s/ELASTICSEARCH_PASSWORD=/ELASTICSEARCH_PASSWORD=${ElasticsearchPassword}/" $WORKINGDIR/.env
else
sed -i "s/OPENVIDU_PRO_ELASTICSEARCH=true/OPENVIDU_PRO_ELASTICSEARCH=false/" $WORKINGDIR/.env
fi
# Replace vars AWS
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
sed -i "s/#AWS_DEFAULT_REGION=/AWS_DEFAULT_REGION=${AWS::Region}/" $WORKINGDIR/.env
sed -i "s/#AWS_IMAGE_ID=/AWS_IMAGE_ID=${kmsAmi}/" $WORKINGDIR/.env
sed -i "s/#AWS_INSTANCE_TYPE=/AWS_INSTANCE_TYPE=${AwsInstanceTypeKMS}/" $WORKINGDIR/.env
sed -i "s/#AWS_INSTANCE_ID=/AWS_INSTANCE_ID=$INSTANCE_ID/" $WORKINGDIR/.env
sed -i "s/#AWS_KEY_NAME=/AWS_KEY_NAME=${KeyName}/" $WORKINGDIR/.env
sed -i "s/#AWS_SUBNET_ID=/AWS_SUBNET_ID=${OpenViduSubnet}/" $WORKINGDIR/.env
sed -i "s/#AWS_STACK_ID=/AWS_STACK_ID=$(echo ${AWS::StackId} | sed 's#/#\\/#g')/" $WORKINGDIR/.env
sed -i "s/#AWS_STACK_NAME=/AWS_STACK_NAME=${AWS::StackName}/" $WORKINGDIR/.env
sed -i "s/#AWS_CLI_DOCKER_TAG=/AWS_CLI_DOCKER_TAG=_AWS_CLI_DOCKER_TAG_/" $WORKINGDIR/.env
sed -i "s/#AWS_VOLUME_SIZE=/AWS_VOLUME_SIZE=50/" $WORKINGDIR/.env
sed -i "s/#OPENVIDU_PRO_AWS_REGION=/OPENVIDU_PRO_AWS_REGION=${AWS::Region}/" $WORKINGDIR/.env
# Get security group id of kms and use it as env variable
SECGRPIDKMS=$(/usr/local/bin/getSecurityGroupKms.sh)
sed -i "s/#AWS_SECURITY_GROUP=/AWS_SECURITY_GROUP=$SECGRPIDKMS/" $WORKINGDIR/.env
# Without Application
if [ "${WantToDeployDemos}" == "false" ]; then
sed -i "s/WITH_APP=true/WITH_APP=false/" $WORKINGDIR/docker-compose.yml
rm $WORKINGDIR/docker-compose.override.yml
fi
# Deploy Coturn in media nodes
if [ "${CoturnInMediaNodes}" == "true" ]; then
sed -i "s/OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=false/OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=true/" $WORKINGDIR/.env
fi
# Recording Configuration
if [ "${Recording}" != "disabled" ]; then
sed -i "s/OPENVIDU_RECORDING=false/OPENVIDU_RECORDING=true/" $WORKINGDIR/.env
sed -i "s/#OPENVIDU_PRO_RECORDING_STORAGE=/OPENVIDU_PRO_RECORDING_STORAGE=${Recording}/" $WORKINGDIR/.env
if [ ! -z "${S3RecordingsBucketName}" ]; then
sed -i "s/#OPENVIDU_PRO_AWS_S3_BUCKET=/OPENVIDU_PRO_AWS_S3_BUCKET=${S3RecordingsBucketName}/" $WORKINGDIR/.env
elif [ "${Recording}" == "s3" ]; then
sed -i "s/#OPENVIDU_PRO_AWS_S3_BUCKET=/OPENVIDU_PRO_AWS_S3_BUCKET=${s3BucketName}/" $WORKINGDIR/.env
fi
fi
- kmsAmi: !GetAtt CloudformationLambdaInvoke.MediaNodeImageId
### Unique bucket name using Stack ID
s3BucketName: !Join ["" , [ 'openvidu-recordings-', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/buildCerts.sh':
content: !Sub |
#!/bin/bash -x
WORKINGDIR=/opt/openvidu
wget --no-check-certificate -O $WORKINGDIR/owncert/certificate.cert ${OwnCertCRT}
wget --no-check-certificate -O $WORKINGDIR/owncert/certificate.key ${OwnCertKEY}
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/getSecurityGroupKms.sh':
content: !Sub |
#!/bin/bash -x
docker run --rm amazon/aws-cli:_AWS_CLI_DOCKER_TAG_ ec2 describe-security-groups \
--region ${AWS::Region} \
--output text \
--filters "Name=tag:aws:cloudformation:logical-id,Values=KMSSecurityGroup" \
"Name=tag:aws:cloudformation:stack-id,Values=${AWS::StackId}" \
--query 'SecurityGroups[].GroupId[]'
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/restartPRO.sh':
content: |
#!/bin/bash -x
WORKINGDIR=/opt/openvidu
# Get new amazon URL
OldPublicHostname=$(cat /usr/share/openvidu/old-host-name)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
PublicHostname=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-hostname)
sed -i "s/$OldPublicHostname/$PublicHostname/" $WORKINGDIR/.env
echo $PublicHostname > /usr/share/openvidu/old-host-name
# Restart all services
pushd /opt/openvidu
export FOLLOW_OPENVIDU_LOGS=false
./openvidu stop
./openvidu start
popd
mode: "000755"
owner: "root"
group: "root"
Properties:
ImageId: !GetAtt CloudformationLambdaInvoke.MasterNodeImageId
InstanceType: !Ref AwsInstanceTypeOV
KeyName: !Ref KeyName
IamInstanceProfile: !Ref OpenviduInstancesProfile
SubnetId: !Ref OpenViduSubnet
SecurityGroupIds:
- !GetAtt 'OpenViduSecurityGroup.GroupId'
Tags:
- Key: Name
Value: 'OpenVidu Pro Master Node'
- Key: 'ov-cluster-member'
Value: 'server'
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash -xe
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenViduServer
export HOME="/root"
# Replace .env variables
/usr/local/bin/feedGroupVars.sh || { echo "[OpenVidu] Parameters incorrect/insufficient"; exit 1; }
# Launch on reboot
echo "@reboot /usr/local/bin/restartPRO.sh" | crontab
# Download certs if "WichCert" mode
if [ "${WhichCert}" == "owncert" ]; then
/usr/local/bin/buildCerts.sh || { echo "[OpenVidu] error with the certificate files"; exit 1; }
fi
# Start openvidu application
pushd /opt/openvidu
export FOLLOW_OPENVIDU_LOGS=false
./openvidu start
popd
# Wait for the app
/usr/local/bin/check_app_ready.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: 200
##########
# Security groups
##########
KMSSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
VpcId: !Ref OpenViduVPC
GroupDescription: SSH, Proxy and KMS WebRTC Ports
GroupName: !Join [ "-", [ !Ref 'AWS::StackName', 'KMSSecurityGroup'] ]
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
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
- IpProtocol: tcp
FromPort: 3000
ToPort: 3000
SourceSecurityGroupId: !Ref OpenViduSecurityGroup
- IpProtocol: tcp
FromPort: 4000
ToPort: 4000
SourceSecurityGroupId: !Ref OpenViduSecurityGroup
- IpProtocol: tcp
FromPort: 8888
ToPort: 8888
SourceSecurityGroupId: !Ref OpenViduSecurityGroup
- IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 40000
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 40000
ToPort: 65535
CidrIpv6: ::/0
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 1
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 1
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
OpenViduSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: SSH, Proxy and OpenVidu WebRTC Ports
GroupName: !Join [ "-", [ !Ref 'AWS::StackName', 'OpenViduSecurityGroup'] ]
VpcId: !Ref OpenViduVPC
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: tcp
FromPort: 3478
ToPort: 3478
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 3478
ToPort: 3478
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 3478
ToPort: 3478
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 3478
ToPort: 3478
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 40000
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 40000
ToPort: 65535
CidrIpv6: ::/0
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 1
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 1
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
OpenViduSecurityGroupIngressELK:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduSecurityGroup
IpProtocol: tcp
FromPort: 9200
ToPort: 9200
SourceSecurityGroupId: !Ref KMSSecurityGroup
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
CreationPolicy:
ResourceSignal:
Timeout: PT25M
Count: 1
MyEIP:
Type: AWS::EC2::EIPAssociation
Condition: PublicElasticIPPresent
Properties:
InstanceId: !Ref OpenViduServer
EIP: !Ref PublicElasticIP
##########
# Lambda which complements Cloudformation to:
# 1. On create: Copy original AMIs from eu-west-1 to the deployment region
# 2. On Delete: Removes media nodes created by OpenVidu Server PRO
##########
CloudformationLambdaRole:
Type: 'AWS::IAM::Role'
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: !Join ['', [ !Ref AWS::StackName, '-cf-lambda-policy'] ]
PolicyDocument:
Version: 2012-10-17
Statement:
# Permissions to copy original Lambda to the region where it is being deployed
- Effect: Allow
Action:
- 'ec2:DescribeImages'
- 'ec2:CopyImage'
Resource: '*'
# Describe instances to get instances which OpenVidu PRO creates
- Effect: Allow
Action:
- 'ec2:DescribeInstances'
Resource: '*'
# Permissions to remove media nodes while destroying the Cloudformation
# Only those created by OpenVidu PRO can be deleted
- Effect: Allow
Action:
- 'ec2:TerminateInstances'
Resource: '*'
Condition:
StringEquals:
'aws:ResourceTag/ov-cluster-member': 'kms'
'aws:ResourceTag/ov-stack-name': !Ref AWS::StackName
'aws:ResourceTag/ov-stack-region': !Ref AWS::Region
RoleName: !Join ['', [ !Ref AWS::StackName, '-cf-lambda-role'] ]
CloudformationLambda:
Type: AWS::Lambda::Function
DeletionPolicy: Delete
Properties:
FunctionName: !Join ['', [ !Ref AWS::StackName, '-cf-lambda'] ]
Code:
ZipFile: |
import boto3
import cfnresponse
from botocore.config import Config
def handler(event, context):
try:
if (event['RequestType'] == 'Create'):
copy_ami(event, context)
elif (event['RequestType'] == 'Delete'):
removeMediaNodes(event, context)
else:
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
def copy_ami_operation(source_image_id, source_region, new_ami_name, ec2_client):
own_ami_filter = [{ 'Name': 'name', 'Values': [new_ami_name] }]
amis_response = ec2_client.describe_images(Filters=own_ami_filter)
if (len(amis_response['Images']) == 1):
# If AMI exists, don't copy
return amis_response['Images'][0]['ImageId']
else:
# If AMI does not exist, copy
new_amis_response = ec2_client.copy_image(
SourceImageId=source_image_id,
SourceRegion=source_region,
Name=new_ami_name
)
return new_amis_response['ImageId']
def copy_ami(event, context):
new_images=[]
cfn_output = {}
source_image_id_master_node = event['ResourceProperties']['MasterNodeAmiSourceId']
source_image_id_media_node = event['ResourceProperties']['MediaNodeAmiSourceId']
source_region = event['ResourceProperties']['AmiSourceRegion']
deployment_region = event['ResourceProperties']['DeploymentRegion']
# Clients init
ec2_client = boto3.client('ec2', config = Config(region_name=deployment_region))
ec2_client_ov = boto3.client('ec2', config = Config(region_name=source_region))
img_exists_waiter= ec2_client.get_waiter('image_exists')
img_avail_waiter = ec2_client.get_waiter('image_available')
# Get original ami name
public_ami_master_node_filter = [{ 'Name': 'image-id', 'Values': [ source_image_id_master_node ] }]
public_ami_media_node_filter = [{ 'Name': 'image-id', 'Values': [ source_image_id_media_node ] }]
response = ec2_client_ov.describe_images(Filters=public_ami_master_node_filter)
new_ami_name_master_node = "[ OpenVidu PRO Master Node AMI Copy ] - " + response['Images'][0]['Name']
response = ec2_client_ov.describe_images(Filters=public_ami_media_node_filter)
new_ami_name_media_node = "[ OpenVidu PRO/ENTERPRISE Media Node AMI Copy ] - " + response['Images'][0]['Name']
# Copy master node AMI and media node AMI
master_node_ami_id = copy_ami_operation(source_image_id_master_node, source_region, new_ami_name_master_node, ec2_client)
new_images.append(master_node_ami_id)
cfn_output['MasterNodeImageId'] = master_node_ami_id
media_node_ami_id = copy_ami_operation(source_image_id_media_node, source_region, new_ami_name_media_node, ec2_client)
new_images.append(media_node_ami_id)
cfn_output['MediaNodeImageId'] = media_node_ami_id
# Wait images to be available
waiter_config = {'Delay': 15, 'MaxAttempts': 59 }
response = img_exists_waiter.wait(ImageIds=new_images, WaiterConfig=waiter_config)
response = img_avail_waiter.wait(ImageIds=new_images, WaiterConfig=waiter_config)
# Return AMIs
cfnresponse.send(event, context, cfnresponse.SUCCESS, cfn_output)
def removeMediaNodes(event, context):
cluster_stack_name = event['ResourceProperties']['StackName']
deployment_region = event['ResourceProperties']['DeploymentRegion']
# Clients init
ec2_client = boto3.client('ec2', config = Config(region_name=deployment_region))
ec2_media_node_filter = [
{ 'Name': 'tag:ov-cluster-member', 'Values': [ 'kms' ] },
{ 'Name': 'tag:ov-stack-region', 'Values': [ deployment_region ] },
{ 'Name': 'tag:ov-stack-name', 'Values': [ cluster_stack_name ] }
]
# Get instances to remove
response_media_nodes = ec2_client.describe_instances(Filters=ec2_media_node_filter, MaxResults=1000)
# Remove instances
instance_ids_to_remove = []
for reservation in response_media_nodes['Reservations']:
for instance in reservation['Instances']:
instance_ids_to_remove.append(instance['InstanceId'])
print(instance_ids_to_remove)
ec2_client.terminate_instances(InstanceIds=instance_ids_to_remove)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
Handler: index.handler
Role:
!GetAtt CloudformationLambdaRole.Arn
Runtime: python3.11
Timeout: 900
CloudformationLambdaInvoke:
Type: AWS::CloudFormation::CustomResource
DeletionPolicy: Delete
Version: "1.0"
Properties:
ServiceToken: !GetAtt CloudformationLambda.Arn
AmiSourceRegion: 'eu-west-1'
MasterNodeAmiSourceId: !FindInMap [OVAMIMAP, 'eu-west-1', AMI]
MediaNodeAmiSourceId: !FindInMap [KMSAMIMAP, 'eu-west-1', AMI]
StackName: !Ref AWS::StackName
DeploymentRegion: !Ref AWS::Region
Outputs:
OpenViduInspector:
Description: "Use this URL to connect OpenVidu with user and password"
Value: !Join
- ''
- - 'https://'
- !GetAtt OpenViduServer.PublicDnsName
- '/inspector'
OpenViduInspectorLE:
Description: "Use this URL to connect to OpenVidu with user and password if you're using Let's Encrypt"
Value: !Join
- ''
- - 'https://'
- !Ref MyDomainName
- '/inspector'
Condition: WhichCertPresent
Kibana:
Description: "Check out graph and performance of your OpenVidu installation"
Value: !Join
- ''
- - 'https://'
- !GetAtt OpenViduServer.PublicDnsName
- '/kibana'
KibanaLE:
Description: "Check out graph and performance of your OpenVidu installation"
Value: !Join
- ''
- - 'https://'
- !Ref MyDomainName
- '/kibana'
Condition: WhichCertPresent