Skip to content

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.


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

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_ca resource. 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 a post_rotation_actions entry. For the complete arguments, attributes and worked examples, see the provider resource docs: mazevault_ca, mazevault_certificate and mazevault_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) — a 404 from MazeVault removes the resource from state, so the next plan creates it again.
  • Destroy — a 404 counts 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. A terraform destroy removes the resource from Terraform state only; the role remains in MazeVault.
  • mazevault_organization — Organizations cannot be deleted via the MazeVault API. A terraform destroy removes the resource from Terraform state with a warning and sends no request; the organization remains in MazeVault.
  • mazevault_deployment — The bootstrap token is returned only at creation and kept in state (sensitive). terraform destroy soft-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) and max_uses (0 = unlimited) set the token's lifetime and use limit for mass rollout from packages; changing either creates a new deployment and token, and expires_at shows 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. The ocsp_url / crl_url attributes 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_group and mazevault_integration (project_id) and mazevault_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 (generate produces 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 (destroying mazevault_shared_secret deletes the share, and only the user who created it can do so). mazevault_project.organization_id and the display_name, description and permissions of mazevault_role (with an unchanged name) 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. Review terraform plan for must be replaced, and keep mazevault_secret.environment non-empty (an empty one is stored as dev, and the next plan would replace the secret).
  • mazevault_shared_secret — the provider reads the value of the secret named by secret_id when 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_url is sensitive: anyone holding the link can read the secret, so Terraform does not print it in plan or apply output. Read it with terraform output -raw or nonsensitive() 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 configured is_production and incident_auto_escalation match; 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_hours must be a positive multiple of 24 (the backend stores whole days as rotation_interval_days); other values fail at plan time. environment is 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_kind and linked_integrations are not stored by the backend; the state keeps the configured values.
  • mazevault_integration — The environment argument 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 — The config_json argument must be a valid JSON object. Use jsonencode() 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) — The environment, rotation_strategy, workflow_steps_json, scope, and grace_period_minutes arguments have been removed. Replace ttl_hours with rotation_interval_days. Run terraform state rm and re-import when upgrading from v1.x.
  • mazevault_certificate_rotation_config — No hard-delete endpoint exists. Destroying sets enabled = false and removes from state only.
  • mazevault_entra_rotation_config — No hard-delete endpoint exists. Destroying sets rotation_enabled = false and removes from state only.
  • mazevault_entra_integration_scope — Destroy is state-only: the backend cannot hold an empty selected_apps scope and the provider never widens the scope to legacy_all implicitly. Bindings are removed together with the owning mazevault_integration. The scope is re-sent only when the selected applications or scope_mode change (a write resets all bindings to pending); a failed Graph synchronisation keeps the scope tracked (synced = false) and is retried on the next apply.
  • mazevault_config_template — Requires the config_management license feature. The resource now follows the backend contract (template_type + definition); the former content/format arguments never worked against the API and were removed.
  • mazevault_ca_account — settings is plaintext metadata; secret-looking keys are rejected and must be supplied through the write-only credentials map.
  • mazevault_certificate — key_storage_mode = "external" needs a reachable secret-manager integration; Orchestrator Mode rejects local. private_key_pem is only populated for local custody. The full Key Vault reference of an offloaded key is exposed as private_key_reference; key_secret_name keeps the name you asked for even if the backend had to add a collision suffix.
  • mazevault_token — Requires a PoC or Enterprise license (feature token_management); the API returns HTTP 403 otherwise. Use token_provider for the provider label. New resources should use value_wo plus value_wo_version; legacy value is sensitive but remains in Terraform state. Import manages metadata only. In-place value renewal is done through the MazeVault UI/API. project_id is 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 = true on outputs exposing secret values or private keys.
  • Inject credentials via env vars — use MAZEVAULT_API_TOKEN or TF_VAR_ prefixed variables; never commit tokens in .tfvars.
  • Pin provider version — ~> 1.0 allows patch updates while blocking unexpected major changes.
  • Consistency groups — add mazevault_consistency_group after provisioning to detect cross-environment drift before it causes incidents.
  • Rotation scheduling — use rotation_interval_days and schedule (cron) to define rotation cadence (v2: ttl_hours/rotation_strategy removed). Post-rotation deployment targets are configured via mazevault_rotation_workflow or the more granular mazevault_secret_rotation_target (supports K8s, database password rotation, agent sync, and cloud vaults). For certificates use mazevault_certificate_rotation_config; for Entra ID credentials use mazevault_entra_rotation_config.
  • Separate state per environment — use workspaces or separate state backends per environment to prevent accidental cross-environment writes.