Data plane setup on AWS#
To set up your data plane on Amazon Web Services (AWS) you must allow Union to provision and maintain compute resources under your AWS account. You will need to set up an IAM role for Union to use that has sufficient permissions to do this provisioning. Setting the permissions can be done either through CloudFormation or the AWS console.
Additionally, if you wish to manage your own Virtual Private Cloud (VPC) then you will need to set up the VPC according to the guidelines described below. If you do not wish to manage your own VPC then no additional configuration is needed.
Setting permissions through CloudFormation#
You can do the setup quickly using AWS CloudFormation.
Confirm the details#
Once you have selected Launch Stack, you will be taken to the CloudFormation interface. Do the following:
Check the profile name in the top right corner to confirm that you are in the correct account.
Leave the default values in place:
union-ai-admin
for Cross Account Role Name.UnionCloudAccess
for the Stack Name.
Select the checkbox indicating that you acknowledge that AWS CloudFormation may create IAM resources with custom names.
Select Create Stack.
Updating permissions through CloudFormation#
From time to time Union may need to update the union-ai-admin
role to support new or improved functionality.
If you used CloudFormation to set up your stack in the first place, you will have to perform the update by replacing your CloudFormation template with a new one.
When an update is required:
The Union team will inform you that you need to perform the update.
The URL of the template will be published above, in the CloudFormation template info box. This is always kept up to date with the latest template.
To perform the update on your system, copy the template URL and follow the directions here:
Update your CloudFormation template#
Log in to the AWS web console and navigate to CloudFormation for the region within which your data plane is deployed.
Select the
UnionCloudAccess
stack.Select Stack Actions > Create change set for current stack.
Select Replace current template.
Input the new CloudFormation template URL provided to you by the Union team (and published above in the Current template info box).
Select Next.
On the Specify stack details page, accept the defaults and select Next.
On the Configure stack options page, accept the defaults and select Next.
On the Review UnionCloudAccess page, accept the acknowledgment at the bottom of the page and select Submit.
Wait for the changeset to be generated by AWS (refresh the page if necessary).
Select Execute change set.
Setting permissions manually#
If you want to perform the setup manually, instead of using the CloudFormation method described above, do the following.
Prepare the policy documents#
First, copy the policy document UnionIAMPolicy.json
below to an editor and replace${AWS::Region}
with the correct region and ${AWS::AccountID}
with your account ID.
You will use this policy in a later step.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:ListTagsLogGroup",
"logs:TagLogGroup",
"logs:UntagLogGroup",
"logs:DescribeLogGroups",
"rds:DescribeDBSubnetGroups",
"logs:DeleteLogGroup",
"eks:CreateNodegroup",
"eks:UpdateNodegroupConfig",
"rds:CreateDBSubnetGroup",
"logs:CreateLogGroup",
"ec2:AllocateAddress",
"eks:DeleteCluster",
"rds:DeleteDBSubnetGroup",
"kms:CreateAlias",
"eks:DescribeCluster",
"logs:PutRetentionPolicy",
"kms:DeleteAlias"
],
"Resource": [
"arn:aws:kms:${AWS::Region}:${AWS::AccountID}:alias/*",
"arn:aws:rds:${AWS::Region}:${AWS::AccountID}:subgrp:*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:elastic-ip/*",
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:cluster/opta-*",
"arn:aws:logs:${AWS::Region}:${AWS::AccountID}:log-group:opta-*",
"arn:aws:logs:${AWS::Region}:${AWS::AccountID}:log-group::log-stream*",
"arn:aws:logs:${AWS::Region}:${AWS::AccountID}:log-group:/aws/eks/opta-*:*"
],
"Effect": "Allow",
"Sid": "0"
},
{
"Action": [
"sqs:CreateQueue",
"sqs:DeleteQueue",
"sqs:SetQueueAttributes",
"sqs:TagQueue",
"sqs:UntagQueue"
],
"Resource": [
"arn:aws:sqs:${AWS::Region}:${AWS::AccountID}:Karpenter*"
],
"Effect": "Allow"
},
{
"Action": [
"events:DescribeRule",
"events:DeleteRule",
"events:ListTargetsByRule",
"events:PutRule",
"events:PutTargets",
"events:RemoveTargets",
"events:TagResource"
],
"Resource": [
"arn:aws:events:${AWS::Region}:${AWS::AccountID}:rule/Karpenter*"
],
"Effect": "Allow"
},
{
"Action": [
"eks:TagResource",
"eks:UntagResource",
"eks:ListTagsForResource",
"eks:CreateAccessEntry",
"eks:DescribeAccessEntry",
"eks:UpdateAccessEntry",
"eks:DeleteAccessEntry"
],
"Resource": [
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:cluster/opta-*"
],
"Effect": "Allow",
"Sid": "112"
},
{
"Action": [
"kms:EnableKeyRotation",
"kms:PutKeyPolicy",
"kms:GetKeyPolicy",
"ec2:AttachInternetGateway",
"kms:ListResourceTags",
"kms:TagResource",
"kms:UntagResource",
"ec2:DetachInternetGateway",
"eks:DescribeNodegroup",
"kms:GetKeyRotationStatus",
"eks:DeleteNodegroup",
"ec2:CreateInternetGateway",
"kms:ScheduleKeyDeletion",
"kms:CreateAlias",
"kms:DescribeKey",
"ec2:DeleteInternetGateway",
"kms:DeleteAlias",
"kms:CreateGrant"
],
"Resource": [
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:nodegroup/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:internet-gateway/*",
"arn:aws:kms:${AWS::Region}:${AWS::AccountID}:key/*"
],
"Effect": "Allow",
"Sid": "1"
},
{
"Action": [
"ec2:CreateNatGateway",
"ec2:DeleteNatGateway"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:natgateway/*"
],
"Effect": "Allow",
"Sid": "2"
},
{
"Action": [
"ec2:CreateRoute",
"ec2:DeleteRoute",
"ec2:CreateRouteTable",
"ec2:DeleteRouteTable",
"ec2:AssociateRouteTable"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:route-table/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:subnet/subnet-*"
],
"Effect": "Allow",
"Sid": "3"
},
{
"Action": [
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:security-group-rule/*"
],
"Effect": "Allow",
"Sid": "4"
},
{
"Action": [
"ec2:RevokeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CreateSecurityGroup",
"ec2:RevokeSecurityGroupEgress",
"ec2:DeleteSecurityGroup"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:security-group/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:vpc/vpc-*"
],
"Effect": "Allow",
"Sid": "5"
},
{
"Action": [
"ec2:DeleteSubnet",
"ec2:CreateNatGateway",
"ec2:CreateSubnet",
"ec2:ModifySubnetAttribute"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:subnet/*"
],
"Effect": "Allow",
"Sid": "6"
},
{
"Action": [
"ec2:CreateNatGateway"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:elastic-ip/eipalloc-*"
],
"Effect": "Allow",
"Sid": "7"
},
{
"Action": [
"ec2:DeleteFlowLogs",
"ec2:CreateFlowLogs"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:vpc-flow-log/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:vpc/vpc*"
],
"Effect": "Allow",
"Sid": "8"
},
{
"Action": [
"ec2:CreateVpc",
"ec2:CreateRouteTable",
"ec2:AttachInternetGateway",
"ec2:ModifyVpcAttribute",
"ec2:DetachInternetGateway",
"ec2:DeleteVpc",
"ec2:CreateSubnet",
"ec2:DescribeVpcAttribute",
"ec2:AssociateVpcCidrBlock"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:vpc/*"
],
"Effect": "Allow",
"Sid": "VisualEditor8"
},
{
"Action": [
"iam:DeleteOpenIDConnectProvider",
"iam:GetOpenIDConnectProvider",
"iam:CreateOpenIDConnectProvider",
"iam:TagOpenIDConnectProvider",
"iam:UntagOpenIDConnectProvider",
"iam:ListOpenIDConnectProviderTags"
],
"Resource": [
"arn:aws:iam::${AWS::AccountID}:oidc-provider/*"
],
"Effect": "Allow",
"Sid": "VisualEditor9"
},
{
"Action": [
"iam:CreatePolicy",
"iam:CreatePolicyVersion",
"iam:DeletePolicyVersion",
"iam:GetPolicyVersion",
"iam:GetPolicy",
"iam:ListPolicyVersions",
"iam:DeletePolicy",
"iam:ListPolicyTags",
"iam:TagPolicy",
"iam:UntagPolicy"
],
"Resource": [
"arn:aws:iam::${AWS::AccountID}:policy/*"
],
"Effect": "Allow",
"Sid": "VisualEditor10"
},
{
"Action": [
"iam:GetRole",
"iam:TagRole",
"iam:UntagRole",
"iam:ListRoleTags",
"iam:CreateRole",
"iam:DeleteRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:ListInstanceProfilesForRole",
"iam:PassRole",
"iam:CreateServiceLinkedRole",
"iam:DetachRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:DeleteRolePolicy",
"iam:ListRolePolicies",
"iam:GetRolePolicy"
],
"Resource": [
"arn:aws:iam::${AWS::AccountID}:role/*"
],
"Effect": "Allow",
"Sid": "VisualEditor111"
},
{
"Action": [
"ec2:DescribeAddresses",
"ec2:EnableEbsEncryptionByDefault",
"ec2:GetEbsEncryptionByDefault",
"ec2:DescribeFlowLogs",
"ec2:ResetEbsDefaultKmsKeyId",
"ec2:DescribeInternetGateways",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeAvailabilityZones",
"ec2:GetEbsDefaultKmsKeyId",
"ec2:DescribeAccountAttributes",
"kms:CreateKey",
"ec2:DescribeNetworkAcls",
"ec2:DescribeRouteTables",
"ec2:ModifyEbsDefaultKmsKeyId",
"eks:CreateCluster",
"eks:UpdateClusterVersion",
"eks:UpdateClusterConfig",
"ec2:ReleaseAddress",
"rds:AddTagsToResource",
"rds:RemoveTagsFromResource",
"rds:ListTagsForResource",
"ec2:DescribeVpcClassicLinkDnsSupport",
"ec2:CreateTags",
"ec2:DescribeNatGateways",
"ec2:DisassociateRouteTable",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVpcClassicLink",
"ec2:DescribeVpcs",
"kms:ListAliases",
"ec2:DisableEbsEncryptionByDefault",
"sts:GetCallerIdentity",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroupRules",
"ec2:AllocateAddress",
"ec2:AssociateAddress",
"ec2:DisassociateAddress",
"ec2:DescribeInstanceTypeOfferings",
"logs:DescribeLogStreams",
"iam:ListRoles",
"iam:ListPolicies",
"ec2:DescribeInstanceTypes",
"servicequotas:GetServiceQuota",
"cloudwatch:GetMetricStatistics"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "VisualEditor12"
},
{
"Action": "dynamodb:*",
"Resource": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountID}:table/opta-*"
],
"Effect": "Allow",
"Sid": "VisualEditor13"
},
{
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::opta-*",
"arn:aws:s3:::opta-*/",
"arn:aws:s3:::union-*",
"arn:aws:s3:::union-*/"
],
"Effect": "Allow",
"Sid": "VisualEditor14"
},
{
"Action": [
"events:DescribeRule",
"events:ListTargetsByRule",
"events:ListTagsForResource",
"events:UntagResource"
],
"Resource": [
"arn:aws:events:${AWS::Region}:${AWS::AccountID}:rule/Karpenter*"
],
"Effect": "Allow"
},
{
"Action": [
"sqs:GetQueueAttributes",
"sqs:ListQueueTags"
],
"Resource": [
"arn:aws:sqs:${AWS::Region}:${AWS::AccountID}:Karpenter*"
],
"Effect": "Allow"
},
{
"Action": [
"elasticache:CreateCacheSubnetGroup",
"elasticache:AddTagsToResource",
"elasticache:RemoveTagsFromResource",
"elasticache:ListTagsForResource",
"elasticache:DescribeCacheSubnetGroups",
"elasticache:DeleteCacheSubnetGroup"
],
"Resource": [
"arn:aws:elasticache:${AWS::Region}:${AWS::AccountID}:subnetgroup:opta-*"
],
"Effect": "Allow",
"Sid": "ElastiCache"
},
{
"Action": [
"iam:CreateInstanceProfile",
"iam:AddRoleToInstanceProfile",
"iam:RemoveRoleFromInstanceProfile",
"iam:DeleteInstanceProfile",
"iam:TagInstanceProfile",
"iam:UntagInstanceProfile",
"iam:ListInstanceProfileTags",
"iam:GetInstanceProfile",
"iam:UpdateAssumeRolePolicy"
],
"Resource": [
"arn:aws:iam::${AWS::AccountID}:instance-profile/*"
],
"Effect": "Allow",
"Sid": "self0"
},
{
"Action": [
"ec2:RunInstances",
"ec2:CreateTags",
"ec2:DescribeTags",
"ec2:DeleteTags",
"ec2:DescribeImages",
"ec2:CreateLaunchTemplate",
"ec2:CreateLaunchTemplateVersion",
"ec2:DescribeLaunchTemplates",
"ec2:DescribeLaunchTemplateVersions",
"ec2:DeleteLaunchTemplate",
"ec2:DeleteLaunchTemplateVersions",
"ec2:ModifyLaunchTemplate"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "self1"
},
{
"Action": [
"autoscaling:CreateAutoScalingGroup",
"autoscaling:DeleteAutoScalingGroup",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:UpdateAutoScalingGroup",
"autoscaling:CreateLaunchConfiguration",
"autoscaling:SetInstanceProtection",
"autoscaling:DescribeScalingActivities",
"autoscaling:CreateOrUpdateTags",
"autoscaling:DescribeTags",
"autoscaling:DeleteTags"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "self2"
},
{
"Action": [
"eks:UpdateNodegroupConfig",
"eks:ListNodegroups",
"eks:UpdateNodegroupVersion",
"eks:TagResource",
"eks:UntagResource",
"eks:ListTagsForResource",
"eks:DescribeUpdate",
"eks:DeleteNodegroup"
],
"Resource": [
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:nodegroup/opta-*/opta-*/*",
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:nodegroup/opta-*",
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:nodegroup/*",
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:cluster/opta-*",
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:addon/opta-*/*/*"
],
"Effect": "Allow",
"Sid": "AllowUpdateNodegroupConfig"
},
{
"Action": [
"eks:CreateAddon",
"eks:UpdateAddon",
"eks:DeleteAddon",
"eks:DescribeAddonVersions",
"eks:DescribeAddon",
"eks:ListAddons"
],
"Resource": [
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:cluster/opta-*",
"arn:aws:eks:${AWS::Region}:${AWS::AccountID}:addon/opta-*/*/*"
],
"Effect": "Allow",
"Sid": "AllowUpdateEKSAddonConfig"
},
{
"Action": [
"ec2:CreateVpcEndpoint",
"ec2:ModifyVpcEndpoint",
"ec2:DeleteVpcEndpoints"
],
"Resource": [
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:vpc/vpc*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:vpc-endpoint/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:route-table/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:subnet/*",
"arn:aws:ec2:${AWS::Region}:${AWS::AccountID}:security-group/*"
],
"Effect": "Allow",
"Sid": "AllowVpcEndpoints"
},
{
"Action": [
"ec2:DescribeVpcEndpoints",
"ec2:DescribePrefixLists"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "AllowVpcEndpointReadPermissions"
},
{
"Action": [
"ecr:CreateRepository",
"ecr:DeleteRepository",
"ecr:TagResource",
"ecr:UntagResource",
"ecr:PutLifecyclePolicy",
"ecr:DeleteLifecyclePolicy",
"ecr:PutImageTagMutability",
"ecr:PutImageScanningConfiguration",
"ecr:BatchDeleteImage",
"ecr:DeleteRepositoryPolicy",
"ecr:SetRepositoryPolicy",
"ecr:GetRepositoryPolicy",
"ecr:PutReplicationConfiguration",
"ecr:DescribeRepositories",
"ecr:ListTagsForResource",
"ecr:GetLifecyclePolicy",
"ecr:GetRepositoryPolicy",
"ecr:DescribeImages"
],
"Resource": [
"arn:aws:ecr:*:${AWS::AccountID}:repository/union/*"
],
"Effect": "Allow",
"Sid": "UnionImageBuilderRepoAdmin"
},
{
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "UnionAdminAuthToken"
}
]
}
Create the role manually#
Next, you must create the role. Follow the directions here:
Sign in to the AWS Management Console as an administrator of your account, and open the IAM console.
Choose Roles and then select Create role.
Under Select trusted entity, choose AWS account.
Under An AWS account, select Another AWS account.
In the Account ID field, enter the Union account ID:
479331373192
.Under Options, you will see two items: Require external ID and Require MFA. At this point in the process, you can leave these unchecked.
Select Next. This will take you to the Add permissions page.
Select Next. We will setup permissions in a later step.
Enter the role name
union-ai-admin
.(Optional) For Description, enter a description for the new role.
(Optional) Under Tags, add tags as key-value pairs. For more information about using tags in IAM, see Tagging IAM resources.
After reviewing the role, choose Create role.
Search for the
union-ai-admin
role in the IAM Roles list and click on it.Click Add permissions and select Create inline policy from the drop down menu.
On the Create policy screen, click the JSON tab.
Replace the contents of the policy editor with the UnionIAMPolicy.json file that you edited earlier.
Click Review policy.
Name the policy UnionIAMPolicyManual and click Create policy.
Share the role ARN#
Now you must obtain the Amazon Resource Name (ARN) of the role, a unique identifier for the role:
In the navigation pane of the IAM console, choose Roles.
In the list of roles, choose the
union-ai-admin
role.In the Summary section of the details pane, copy the role ARN value.
Share the ARN with the Union team. The Union team will get back to you to verify that they are able to assume the role.
Updating permissions manually#
From time to time Union may need to update the union-ai-admin
role to support new or improved functionality.
If you set up your role manually in the first place (as opposed to using CloudFormation), you will have to perform the update manually as well.
follow the directions here:
Sign in to the AWS Management Console as an administrator of your account, and open the IAM console.
Choose Roles
Search for the
union-ai-admin
role in the IAM Roles list and click on it.Under Permissions policies, select the previously created policy (if you followed the above directions, it should be called UnionIAMPolicyManual).
The next screen will display the JSON for current policy.
Replace the current policy JSON with the updated copy of UnionIAMPolicy.json and click Next.
On the next page, review the new policy and click Save changes.
Setting up and managing your own VPC (optional)#
If you decide to manage your own VPC, instead of leaving it to Union, then you will need to set it up yourself. The VPC should be configured with the following characteristics.
Multiple availability zones:
We recommend a minimum of 3.
A sufficiently large CIDR range:
We recommend a /16 for the VPC, /28 for each public subnet, and /18 for each private subnet.
With most CNIs, a safe assumption is one IP allocated per pod. Small subnets can limit the number of pods that can be spun up when projects scale.
A public subnet with:
An internet gateway configured for internet access.
A private subnet with:
A NAT gateway setup for internet access.
Enable (Recommended) VPC Endpoints to mitigate unnecessary NAT gateway network traffic:
Enable S3 VPC gateway endpoint with appropriate route table association.
Enable VPC interface endpoints for the following services
com.amazonaws.<REGION>.logs
,com.amazonaws.<REGION>.ecr.dkr
,com.amazonaws.<REGION>.ec2
Ensure the service names include the region that contains the aforementioned availability zones.
Ensure the subnet IDs are configured to include all the aforementioned availability zones.
Ensure the security groups allow all traffic from within the VPC.
Enable Private DNS to support out of the box compatibility with data plane services.
Once your VPC is set up, you will need to provide the Union team with the following information:
VPC ID
Example:
vpc-8580ec61d96caf837
Public subnet IDs (one per availability zone)
Example:
subnet-d7d3ce57d1a546401
Private subnet IDs (one per availability zone)
Example:
subnet-bc2eafd5c11180be0