Create an Enterprise Data Plane on AWS
This guide covers AWS provisioning for the Dedicated, BYOK, and BYOC deployment models.
Before you begin
- Confirm the Enterprise deployment model and target AWS region with the PromptQL team.
- Confirm the target region is enabled and has sufficient service quotas.
- Record the Data Plane name, domain, availability-zone IDs, network CIDRs, and private-connectivity requirements.
- BYOC requires a dedicated AWS account with administrative access for the initial bootstrap.
- BYOK requires a customer-managed AWS KMS key in the same region as the Data Plane.
Network and availability plan
- Select one AWS region and at least two Availability Zones. Use AZ IDs such as
use1-az1, not account-specific names such asus-east-1a. - Reserve a non-overlapping VPC CIDR. A /16 to /20 is recommended. If routable address space is constrained, AWS supports a /21 to /23 VPC CIDR when a separate /16 to /20 Pod IP CIDR is supplied.
- Reserve a non-overlapping Kubernetes Service CIDR. If using a Pod IP CIDR, it must not overlap either the VPC or service CIDR.
- Confirm the network path to each data source: VPC peering, Transit Gateway, VPN, PrivateLink, or another approved private path.
Dedicated
- In the PromptQL Enterprise console, open Data Planes and select Create Data Plane.
- Choose Dedicated, enter the Data Plane name and domain, and select the cloud, region, and availability zones.
- Enter the approved network ranges and private-connectivity settings.
- Review the configuration and create the Data Plane.
- Monitor provisioning until the status is Active. Initial provisioning typically takes about 60 minutes.
- Validate project creation, user authentication, connector deployment, and private connectivity to each required data source.
BYOK
What you need to do
- Confirm the AWS region for your Data Plane with PromptQL.
- Create a customer-managed KMS key in that same region.
- Apply the key policy below so PromptQL can create the required encryption grant.
- Paste the key ARN into the Customer-managed KMS Key ARN field and submit the Data Plane form.
- Keep the key enabled and preserve the required policy while the Data Plane is active.
PromptQL then provisions and operates the Dedicated Data Plane using your key for encryption at rest.
Detailed AWS setup
Before you start
- AWS account with administrative access
- AWS CLI installed and configured
AWS_REGIONenvironment variable set to your desired region (e.g.,export AWS_REGION=us-east-1)
Setup instructions
-
Create a KMS key
Create a symmetric encryption key in the same region as your Data Plane. A single key per region is used for all data-at-rest encryption in that region (S3 buckets and RDS in the primary region; EKS secrets per region).
aws kms create-key \ --region $AWS_REGION \ --description "PromptQL customer-managed encryption key" \ --key-usage ENCRYPT_DECRYPT \ --origin AWS_KMS \ --query 'KeyMetadata.{KeyId:KeyId,Arn:Arn}' \ --output tableOptionally, create an alias for easier identification:
aws kms create-alias \ --region $AWS_REGION \ --alias-name alias/hasura-ddn-cmek \ --target-key-id <key-id-from-above> -
Configure key policy
Configure the key policy with three statements:
EnableIAMUserPermissions: grants management access to whoever should be able to administer this key. The example below uses your account root (arn:aws:iam::<YOUR_ACCOUNT_ID>:root), but you can replace thePrincipalwith whichever IAM entity will manage the key: for example, your admin role (arn:aws:iam::<YOUR_ACCOUNT_ID>:role/<ADMIN_ROLE>) or admin user (arn:aws:iam::<YOUR_ACCOUNT_ID>:user/<ADMIN_USER>). Without this statement, no IAM principal can manage the key, which can lead to lockout.AllowHasuraDDNCreateGrant: lets PromptQL create a KMS grant that delegates key access to your Data Plane account during provisioning. Thekms:GrantOperationscondition restricts the grant to only the operations required for data-at-rest encryption (Encrypt, Decrypt, GenerateDataKey, etc.): PromptQL cannot create a grant that includes destructive operations likeScheduleKeyDeletion.AllowHasuraDDNDescribeKey: lets PromptQL read key metadata and list grants for visibility during provisioning. PromptQL does not retire or revoke grants on Data Plane teardown (see "Revoking access" below for what happens on destroy).
Replace
<YOUR_ACCOUNT_ID>with your AWS account ID.
Key policy JSON
{
"Version": "2012-10-17",
"Id": "hasura-ddn-cmek-policy",
"Statement": [
{
"Sid": "EnableIAMUserPermissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<YOUR_ACCOUNT_ID>:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowHasuraDDNCreateGrant",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::824267124885:role/HasuraDataPlaneAutomation"
},
"Action": "kms:CreateGrant",
"Resource": "*",
"Condition": {
"ForAllValues:StringEquals": {
"kms:GrantOperations": [
"Encrypt",
"Decrypt",
"ReEncryptFrom",
"ReEncryptTo",
"GenerateDataKey",
"GenerateDataKeyWithoutPlaintext",
"DescribeKey",
"CreateGrant",
"RetireGrant"
]
}
}
},
{
"Sid": "AllowHasuraDDNDescribeKey",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::824267124885:role/HasuraDataPlaneAutomation"
},
"Action": ["kms:DescribeKey", "kms:ListGrants"],
"Resource": "*"
}
]
}
Save the above as key-policy.json (replacing <YOUR_ACCOUNT_ID>) and apply it:
aws kms put-key-policy \
--key-id <KEY_ID> \
--policy-name default \
--policy file://key-policy.json
What to provide on the form
Paste the full KMS key ARN into the Customer-managed KMS Key ARN field:
arn:aws:kms:<region>:<your-account-id>:key/<key-id>
The key must be in the same region as the Data Plane. A Data Plane is deployed in one region and may span multiple Availability Zones within that region.
Revoking access (AWS)
Impact of key revocation
Revoking, disabling, or deleting a customer-managed KMS key that is in use immediately impacts the Data Plane. Treat any of these key-state changes as a non-recoverable action. Storage and database access can fail, and affected resources may become permanently inaccessible. Do not rely on re-enabling the key as a recovery procedure.
Avoid this scenario
Do not revoke, disable, or delete a customer-managed key that is in use by a Data Plane. If you only need to temporarily cut off PromptQL's access, contact the PromptQL team before changing the key policy or grants so the change can be planned without making encrypted resources inaccessible.
What happens to the KMS grant when the Data Plane is destroyed
When you destroy a Data Plane, PromptQL does not retire or revoke the KMS grant that was created on your key. The grant remains on your key in a dormant state: the Data Plane account that was the grantee no longer has any resources using it, so the grant has no practical effect.
This is intentional: it makes Data Plane teardown resilient to any subsequent changes to your key policy. You don't need
to keep kms:RetireGrant permissions in place for PromptQL indefinitely.
To remove the residual grant at your convenience:
# List grants on the key to find the grant ID
aws kms list-grants --key-id <KEY_ID> --region <KEY_REGION>
# Retire the grant from the Data Plane account (the grantee). Any IAM principal in
# that account can retire it. If the Data Plane account no longer exists, AWS will
# eventually clean up grants for closed accounts automatically.
aws kms retire-grant \
--region <KEY_REGION> \
--key-id <KEY_ARN> \
--grant-id <GRANT_ID>
BYOC
What you need to do
- Prepare a dedicated AWS account and confirm the target region, quotas, availability zones, and network ranges.
- Deploy the CloudFormation bootstrap stack below in your account.
- Wait for the stack to complete and retrieve its Role ARN.
- Provide the Role ARN, region, network plan, and any custom External ID to PromptQL.
- Keep the bootstrap stack and IAM role in place while PromptQL operates the Data Plane.
PromptQL then assumes the scoped role to provision, upgrade, and operate the approved resources in your AWS account.
Detailed AWS setup
The setup involves creating an IAM role in your AWS account that establishes a trust relationship with PromptQL's AWS automation role (PulumiDDNCli). This role will be used to deploy and manage workloads necessary for PromptQL.
Before you start
- Dedicated AWS Account with administrative access
- AWS CLI installed and configured
AWS_REGIONenvironment variable set to your desired region (e.g.,export AWS_REGION=us-east-1)- Ensure the AWS region where you plan to deploy is enabled in your account
aws account get-regions --region-opt-status-contains ENABLED --query 'Regions[*].RegionName'
Apply the bootstrap
- Copy the following template and save it as
cloudformation.yaml
cloudformation.yaml
Parameters:
ExternalId:
Type: String
Default: hasura-cloud
Description: External ID for the trust relationship with PromptQL
MinLength: 2
MaxLength: 1224
AllowedPattern: "[A-Za-z0-9+=,.@:\\/-]*"
GCPProjectName:
Type: String
Default: hasura-lux
Description: GCP project ID for OIDC provider
MinLength: 1
MaxLength: 255
AllowedPattern: "[a-z0-9-]*"
Resources:
BootstrapRole:
Type: AWS::IAM::Role
Properties:
RoleName: HasuraCloudBYOC
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: arn:aws:iam::760537944023:role/PulumiDDNCli
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref ExternalId
BootstrapPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: HasuraCloudBYOC
Roles:
- !Ref BootstrapRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:DescribeAddresses
- ec2:DescribeAddressesAttribute
- ec2:DescribeAvailabilityZones
- ec2:DescribeInternetGateways
- ec2:DescribeNatGateways
- ec2:DescribeNetworkInterfaces
- ec2:DescribeRegions
- ec2:DescribeRouteTables
- ec2:DescribeSecurityGroupRules
- ec2:DescribeSecurityGroups
- ec2:DescribeSubnets
- ec2:DescribeTags
- ec2:DescribeVpcAttribute
- ec2:DescribeVpcs
- ec2:DescribeVpcEndpointServices
- ec2:DescribeVpcEndpoints
- ec2:DescribeVpcEndpointServiceConfigurations
- eks:DeleteAddon
- eks:DescribeAddon
- eks:DescribeCluster
- eks:DescribeNodegroup
- eks:ListClusters
- iam:GetRole
- iam:GetServiceLinkedRoleDeletionStatus
- sqs:GetQueueAttributes
- rds:DescribeDBInstances
- rds:DescribeOrderableDBInstanceOptions
- s3:ListBucket
- s3:ListBucketVersions
- kms:ListAliases
- elasticloadbalancing:DescribeTargetGroups
- elasticloadbalancing:DescribeLoadBalancers
- elasticloadbalancing:DescribeTargetGroupAttributes
- elasticloadbalancing:DescribeLoadBalancerAttributes
- elasticloadbalancing:DescribeTags
- elasticloadbalancing:DescribeListeners
- elasticloadbalancing:DescribeListenerAttributes
Resource: '*'
- Effect: Allow
Action:
- ec2:AllocateAddress
- ec2:AssociateAddress
- ec2:AssociateRouteTable
- ec2:CreateInternetGateway
- ec2:CreateNatGateway
- ec2:CreateRoute
- ec2:CreateRouteTable
- ec2:CreateSubnet
- ec2:CreateTags
- ec2:CreateVpc
- ec2:CreateSecurityGroup
- ec2:AuthorizeSecurityGroupIngress
- ec2:CreateVpcEndpoint
- ec2:CreateVpcEndpointServiceConfiguration
- eks:CreateCluster
- eks:CreateNodegroup
- globalaccelerator:CreateAccelerator
- globalaccelerator:CreateEndpointGroup
- globalaccelerator:CreateListener
- globalaccelerator:TagResource
- sqs:CreateQueue
- sqs:TagQueue
- acm:RequestCertificate
- events:PutRule
- events:TagResource
- iam:CreateOpenIDConnectProvider
- iam:TagOpenIDConnectProvider
- rds:CreateDBSubnetGroup
- rds:CreateDBInstance
- elasticloadbalancing:CreateTargetGroup
- elasticloadbalancing:CreateLoadBalancer
- lambda:CreateFunction
Resource: '*'
Condition:
StringEquals:
aws:RequestTag/Created-By: HasuraCloud
- Effect: Allow
Action:
- ec2:CreateTags
Resource:
- !Sub arn:aws:ec2:*:${AWS::AccountId}:security-group/*
Condition:
StringEquals:
aws:RequestTag/karpenter.sh/discovery: dataplane
- Effect: Allow
Action:
- ec2:DeleteTags
Resource:
- !Sub arn:aws:ec2:*:${AWS::AccountId}:security-group/*
Condition:
StringEquals:
aws:ResourceTag/karpenter.sh/discovery: dataplane
- Effect: Allow
Action:
- ec2:AuthorizeSecurityGroupIngress
- ec2:RevokeSecurityGroupIngress
Resource:
- !Sub arn:aws:ec2:*:${AWS::AccountId}:security-group/*
Condition:
StringEquals:
aws:ResourceTag/aws:eks:cluster-name: dataplane
- Effect: Allow
Action:
- eks:AssociateAccessPolicy
- eks:DisassociateAccessPolicy
Resource:
- !Sub arn:aws:eks:*:${AWS::AccountId}:access-entry/dataplane/*
- Effect: Allow
Action:
- iam:AttachRolePolicy
- iam:CreateInstanceProfile
- iam:CreatePolicy
- iam:CreateRole
- iam:CreatePolicyVersion
- iam:DeleteInstanceProfile
- iam:DeleteOpenIDConnectProvider
- iam:DeletePolicy
- iam:DeleteRolePolicy
- iam:DeleteRole
- iam:DeleteServiceLinkedRole
- iam:DetachRolePolicy
- iam:UpdateAssumeRolePolicy
- iam:UpdateOpenIDConnectProviderThumbprint
- iam:GetInstanceProfile
- iam:GetOpenIDConnectProvider
- iam:GetPolicy
- iam:GetPolicyVersion
- iam:GetRolePolicy
- iam:ListAttachedRolePolicies
- iam:ListInstanceProfilesForRole
- iam:ListOpenIDConnectProviderTags
- iam:ListPolicyVersions
- iam:ListRolePolicies
- iam:PassRole
- iam:PutRolePolicy
- iam:RemoveRoleFromInstanceProfile
- iam:TagInstanceProfile
- iam:TagOpenIDConnectProvider
- iam:TagPolicy
- iam:TagRole
Resource:
# Roles
- !Sub arn:aws:iam::${AWS::AccountId}:role/KarpenterNodeRole
- !Sub arn:aws:iam::${AWS::AccountId}:role/KarpenterSandboxNodeRole
- !Sub arn:aws:iam::${AWS::AccountId}:role/eksClusterRole-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/lb-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/autoscaler-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/global-accelerator-operator-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/karpenter-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/HasuraWorkloadAutomationRole-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/vpc-cni-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/ebsCsiDriverRole-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/kms-control-plane-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/bee-control-plane-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/bee-data-plane-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-artifact-server-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/dataplane-eks-api-lambda-role-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-warehouse-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-sandbox-broker-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-sandbox-base-publisher-*
# Instance Profiles
- !Sub arn:aws:iam::${AWS::AccountId}:instance-profile/dataplane_*
# Policies
- !Sub arn:aws:iam::${AWS::AccountId}:policy/lb-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/autoscaler-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/global-accelerator-operator-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/karpenter-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/dataplane-eks-api-register-targets-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/dataplane-eks-api-deregister-targets-*
# OIDC Providers
- !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.*
- !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/container.googleapis.com/v1/projects/${GCPProjectName}/*
# Service Roles
- !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot
- Effect: Allow
Action:
- iam:CreateServiceLinkedRole
Resource: '*'
Condition:
StringEquals:
iam:AWSServiceName:
- spot.amazonaws.com
- globalaccelerator.amazonaws.com
- eks.amazonaws.com
- eks-nodegroup.amazonaws.com
- rds.amazonaws.com
- Effect: Allow
Action:
- ec2:*
- eks:*
- globalaccelerator:*
- sqs:*
- acm:*
- events:*
- rds:*
- s3:*
- kms:*
- elasticloadbalancing:*
- lambda:*
Resource: '*'
Condition:
StringEquals:
aws:ResourceTag/Created-By: HasuraCloud
- Effect: Allow
Action:
- s3:Get*
- s3:CreateBucket
- s3:PutBucketPublicAccessBlock
- s3:PutBucketVersioning
- s3:PutBucketPolicy
- s3:PutBucketTagging
- s3:DeleteBucketPolicy
- s3:DeleteObjectVersion
- s3:DeleteBucket
Resource:
- !Sub arn:aws:s3:::metadata-store-keys-*
- !Sub arn:aws:s3:::metadata-store-*
- !Sub arn:aws:s3:::promptql-store-*
- !Sub arn:aws:s3:::promptql-warehouse-*
- !Sub arn:aws:s3:::promptql-sandbox-*
- Effect: Allow
Action:
- kms:CreateAlias
- kms:DeleteAlias
Resource:
- !Sub arn:aws:kms:*:${AWS::AccountId}:alias/bee
- Effect: Allow
Action:
- vpce:AllowMultiRegion
Resource: '*'
- Effect: Allow
Action:
- route53:AssociateVPCWithHostedZone
- route53:DisassociateVPCFromHostedZone
Resource: 'arn:aws:route53:::hostedzone/*'
Outputs:
RoleArn:
Description: "ARN of the BYOC IAM role"
Value: !GetAtt BootstrapRole.Arn
-
Deploy the CloudFormation stack:
First, check if the stack already exists:
aws cloudformation describe-stacks --stack-name hasura-cloud-byocThen, based on the result:
-
If you see an error "Stack with id hasura-cloud-byoc does not exist":
# Create new stack aws cloudformation create-stack \ --stack-name hasura-cloud-byoc \ --template-body file://cloudformation.yaml \ --capabilities CAPABILITY_NAMED_IAM \ --parameters ParameterKey=ExternalId,ParameterValue=hasura-cloud # Wait for creation to complete aws cloudformation wait stack-create-complete \ --stack-name hasura-cloud-byoc -
If the stack exists:
# Update existing stack aws cloudformation update-stack \ --stack-name hasura-cloud-byoc \ --template-body file://cloudformation.yaml \ --capabilities CAPABILITY_NAMED_IAM \ --parameters ParameterKey=ExternalId,ParameterValue=hasura-cloud # Wait for update to complete aws cloudformation wait stack-update-complete \ --stack-name hasura-cloud-byoc
-
-
Monitor stack status:
aws cloudformation describe-stacks \ --stack-name hasura-cloud-byoc \ --query 'Stacks[0].StackStatus' -
Once complete, retrieve the Role ARN:
aws cloudformation describe-stacks \ --stack-name hasura-cloud-byoc \ --query 'Stacks[0].Outputs[?OutputKey==`RoleArn`].OutputValue' \ --output text
What to provide to PromptQL
Share the following with the PromptQL team:
- (Required) Role ARN (From output above)
- (Required) AWS Region
- (Optional) External ID
- The external ID used in the trust relationship between your AWS account and PromptQL's AWS account
- This is the value you specified for the
ExternalIdparameter in the CloudFormation template - If not specified, the default value "hasura-cloud" will be used
- Must have a minimum of 2 characters and a maximum of 1,224 characters
- Must be alphanumeric without white space, but can include the following symbols: plus (+), equal (=), comma (,), period (.), at (@), colon (:), forward slash (/), and hyphen (-)
- Important: Make sure to provide this value to the PromptQL team if you've customized it
- (Optional) Preferred Availability Zones
- Use AZ IDs (e.g., use1-az1, use1-az2) instead of AZ names (us-east-1a, us-east-1b)
- You can get the AZ IDs by running:
aws ec2 describe-availability-zones \ --region <region> \ --output table \ --query "AvailabilityZones[?State=='available'] | [].{ZoneName: ZoneName, ZoneId: ZoneId}" - If you have specific zones which you'd like to use, please pass it along. Otherwise, PromptQL will assign accordingly.
- (Optional) VPC CIDR (/16-/19 CIDR)
- If you have a specific CIDR in mind for the VPC setup, please pass it along. If not specified, PromptQL will assign 10.0.0.0/16.
- A /21-/23 VPC CIDR is also supported if you provide a Pod IP CIDR (see below), which enables VPC CNI custom networking.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.
- (Optional) Kubernetes Service CIDR (/16-20 CIDR)
- A /16-/20 CIDR block used for Kubernetes service cluster IP addresses in your Data Plane. If not specified, PromptQL will assign 172.20.0.0/16.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.
- (Optional) Pod IP CIDR (/16-/20 CIDR)
- A /16-/20 CIDR block that Kubernetes pods draw IP addresses from, via AWS VPC CNI custom networking. When set, the VPC CIDR can be as small as /23. If not specified, PromptQL will assign 100.64.0.0/16.
- Optional for a /16-/20 VPC CIDR; required for a /21-/23 VPC CIDR. Must not overlap the VPC CIDR or the Kubernetes Service CIDR.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.