Manage AccessFlow with Terraform.
Last updated
What you are building
Everything you configured by clicking — datasources, review plans, routing, masking and
row-security policies, AI configs, notification channels — can be declared in HCL
instead and applied from CI. This guide gets you from nothing to a first
apply.
The provider is published as bablsoft/accessflow and works with both
Terraform and OpenTofu; the two registries carry the
same namespace and name. You need an AccessFlow instance reachable from wherever you
run terraform, and about fifteen minutes.
Can AccessFlow be managed with Terraform?
Yes. The official provider manages datasources, review plans, routing, row-security and masking policies, AI configs and notification channels over the REST API, authenticating with a service-account API key. Requests themselves — queries, API calls, deployments — are runtime objects and are deliberately not Terraform-managed.
1. Get a service-account API key
Terraform needs credentials that do not depend on anyone's browser session. The intended path is a service account: an API-key-only user whose password login is disabled and whose key you generate yourself, so AccessFlow only ever stores its hash.
Generate a key first — it is just a random token with an af_ prefix:
kubectl create secret generic accessflow-bootstrap-secrets \ --from-literal=admin-password="$(openssl rand -base64 24)" \ --from-literal=ci-api-key="af_$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')"
Then declare the account in your Helm values. Bootstrap creates it on the next start:
bootstrap:
enabled: true
serviceAccounts:
- email: terraform@acme.example.com
displayName: Terraform CI
role: ADMIN
apiKeyName: terraform
apiKeySecretRef:
name: accessflow-bootstrap-secrets
key: ci-api-key
# Optional ISO-8601 instant; never expires when omitted.
# apiKeyExpiresAt: "2027-01-01T00:00:00Z"
Outside Kubernetes the same fields are environment variables —
ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_EMAIL,
_DISPLAY_NAME, _ROLE, _API_KEY_NAME,
_API_KEY and the optional _API_KEY_EXPIRES_AT. Rotating the
key is a matter of changing the secret: bootstrap re-imports it in place.
apply, but a personal key inherits that
person's account — use a service account for anything that outlives the experiment.
2. Configure the provider
Two arguments, both optional in HCL because each falls back to an environment variable — which is what you want in CI, so the key never lands in a state file or a diff.
terraform {
required_providers {
accessflow = {
source = "bablsoft/accessflow"
}
}
}
# endpoint and api_key also read ACCESSFLOW_ENDPOINT and ACCESSFLOW_API_KEY.
provider "accessflow" {
endpoint = "https://accessflow.example.com"
api_key = var.accessflow_api_key
}
variable "accessflow_api_key" {
type = string
sensitive = true
}
An explicit HCL value wins over the environment variable. Supply neither and the provider stops with a message naming the argument and the variable it looked for.
3. Declare something and apply it
Start with a review plan and a datasource that references it — the smallest pair that does something real.
resource "accessflow_review_plan" "standard" { name = "standard" description = "AI review then one human approval; reads auto-approved." requires_ai_review = true requires_human_approval = true min_approvals_required = 1 approval_timeout_hours = 24 auto_approve_reads = true approvers = [ { user_id = "11111111-1111-1111-1111-111111111111" stage = 1 } ] } resource "accessflow_datasource" "prod_postgres" { name = "prod-postgres" db_type = "POSTGRESQL" host = "postgres.prod.internal" port = 5432 database_name = "app" username = "af_reader" password = var.prod_postgres_password # write-only ssl_mode = "REQUIRE" require_review_writes = true ai_analysis_enabled = true review_plan_id = accessflow_review_plan.standard.id }
Then the usual three commands — tofu works identically:
export ACCESSFLOW_ENDPOINT="https://accessflow.example.com" export ACCESSFLOW_API_KEY="af_…" terraform init terraform plan terraform apply
What the provider manages
| Resource | Manages |
|---|---|
accessflow_datasource | A governed database connection — host, credentials, SSL mode, and the review plan and AI config attached to it. |
accessflow_review_plan | An approval policy: AI review, human approval, minimum approvals, timeout, and the approver rows. |
accessflow_routing_policy | Attribute-based routing — auto-approve, auto-reject, require more approvals, or escalate. |
accessflow_row_security_policy | A row-level predicate, nested under a datasource. |
accessflow_masking_policy | A column masking strategy, nested under a datasource. |
accessflow_ai_config | An AI analyzer configuration. Its API key is write-only. |
accessflow_notification_channel | A notification channel — email, Slack, webhook, Discord, Telegram, Microsoft Teams or PagerDuty. Changing its type replaces the resource. |
Two data sources — accessflow_datasource and
accessflow_review_plan — look an existing object up by id.
Every resource supports terraform import. The two nested policies take a
composite id:
# Nested policies import as datasource_id/policy_id.
terraform import accessflow_row_security_policy.tenant_isolation \
11111111-2222-3333-4444-555555555555/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Running it from CI, without Terraform
If you only need one or two operations in a pipeline, the ready-made CI wrappers are lighter than standing up Terraform state. Both authenticate with the same service-account key.
include: - remote: "https://raw.githubusercontent.com/bablsoft/accessflow/main/ci-templates/gitlab/accessflow.gitlab-ci.yml" provision_prod_db: stage: provision extends: .accessflow_provision_datasource variables: ACCESSFLOW_ENDPOINT: "https://accessflow.example.com" AF_NAME: "prod-postgres" AF_DB_TYPE: "POSTGRESQL" AF_HOST: "postgres.prod.internal" AF_PORT: "5432" AF_DATABASE_NAME: "app" AF_USERNAME: "af_reader" AF_PASSWORD: "$PROD_DB_PASSWORD" AF_SSL_MODE: "REQUIRE"
Note the two names that are not AF_-prefixed:
ACCESSFLOW_ENDPOINT and ACCESSFLOW_API_KEY. Set the key as a
masked, protected variable rather than inline. The equivalent GitHub Actions are
provision-datasource and run-query; a run-query job submits
the SQL, waits for the decision, and executes it once approved.
Full argument reference for every resource, the CI action inputs and the release process live in Infrastructure as Code.