Terraform Provider for MazeVault¶
The MazeVault Terraform Provider lets you manage your entire MazeVault infrastructure as code — projects, secrets, certificates, rotations, access control, and integrations with external systems.
- Registry: registry.terraform.io/providers/MazeVault/mazevault
- Requires: Terraform ≥ 1.11, MazeVault ≥ 1.0.48
- Full API reference: Terraform Registry documentation
Installation¶
terraform {
required_version = ">= 1.11"
required_providers {
mazevault = {
source = "MazeVault/mazevault"
version = "~> 1.0"
}
random = {
source = "hashicorp/random"
}
}
}
Provider Configuration¶
provider "mazevault" {
server_url = "https://vault.example.com"
api_token = var.mazevault_token # or env: MAZEVAULT_API_TOKEN
}
| Argument | Env variable | Description |
|---|---|---|
server_url |
MAZEVAULT_SERVER_URL |
MazeVault server base URL |
api_token |
MAZEVAULT_API_TOKEN |
API token (recommended) |
client_id |
MAZEVAULT_CLIENT_ID |
Service account OAuth2 client ID |
client_secret |
MAZEVAULT_CLIENT_SECRET |
Service account OAuth2 client secret |
timeout |
— | Request timeout, e.g. 30s (default) |
skip_tls_verify |
— | Skip TLS check — development only |
Secret handling
Never put credentials directly in .tf files. Use environment variables or a CI/CD secrets store. Never commit .tfvars files containing tokens or passwords.
Authentication¶
API Token (recommended)¶
export MAZEVAULT_SERVER_URL="https://vault.example.com"
export MAZEVAULT_API_TOKEN="mv_pat_xxxxx"
terraform plan
Generate tokens in Admin → API Tokens or with mazevault_api_token resource.
Service Account (CI/CD pipelines)¶
provider "mazevault" {
server_url = var.server_url
client_id = var.client_id
client_secret = var.client_secret
}
Example: External CA Account Settings¶
Use mazevault_ca_account.settings for non-secret CA provider configuration that MazeVault must reuse later during product sync, renewal polling, and DCV checks. Keep API keys, passwords, and EAB HMAC keys in the dedicated sensitive attributes, not in settings. Set provider_type to the documented lowercase value, such as digicert, letsencrypt, or acme; mixed case and surrounding whitespace are rejected before MazeVault receives the request.
resource "mazevault_ca_account" "digicert" {
organization_id = mazevault_organization.acme.id
name = "DigiCert Production"
provider_type = "digicert"
api_key = var.digicert_api_key
base_url = "https://www.digicert.com"
settings = {
organization_id = var.digicert_organization_id
server_platform_id = "45"
dcv_method = "dns_txt"
certificate_dcv_scope = "base_domain"
}
}
Example: Secrets Management with Azure Key Vault Sync¶
Provision a project, store a database password, and sync it to Azure Key Vault with automatic 30-day rotation.
resource "mazevault_organization" "acme" {
name = "Acme Corp"
}
resource "mazevault_project" "backend" {
organization_id = mazevault_organization.acme.id
name = "Backend Services"
type = "secret"
}
resource "mazevault_integration" "azure_kv" {
project_id = mazevault_project.backend.id
name = "Production Key Vault"
type = "azure_key_vault"
environment = "production"
config = {
vault_url = var.keyvault_url
use_managed_identity = "true"
}
}
ephemeral "random_password" "db" {
length = 40
special = true
override_special = "_-+=@#"
}
resource "mazevault_secret" "db_password" {
project_id = mazevault_project.backend.id
key = "POSTGRES_PASSWORD"
environment = "production"
value_wo = ephemeral.random_password.db.result
value_wo_version = 1
}
resource "mazevault_secret_link" "db_link" {
secret_id = mazevault_secret.db_password.id
integration_id = mazevault_integration.azure_kv.id
link_type = "database"
metadata = {
db_user = "app_user"
db_host = "db.prod.example.com"
db_name = "appdb"
}
}
resource "mazevault_rotation_config" "db" {
secret_id = mazevault_secret.db_password.id
rotation_interval_days = 30
enabled = true
notification_emails = ["ops@example.com"]
}
# Deploy the rotated value to a Kubernetes secret after each rotation.
resource "mazevault_secret_rotation_target" "k8s" {
secret_id = mazevault_secret.db_password.id
target_type = "kubernetes_secret"
priority = 10
enabled = true
config_json = jsonencode({
namespace = "production"
secret_name = "app-db-credentials"
secret_key = "POSTGRES_PASSWORD"
agent_id = "your-agent-uuid" # agent with K8s cluster access
})
}
# Also rotate the password on the database itself.
resource "mazevault_secret_rotation_target" "postgres" {
secret_id = mazevault_secret.db_password.id
target_type = "database_password"
priority = 20
enabled = true
config_json = jsonencode({
db_provider = "postgres"
host = "db.prod.example.com"
port = 5432
database = "appdb"
username = "app_user"
admin_credential_secret_id = "your-admin-secret-uuid"
})
}
State-safe secret values¶
For Terraform-managed secret material, prefer value_wo with Terraform 1.11+
ephemeral resources. The provider reads the value from configuration during
apply, sends it to MazeVault, and does not store it in Terraform plan or state.
Increment value_wo_version when a new write-only value should be sent.
ephemeral "random_password" "api" {
length = 48
special = true
override_special = "_-+=@#"
}
resource "mazevault_secret" "api_password" {
project_id = mazevault_project.backend.id
key = "API_PASSWORD"
environment = "production"
value_wo = ephemeral.random_password.api.result
value_wo_version = 1
}
The legacy value argument remains supported for existing configurations, but
Terraform stores sensitive values in state. Avoid reading secret material back
with the mazevault_secret data source when state must remain free of plaintext.
Server-side secret generation¶
Instead of supplying value, add a generate { ... } block and MazeVault
creates the value for you using cryptographically secure, DB/URL-safe
characters. Exactly one of value, value_wo, or generate must be set. The generated
plaintext is never returned to Terraform (it never enters state); in
Orchestrator Mode it is offloaded directly to the external secret manager.
resource "mazevault_secret" "api_key" {
project_id = mazevault_project.backend.id
key = "API_KEY"
environment = "production"
generate {
length = 48
include_symbols = true
}
}
generate field |
Default | Description |
|---|---|---|
length |
40 |
Length of the generated value |
include_upper |
true |
Include uppercase letters |
include_lower |
true |
Include lowercase letters |
include_digits |
true |
Include digits |
include_symbols |
true |
Include DB/URL-safe symbols |
excluded_chars |
— | Characters to exclude from the result |
Azure Key Vault and Entra integrations¶
The typed Azure DevOps azure_* fields are deprecated (Azure DevOps integrations
are no longer supported, the API refuses them) and will be removed in the next
major version of the provider. mazevault_integration also
accepts a generic non-secret config map and a write-only sensitive_config
map (secret values, never read back from the server), plus
interactive_auth_method. This enables Azure Key Vault and Microsoft Entra
integrations.
resource "mazevault_integration" "kv" {
project_id = mazevault_project.backend.id
name = "prod-keyvault"
type = "azure_key_vault"
provider_name = "azure_keyvault"
environment = "production"
config = {
vault_url = "https://myvault.vault.azure.net/"
auth_method = "client_secret"
tenant_id = var.tenant_id
client_id = var.client_id
}
sensitive_config = {
client_secret = var.client_secret
}
}
Entra ID credential rotation¶
post_rotation_actions is the single source of truth for post-rotation
delivery. If you define no action blocks, nothing is delivered. Key Vault
write-back and Spring Boot refresh are expressed as actions here — not as
separate top-level fields. The kv_integration_ids, secret_name,
spring_endpoints, and webhook_urls attributes are now read-only computed
mirrors of these actions.
resource "mazevault_entra_rotation_config" "app" {
credential_id = var.entra_credential_id
rotation_days_before_expiry = 30
post_rotation_actions {
type = "azure_keyvault"
config = {
integration_id = mazevault_integration.kv.id
secret_name = "app-client-secret"
}
}
post_rotation_actions {
type = "spring_actuator_refresh"
config = { actuator_url = "https://app.example.com/actuator/refresh" }
}
}
Example: Certificate Lifecycle Automation¶
Issue a TLS certificate via a CA account and set up automatic renewal.
A mazevault_certificate_template is a certificate-category Project Template:
it is created at the organization level (resolved from project_id) and appears
in the frontend Project Templates view for certificate-management projects
(type = "certificate").
resource "mazevault_project" "pki" {
organization_id = mazevault_organization.acme.id
name = "PKI"
type = "certificate"
}
resource "mazevault_ca_account" "digicert" {
organization_id = mazevault_organization.acme.id
name = "DigiCert Production"
provider_type = "digicert"
api_key = var.digicert_api_key
base_url = "https://www.digicert.com"
}
Certificate templates are certificate-category Project Templates
mazevault_certificate_template creates a Project Template with category
certificates at the organization level (resolved from project_id), so it
shows up in the Project Templates view. MazeVault automatically creates
the underlying certificate template, exposed as the computed
certificate_template_id. validity_period is a whole number of days,
written as <n>d (e.g. 365d) or in hours (e.g. 8760h); other values
are rejected at plan time; type is the certificate subtype (e.g. ssl_tls).
ACME CA accounts (Let's Encrypt / ZeroSSL / custom)¶
ACME providers use email and directory_url (and, when the CA uses External
Account Binding, eab_kid / eab_hmac_key) instead of api_key. Every
non-internal provider must supply credentials — the provider validates this
client-side and returns a clear error before calling the server if they are
missing. directory_url is required for a custom acme provider and is
defaulted automatically for letsencrypt and zerossl. The ACME challenge
policy is set on the certificate template via challenge_type.
resource "mazevault_ca_account" "letsencrypt" {
organization_id = mazevault_organization.acme.id
name = "Let's Encrypt"
provider_type = "letsencrypt"
email = "pki@example.com"
}
resource "mazevault_ca_account" "zerossl" {
organization_id = mazevault_organization.acme.id
name = "ZeroSSL"
provider_type = "zerossl"
email = "pki@example.com"
eab_kid = var.zerossl_eab_kid
eab_hmac_key = var.zerossl_eab_hmac_key
}
resource "mazevault_ca_account" "internal_acme" {
organization_id = mazevault_organization.acme.id
name = "Internal ACME"
provider_type = "acme"
email = "pki@example.com"
directory_url = "https://ca.internal.example.com/acme/acme/directory"
}
resource "mazevault_certificate_template" "acme_web" {
project_id = mazevault_project.pki.id
name = "ACME Web TLS"
type = "tls_server"
validity_period = "2160h"
# auto (default) | dns-01 | http-01 | tls-alpn-01
challenge_type = "dns-01"
}
For external commercial CAs that require domain control validation (for
example SSLMarket), the per-template validation method is set via dcv_method
(email, dns, or file). An empty value uses the provider/account default.
dcv_method is ignored by internal and ACME issuers.
Public TLS certificates are valid for at most 200 days, while an SSLMarket
order is a multi-year subscription: within it the CA issues follow-up
certificates by itself and MazeVault adopts them (same key, full deployment,
hand-over only after success) without placing a new paid order. Bind the
template to the account and product, choose the subscription length with
order_years, and set the technical contact the CA receives with every order:
resource "mazevault_ca_account" "sslmarket" {
organization_id = mazevault_organization.acme.id
name = "SSLmarket"
provider_type = "sslmarket"
api_key = var.sslmarket_api_token
settings = {
dv_auth_method = "dns"
order_wait_alert_hours = "24"
# Let MazeVault place the _dnsauth TXT records itself; without a DNS
# provider it opens a ticket in the organization ticketing integration.
dns_solver_type = "azure_dns"
dns_solver_config = jsonencode({ subscription_id = var.sub_id, resource_group = "rg-dns", zone = "example.cz" })
owner_name = "Example a.s."
owner_country = "CZ"
invoice_email = "invoices@example.cz"
tech_email = "pki@example.cz"
}
}
resource "mazevault_certificate_template" "sslmarket_dv" {
project_id = mazevault_project.pki.id
name = "Public TLS (SSLMarket)"
type = "ssl_tls"
validity_period = "4800h" # 200 days
ca_account_id = mazevault_ca_account.sslmarket.id
ca_product_id = var.sslmarket_product_id # from the synced catalog
dcv_method = "dns"
order_years = 2
tech_contact = {
firstname = "Jana"
lastname = "Nováková"
email = "pki@example.cz"
}
}
# A request may override the template for one certificate; empty = inherit.
resource "mazevault_certificate" "shop" {
common_name = "shop.example.cz"
project_id = mazevault_project.pki.id
template_id = mazevault_certificate_template.sslmarket_dv.certificate_template_id
order_years = 3
}
The resolved settings are validated against the product catalog before the
order is placed (validation method, SAN count, wildcard, maximum years), so an
invalid combination fails at terraform apply, never as a paid order. The
mazevault_certificate resource returns the pending certificate for such
asynchronous orders; MazeVault validates the domain and fills in the material
when the CA issues.
To use SSLMarket's ACME endpoint instead of the order API, set
settings = { mode = "acme", acme_product_code = "...", acme_certificate_validity_days = "90" }
on the account (HTTP-01 only); MazeVault provisions the ACME key and EAB
material on connect.
The organization's internal root CA can be managed with the
mazevault_caresource. Each organization has at most one internal CA; its parameters are immutable (changing them replaces the CA), and destroying the resource soft-deletes it — the delete is rejected while active certificates were issued by it.
resource "mazevault_ca" "org_root" {
organization_id = mazevault_organization.acme.id
name = "ACME Internal CA"
valid_years = 10
key_size = 4096
}
resource "mazevault_renewal_policy" "default" {
organization_id = mazevault_organization.acme.id
name = "30-day lead"
lead_days = 30
auto_approve = true
notify_emails = "ops@example.com"
}
resource "mazevault_certificate_template" "web_tls" {
project_id = mazevault_project.pki.id
name = "Web TLS 1Y"
type = "ssl_tls"
validity_period = "8760h"
key_usage = ["digitalSignature", "keyEncipherment"]
}
resource "mazevault_certificate" "api_tls" {
common_name = "api.example.com"
ttl = "8760h"
key_size = 2048
organization_ca_account_id = mazevault_ca_account.digicert.id
}
output "api_cert_pem" {
value = mazevault_certificate.api_tls.certificate_pem
sensitive = false
}
output "api_key_pem" {
value = mazevault_certificate.api_tls.private_key_pem
sensitive = true
}
Issuing from the organization internal CA and deploying to Key Vault: a certificate can be issued by the organization internal CA (
mazevault_ca), assigned to a project, and delivered into an Azure Key Vault on renewal via apost_rotation_actionsentry. For the complete arguments, attributes and worked examples, see the provider resource docs:mazevault_ca,mazevault_certificateandmazevault_certificate_rotation_config.
Reading a certificate with its issuer chain¶
Use the mazevault_certificate data source when another module needs the
public certificate together with the CA chain — for example to feed an
intermediate CA (and its root) into a trust store managed outside MazeVault:
data "mazevault_certificate" "issuing_ca" {
id = var.issuing_ca_certificate_id
}
resource "local_file" "issuing_ca_bundle" {
content = data.mazevault_certificate.issuing_ca.certificate_pem # leaf + issuer chain
filename = "${path.module}/issuing-ca-fullchain.pem"
}
certificate_pem and chain_pem always carry the effective issuer chain —
the same one the export API returns and agents deploy. A chain captured when the
certificate was imported or issued is returned unchanged (chain_source = "stored").
If no chain was stored, MazeVault looks for the issuer among the CA certificates
managed in the same organization and uses it only when it cryptographically
verifies the certificate (chain_source = "inventory"). chain_complete tells you
whether the chain ends at a self-signed root.
Example: RBAC and Service Account Setup¶
Create a CI/CD service account with read-only access to a specific project.
resource "mazevault_role" "ci_readonly" {
name = "ci-readonly"
description = "Read-only access for deployment pipelines"
permissions = ["secrets:read", "certificates:read", "projects:read"]
}
resource "mazevault_service_identity" "github_actions" {
display_name = "GitHub Actions CI"
description = "Read-only service account for production deployments"
owner_email = "platform@example.com"
}
resource "mazevault_service_identity_role" "github_actions_readonly" {
service_identity_id = mazevault_service_identity.github_actions.id
role_id = mazevault_role.ci_readonly.id
project_id = var.project_id
}
resource "mazevault_api_token" "ci_token" {
name = "github-actions-prod"
scopes = ["secrets:read", "certificates:read"]
}
output "ci_client_id" {
value = mazevault_service_identity.github_actions.client_id
}
output "ci_client_secret" {
value = mazevault_service_identity.github_actions.client_secret
sensitive = true
}
A service identity belongs to the organization of the provider's credentials; mazevault_service_identity exports it as organization_id. mazevault_service_identity_role gives it a role in that organization (omit project_id) or in one of its projects; a scope in another organization is rejected, and you can assign only permissions your own credentials hold in that scope.
Example: Sync Rules and Rotation Templates¶
Continuously pull secrets from Azure Key Vault into a MazeVault project and maintain a separate organization-scoped catalog entry for approved rotation settings.
No automatic application
The rotation template resource manages the catalog entry only. It does not automatically apply or inherit values into rotation configurations; configure each rotation resource explicitly.
Numeric fields
rotation_interval_days, lead_time_days, grace_period_days, max_retry_attempts, retry_delay_seconds, and timeout_minutes must be non-negative integers. An explicit 0 is a valid value and is preserved; omit a field to keep the server default (on create) or leave it unchanged (on update).
# Reusable rotation policy template
resource "mazevault_rotation_template" "standard_90d" {
name = "standard-90-day"
description = "Standard 90-day rotation with 14-day certificate lead time"
rotation_interval_days = 90
lead_time_days = 14
grace_period_days = 3
max_retry_attempts = 3
retry_delay_seconds = 30
timeout_minutes = 30
is_default = true
# project_id = mazevault_project.backend.id # optional: scope to one project
}
# Pull secrets from Azure KV into production environment
resource "mazevault_sync_rule" "azure_kv_pull" {
name = "azure-kv-production-pull"
project_id = mazevault_project.backend.id
integration_id = mazevault_integration.azure_kv.id
target_environment = "production"
source_path = "secrets/app/"
sync_direction = "pull"
sync_mode = "incremental"
conflict_strategy = "external_wins"
}
# Post-rotation action that targets a specific gateway
resource "mazevault_rotation_workflow" "db_with_gateway" {
secret_id = mazevault_secret.db_password.id
environment = "production"
rotation_interval_days = 90
post_rotation_actions {
type = "azure_keyvault"
order = 1
on_failure = "rollback"
gateway_id = "gateway-eu-west-01"
config = {
vault_url = "https://myvault.vault.azure.net"
secret_name = "db-password"
}
}
post_rotation_actions {
type = "spring_actuator_refresh"
order = 2
on_failure = "continue"
target_environment = "production"
config = {
url = "https://api.example.com/actuator/refresh"
}
}
}
Example: Environment Tiers¶
Define environment tiers for an organization. name and slug are immutable —
changing either forces the environment to be recreated. Only is_production and
incident_auto_escalation can be updated in place.
resource "mazevault_environment" "production" {
organization_id = mazevault_organization.acme.id
name = "production"
slug = "prod"
is_production = true
incident_auto_escalation = true
}
Example: External API Token Management¶
Many external systems (Jira, Signi, …) issue long-lived API tokens that can only be renewed
manually in their own UI. mazevault_token lets you register such a token with a mandatory
expiry date so MazeVault raises an incident before it lapses. The value is stored encrypted
(or offloaded to your secret manager in Orchestrator Mode). This feature requires a
PoC or Enterprise license. The UI workflow, alerting, and deployment targets are described
in the Token Management guide; the REST contract is in the
Tokens API.
resource "mazevault_token" "jira" {
name = "jira-automation-token"
token_provider = "jira" # free-text label; jira/signi get dedicated icons in the UI
description = "CI automation token for Jira Cloud"
value_wo = var.jira_token # Terraform 1.11+; absent from plan and state
value_wo_version = 1 # incrementing this replaces the token
expires_at = "2027-01-01T00:00:00Z" # mandatory
lead_time_days = 30 # alert 30 days before expiry
}
# Surface tokens that have entered their alert window.
data "mazevault_tokens" "all" {}
output "tokens_needing_renewal" {
value = [for t in data.mazevault_tokens.all.tokens : t.name if t.days_until_expiry <= t.lead_time_days]
}
When the token is renewed in the external system, update its value through the MazeVault UI/API
renew flow (which also re-deploys any configured agent targets). Changing value in Terraform
forces the resource to be recreated.
The deprecated value argument remains available for existing configurations, but Terraform
stores it in state. Do not transparently replace value with value_wo on an existing resource:
the change recreates the token, changes its ID, and deletes its MazeVault deployment targets.
Use value_wo for new tokens. For an intentional migration, inventory and recreate all targets in
a maintenance window. Editing HCL does not remove plaintext from historical remote-state versions;
rotate the external token and follow the state backend's retention and purge procedure.
Example: Onboarding a Fresh Installation¶
A new MazeVault installation starts with one organization and no policies. The
resources below turn it into a governed platform in a single terraform apply:
environments, organization-wide Configuration Management policies, Project
Templates, the internal root CA and a Smallstep step-ca account. The only step
Terraform cannot perform is the very first login and API-token creation
(the provider needs a token to run) — do that once with the bootstrap API or the
UI, then hand the token to the provider.
License
mazevault_config_template requires the config_management license
feature (PoC and Enterprise tiers). Other resources in this section work on
every tier.
Environments¶
locals {
environments = {
dev = { name = "DEV", production = false }
itg = { name = "ITG", production = false }
stg = { name = "STG", production = false }
tst = { name = "TST", production = false }
pre = { name = "PRE", production = false }
prod = { name = "PRO", production = true }
}
}
resource "mazevault_environment" "tier" {
for_each = local.environments
organization_id = var.organization_id
name = each.value.name
slug = each.key
is_production = each.value.production
}
Configuration Management policies¶
Policy documents are typed: template_type selects the backend validator and
definition carries the document. Validation errors are reported at plan time
with the offending field, so a wrong policy never reaches the database.
resource "mazevault_config_template" "naming" {
organization_id = var.organization_id
name = "Corporate naming convention"
template_type = "naming_convention"
definition = jsonencode({
patterns = {
project = "{company}-{env}-{project}"
key_vault = "{company}{env}{project}kv{index}"
secret = "{project}-{purpose}"
}
non_prod_prefix = "npr"
non_prod_environments = ["dev", "itg", "stg", "tst", "pre"]
variables = [
{ name = "company", required = true, max_length = 4, pattern = "^[a-z]{2,4}$" },
{ name = "env", required = true },
{ name = "project", required = true, max_length = 12 },
{ name = "purpose", required = false },
{ name = "index", required = false, default = "01" },
]
resource_constraints = {
key_vault = { max_length = 24, allowed_chars = "^[a-z0-9-]+$" }
}
})
}
resource "mazevault_config_template" "secret_patterns" {
organization_id = var.organization_id
name = "Password assignments"
template_type = "secret_pattern"
definition = jsonencode({
patterns = [{ name = "password-assignment", type = "regex", regex = "(?i)password\\s*=", severity = "high" }]
entropy_threshold = 3.5
min_length = 8
max_length = 128
})
}
Supported template_type values: naming_convention, secret_pattern,
secret_naming_policy, discovery_policy, exclusion_rule,
resource_visibility_policy (administrators only) and parser.
Project Templates¶
Project Templates pre-configure how new projects are created. Secret- and
key-category templates use mazevault_project_template; certificate-category
templates use mazevault_certificate_template, which also provisions the
issuing certificate template.
resource "mazevault_project_template" "secrets_kv" {
organization_id = var.organization_id
name = "Secrets — Azure Key Vault"
category = "secrets"
subtype = "secrets_manager"
protocol_config = jsonencode({ provider = "azure_key_vault", rotation_days = 30 })
}
Internal root CA and Smallstep step-ca¶
resource "mazevault_ca" "root" {
organization_id = var.organization_id
name = "Corporate Root CA"
key_algorithm = "RSA"
key_size = 4096
valid_years = 10
}
resource "mazevault_ca_account" "stepca" {
organization_id = var.organization_id
name = "Shared step-ca"
provider_type = "smallstep"
settings = {
server_url = var.step_ca_url
provisioner_name = "mazevault"
provisioner_type = "JWK"
root_fingerprint = var.step_ca_root_fingerprint
default_ttl = "24h"
}
credentials_wo = {
provisioner_password = var.step_ca_provisioner_password
}
credentials_wo_version = 1
}
Secrets never go into settings
settings is stored as plaintext CA account metadata and returned by the
API. Put passwords, tokens and keys into credentials_wo (write-only,
Terraform 1.11+, never stored in the Terraform state; bump
credentials_wo_version to re-send rotated values) or, on older Terraform,
into the sensitive credentials map — the provider rejects secret-looking
keys in settings at plan time.
Entra ID: select applications into a project and rotate their client secret¶
resource "mazevault_integration" "entra" {
project_id = mazevault_project.identity.id
name = "Entra ID — corporate tenant"
type = "identity_provider"
provider_name = "entra_id"
environment = "prod"
config = {
tenant_id = var.tenant_id
client_id = var.automation_client_id
auth_method = "client_secret"
}
sensitive_config = { client_secret = var.automation_client_secret }
}
resource "mazevault_entra_integration_scope" "payments" {
integration_id = mazevault_integration.entra.id
selected_apps = [{
app_id = var.payments_app_client_id
object_id = var.payments_app_object_id
display_name = "APP_PAYMENTS_PROD"
}]
}
data "mazevault_entra_app_registration" "payments" {
integration_id = mazevault_integration.entra.id
app_id = var.payments_app_client_id
depends_on = [mazevault_entra_integration_scope.payments]
}
resource "mazevault_entra_rotation_config" "payments" {
credential_id = data.mazevault_entra_app_registration.payments.primary_credential_id
staged_rotation_enabled = true
post_rotation_actions {
type = "azure_keyvault"
order = 1
config = {
integration_id = mazevault_integration.kv.id
secret_name = "payments-client-secret"
}
}
}
The scope resource synchronises the selected applications from Microsoft Graph
after each apply, so the data source can resolve primary_credential_id in the
same run. The automation identity configured on the integration needs the
Application.ReadWrite.OwnedBy application permission and ownership of the
selected applications — never use an application that is itself a rotation
target as the automation identity.
Private key custody in Orchestrator Mode¶
Instances running in Orchestrator Mode store no private keys locally. Ask the backend to offload server-side generated keys to a secret-manager integration:
resource "mazevault_certificate" "app" {
common_name = "app.example.com"
project_id = mazevault_project.pki.id
template_id = mazevault_certificate_template.web_tls.certificate_template_id
key_storage_mode = "external"
key_integration_id = mazevault_integration.kv.id
}
Resources Reference¶
Full attribute documentation for each resource is available on the Terraform Registry.
| Resource | Description |
|---|---|
mazevault_organization |
Top-level organization |
mazevault_project |
Project (RBAC boundary, groups secrets/certs) |
mazevault_project_settings |
Per-project operational settings (retention, sync, notifications) |
mazevault_secret |
Encrypted secret with optional rotation |
mazevault_secret_link |
Links a secret to an external integration for write-back |
mazevault_shared_secret |
One-time share link for a secret |
mazevault_rotation_config |
Full rotation pipeline configuration for a secret (v2: uses rotation_interval_days instead of legacy ttl_hours/rotation_strategy) |
mazevault_rotation_workflow |
Simple rotation schedule for a secret |
mazevault_rotation_template |
Reusable rotation policy template |
mazevault_secret_rotation_target |
Structured deployment target in a secret's rotation pipeline (K8s, DB, Agent, cloud vault) |
mazevault_certificate_rotation_config |
Automatic renewal and rotation configuration for a specific certificate |
mazevault_entra_rotation_config |
Entra ID credential rotation scheduling (days-before-expiry, staged rotation) |
mazevault_sync_rule |
Synchronization rule between MazeVault and an external KV/secrets provider |
mazevault_certificate |
Certificate issued by or tracked in MazeVault |
mazevault_certificate_template |
Reusable certificate issuance policy |
mazevault_ca |
Organization internal root Certificate Authority (singleton) |
mazevault_ca_account |
CA account integration or reference for external providers, self-hosted providers, and internal CA account references |
mazevault_renewal_policy |
Automatic certificate renewal timing policy |
mazevault_integration |
External secrets store / deployment target (Azure KV, AWS SM, K8s, …) |
mazevault_integration_group |
Multi-environment integration routing |
mazevault_consistency_group |
Cross-environment secret parity monitoring |
mazevault_role |
Custom RBAC role |
mazevault_group_mapping |
Maps external IdP group to a MazeVault role |
mazevault_user |
User account |
mazevault_user_role |
Assigns a role to a user in a project |
mazevault_service_identity |
Machine identity (OAuth2 client) |
mazevault_service_identity_role |
Assigns a role to a service identity in its organization or one of its projects |
mazevault_api_token |
Scoped API token |
mazevault_identity_provider |
Entra ID / LDAP SSO provider |
mazevault_environment |
Environment definition |
mazevault_approval_policy |
Approval workflow for sensitive operations |
mazevault_keytab |
Kerberos keytab for agent authentication |
mazevault_token |
External API token (Jira, Signi, …) with mandatory expiry tracking |
mazevault_deployment |
Agent deployment bootstrap package |
mazevault_config_template |
Config-management template (typed JSON definition: secret pattern, parser, exclusion rule, discovery or visibility policy) |
mazevault_project_template |
Project Template of category secrets or keys |
mazevault_entra_integration_scope |
Selects the Entra ID applications a project integration manages |
Data Sources Reference¶
| Data Source | Description |
|---|---|
mazevault_organization |
Look up an organization by ID |
mazevault_project |
Look up a project by ID |
mazevault_secret |
Read a secret value (sensitive) |
mazevault_certificate |
Read a certificate by ID |
mazevault_project_certificates |
List certificates in a project |
mazevault_project_cas |
List CA accounts in a project |
mazevault_project_certificate_templates |
List certificate templates in a project |
mazevault_project_csrs |
List pending CSRs in a project |
mazevault_environments |
List environments in an organization |
mazevault_ca_accounts |
List CA accounts in an organization |
mazevault_users |
List all users |
mazevault_roles |
List all RBAC roles |
mazevault_integrations |
List integrations in a project |
mazevault_audit_logs |
Read audit log entries |
mazevault_rotation_executions |
List rotation execution records |
mazevault_renewal_queue |
List certificates pending renewal |
mazevault_consistency_status |
Cross-environment consistency check result |
mazevault_rotation_resources |
List all rotation-managed resources with their status |
mazevault_rotation_resource_history |
Read rotation execution history for a specific resource |
mazevault_project_rotation_configs |
List all rotation configs within a project |
mazevault_tokens |
List managed external API tokens with their expiry status |
mazevault_entra_app_registration |
Resolve a synchronised Entra application and its credential IDs |
Importing Existing Resources¶
Bring existing MazeVault resources under Terraform management with terraform import. Resource UUIDs are visible in the UI URL or via GET /api/v1/....
terraform import mazevault_project.backend <project-uuid>
terraform import mazevault_secret.db_password <secret-uuid>
terraform import mazevault_certificate.tls <certificate-uuid>
terraform import mazevault_integration.azure_kv <integration-uuid>
# resources scoped by a parent use "<parent_id>/<id>":
terraform import mazevault_deployment.edge <organization-uuid>/<deployment-uuid>
terraform import mazevault_certificate_template.web <project-uuid>/<template-uuid>
terraform import mazevault_shared_secret.handover <share-uuid>/<secret-uuid>
Objects Deleted Outside Terraform¶
Every resource follows the same rule when the object it manages is gone:
- Refresh (
terraform plan/apply) — a404from MazeVault removes the resource from state, so the next plan creates it again. - Destroy — a
404counts as already deleted and the destroy succeeds. - Any other error (
401,403,5xx, network) is reported and the resource stays in state; nothing is recreated because of a temporary failure.
Known Limitations¶
mazevault_role— Roles cannot be deleted via the MazeVault API. Aterraform destroyremoves the resource from Terraform state only; the role remains in MazeVault.mazevault_organization— Organizations cannot be deleted via the MazeVault API. Aterraform destroyremoves the resource from Terraform state with a warning and sends no request; the organization remains in MazeVault.mazevault_deployment— The bootstraptokenis returned only at creation and kept in state (sensitive).terraform destroysoft-deletes the deployment record and invalidates the enrollment token; agents already registered from it keep running. Destroying a record deleted externally is idempotent.expires_in_days(default 30,0= never) andmax_uses(0= unlimited) set the token's lifetime and use limit for mass rollout from packages; changing either creates a new deployment and token, andexpires_atshows the resulting expiry.mazevault_ca— Manages the organization's single internal root CA (organization_id). All parameters are immutable; changing any of them replaces the CA. Destroying the resource soft-deletes the internal CA, but the delete is rejected (HTTP 409) while active (non-revoked, non-expired) certificates were issued by it. Theocsp_url/crl_urlattributes are read-only and reflect the URLs published for issued certificates.- Attributes the API cannot change force replacement or fail the plan —
mazevault_group_mapping(group_name,role_id),mazevault_secret_link(all arguments),mazevault_shared_secret(ttl_hours,max_views),mazevault_api_token(name,scopes,duration),mazevault_secret(project_id,key,environment),mazevault_integration_group,mazevault_consistency_groupandmazevault_integration(project_id) andmazevault_hsm_device(provider_type) plan destroy and create when changed, and so does every resource that references the replaced one. Replacing a secret deletes its version history and writes the configured value again (generateproduces a new random value; a value rotated on the server reverts), replacing an API token revokes the old token, and a replaced share link is revoked (destroyingmazevault_shared_secretdeletes the share, and only the user who created it can do so).mazevault_project.organization_idand thedisplay_name,descriptionandpermissionsofmazevault_role(with an unchangedname) cannot be changed at all: the plan fails, because a replacement would delete the project's secrets or cannot succeed for a role the API can neither change nor delete. Reviewterraform planformust be replaced, and keepmazevault_secret.environmentnon-empty (an empty one is stored asdev, and the next plan would replace the secret). mazevault_shared_secret— the provider reads the value of the secret named bysecret_idwhen it creates the share and never stores it in state; a secret whose value the server cannot read (zero-knowledge, names-only, external) cannot be shared.share_urlis sensitive: anyone holding the link can read the secret, so Terraform does not print it in plan or apply output. Read it withterraform output -rawornonsensitive()where you hand it over.mazevault_environment— When the backend refuses the create because an environment with the same name or slug already exists, the provider adopts it only if its configuredis_productionandincident_auto_escalationmatch; otherwise the apply fails and names the differing flags. Import the environment (terraform import <address> <organization_id>/<name>) or change the configuration; nothing is changed on the existing environment.mazevault_rotation_workflow—ttl_hoursmust be a positive multiple of 24 (the backend stores whole days asrotation_interval_days); other values fail at plan time.environmentis sent with an update right after the create (the create endpoint does not take it) and read back from the rotation resource.rotation_strategy,target_environment,resource_kindandlinked_integrationsare not stored by the backend; the state keeps the configured values.mazevault_integration— Theenvironmentargument is required and forces resource replacement on change. The backend does not accept environment changes via update; always recreate when changing environment.mazevault_secret_rotation_target— Theconfig_jsonargument must be a valid JSON object. Usejsonencode()in Terraform. Supplying malformed JSON produces a diagnostic error at apply time. Read operations use a list-scan (no direct GET-by-ID endpoint). Destroying a target that has already been deleted externally is idempotent and succeeds silently.mazevault_rotation_config(v2.0 BREAKING) — Theenvironment,rotation_strategy,workflow_steps_json,scope, andgrace_period_minutesarguments have been removed. Replacettl_hourswithrotation_interval_days. Runterraform state rmand re-import when upgrading from v1.x.mazevault_certificate_rotation_config— No hard-delete endpoint exists. Destroying setsenabled = falseand removes from state only.mazevault_entra_rotation_config— No hard-delete endpoint exists. Destroying setsrotation_enabled = falseand removes from state only.mazevault_entra_integration_scope— Destroy is state-only: the backend cannot hold an emptyselected_appsscope and the provider never widens the scope tolegacy_allimplicitly. Bindings are removed together with the owningmazevault_integration. The scope is re-sent only when the selected applications orscope_modechange (a write resets all bindings topending); a failed Graph synchronisation keeps the scope tracked (synced = false) and is retried on the next apply.mazevault_config_template— Requires theconfig_managementlicense feature. The resource now follows the backend contract (template_type+definition); the formercontent/formatarguments never worked against the API and were removed.mazevault_ca_account—settingsis plaintext metadata; secret-looking keys are rejected and must be supplied through the write-onlycredentialsmap.mazevault_certificate—key_storage_mode = "external"needs a reachable secret-manager integration; Orchestrator Mode rejectslocal.private_key_pemis only populated forlocalcustody. The full Key Vault reference of an offloaded key is exposed asprivate_key_reference;key_secret_namekeeps the name you asked for even if the backend had to add a collision suffix.mazevault_token— Requires a PoC or Enterprise license (featuretoken_management); the API returns HTTP 403 otherwise. Usetoken_providerfor the provider label. New resources should usevalue_woplusvalue_wo_version; legacyvalueis sensitive but remains in Terraform state. Import manages metadata only. In-place value renewal is done through the MazeVault UI/API.project_idis immutable, and any Terraform replacement also deletes the token's MazeVault deployment targets.
Best Practices¶
- Remote state — use Terraform Cloud, Azure Blob Storage, or S3 with state locking.
- Sensitive outputs — always set
sensitive = trueon outputs exposing secret values or private keys. - Inject credentials via env vars — use
MAZEVAULT_API_TOKENorTF_VAR_prefixed variables; never commit tokens in.tfvars. - Pin provider version —
~> 1.0allows patch updates while blocking unexpected major changes. - Consistency groups — add
mazevault_consistency_groupafter provisioning to detect cross-environment drift before it causes incidents. - Rotation scheduling — use
rotation_interval_daysandschedule(cron) to define rotation cadence (v2:ttl_hours/rotation_strategyremoved). Post-rotation deployment targets are configured viamazevault_rotation_workflowor the more granularmazevault_secret_rotation_target(supports K8s, database password rotation, agent sync, and cloud vaults). For certificates usemazevault_certificate_rotation_config; for Entra ID credentials usemazevault_entra_rotation_config. - Separate state per environment — use workspaces or separate state backends per environment to prevent accidental cross-environment writes.