Terraform
Provision ScaiVault resources — secrets, policies, rotation policies, PKI roles, dynamic engines — declaratively. The Terraform provider wraps the same REST API the SDKs use, so what you can do via curl you can do via tf apply.
Install the provider#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
For Terraform Cloud / Atlantis / CI runs, set SCAIVAULT_TOKEN as an environment variable. Don't put it in .tfvars.
Anti-pattern: managing secret values in Terraform#
1 2 3 4 5 6 7 | |
The secret value ends up in Terraform state, defeating the point of using ScaiVault. Two better patterns:
Pattern A: Provision the metadata (path, type, rotation policy) but not the value. Use lifecycle.ignore_changes on data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
The placeholder gets written once; subsequent rotations update the value without Terraform fighting them.
Pattern B: Generate via secret_policy. Terraform doesn't see the value at all:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
generate_from writes the secret by running the generator, returning only metadata to Terraform. The value never enters state.
What to manage in Terraform#
The boundary that works in practice:
| Resource | Manage in Terraform? | Why |
|---|---|---|
| Access policies | Yes | Versioned, reviewed, code-review-friendly |
| Policy bindings | Yes | Same |
| Rotation policies | Yes | Schedule and grace period are config, not data |
| Secret policies (value generation) | Yes | Templates are config |
| PKI roles | Yes | Config |
| PKI CAs | Yes (creation), No (root key) | The CA cert is config; the key stays in ScaiVault |
| Dynamic engines | Yes | Connection config |
| Dynamic roles | Yes | SQL templates are config |
| Federation backends | Yes | Connection config |
| Webhooks | Yes | URL and event list |
| Subscriptions | Sometimes | If long-lived, yes; if per-pod, manage from app |
| Secret values | No | Lives in ScaiVault; managed by services or rotation |
| Service accounts | In ScaiKey, not ScaiVault | Identity is upstream |
Example: production billing service setup#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
Reading secrets in Terraform#
There's a data source for read access, useful for fetching values that already exist to feed into other resources (e.g., into an AWS provider that needs an API key):
1 2 3 4 5 6 7 8 | |
The value lands in Terraform state. If your state file is backed by S3+KMS with restricted access, this is acceptable for build-time configuration. For values you don't want in state, don't read them via data source — have the application read them at runtime instead.
State backend security#
Whichever way you go, treat the Terraform state as sensitive material:
- Encrypted-at-rest backend (S3+KMS, Terraform Cloud with workspace-level encryption, GCS+CMEK).
- IAM scoped tightly — only Terraform runners and a small admin set.
- State lock backend (DynamoDB, gcs locks) — concurrent modification of policies is a bad time.
- No state files in git, even encrypted.
CI integration#
Typical pipeline:
terraform planruns on every PR. Output reviewed.- On merge,
terraform applyruns against the production workspace. - Token comes from a short-lived ScaiKey-issued JWT, scoped to whatever the runner needs (often quite broad — provisioning policies and rotation policies needs
adminfor that subset of operations). - After apply, Terraform Cloud runs a sanity-check job that reads back a few critical policies via
data.scaivault_policyand asserts shape.
Common questions#
"Can I import existing ScaiVault resources into Terraform?" Yes:
1 2 | |
The provider supports import for all resource types. Run terraform plan after import to see drift between state and config; close the gap by adjusting either side.
"What about workspaces?" Use Terraform workspaces (or Terraform Cloud workspaces) to separate environments. The provider config can be parameterized: tenant_id = var.tenant_id, set per-workspace.
"Drift?" Inevitable. A human flips a policy via the admin UI; Terraform reports drift on next plan. Either revert the change or codify it. Bias toward codifying — humans aren't authoritative about what's intended.
"How do I handle dependencies between resources?" Standard Terraform dependency tracking. scaivault_policy_binding depends on scaivault_policy via interpolation; Terraform sequences correctly. The provider's API doesn't have batch operations, so a large initial apply can take a few minutes for ~100 resources.
What's next#
- GitHub Actions — wiring CI/CD to read secrets.
- Kubernetes — once Terraform sets up the policies, runtime consumes them.
- Policies guide — what the rules and conditions actually mean.