Create an Enterprise Data Plane on GCP
This guide covers GCP provisioning for the Dedicated, BYOK, and BYOC deployment models.
Before you begin
- Confirm the Enterprise deployment model and target GCP region with the PromptQL team.
- Record the Data Plane name, domain, zones, network CIDR, and private-connectivity requirements.
- BYOC requires a dedicated GCP project with billing enabled and permission to create project-level custom roles and IAM bindings.
- BYOK requires a customer-managed Cloud KMS key in the same region as the Data Plane.
Network and availability plan
- Select one GCP region and at least two zones.
- Reserve a non-overlapping VPC CIDR, normally /16 to /20.
- Confirm the network path to each data source: VPC Network Peering, VPN, Private Service Connect, or another approved private path.
- For BYOC, use a dedicated GCP project and confirm organization policies and quotas permit the required GKE, Cloud SQL, storage, load-balancing, KMS, and networking resources.
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 GCP region for your Data Plane with PromptQL.
- Create a Cloud KMS key ring and customer-managed key in that same region.
- Grant PromptQL's provisioning service account the key-scoped IAM access documented below.
- Paste the key resource name into the Customer-managed KMS Key Resource Name field and submit the Data Plane form.
- Keep the key enabled and preserve the required IAM access while the Data Plane is active.
PromptQL then provisions and operates the Dedicated Data Plane using your key for encryption at rest.
Detailed GCP setup
Before you start
- GCP project with the Cloud KMS API enabled
gcloudCLI installed and authenticated as a project ownerGCP_REGIONenvironment variable set to your desired region (e.g.,export GCP_REGION=us-central1)
Apply the setup
-
Create a key ring and KMS key
Create the key ring and 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 (GCS and Cloud SQL in the primary region; GKE etcd secrets per region).
gcloud kms keyrings create hasura-ddn-cmek \ --location=$GCP_REGION gcloud kms keys create hasura-ddn-cmek-key \ --location=$GCP_REGION \ --keyring=hasura-ddn-cmek \ --purpose=encryption -
Grant PromptQL's provisioning service account access
PromptQL needs three permissions on the key: read it, read its IAM policy, and add IAM bindings. During provisioning it can attach the Data Plane's Cloud SQL, GCS, and GKE service agents to your key.
PromptQL provisioning service account:
- Production:
ddn-automation@hasura-ddn.iam.gserviceaccount.com
We recommend using a custom role with only the three permissions PromptQL actually uses. This is the GCP equivalent of the least-privilege key policy on AWS.
# Create a custom role at the project level (one-time per project). gcloud iam roles create hasuraDDNCMEKKeyAdmin \ --project=$GCP_PROJECT \ --title="PromptQL CMEK Key Admin" \ --description="Lets PromptQL automation read and bind IAM on a single KMS key, nothing else." \ --permissions=cloudkms.cryptoKeys.get,cloudkms.cryptoKeys.getIamPolicy,cloudkms.cryptoKeys.setIamPolicy \ --stage=GA # Bind the custom role to PromptQL's service account, scoped to this key only. gcloud kms keys add-iam-policy-binding hasura-ddn-cmek-key \ --location=$GCP_REGION \ --keyring=hasura-ddn-cmek \ --member="serviceAccount:ddn-automation@hasura-ddn.iam.gserviceaccount.com" \ --role="projects/$GCP_PROJECT/roles/hasuraDDNCMEKKeyAdmin"With this custom role, PromptQL cannot disable, destroy, rotate, or otherwise modify the key: only read it and add the service-agent IAM bindings required for data-at-rest encryption.
- Production:
Alternative: use the predefined `roles/cloudkms.admin`
If you'd rather skip creating a custom role, you can grant roles/cloudkms.admin scoped to the single key. This is
broader than necessary (it also lets PromptQL update the key, schedule version destruction, etc.) but the scope is
still limited to this one key, not the whole project.
gcloud kms keys add-iam-policy-binding hasura-ddn-cmek-key \
--location=$GCP_REGION \
--keyring=hasura-ddn-cmek \
--member="serviceAccount:ddn-automation@hasura-ddn.iam.gserviceaccount.com" \
--role="roles/cloudkms.admin"
What to provide on the form
Paste the full Cloud KMS resource name into the Customer-managed KMS Key Resource Name field:
projects/<your-project>/locations/<region>/keyRings/hasura-ddn-cmek/cryptoKeys/hasura-ddn-cmek-key
The key must be in the same location as the Data Plane. A Data Plane is deployed in one region and may span multiple zones within that region.
Revoking access (GCP)
Impact of key revocation
Revoking access to, disabling, or destroying a Cloud KMS key version 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 restoring or re-enabling the key version as a recovery procedure.
Avoid this scenario
Do not revoke access to, disable, or destroy a Cloud KMS key version 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 IAM bindings or key state so the change can be planned without making encrypted resources inaccessible.
What happens to the IAM bindings when the Data Plane is destroyed
When you destroy a Data Plane, PromptQL does not remove the IAM bindings it created on your key for the Cloud SQL, GCS, and GKE service agents. The bindings remain in place but are dormant: the Data Plane project that owned those service agents is gone, so the bindings have no practical effect.
This is intentional: teardown is resilient to any subsequent changes to your key's IAM policy. You don't need to keep
cloudkms.cryptoKeys.setIamPolicy permissions in place for PromptQL indefinitely.
To remove the residual bindings at your convenience:
# 1. Inspect the IAM policy on the key
gcloud kms keys get-iam-policy hasura-ddn-cmek-key \
--location=$GCP_REGION \
--keyring=hasura-ddn-cmek
# 2. Remove each stale binding. Replace <NUMBER> with the data plane project's
# project number (visible in the bindings from step 1). Three service agents
# were granted access: Cloud SQL, GCS, and GKE: so remove all three:
# Cloud SQL service agent
gcloud kms keys remove-iam-policy-binding hasura-ddn-cmek-key \
--location=$GCP_REGION \
--keyring=hasura-ddn-cmek \
--member="serviceAccount:service-<NUMBER>@gcp-sa-cloud-sql.iam.gserviceaccount.com" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter"
# GCS service agent (Cloud Storage)
gcloud kms keys remove-iam-policy-binding hasura-ddn-cmek-key \
--location=$GCP_REGION \
--keyring=hasura-ddn-cmek \
--member="serviceAccount:service-<NUMBER>@gs-project-accounts.iam.gserviceaccount.com" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter"
# GKE / Compute Engine service agent (etcd application-layer secret encryption)
gcloud kms keys remove-iam-policy-binding hasura-ddn-cmek-key \
--location=$GCP_REGION \
--keyring=hasura-ddn-cmek \
--member="serviceAccount:service-<NUMBER>@compute-system.iam.gserviceaccount.com" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter"
BYOC
What you need to do
- Prepare a dedicated GCP project with billing enabled and confirm the target region, quotas, organization policies, and network range.
- Apply the Terraform configuration below as a project administrator.
- Verify that all 12 custom roles and their IAM bindings were created.
- Provide the project ID, region, and network plan to PromptQL.
- Keep the custom roles, IAM bindings, required APIs, and project quotas available while PromptQL operates the Data Plane.
PromptQL then uses its scoped provisioning service account to provision, upgrade, and operate the approved resources in your GCP project.
Detailed GCP setup
The setup involves enabling APIs and creating IAM policy bindings. The IAM policy bindings establish a trust relationship with PromptQL's GCP service account, which is used to deploy and manage the infrastructure required for PromptQL.
Before you start
- Dedicated GCP Project
Apply the setup
- Create the project-level custom IAM roles for PromptQL's
ddn-automationservice account and bind them (see below). These grant only the granular permissions PromptQL's provisioning automation uses, avoiding the broader capabilities bundled into predefined roles such ascloudsql.adminorstorage.admin.
Keep this configuration up to date
New PromptQL features may require additional GCP permissions. When PromptQL provides an updated version of this Terraform configuration for a new feature, review its
terraform planand apply it to your GCP project before enabling that feature. This updates the custom roles and IAM bindings with the permissions the feature requires.If you see
PERMISSION_DENIEDafter applying the latest configuration, contact PromptQL support rather than changing the custom roles manually.
Copy the following and save it as main.tf, then apply it. It is the GCP counterpart to the AWS
CloudFormation template above. A single terraform apply enables the required APIs, creates all 12
custom roles, binds them to ddn-automation, and applies the IAM grant-restriction condition. When PromptQL provides
a newer version, replace your local main.tf, review the resulting terraform plan, and apply it.
main.tf
# GCP BYOC: least-privilege IAM for PromptQL.
#
# A single, self-contained Terraform config a project admin applies in their own project. It:
# 1. enables the APIs PromptQL's provisioning automation needs,
# 2. creates 12 custom least-privilege project roles (replacing the broad predefined roles),
# 3. binds them to PromptQL's `ddn-automation` service account,
# 4. applies an IAM grant-restriction condition on the four roles that carry a *.setIamPolicy
# permission, so `ddn-automation` may only grant the specific roles the provision hands out.
#
# Adding a permission later = edit the relevant role's list below and `terraform apply`. Widening
# what the automation may grant = add the role to the relevant role's `grantable_roles`.
terraform {
required_version = ">= 1.3"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 5.0, < 7.0"
}
}
}
provider "google" {
project = var.project_id
}
locals {
member = "serviceAccount:${var.ddn_automation_service_account}"
# APIs the data-plane project must have enabled for provisioning to run.
required_apis = [
"compute.googleapis.com",
"container.googleapis.com",
"dns.googleapis.com",
"servicenetworking.googleapis.com",
"sqladmin.googleapis.com",
"cloudkms.googleapis.com",
"certificatemanager.googleapis.com",
"gkehub.googleapis.com",
"multiclusterservicediscovery.googleapis.com",
"multiclusteringress.googleapis.com",
"trafficdirector.googleapis.com",
"monitoring.googleapis.com",
"storage.googleapis.com",
"iam.googleapis.com",
"iamcredentials.googleapis.com",
"cloudresourcemanager.googleapis.com", # google_project_iam_member goes through Resource Manager
]
# Each setIamPolicy-bearing role below carries its OWN `grantable_roles` allowlist, scoped to
# exactly the roles ddn-automation grants at that resource level (project / SA / bucket / key).
# The grant-restriction condition on each binding is built from that per-role list: so e.g. the
# project-IAM role can grant only the three project-level roles, never storage/KMS roles.
# 12 custom roles. A role with `grants_iam = true` also defines `grantable_roles`: its binding
# gets a grant-restriction condition permitting ONLY those roles at that scope (see below).
custom_roles = {
compute_network_admin = {
role_id = "promptqlDdnComputeNetworkAdmin"
title = "PromptQL DDN - Compute Network Admin"
description = "Least-privilege replacement for roles/compute.networkAdmin, scoped to PromptQL provisioning"
permissions = [
"compute.networks.create",
"compute.networks.get",
"compute.networks.list",
"compute.networks.update",
"compute.networks.delete",
"compute.networks.use",
"compute.networks.updatePolicy", # add subnets/routers/PSA peering to the VPC
"compute.subnetworks.create",
"compute.subnetworks.get",
"compute.subnetworks.list",
"compute.subnetworks.update",
"compute.subnetworks.delete",
"compute.subnetworks.use",
"compute.subnetworks.setPrivateIpGoogleAccess",
"compute.routers.create",
"compute.routers.get",
"compute.routers.list",
"compute.routers.update", # also covers RouterNat create/delete (a router PATCH)
"compute.routers.delete",
"compute.routers.use",
"compute.addresses.create",
"compute.addresses.get",
"compute.addresses.list",
"compute.addresses.delete",
"compute.addresses.use",
"compute.globalAddresses.create", # external gateway IP
"compute.globalAddresses.get",
"compute.globalAddresses.list",
"compute.globalAddresses.delete",
"compute.globalAddresses.use",
"compute.globalAddresses.createInternal", # internal PSA range (AddressType=INTERNAL)
"compute.globalAddresses.deleteInternal", # tear down the PSA range on destroy
"compute.globalOperations.get",
"compute.globalOperations.list",
"compute.regionOperations.get",
"compute.regionOperations.list",
"compute.projects.get",
"servicenetworking.services.addPeering",
"servicenetworking.services.get",
"servicenetworking.services.deleteConnection", # tear down the PSA peering on destroy
"servicenetworking.operations.get",
]
}
dns_admin = {
role_id = "promptqlDdnDnsAdmin"
title = "PromptQL DDN - DNS Admin"
description = "Least-privilege replacement for roles/dns.admin, scoped to PromptQL provisioning"
permissions = [
"dns.managedZones.create",
"dns.managedZones.get",
"dns.managedZones.list",
"dns.managedZones.update",
"dns.managedZones.delete",
"dns.resourceRecordSets.create",
"dns.resourceRecordSets.get",
"dns.resourceRecordSets.list",
"dns.resourceRecordSets.update",
"dns.resourceRecordSets.delete",
"dns.changes.create",
"dns.changes.get",
"dns.changes.list",
"dns.managedZoneOperations.get",
"dns.managedZoneOperations.list",
"dns.projects.get",
"dns.networks.bindPrivateDNSZone", # bind the private managed zone to the VPC
]
}
gkehub_editor = {
role_id = "promptqlDdnGkeHubEditor"
title = "PromptQL DDN - GKE Hub Editor"
description = "Least-privilege replacement for roles/gkehub.editor, scoped to PromptQL provisioning"
permissions = [
"gkehub.features.create",
"gkehub.features.get",
"gkehub.features.list",
"gkehub.features.update",
"gkehub.features.delete",
"gkehub.memberships.create",
"gkehub.memberships.get",
"gkehub.memberships.list",
"gkehub.memberships.update",
"gkehub.memberships.delete",
"gkehub.operations.get",
"gkehub.operations.list",
"gkehub.locations.get",
"gkehub.locations.list",
]
}
container_cluster_admin = {
role_id = "promptqlDdnContainerClusterAdmin"
title = "PromptQL DDN - Container Cluster Admin"
description = "Least-privilege replacement for roles/container.clusterAdmin, scoped to PromptQL provisioning"
permissions = [
"container.clusters.create",
"container.clusters.get",
"container.clusters.list",
"container.clusters.update",
"container.clusters.delete",
"container.operations.get",
"container.operations.list",
"resourcemanager.projects.get",
"compute.instanceGroupManagers.get",
"compute.instanceGroupManagers.list",
"compute.instanceGroups.get",
"compute.instanceGroups.list",
"compute.instances.get",
"compute.instances.list",
]
}
monitoring_metrics_scopes_admin = {
role_id = "promptqlDdnMonitoringMetricsScopesAdmin"
title = "PromptQL DDN - Monitoring Metrics Scopes Admin"
description = "Least-privilege replacement for roles/monitoring.metricsScopesAdmin, scoped to PromptQL provisioning"
permissions = [
"monitoring.metricsScopes.link",
"resourcemanager.projects.get",
]
}
certificate_manager_editor = {
role_id = "promptqlDdnCertificateManagerEditor"
title = "PromptQL DDN - Certificate Manager Editor"
description = "Least-privilege replacement for roles/certificatemanager.editor, scoped to PromptQL provisioning"
permissions = [
"certificatemanager.certmaps.create",
"certificatemanager.certmaps.get",
"certificatemanager.certmaps.list",
"certificatemanager.certmaps.update",
"certificatemanager.certmaps.delete",
"certificatemanager.certmapentries.create",
"certificatemanager.certmapentries.get",
"certificatemanager.certmapentries.list",
"certificatemanager.certmapentries.update",
"certificatemanager.certmapentries.delete",
"certificatemanager.certs.create",
"certificatemanager.certs.get",
"certificatemanager.certs.list",
"certificatemanager.certs.update",
"certificatemanager.certs.delete",
"certificatemanager.dnsauthorizations.create",
"certificatemanager.dnsauthorizations.get",
"certificatemanager.dnsauthorizations.list",
"certificatemanager.dnsauthorizations.update",
"certificatemanager.dnsauthorizations.delete",
"certificatemanager.locations.get",
"certificatemanager.locations.list",
"certificatemanager.operations.get",
"certificatemanager.operations.list",
# .use perms: the SA itself creates the Certificate referencing the DnsAuthorization and
# the CertificateMapEntry referencing the cert during provision (not at GKE runtime).
"certificatemanager.dnsauthorizations.use",
"certificatemanager.certs.use",
"certificatemanager.certmaps.use",
]
}
project_iam_admin = {
role_id = "promptqlDdnProjectIamAdmin"
title = "PromptQL DDN - Project IAM Admin"
description = "Least-privilege replacement for roles/resourcemanager.projectIamAdmin, scoped to PromptQL provisioning"
grants_iam = true # resourcemanager.projects.setIamPolicy
# Only the three project-level roles the provision grants; never storage/KMS at project scope.
grantable_roles = [
"roles/container.defaultNodeServiceAccount", # -> GKE node SA
"roles/container.admin", # -> argocd
"roles/compute.networkViewer", # -> gke-mcs importer
]
permissions = [
"resourcemanager.projects.getIamPolicy",
"resourcemanager.projects.setIamPolicy",
]
}
service_account_admin = {
role_id = "promptqlDdnServiceAccountAdmin"
title = "PromptQL DDN - Service Account Admin"
description = "Least-privilege replacement for roles/iam.serviceAccountAdmin, scoped to PromptQL provisioning"
grants_iam = true # iam.serviceAccounts.setIamPolicy
# Only the actAs grant on the freshly-created GKE node SA.
grantable_roles = ["roles/iam.serviceAccountUser"]
permissions = [
"iam.serviceAccounts.create",
"iam.serviceAccounts.get",
"iam.serviceAccounts.list",
"iam.serviceAccounts.update",
"iam.serviceAccounts.delete",
"iam.serviceAccounts.getIamPolicy",
"iam.serviceAccounts.setIamPolicy",
]
}
cloudsql_admin = {
role_id = "promptqlDdnCloudsqlAdmin"
title = "PromptQL DDN - Cloud SQL Admin"
description = "Least-privilege replacement for roles/cloudsql.admin, scoped to PromptQL provisioning"
# Excludes cloudsql.instances.connect/export/import: the automation provisions instances,
# databases and users but never connects to or reads a customer database.
permissions = [
"cloudsql.instances.create",
"cloudsql.instances.get",
"cloudsql.instances.list",
"cloudsql.instances.update",
"cloudsql.instances.delete",
"cloudsql.databases.create",
"cloudsql.databases.get",
"cloudsql.databases.list",
"cloudsql.databases.update",
"cloudsql.databases.delete",
"cloudsql.users.create",
"cloudsql.users.get",
"cloudsql.users.list",
"cloudsql.users.update",
"cloudsql.users.delete",
]
}
workload_identity_pool_admin = {
role_id = "promptqlDdnWorkloadIdentityPoolAdmin"
title = "PromptQL DDN - Workload Identity Pool Admin"
description = "Least-privilege replacement for roles/iam.workloadIdentityPoolAdmin, scoped to PromptQL provisioning"
# Workload Identity Federation permissions use the service-qualified form in custom roles.
permissions = [
"iam.googleapis.com/workloadIdentityPools.create",
"iam.googleapis.com/workloadIdentityPools.get",
"iam.googleapis.com/workloadIdentityPools.list",
"iam.googleapis.com/workloadIdentityPools.update",
"iam.googleapis.com/workloadIdentityPools.delete",
"iam.googleapis.com/workloadIdentityPoolProviders.create",
"iam.googleapis.com/workloadIdentityPoolProviders.get",
"iam.googleapis.com/workloadIdentityPoolProviders.list",
"iam.googleapis.com/workloadIdentityPoolProviders.update",
"iam.googleapis.com/workloadIdentityPoolProviders.delete",
]
}
storage_admin = {
role_id = "promptqlDdnStorageAdmin"
title = "PromptQL DDN - Storage Admin"
description = "Least-privilege replacement for roles/storage.admin, scoped to PromptQL provisioning"
grants_iam = true # storage.buckets.setIamPolicy
# Only the bucket-level roles the provision grants to workloads (on buckets, never project-wide).
grantable_roles = [
"roles/storage.objectAdmin",
"roles/storage.objectViewer",
"roles/storage.legacyBucketReader",
]
# storage.objects.list/delete are needed because buckets are created with force_destroy,
# which empties objects before deleting the bucket; the automation never reads object content.
permissions = [
"storage.buckets.create",
"storage.buckets.get",
"storage.buckets.list",
"storage.buckets.update",
"storage.buckets.delete",
"storage.buckets.getIamPolicy",
"storage.buckets.setIamPolicy",
"storage.objects.list",
"storage.objects.delete",
]
}
cloudkms_admin = {
role_id = "promptqlDdnCloudkmsAdmin"
title = "PromptQL DDN - Cloud KMS Admin"
description = "Least-privilege replacement for roles/cloudkms.admin, scoped to PromptQL provisioning"
grants_iam = true # cloudkms.cryptoKeys.setIamPolicy
# Only the key-level roles the provision grants on the bee key (on the key, never project-wide).
grantable_roles = [
"roles/cloudkms.cryptoKeyDecrypter",
"roles/cloudkms.cryptoKeyEncrypterDecrypter",
]
# Key rings and keys cannot be deleted in GCP, so on `pulumi destroy` the provider lists and
# destroys the key's versions instead (cryptoKeyVersions.list/destroy). Still excludes
# encrypt/decrypt: the automation never performs cryptographic operations itself.
permissions = [
"cloudkms.keyRings.create",
"cloudkms.keyRings.get",
"cloudkms.keyRings.list",
"cloudkms.cryptoKeys.create",
"cloudkms.cryptoKeys.get",
"cloudkms.cryptoKeys.list",
"cloudkms.cryptoKeys.update",
"cloudkms.cryptoKeys.getIamPolicy",
"cloudkms.cryptoKeys.setIamPolicy",
"cloudkms.cryptoKeyVersions.list", # teardown: enumerate versions to destroy
"cloudkms.cryptoKeyVersions.destroy", # teardown: destroy versions (key itself can't be deleted)
]
}
}
}
# 1. Enable required APIs (disable_on_destroy=false so teardown never disables a shared API).
resource "google_project_service" "apis" {
for_each = toset(local.required_apis)
project = var.project_id
service = each.value
disable_on_destroy = false
disable_dependent_services = false
}
# 2. Create the 12 custom roles.
resource "google_project_iam_custom_role" "roles" {
for_each = local.custom_roles
project = var.project_id
role_id = each.value.role_id
title = each.value.title
description = each.value.description
permissions = each.value.permissions
stage = "GA"
# iam.googleapis.com (which backs custom roles) is enabled by this same module, so the roles
# must wait for API enablement: otherwise the first apply on a fresh project can race it.
depends_on = [google_project_service.apis]
}
# 3a. Bind roles that do NOT grant IAM to others (no grant-restriction condition needed).
resource "google_project_iam_member" "bindings" {
for_each = { for k, v in local.custom_roles : k => v if !try(v.grants_iam, false) }
project = var.project_id
role = google_project_iam_custom_role.roles[each.key].id
member = local.member
}
# 3b. Bind the four roles that carry a setIamPolicy permission, WITH the grant-restriction
# condition. modifiedGrantsByRole is only populated on setIamPolicy calls, so non-grant
# operations (create/get/delete/etc.) see an empty set and pass the condition unconditionally.
resource "google_project_iam_member" "iam_granting_bindings" {
for_each = { for k, v in local.custom_roles : k => v if try(v.grants_iam, false) }
project = var.project_id
role = google_project_iam_custom_role.roles[each.key].id
member = local.member
condition {
title = "Restrict IAM Granting for ddn-automation"
description = "Roles ddn-automation may grant via ${each.value.role_id}"
expression = "api.getAttribute(\"iam.googleapis.com/modifiedGrantsByRole\", []).hasOnly(${jsonencode(each.value.grantable_roles)})"
}
}
variable "project_id" {
type = string
description = "The GCP project ID where the PromptQL data plane will be provisioned."
}
variable "ddn_automation_service_account" {
type = string
description = "PromptQL's provisioning service account the custom roles are bound to."
default = "ddn-automation@hasura-ddn.iam.gserviceaccount.com"
validation {
condition = can(regex("^[^@]+@[^.]+\\.iam\\.gserviceaccount\\.com$", var.ddn_automation_service_account))
error_message = "ddn_automation_service_account must be a GCP service account email (name@project.iam.gserviceaccount.com)."
}
}
Apply it in your project:
export GCP_PROJECT_ID="your-project-id"
gcloud auth application-default login # credentials Terraform will use
terraform init
terraform apply -var project_id="${GCP_PROJECT_ID}"
Confirm all 12 roles were created:
gcloud iam roles list --project=${GCP_PROJECT_ID} --filter="name:promptqlDdn" --format="value(name)" | wc -l
# Should print 12
Note
The four roles that can modify IAM policies:
promptqlDdnProjectIamAdmin,promptqlDdnServiceAccountAdmin,promptqlDdnStorageAdminandpromptqlDdnCloudkmsAdmin: are bound with amodifiedGrantsByRolegrant-restriction condition. It restrictsddn-automationto granting only the specific roles the provision hands out, across project, service-account, bucket, and KMS-key scope (seegrantable_rolesin the Terraform), so the service account cannot escalate privileges by granting any other role.
What to provide to PromptQL
Share the following with the PromptQL team:
- (Required) Project ID
- (Required) GCP Region
- (Optional) Preferred Availability Zones
- 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.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.