Documentation

Infrastructure as Code.

Infrastructure as Code (Terraform / OpenTofu & CI Actions)

Beyond the env-driven GitOps bootstrap, AccessFlow ships an official Terraform / OpenTofu provider (bablsoft/accessflow) and reusable GitHub Actions + a GitLab CI template for managing governance resources declaratively over the REST API. Both authenticate with an API key — the provider manages datasources, review plans, routing / row-security / masking policies, AI configs, and notification channels with the same authoritative-upsert semantics as the bootstrap reconciler.

Service-account API keys

A pipeline needs credentials without an interactive login. Bootstrap can seed a service account — an API-key-only user (password login disabled) whose raw key you supply from a Secret (only its hash is stored, rotated in place when it changes). Set these operator env vars (or the equivalent Helm bootstrap.serviceAccounts[] with an apiKeySecretRef):

  • ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_EMAIL
  • ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_DISPLAY_NAME
  • ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_ROLE — default ADMIN
  • ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_API_KEY_NAME
  • ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_API_KEY — the raw af_-prefixed token
  • ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_API_KEY_EXPIRES_AT — optional ISO-8601

You can also mint a key interactively at POST /api/v1/me/api-keys.

Terraform / OpenTofu provider

main.tf
terraform {
  required_providers {
    accessflow = { source = "bablsoft/accessflow" }
  }
}

provider "accessflow" {
  endpoint = "https://accessflow.example.com" # or ACCESSFLOW_ENDPOINT
  api_key  = var.accessflow_api_key            # or ACCESSFLOW_API_KEY
}

resource "accessflow_datasource" "prod" {
  name     = "prod-postgres"
  db_type  = "POSTGRESQL"
  host     = "postgres.prod.internal"
  port     = 5432
  ssl_mode = "REQUIRE"
}

Works with both tofu and terraform, and is published at registry.terraform.io/providers/bablsoft/accessflow (and the OpenTofu registry as bablsoft/accessflow). Write-only secrets (password, api_key, notification config values) are never returned by the API, so the provider applies changes to them but can't detect drift — treat the HCL as the source of truth.

Resources & data sources

The provider drives the existing REST endpoints — /datasources, /review-plans, /admin/routing-policies, /admin/ai-configs, /admin/notification-channels, and the nested /datasources/{id}/{row-security,masking}-policies. No AccessFlow-specific endpoints were added for it. Idempotency comes from Terraform state (create → store UUID → read / update / delete by id), matching the bootstrap reconciler's authoritative-upsert intent.

ResourceNotes
accessflow_datasourceCreate / read / update / delete, plus import by UUID
accessflow_review_planNested approvers and notify_channels
accessflow_routing_policycondition is the typed tree as a JSON string
accessflow_row_security_policyNested under a datasource; import as datasource_id/policy_id
accessflow_masking_policyNested under a datasource; import as datasource_id/policy_id
accessflow_ai_configapi_key is write-only
accessflow_notification_channelconfig map; channel_type is immutable and forces replacement

Two data sources — accessflow_datasource and accessflow_review_plan — look an existing resource up by id.

Local development

From terraform-provider/: make build compiles it, make test runs the unit tests with no live stack, make testacc runs acceptance tests against a real backend (needs ACCESSFLOW_ENDPOINT and ACCESSFLOW_API_KEY), and make docs regenerates the provider docs via tfplugindocs. To try an unreleased build, point a CLI dev override for bablsoft/accessflow at the go install-ed binary.

CI Actions

The provision-datasource and run-query GitHub composite actions (referenced as bablsoft/accessflow/.github/actions/<name>@v1) and the include-able GitLab template wrap provisioning a datasource and submitting a governed query from a pipeline. run-query waits for a terminal status and sends an X-AccessFlow-CI header so context-aware routing policies recognise the CI origin.

.github/workflows/migrate.yml
- id: ds
  uses: bablsoft/accessflow/.github/actions/provision-datasource@v1
  with:
    endpoint: https://accessflow.example.com
    api-key: ${{ secrets.ACCESSFLOW_API_KEY }}
    name: prod-postgres
    db-type: POSTGRESQL
    host: postgres.prod.internal
    port: "5432"
    database-name: app
    username: af_reader
    password: ${{ secrets.PROD_DB_PASSWORD }}
    ssl-mode: REQUIRE

- uses: bablsoft/accessflow/.github/actions/run-query@v1
  with:
    endpoint: https://accessflow.example.com
    api-key: ${{ secrets.ACCESSFLOW_API_KEY }}
    datasource-id: ${{ steps.ds.outputs.id }}
    sql: "SELECT count(*) FROM orders"

provision-datasource is idempotent — it looks the datasource up by name, then creates or updates it — so re-running a pipeline is safe. run-query fails the step on any terminal status other than EXECUTED, so a rejected or timed-out query breaks the build rather than passing silently. For unattended execution, pair it with an AUTO_APPROVE routing policy scoped to the CI origin.

On GitLab, include: the ci-templates/gitlab/accessflow.gitlab-ci.yml template and then extends: .accessflow_provision_datasource or .accessflow_run_query, passing the same values as AF_* variables.

Full provider reference, all resources, and the registry-publishing runbook (the provider is released to a dedicated terraform-provider-accessflow repo that opentofu.org and registry.terraform.io ingest) are in docs/16-iac.md.