Amazon Web Services
This document describes the process for creating and managing a customer private cloud at Amazon Web Services (AWS).
Create the IAM Roles
Telestream Cloud uses AWS Identity and Access Management (IAM) roles to create and manage a customer private cloud.
Using the AWS console, the AWS command line interface (CLI) or a Terraform script the customer creates IAM roles in their account that delegate specific permissions to the Telestream Cloud AWS account (078992246105).
There are three roles that need to be created by the customer. Each role will be identified by an Amazon Resource Name (ARN) that should be provided to Telestream Cloud support.
autoscaler
The autoscaler
role allows Telestream Cloud to create EC2 instances within the customer VPC. This role requires the following permissions:
ec2:CreateTags
ec2:DescribeTags
ec2:RunInstances
ec2:DescribeInstances
ec2:TerminateInstances
ec2:CancelSpotFleetRequests
ec2:RequestSpotFleet
ec2:RequestSpotInstances
ec2:StopInstances
ec2:DescribeSecurityGroups
ec2:DescribeSpotFleetRequests
ec2:DescribeSpotPriceHistory
ec2:DescribeImages
ec2:DescribeSpotFleetInstances
ec2:StartInstances
ec2:DescribeVolumes
ec2:DescribeSubnets
pricing:GetProducts
Instances can be provisioned from on-demand and reserved pools or using a Spot Fleet.
instance
This role allows the instance to attach and detach additional block storage and to manage instance tags. This role requires the following permissions:
ec2:CreateTags
ec2:DeleteTags
ec2:DescribeTags
ec2:DetachVolume
ec2:AttachVolume
ec2:CreateVolume
ec2:DeleteVolume
ec2:DescribeVolumes
ec2:ModifyInstanceAttribut
The instance
role is passed to the EC2 instances using an instance profile.
Terraform
Terraform is a third-party Infrastructure tool used to create and manage cloud infrastructure. The following Terraform script can be used by the customer to create the required IAM roles:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 0.14.9"
}
variable profile {
type = string
default = "default"
}
variable account_id {
type = string
description = "Account ID in Telestream Cloud, used as external-id for aws assume role"
}
provider "aws" {
profile = var.profile
}
data "aws_caller_identity" "current" {}
resource "aws_iam_role_policy" "telestream_cloud_instance" {
name = "telestream-cloud-instance"
role = aws_iam_role.telestream_cloud_role_instance.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Resource = "*"
Action = [
"ec2:CreateTags",
"ec2:DeleteTags",
"ec2:DescribeTags",
"ec2:DetachVolume",
"ec2:AttachVolume",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:DescribeVolumes",
"ec2:ModifyInstanceAttribute"
]
},
]
})
}
resource "aws_iam_role" "telestream_cloud_role_instance" {
name = "telestream-cloud-instance"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
resource "aws_iam_role_policy" "telestream_cloud_autoscaling" {
name = "telestream-cloud-autoscaling"
role = aws_iam_role.telestream_cloud_role_autoscaling.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "VisualEditor1"
Effect = "Allow"
Action = [
"ec2:CreateTags",
"ec2:DescribeTags",
"ec2:RunInstances",
"ec2:DescribeInstances",
"ec2:TerminateInstances",
"ec2:CancelSpotFleetRequests",
"ec2:RequestSpotFleet",
"ec2:RequestSpotInstances",
"ec2:StopInstances",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSpotFleetRequests",
"ec2:DescribeSpotPriceHistory",
"ec2:DescribeImages",
"ec2:DescribeSpotFleetInstances",
"ec2:StartInstances",
"ec2:DescribeVolumes",
"ec2:DescribeSubnets",
"pricing:GetProducts"
]
Resource = "*"
},
{
Effect = "Allow"
Action = ["iam:CreateServiceLinkedRole"]
Resource = "*"
},
{
Sid = "VisualEditor2",
Effect = "Allow",
Action = [
"iam:PassRole"
]
Condition = {
StringEquals = {
"iam:PassedToService" = [
"ec2.amazonaws.com"
]
}
},
Resource = [
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/telestream-cloud-autoscaling",
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/telestream-cloud-instance"
]
}
]
})
}
resource "aws_iam_role" "telestream_cloud_role_autoscaling" {
name = "telestream-cloud-autoscaling"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
"Effect" = "Allow",
"Principal" = {
"Service" = "ec2.amazonaws.com"
},
"Action" = "sts:AssumeRole"
},
{
"Sid" = "",
"Effect" = "Allow",
"Principal" = {
"Service" = "spotfleet.amazonaws.com"
},
"Action" = "sts:AssumeRole"
},
{
"Effect" = "Allow",
"Principal" = {
"AWS" = "arn:aws:iam::078992246105:root"
},
"Condition" = {
"StringEquals": {
"sts:ExternalId" : var.account_id
}
}
"Action" = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_instance_profile" "instance_profile" {
name = "telestream-cloud-instance"
role = aws_iam_role.telestream_cloud_role_instance.name
}
output "instance_role" {
value = aws_iam_role.telestream_cloud_role_instance.arn
description = "ARN of the IAM role attached to EC2 instances created by Telestream Cloud Autoscaler"
}
output "autoscaling_role" {
value = aws_iam_role.telestream_cloud_role_autoscaling.arn
description = "ARN of the IAM role used by Telestream Cloud Autoscaler"
}
output "instance_profile" {
value = aws_iam_instance_profile.instance_profile.arn
description = "ARN of the IAM instance profile attached to EC2 instances"
}
output "account_id" {
value = var.account_id
description = "Account ID in Telestream Cloud, used as external-id for aws assume role"
}
Use the following command to run the Terraform script. Visit AWS Terraform Tutorial for additional details.
$ terraform init
$ terraform plan
$ terraform apply
If the script executes successfully it will produce output similar to the following:
Navigate to directory and confirm main.tf file is present.
*****************************************************************************************************************************************************************************
$ ls -l
total 135784
-rw-r--r-- 1 root 1195003546 5601 Dec 15 07:30 main.tf
-rwxr-xr-x@ 1 root 1195003546 69501360 Dec 8 13:15 terraform
Run and initialize Terraform.
*****************************************************************************************************************************************************************************
$ ./terraform init -input=false
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.27"...
- Installing hashicorp/aws v3.69.0...
- Installed hashicorp/aws v3.69.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Create a Terraform plan based off of main.tf parameters.
*****************************************************************************************************************************************************************************
$ ./terraform plan -out=tfplan -input=false
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_iam_role.telestream_cloud_role_autoscaling will be created
+ resource "aws_iam_role" "telestream_cloud_role_autoscaling" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "ec2.amazonaws.com"
}
},
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "spotfleet.amazonaws.com"
}
+ Sid = ""
},
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ AWS = "arn:aws:iam::XXXXXXXXXXXX:root"
}
},
]
+ Version = "2012-10-17"
}
)
+ create_date = (known after apply)
+ force_detach_policies = false
+ id = (known after apply)
+ managed_policy_arns = (known after apply)
+ max_session_duration = 3600
+ name = "telestream-cloud-autoscaling"
+ name_prefix = (known after apply)
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
+ inline_policy {
+ name = "autoscaler_policy"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = [
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "pricing: XXXXXXXXXXXX",
]
+ Effect = "Allow"
+ Resource = "*"
+ Sid = "VisualEditor1"
},
+ {
+ Action = "iam:PassRole"
+ Condition = {
+ StringEquals = {
+ iam:PassedToService = [
+ "ec2.amazonaws.com",
]
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-autoscaling",
+ "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-instance",
]
+ Sid = "VisualEditor2"
},
]
+ Version = "2012-10-17"
}
)
}
}
# aws_iam_role.telestream_cloud_role_bootstrap will be created
+ resource "aws_iam_role" "telestream_cloud_role_bootstrap" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ AWS = "arn:aws:iam:: XXXXXXXXXXXX:root"
}
+ Sid = ""
},
]
+ Version = "2012-10-17"
}
)
+ create_date = (known after apply)
+ force_detach_policies = false
+ id = (known after apply)
+ managed_policy_arns = (known after apply)
+ max_session_duration = 3600
+ name = "telestream-cloud-bootstrap"
+ name_prefix = (known after apply)
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
+ inline_policy {
+ name = "bootstrap_policy"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = [
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
]
+ Effect = "Allow"
+ Resource = "*"
},
]
+ Version = "2012-10-17"
}
)
}
}
# aws_iam_role.telestream_cloud_role_instance will be created
+ resource "aws_iam_role" "telestream_cloud_role_instance" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ AWS = "arn:aws:iam:: XXXXXXXXXXXX:root"
}
+ Sid = ""
},
]
+ Version = "2012-10-17"
}
)
+ create_date = (known after apply)
+ force_detach_policies = false
+ id = (known after apply)
+ managed_policy_arns = (known after apply)
+ max_session_duration = 3600
+ name = "telestream-cloud-instance"
+ name_prefix = (known after apply)
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
+ inline_policy {
+ name = "instance_policy"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = [
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
+ "ec2: XXXXXXXXXXXX",
]
+ Effect = "Allow"
+ Resource = "*"
},
]
+ Version = "2012-10-17"
}
)
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ autoscaling_role = (known after apply)
+ bootstrap_role = (known after apply)
+ instance_role = (known after apply)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
Check and confirm that tfplan file is generated.
*****************************************************************************************************************************************************************************
$ ls -l
total 135784
-rw-r--r-- 1 root 1195003546 5601 Dec 15 07:30 main.tf
-rwxr-xr-x@ 1 root 1195003546 69501360 Dec 8 13:15 terraform
-rw-r--r-- 1 root 1195003546 4383 Dec 15 07:38 tfplan
Run newly created Terraform Plan "tfplan".
*****************************************************************************************************************************************************************************
$ ./terraform apply -input=false tfplan
aws_iam_role.telestream_cloud_role_instance: Creating...
aws_iam_role.telestream_cloud_role_autoscaling: Creating...
aws_iam_role.telestream_cloud_role_bootstrap: Creating...
aws_iam_role.telestream_cloud_role_autoscaling: Creation complete after 2s [id=telestream-cloud-autoscaling]
aws_iam_role.telestream_cloud_role_instance: Creation complete after 2s [id=telestream-cloud-instance]
aws_iam_role.telestream_cloud_role_bootstrap: Creation complete after 2s [id=telestream-cloud-bootstrap]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
autoscaling_role = "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-autoscaling"
bootstrap_role = "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-bootstrap"
instance_role = "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-instance"
Confirm current terraform.tfstate file is created and save for reference.
*****************************************************************************************************************************************************************************
$ ls -l
total 135824
-rw-r--r-- 1 root 1195003546 5601 Dec 15 07:30 main.tf
-rwxr-xr-x@ 1 root 1195003546 69501360 Dec 8 13:15 terraform
-rw-r--r-- 1 root 1195003546 7092 Dec 15 07:39 terraform.tfstate
-rw-r--r-- 1 root 1195003546 4383 Dec 15 07:38 tfplan
Please provide the 3 ARN role information to be used for VPC configuration to Telestream engineers.
*****************************************************************************************************************************************************************************
account_id = "YYYYYYYYYYYYYYYYYYYYYYYY"
autoscaling_role = "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-autoscaling"
instance_role = "arn:aws:iam::XXXXXXXXXXXX:role/telestream-cloud-instance"
instance_profile = "arn:aws:iam::XXXXXXXXXXXX:instance-profile/telestream-cloud-instance-demo"
Send the ARN for each role to the Telestream Support Team at [[email protected]].
The second Terraform script will setup AWS network:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 0.14.9"
}
variable "profile" {
type = string
default = "default"
}
variable "region" {
type = string
default = "us-east-1"
}
provider "aws" {
profile = var.profile
region = var.region
}
data "aws_caller_identity" "current" {}
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_vpc" "telestream_cloud_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "tc-vpc"
}
}
resource "aws_subnet" "main" {
# Create a subnet for every availability zone.
count = length(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.telestream_cloud_vpc.id
cidr_block = cidrsubnet("10.0.0.0/16", 5, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "TelestreamCloudVPC"
}
}
resource "aws_subnet" "nat_subnet" {
# Subnet used for the NAT gateway. We use a single one here, for resiliency, a separate one for each AZ could be created.
vpc_id = aws_vpc.telestream_cloud_vpc.id
cidr_block = "10.0.255.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "TelestreamCloudVPC_nat_subnet"
}
}
resource "aws_eip" "nat_ip" {
domain = "vpc"
}
resource "aws_nat_gateway" "nat_gw" {
allocation_id = aws_eip.nat_ip.id
subnet_id = aws_subnet.nat_subnet.id
tags = {
Name = "TC gw NAT"
}
# To ensure proper ordering, it is recommended to add an explicit dependency
# on the Internet Gateway for the VPC.
depends_on = [aws_internet_gateway.gw]
}
resource "aws_default_route_table" "main" {
default_route_table_id = aws_vpc.telestream_cloud_vpc.default_route_table_id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.nat_gw.id
}
tags = {
Name = "tc-nat-gateway"
}
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.telestream_cloud_vpc.id
tags = {
Name = "main"
}
}
resource "aws_route_table" "nat_route_table" {
vpc_id = aws_vpc.telestream_cloud_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "tc-nat-route-table"
}
}
resource "aws_route_table_association" "a" {
subnet_id = aws_subnet.nat_subnet.id
route_table_id = aws_route_table.nat_route_table.id
}
resource "aws_security_group" "tc_worker" {
name = "tc-worker"
description = "Allow Telestream Cloud maintenance and outgoing traffic"
vpc_id = aws_vpc.telestream_cloud_vpc.id
# This part is negotiable. SSH access is needed for diagnostics, but we could narrow the allowed address range to bastion hosts.
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "tc_worker"
}
}
resource "tls_private_key" "tls_key" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "instance_key" {
key_name = "telestream_cloud"
public_key = tls_private_key.tls_key.public_key_openssh
}
output "vpc_arn" {
value = aws_vpc.telestream_cloud_vpc.arn
description = "ARN of the AWS VPC"
}
output "security_group_arn" {
value = aws_security_group.tc_worker.arn
description = "ARN of the Security Group"
}
output "subnet_id" {
value = aws_subnet.main.*.arn
description = "Main subnets"
}
output "key_name" {
value = aws_key_pair.instance_key.key_name
description = "Name of ec2 key pair that will be attached to worker instances"
}
Updated 11 days ago