Prerequisites
Before you start, ensure you have an IAM role in your AWS that permits you to create CloudFormation Templates. Latch utilizes CloudFormation Templates to establish an IAM role that provisions AWS Resources to create a forch domainInstructions
Connecting an AWS Account
1
Go to this [link](https://console.aws.amazon.com/cloudformation/home?#/stacks/quickcreate?templateURL=https://forch-deployment.s3.us-west-2.amazonaws.com/forch_agent_template.json&stackName=forch)
2
Log into the AWS account
3
You will be directed to an AWS CloudFormation 'Quick create stack' template.
When you open the CloudFormation template, you’ll see an acknowledgment stating “The following resource(s) require capabilities: [AWS::IAM::Role]. I acknowledge that AWS CloudFormation might create IAM resources with custom names.” This pertains to you as the customer executing the CloudFormation stack.
The stack creates a role that has permission to provision cloud resources, AWS ensures that you are aware of this action. The permissions of this role which can be verified by inspecting the cloudformation template in the AWS UI.Refer to [Advanced Notes](#Advanced Notes) for an overview of the permission this IAM role.

4
Click on the checkbox to acknowledge that this stack will create an iam role.
5
Click 'Create Stack' and wait for it to be created.
6
Notify someone at Latch with the following details.
- AWS Account Id
- Target AWS Region for this deployment (us-west-2, $, etc)
Advanced Notes
Each forch domain requires a minimum of 4 IAM role to operate:- forch-agent for provisioning cloud resources to setup the forch-domain
- forch-orchestrator for scheduling and managing tasks in the forch-domain
- forch-node for running the tasks in an compute instances the forch-domain
- forch-nat-* for running NAT instance in the forch-domain (1 per vpc)
forch-agent
forch-agent
is created by the Cloudformation Stack from previous section. This role is used when provisioning cloud resources to setup the forch domain. The list of cloud resources created are as follows:
- Network resource including vpcs, subnets, internet gateways, security groups, network acls, route tables and elastic ip addresses
- An S3 Bucket for storing logs
- forch-orchestrator, forch-node and forch-nat-* roles and their policies
- A KMS key for encrypting and decrypting volumes created by forch
- An ec2 instance running the NAT server on the vpc
- Forch specific secrets
Copy
Ask AI
{
"Statement": [
{
"Action": [
"s3:CreateBucket",
"s3:GetBucket*",
"s3:ListBucket*",
"s3:PutBucketCORS",
"s3:GetAccelerateConfiguration",
"s3:PutBucketVersioning",
"s3:GetLifecycleConfiguration",
"s3:GetReplicationConfiguration",
"s3:GetEncryptionConfiguration",
"s3:PutEncryptionConfiguration",
"s3:PutBucketRequestPayment"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::forch-${aws_account_id}"
},
{
"Action": [
"iam:CreateRole",
"iam:GetRole",
"iam:AttachRolePolicy",
"iam:DeleteRole",
"iam:ListRolePolicies",
"iam:PutRolePolicy",
"iam:ListInstanceProfilesForRole",
"iam:ListAttachedRolePolicies",
"iam:GetRolePolicy",
"iam:CreateServiceLinkedRole"
],
"Effect": "Allow",
"Resource": [
"arn:aws:iam::*:role/forch-orchestrator",
"arn:aws:iam::*:role/forch-node",
"arn:aws:iam::*:role/forch-nat-*",
"arn:aws:iam::*:role/forch-agent"
]
},
{
"Action": ["iam:CreateServiceLinkedRole"],
"Effect": "Allow",
"Resource": ["arn:aws:iam::*:role/forch-agent"],
"Condition": {
"StringEquals": {
"iam:AWSServiceName": "kms.amazonaws.com"
}
}
},
{
"Action": ["iam:PassRole"],
"Effect": "Allow",
"Resource": ["arn:aws:iam::*:role/forch-nat-*", "arn:aws:iam::*:role/forch-node"]
},
{
"Action": [
"iam:CreatePolicy",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:ListPolicyVersions"
],
"Effect": "Allow",
"Resource": [
"arn:aws:iam::*:policy/forch-orchestrator-base",
"arn:aws:iam::*:policy/forch-node-base",
"arn:aws:iam::*:policy/forch-agent-delete-permissions"
]
},
{
"Action": [
"iam:CreateInstanceProfile",
"iam:GetInstanceProfile",
"iam:DeleteInstanceProfile",
"iam:AddRoleToInstanceProfile"
],
"Effect": "Allow",
"Resource": [
"arn:aws:iam::*:instance-profile/forch-nat-*",
"arn:aws:iam::*:instance-profile/forch-node"
]
},
{
"Action": "ec2:CreateTags",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": ["ec2:CreateKeyPair", "ec2:DeleteKeyPair", "ec2:ImportKeyPair"],
"Resource": "arn:aws:ec2:*:*:key-pair/forch/debug-root",
"Effect": "Allow"
},
{
"Action": "ec2:DescribeKeyPairs",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": ["ec2:CreateVpc", "ec2:DescribeVpcs", "ec2:DescribeVpcAttribute"],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"ec2:CreateInternetGateway",
"ec2:AttachInternetGateway",
"ec2:DescribeInternetGateways"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:CreateSecurityGroup",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:security-group/*"
},
{
"Action": "ec2:CreateSecurityGroup",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:vpc/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Created By": "Forch"
}
}
},
{
"Action": [
"ec2:RevokeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": ["ec2:DescribeSecurityGroups", "ec2:DescribeSecurityGroupRules"],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:CreateSubnet",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:subnet/*"
},
{
"Action": "ec2:CreateSubnet",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:vpc/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Created By": "Forch"
}
}
},
{
"Action": ["ec2:DescribeSubnets", "ec2:ModifySubnetAttribute"],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:CreateRouteTable",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:route-table/*"
},
{
"Action": "ec2:CreateRouteTable",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:vpc/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Created By": "Forch"
}
}
},
{
"Action": "ec2:CreateRoute",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:DescribeRouteTables",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:CreateNetworkAcl",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:network-acl/*"
},
{
"Action": "ec2:CreateNetworkAcl",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:vpc/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Created By": "Forch"
}
}
},
{
"Action": "ec2:DescribeNetworkAcls",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"ec2:DeleteNetworkAclEntry",
"ec2:CreateNetworkAclEntry",
"ec2:ReplaceNetworkAclAssociation"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:AllocateAddress",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:AssociateAddress",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:elastic-ip/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/forch/allow": "true"
}
}
},
{
"Action": "ec2:AssociateAddress",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:instance/*"
},
{
"Action": ["ec2:DescribeAddresses", "ec2:DescribeAddressesAttribute"],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:DescribeImages",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ec2:RunInstances",
"Condition": {
"ArnLike": {
"ec2:InstanceProfile": [
"arn:aws:iam::*:instance-profile/forch-node",
"arn:aws:iam::*:instance-profile/forch-nat-*"
]
}
},
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:instance/*"
},
{
"Action": "ec2:RunInstances",
"Effect": "Allow",
"Resource": [
"arn:aws:ec2:*:*:network-interface/*",
"arn:aws:ec2:*:*:security-group/*",
"arn:aws:ec2:*:*:subnet/*"
]
},
{
"Action": "ec2:RunInstances",
"Condition": {
"StringEquals": {
"ec2:Owner": "812206152185"
}
},
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:image/*"
},
{
"Action": "ec2:RunInstances",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:key-pair/forch/debug-root"
},
{
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:volume/*",
"Effect": "Allow"
},
{
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceTypes",
"ec2:DescribeTags",
"ec2:DescribeInstanceAttribute"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": ["ec2:DescribeVolumes"],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": ["ec2:DescribeAvailabilityZones"],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "ssm:GetParameters",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:DescribeSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:DeleteSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:TagResource",
"secretsmanager:GetResourcePolicy"
],
"Effect": "Allow",
"Resource": [
"arn:aws:secretsmanager:*:*:secret:forch/nat-jwt*",
"arn:aws:secretsmanager:*:*:secret:forch/ssh-forwarder/ssh_host_ecdsa_key*",
"arn:aws:secretsmanager:*:*:secret:forch/ssh-forwarder/ssh_host_rsa_key*",
"arn:aws:secretsmanager:*:*:secret:forch/ssh-forwarder/ssh_host_ed25519_key*"
]
},
{
"Action": [
"kms:CreateKey",
"kms:ReplicateKey",
"kms:DescribeKey",
"kms:TagResource",
"kms:GetKeyPolicy",
"kms:GetKeyRotationStatus",
"kms:ListResourceTags",
"kms:PutKeyPolicy",
"kms:EnableKeyRotation",
"kms:CreateAlias",
"kms:ListAliases"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey",
"kms:CreateGrant"
],
"Resource": "*"
}
],
"Version": "2012-10-17"
}
forch-orchestrator
forch-orchestrator
is assumed at runtime to schedule and manage tasks. It has permissions to:
- Run and Terminate ec2 instances on forch created vpc
- Assign Private IP Addresses to ec2 instances
- Create, Modify, Detach, Attach and Delete volumes
- Describe, Associate and Disassociate forch created elastic ip addresses
- Use KMS Keys to decrypt volumes
- Get and List objects in the S3 bucket storing logs
Copy
Ask AI
{
"Statement": [
{
"Action": "ec2:RunInstances",
"Condition": {
"StringEquals": {
"ec2:InstanceProfile": "arn:aws:iam::${aws_account_id}:instance-profile/forch-node"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:instance/*",
"Sid": "ForchRunInstance"
},
{
"Action": "ec2:RunInstances",
"Condition": {
"StringEquals": {
"ec2:Vpc": "arn:aws:ec2:${aws_region}:${aws_account_id}:vpc/${vpc_id}""ec2:Vpc": "${vpc_arn}"
}
},
"Effect": "Allow",
"Resource": [
"arn:aws:ec2:*:*:subnet/*",
"arn:aws:ec2:*:*:security-group/*",
"arn:aws:ec2:*:*:network-interface/*"
],
"Sid": "ForchRunInstanceVpcPolicy"
},
{
"Action": "ec2:RunInstances",
"Condition": {
"StringEquals": {
"ec2:Owner": "812206152185"
}
},
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:image/*",
"Sid": "ForchRunInstanceImages"
},
{
"Action": "ec2:RunInstances",
"Effect": "Allow",
"Resource": "arn:aws:ec2:${aws_region}:${aws_account_id}:key-pair/forch/debug-root",
"Sid": "ForchRunInstanceKeyPair"
},
{
"Action": "ec2:RunInstances",
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:volume/*",
"Sid": "ForchRunInstanceVolumes"
},
{
"Action": "iam:PassRole",
"Effect": "Allow",
"Resource": "arn:aws:iam::${aws_account_id}:role/forch-node",
"Sid": "ForchNodePassRole"
},
{
"Action": "ssm:GetParameters",
"Effect": "Allow",
"Resource": "*",
"Sid": "SystemMangerParamters"
},
{
"Action": "ec2:TerminateInstances",
"Condition": {
"StringEquals": {
"ec2:ResourceTag/Created By": "Forch"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:instance/*",
"Sid": "ForchTerminateNode"
},
{
"Action": [
"ec2:AssignPrivateIpAddresses",
"ec2:AssignIpv6Addresses"
],
"Condition": {
"StringEquals": {
"ec2:Vpc": "arn:aws:ec2:${aws_region}:${aws_account_id}:vpc/${vpc_id}"
}
},
"Effect": "Allow",
"Resource": "*",
"Sid": "ForchNodeIps"
},
{
"Action": [
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus"
],
"Effect": "Allow",
"Resource": "*",
"Sid": "ForchNodeDescribeInfo"
},
{
"Action": [
"ec2:ModifyVolume",
"ec2:DetachVolume",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:AttachVolume"
],
"Condition": {
"StringEquals": {
"ec2:ResourceTag/CreatedBy": [
"nucleus/create_volume",
"nucleus/restore_snapshot",
"nucleus-workflows"
]
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:volume/*",
"Sid": "ForchNodeVolumes"
},
{
"Action": [
"ec2:DetachVolume",
"ec2:AttachVolume"
],
"Condition": {
"StringEquals": {
"ec2:InstanceProfile": "arn:aws:iam::${aws_account_id}:instance-profile/forch-node",
"ec2:ResourceTag/Created By": "Forch"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:instance/*",
"Sid": "ForchNodeVolumesAllowedInstances"
},
{
"Action": [
"ec2:DescribeVolumesModifications",
"ec2:DescribeVolumes"
],
"Effect": "Allow",
"Resource": "*",
"Sid": "ForchNodeDescribeVolumes"
},
{
"Action": [
"ec2:DisassociateAddress",
"ec2:AssociateAddress"
],
"Effect": "Allow",
"Resource": [
"arn:aws:ec2:${aws_region}:${aws_account_id}:elastic-ip/${eipalloc_id}",
"arn:aws:ec2:${aws_region}:${aws_account_id}:elastic-ip/${eipalloc_id}"
],
"Sid": "ForchElasticIp"
},
{
"Action": [
"ec2:DisassociateAddress",
"ec2:AssociateAddress"
],
"Condition": {
"StringEquals": {
"ec2:Vpc": "arn:aws:ec2:${aws_region}:${aws_account_id}:vpc/${vpc_id}"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:network-interface/*",
"Sid": "ForchElasticIpNetworkInterface"
},
{
"Action": [
"ec2:DisassociateAddress",
"ec2:AssociateAddress"
],
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:instance/*",
"Sid": "ForchElasticIpInstances"
},
{
"Action": "ec2:DescribeAddresses",
"Effect": "Allow",
"Resource": "*",
"Sid": "ForchDescribeElasticIp"
},
{
"Action": "ec2:CreateTags",
"Condition": {
"StringEquals": {
"ec2:InstanceProfile": "arn:aws:iam::${aws_account_id}:instance-profile/forch-node"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:instance/*",
"Sid": "ForchTagInstances"
},
{
"Action": "ec2:CreateTags",
"Condition": {
"StringEquals": {
"ec2:ResourceTag/Created By": "Forch"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:volume/*",
"Sid": "ForchTagVolumes"
},
{
"Action": "ec2:CreateTags",
"Condition": {
"StringEquals": {
"ec2:Vpc": "arn:aws:ec2:${aws_region}:${aws_account_id}:vpc/${vpc_id}"
}
},
"Effect": "Allow",
"Resource": "arn:*:ec2:*:*:network-interface/*",
"Sid": "ForchNetworkInterfaces"
},
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::forch-${aws_account_id}/logs/*",
"arn:aws:s3:::forch-${aws_account_id}/logs"
],
"Sid": "ForchFluentdReadWrite"
},
{
"Action": "s3:ListBucket",
"Effect": "Allow",
"Resource": "arn:aws:s3:::forch-${aws_account_id}",
"Sid": "ForchFluentdList"
},
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": "*",
"Sid": "AllowAssumeRole"
},
{
"Action": [
"secretsmanager:UpdateSecret",
"secretsmanager:CreateSecret"
],
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/forch/allow": "true"
}
},
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Encrypt",
"kms:DescribeKey",
"kms:Decrypt",
"kms:CreateGrant"
],
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
}
forch-node
forch-nat-*
role has permission to get the task secrets from secretsmanager, read and write logs to the s3 bucket and also assume the forch-node-shared
role to get access to ecr images and secrets from Latch’s aws account. This role’s can only be assumed by roles within the forch domains’ cloud account.
Copy
Ask AI
{
"Statement": [
{
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::forch-${aws_account_id}/logs/*",
"arn:aws:s3:::forch-${aws_account_id}/logs"
],
"Sid": "ForchFluentdReadWrite"
},
{
"Action": "s3:ListBucket",
"Effect": "Allow",
"Resource": "arn:aws:s3:::forch-${aws_account_id}",
"Sid": "ForchFluentdList"
},
{
"Action": "ec2:DescribeAddresses",
"Effect": "Allow",
"Resource": "*",
"Sid": "ForchDescribeAddresses"
},
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": "*",
"Sid": "AllowAssumeRole"
},
{
"Action": "secretsmanager:BatchGetSecretValue",
"Effect": "Allow",
"Resource": "*",
"Sid": "BatchGetSecretValue"
},
{
"Action": "secretsmanager:GetSecretValue",
"Effect": "Allow",
"Resource": "*",
"Sid": "GetSecretValue",
"Condition": {
"StringEquals": {
"aws:ResourceTag/forch/account_id": "${aws:PrincipalAccount}",
"aws:ResourceTag/forch/allow": "true"
}
}
}
],
"Version": "2012-10-17"
}
forch-nat-*
forch-nat-*
role has permission to get the nat-jwt-*
secret from secretsmanager and also assume the forch-node-shared
role to get access to ecr images and secrets from Latch’s aws account. This role’s can only be assumed by roles within the forch domains’ cloud account.
forch-node-base
Copy
Ask AI
{
"Statement": [
{
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::forch-${aws_account_id}/logs/*",
"arn:aws:s3:::forch-${aws_account_id}/logs"
],
"Sid": "ForchFluentdReadWrite"
},
{
"Action": "s3:ListBucket",
"Effect": "Allow",
"Resource": "arn:aws:s3:::forch-${aws_account_id}",
"Sid": "ForchFluentdList"
},
{
"Action": "ec2:DescribeAddresses",
"Effect": "Allow",
"Resource": "*",
"Sid": "ForchDescribeAddresses"
},
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": "*",
"Sid": "AllowAssumeRole"
},
{
"Action": "secretsmanager:BatchGetSecretValue",
"Effect": "Allow",
"Resource": "*",
"Sid": "BatchGetSecretValue"
},
{
"Action": "secretsmanager:GetSecretValue",
"Effect": "Allow",
"Resource": "*",
"Sid": "GetSecretValue",
"Condition": {
"StringEquals": {
"aws:ResourceTag/forch/account_id": "${aws:PrincipalAccount}",
"aws:ResourceTag/forch/allow": "true"
}
}
}
],
"Version": "2012-10-17"
}
jwt-access
Copy
Ask AI
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "secretsmanager:GetSecretValue",
"Effect": "Allow",
"Resource": "arn:aws:secretsmanager:${aws_region}:${aws_account_id}:secret:forch/nat-jwt-*",
"Sid": "JwtSecret"
}
]
}