Terraform Provider
Everything an operator configures through Platform Admin, and everything an org admin configures
through Organization Settings, is reachable over the REST API — which means it can be managed as
code. OpenTremor-terraform-provider is a Terraform provider covering that surface.
It does not provision OpenTremor itself. Containers, Helm charts and MongoDB stay Deployment’s job; this provider configures an instance that is already running. The two compose naturally: deploy the instance, then point Terraform at it.
Resources
Section titled “Resources”| Resource | What it manages |
|---|---|
opentremor_organization | Tenants — creation, plan tier, lock state, org-wide 2FA policy |
opentremor_platform_settings | Instance-wide runtime settings (logging, registration, retention, GitHub) |
opentremor_pricing | The metered price list |
opentremor_organization_settings | An organization’s LLM spend cap and report-link TTL |
opentremor_api_key | Service accounts, with role and IP allowlist |
opentremor_sso_connection | An organization’s OIDC identity provider |
opentremor_scim_config | An organization’s SCIM 2.0 provisioning, including token rotation |
opentremor_team / opentremor_team_member | Teams and their membership |
opentremor_rule_category / opentremor_rule | Custom security rules and their grouping |
opentremor_report_template | Org-owned Jinja2 report templates |
Credentials
Section titled “Credentials”The provider authenticates as two of the five credential shapes described in Authentication — Access scope at a glance:
provider "opentremor" { base_url = "https://api.example.com"
# Organization-scoped resources. api_key = var.opentremor_api_key
# Platform-admin resources, and organization creation. admin_email = var.opentremor_admin_email admin_password = var.opentremor_admin_password}Set at least one. Which you need depends on the resources you use:
- Organization-scoped resources (
/orgs/{org_id}/*,/auth/keys) work with either. The API key is preferred when both are set — it is already bound to the right organization and needs no login round-trip. - Platform-admin resources (
/admin/*) requireadmin_email/admin_password. OpenTremor never grantsis_superadminto a stored API key, so no key can reach these — not even the bootstrap key, which is auto-superadmin but has nouser_id. - Creating an organization (
POST /orgs) requires the session specifically, because the endpoint checks for a realuser_idrather than for the superadmin flag.
One provider block, one organization
Section titled “One provider block, one organization”Both credential types are single-org, so every organization-scoped resource’s org_id defaults to
whichever organization the provider’s credential belongs to — resolved once per run via
GET /auth/me. There is nothing to configure in the common case.
Setting org_id to a different organization is rejected before the request goes out. That mirrors
require_same_org, which answers every cross-org request on /orgs/{org_id}/* with a 404 rather
than a 403 — deliberately, so one organization can never confirm another’s resources exist. The
provider raises the same refusal with an explanation attached.
To manage several organizations, use one provider alias per organization:
provider "opentremor" { alias = "acme" base_url = "https://api.example.com" api_key = var.acme_api_key}
resource "opentremor_team" "acme_platform" { provider = opentremor.acme name = "Platform"}A single block cannot span organizations, because re-scoping a session
(POST /auth/session/switch) mutates one shared cookie while Terraform applies resources
concurrently — a switch issued for one resource would silently retarget every in-flight request
for another.
Worked example
Section titled “Worked example”# Platform-wide, requires the admin session.resource "opentremor_platform_settings" "this" { overrides = jsonencode({ logging_level = "INFO" registration_allow_public_signup = false retention_audit_days = 730 })}
# Organization-scoped, works with the API key.resource "opentremor_organization_settings" "this" { monthly_budget_usd = 500 report_link_ttl_hours = 72}
resource "opentremor_sso_connection" "entra" { display_name = "Acme Corp" issuer = "https://login.microsoftonline.com/${var.tenant_id}/v2.0" client_id = var.client_id client_secret = var.client_secret default_role = "member" allowed_domains = ["acme.com"]}
resource "opentremor_rule_category" "storage" { name = "Storage & data"}
resource "opentremor_rule" "no_public_buckets" { title = "No publicly readable object storage" description = "Flag any bucket whose ACL or policy would let an unauthenticated principal read objects." category = opentremor_rule_category.storage.id severity = "HIGH" analyzers = ["terraform-plan"]}Platform settings are JSON, not typed attributes
Section titled “Platform settings are JSON, not typed attributes”opentremor_platform_settings.overrides takes a JSON object of flat {section}_{field} keys
rather than a fixed set of Terraform attributes. That is deliberate: the settings field vocabulary
is built at runtime from a registry that every installed domain contributes its own sections to
(see Data Retention for a section added exactly that way). A fixed
schema in the provider would silently refuse any field added on the server after the provider
binary was built.
The resource’s effective attribute reports every editable field with its value as actually in
force, which is the authoritative list for a given instance:
terraform state show opentremor_platform_settings.thisSettings that are excluded from this surface — storage, cors, auth, jwt, llm,
telemetry, deployment — stay file/env-driven and cannot be set here. GET /admin/settings
reports each exclusion with its reason.
Behaviour worth knowing before you rely on it
Section titled “Behaviour worth knowing before you rely on it”Some of these follow from what the API does or does not offer, rather than from a choice the provider made:
- Organizations cannot be renamed or deleted. There is no endpoint for either. Changing
namecreates a new organization;terraform destroydrops the resource from state and warns that the organization is left behind on the server. - API keys are replace-only, and cannot be imported. The raw key exists only in the create
response, and nothing about a stored key is editable. Any change to
name,roleorip_allowlistissues a new key and revokes the old one — hand the replacement out before applying, not after. - Secrets cannot be read back. An SSO
client_secretis stored encrypted and a SCIMtokenas a hash, so Terraform cannot detect drift on either. An imported SSO connection shows aclient_secretchange on its first plan (accurate — Terraform genuinely does not know the current value), and an imported SCIM config has no token until it is rotated. - SCIM provisioning and
opentremor_teamoverlap. A SCIM group maps one-to-one onto a team, so an IdP with provisioning enabled creates and deletes teams and memberships itself. Managing the same teams from Terraform as well puts two writers on one set of rows. - Enabling any custom rule for an analyzer replaces its built-in ruleset for that analyzer+variant — OpenTremor stops rendering the built-in bullets entirely. Manage the whole set for those analyzers rather than adding a single rule.
opentremor_pricingcannot be destroyed back to the defaults. There is no endpoint to clear a stored price list, and the API reports the built-in defaults and an identical custom list the same way. Destroy warns and leaves the tiers in force.
Not covered yet
Section titled “Not covered yet”The product-side configuration surface: LLM models and credentials, per-organization analyzer enablement, and integrations. Those live in OpenTremor Core rather than the platform layer and are the next batch, not an oversight.