Guides

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:

create the secret
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:

values.yaml
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.

Just evaluating? Any user can mint a key for themselves at Profile settingsAPI keys. The raw value is shown once. It works fine for a first 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.

main.tf
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.

main.tf
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:

apply
export ACCESSFLOW_ENDPOINT="https://accessflow.example.com"
export ACCESSFLOW_API_KEY="af_…"

terraform init
terraform plan
terraform apply
Secrets do not appear in a plan. Database passwords, AI API keys and notification channel config are write-only: AccessFlow never returns them, so Terraform cannot detect that one changed outside its state. Change the value in your variable and it is applied; change it in the AccessFlow UI and Terraform will not notice.

What the provider manages

ResourceManages
accessflow_datasourceA governed database connection — host, credentials, SSL mode, and the review plan and AI config attached to it.
accessflow_review_planAn approval policy: AI review, human approval, minimum approvals, timeout, and the approver rows.
accessflow_routing_policyAttribute-based routing — auto-approve, auto-reject, require more approvals, or escalate.
accessflow_row_security_policyA row-level predicate, nested under a datasource.
accessflow_masking_policyA column masking strategy, nested under a datasource.
accessflow_ai_configAn AI analyzer configuration. Its API key is write-only.
accessflow_notification_channelA 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.

Not covered by the provider. API connectors and deployment pipelines are configured in the app, not in HCL. Their runtime half has its own automation: see Gate a CI/CD pipeline.

Every resource supports terraform import. The two nested policies take a composite id:

import
# 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.

.gitlab-ci.yml
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.