Overview

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 as us-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

  1. In the PromptQL Enterprise console, open Data Planes and select Create Data Plane.
  2. Choose Dedicated, enter the Data Plane name and domain, and select the cloud, region, and availability zones.
  3. Enter the approved network ranges and private-connectivity settings.
  4. Review the configuration and create the Data Plane.
  5. Monitor provisioning until the status is Active. Initial provisioning typically takes about 60 minutes.
  6. Validate project creation, user authentication, connector deployment, and private connectivity to each required data source.

BYOK

What you need to do

  1. Confirm the AWS region for your Data Plane with PromptQL.
  2. Create a customer-managed KMS key in that same region.
  3. Apply the key policy below so PromptQL can create the required encryption grant.
  4. Paste the key ARN into the Customer-managed KMS Key ARN field and submit the Data Plane form.
  5. 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_REGION environment variable set to your desired region (e.g., export AWS_REGION=us-east-1)
Setup instructions
  1. 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 table
    

    Optionally, 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>
    
  2. 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 the Principal with 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. The kms:GrantOperations condition 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 like ScheduleKeyDeletion.
    • 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

  1. Prepare a dedicated AWS account and confirm the target region, quotas, availability zones, and network ranges.
  2. Deploy the CloudFormation bootstrap stack below in your account.
  3. Wait for the stack to complete and retrieve its Role ARN.
  4. Provide the Role ARN, region, network plan, and any custom External ID to PromptQL.
  5. 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_REGION environment 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
  1. 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
  1. Deploy the CloudFormation stack:

    First, check if the stack already exists:

    aws cloudformation describe-stacks --stack-name hasura-cloud-byoc
    

    Then, 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
      
  2. Monitor stack status:

    aws cloudformation describe-stacks \
      --stack-name hasura-cloud-byoc \
      --query 'Stacks[0].StackStatus'
    
  3. 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 ExternalId parameter 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.