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_EMAILACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_DISPLAY_NAMEACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_ROLE— defaultADMINACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_API_KEY_NAMEACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_0_API_KEY— the rawaf_-prefixed tokenACCESSFLOW_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
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.
| Resource | Notes |
|---|---|
accessflow_datasource | Create / read / update / delete, plus import by UUID |
accessflow_review_plan | Nested approvers and notify_channels |
accessflow_routing_policy | condition is the typed tree as a JSON string |
accessflow_row_security_policy | Nested under a datasource; import as datasource_id/policy_id |
accessflow_masking_policy | Nested under a datasource; import as datasource_id/policy_id |
accessflow_ai_config | api_key is write-only |
accessflow_notification_channel | config 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.
- 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.