# Terraform Provider

Manage part of HelixIAM as code — version your **applications**, **realm roles**, **EU Digital Identity Wallet connections** and the wallet features built on them in the same workflow as the rest of your infrastructure.

## What it is

`terraform-provider-helix` lets you declare Helix resources in HCL and reconcile them with `terraform apply`. Configuration becomes reviewable, repeatable, and promotable between environments — no click-ops drift.

The provider ships these resources:

| Resource | Manages |
| --- | --- |
| `helix_application` | An [application](../manage/applications.md) and its OIDC/SAML client |
| `helix_realm_role` | A [realm role](../manage/roles.md) |
| `helix_wallet_identity_provider` | An [EU Digital Identity Wallet](../federation/eudi-wallet.md) (OpenID4VP verifier) identity-provider connection (*Beta*) |
| `helix_client_verified_claims` | A client's [verified-claims release policy](verified-claims.md) (*Preview*) |
| `helix_client_verification_api` · `helix_verification_template` | A client's [verification API](verification-api.md) settings and templates (*Preview*) |
| `helix_wallet_profile_settings` | A verify-only wallet connection's [step-up and profile settings](../federation/wallet-step-up-and-profile.md) (*Preview*) |
| `helix_credential_issuer` · `helix_credential_template` | The realm's [credential issuer](../federation/credential-issuance.md) settings and templates (*Preview*) |

!!! note "Scope today"
    Realms, standalone OIDC clients, users, flows, agents, trusted list sources, verifier access keys, credential issuer keys and offers, and identity-provider connections other than the wallet one are **not yet** Terraform resources. Manage those in the admin console or the [admin API](api-reference.md), or move them between environments with [import / export](import-export.md). The provider is not published to the Terraform Registry; it is available to licensed customers from KubeDNA B.V. ([contact@helixiam.com](mailto:contact@helixiam.com)), with instructions for a local install.

## Authentication

The provider authenticates with **OAuth2 client credentials**. The realm is set once on the provider, and resources are created within it. Configure it inline or via environment variables (`HELIX_ISSUER`, `HELIX_REALM`, `HELIX_CLIENT_ID`, `HELIX_CLIENT_SECRET`), which take over when the matching argument is omitted.

```hcl
terraform {
  required_providers {
    helix = {
      source = "melmarzgioui/helix"
    }
  }
}

provider "helix" {
  issuer        = "https://auth.example.com/realms/acme" # the realm issuer that mints the admin token
  realm         = "acme"
  client_id     = "terraform"
  client_secret = var.helix_client_secret # or HELIX_CLIENT_SECRET
}
```

!!! tip "Use a dedicated automation client"
    Create a confidential client for Terraform with just the admin scopes it needs, and supply the secret via `HELIX_CLIENT_SECRET` so it never lands in state or VCS.

## Usage

Declare an application (which owns its OIDC/SAML client) and a realm role in the provider's realm. The resource arguments mirror the admin API bodies documented in the [API reference](api-reference.md).

```hcl
# An application — owns its OIDC/SAML client, shared subject claim, and login flow
resource "helix_application" "web" {
  name            = "acme-web"
  display_name    = "Acme Web"
  subject_claim   = "email"
  auth_flow_alias = "browser"
  enabled         = true
}

# A realm role
resource "helix_realm_role" "billing_admin" {
  name        = "billing-admin"
  description = "Manage billing"
}
```

```bash
terraform init
terraform plan
terraform apply
```

### `helix_application`

| Argument | Required | Description |
| --- | --- | --- |
| `name` | yes | Application name (also the resource id) |
| `display_name` | no | Human-friendly name shown in the console |
| `description` | no | Free-text description |
| `subject_claim` | no | Claim used as the token subject (e.g. `email`) |
| `auth_flow_alias` | no | Authentication flow bound to this app |
| `enabled` | no | Whether the application is enabled (default `true`) |

### `helix_realm_role`

| Argument | Required | Description |
| --- | --- | --- |
| `name` | yes | Role name (also the resource id) |
| `description` | no | Free-text description |
| `role_id` | computed | Server-assigned role id |

### `helix_wallet_identity_provider`

An EU Digital Identity Wallet connection: an identity provider with protocol `eudi-wallet`. Every attribute maps to a key of the connection's `config` map ([configuration keys](../federation/eudi-wallet.md#configuration-keys)); an attribute left unset is left out of the write, so the server's own default applies and `terraform plan` shows no drift for it. Import an existing connection with `terraform import helix_wallet_identity_provider.<name> <alias>`. A `400` from the admin API becomes one diagnostic per field, on the right attribute where known.

```hcl
resource "helix_wallet_identity_provider" "eudi_wallet" {
  alias        = "eudi-wallet"
  display_name = "EU Digital Identity Wallet"
  enabled      = true

  access_key_ref     = "software:acme/primary" # an imported verifier key
  trust_anchors      = file("${path.module}/pid-issuer-ca.pem")
  credential_formats = ["dc+sd-jwt", "mso_mdoc"]

  pid_requested_claims = ["given_name", "family_name", "birthdate"]
  minimum_loa          = "http://eidas.europa.eu/LoA/high"
}
```

Terraform does not manage the verifier access key: import it first ([step 1](../federation/eudi-wallet.md#step-1-import-the-verifier-access-key)) and reference it with `access_key_ref`.

| Attribute | Required | Description |
| --- | --- | --- |
| `alias` | yes, forces replace | Connection alias, unique in the realm (also the resource id) |
| `display_name` / `enabled` | no | Label on the console and login page; enabled (default `true`) |
| `access_key_ref` | recommended | Verifier key reference (e.g. `software:acme/primary`); never written to state |
| `access_certificate_chain` | no | PEM chain, leaf first, no root |
| `access_certificate_private_key` | no, sensitive | A raw PEM key instead of `access_key_ref`. The server accepts it only in the `dev` profile. Write-only against the API but **stored in plaintext in Terraform state**; removing it from the configuration clears it on the server |
| `client_id_scheme` / `client_id_host` | no | `x509_hash` (default) or `x509_san_dns` with its DNS host |
| `registration_certificate` | no | PEM bundle for OpenID Federation registration |
| `invocation_scheme` / `response_mode` / `request_uri_method` / `transaction_ttl_seconds` | no | Request settings (defaults `haip-vp`, `direct_post.jwt`, `get`, `300`) |
| `credential_formats`, `pid_sd_jwt_vcts`, `pid_mdoc_doctype`, `pid_requested_claims` | no | What the wallet is asked for |
| `custom_credential_queries` | no | Not supported yet (must stay empty) |
| `trust_anchors` | no | PEM bundle of trusted PID-issuer CAs |
| `trusted_lists` / `trusted_list_service_types` | no | Names of the realm's [trusted list sources](../federation/trusted-lists.md) whose anchors are trusted in addition to `trust_anchors` (each must exist in the realm), optionally narrowed to service type URIs |
| `dcql_trusted_authorities` | no | HAIP `trusted_authorities` in the DCQL query. Unset: on for `x509_hash` + `direct_post.jwt`, off otherwise. `true` requires every anchor to carry a SubjectKeyIdentifier |
| `dc_api_enabled` / `dc_api_request` / `dc_api_expected_origins` | no | The [W3C Digital Credentials API](../federation/eudi-wallet.md#the-w3c-digital-credentials-api) (server default off; `signed`, `unsigned` or `multisigned`; expected origins) |
| `dc_api_additional_key_ref` | no | The second realm key of a [multi-signed](../federation/wallet-transaction-confirmation.md#multi-signed-digital-credentials-api-requests) request (*Preview*) |
| `verifier_info_source` / `verifier_info_format` / `verifier_info_json` | no | OpenID4VP [`verifier_info`](../federation/wallet-transaction-confirmation.md#verifier_info) (*Preview*) |
| `transaction_data_types` | no | Verify-only: the [transaction types](../federation/wallet-transaction-confirmation.md) the connection may confirm (*Preview*) |
| `credentials` / `credential_sets` | no | JSON strings (`jsonencode([...])`): [credentials beyond the PID](../federation/wallet-credentials.md) and how they combine; read back without drift when the server's canonical JSON means the same (*Preview*) |
| `holder_binding_same_key` | no | Refuse presentations bound to different holder keys (server default `false`) |
| `did_issuers` | no | PID issuers identified by a [DID](../federation/dids.md) (full DIDs, or a bare domain accepting only `did:web:<domain>`); per credential: `didIssuers` inside `credentials` (*Preview*) |
| `revocation_checking` | no | Issuer certificate revocation, default `HARD_FAIL` |
| `status_list_required` / `status_list_on_unavailable` / `status_list_max_cache_seconds` / `status_list_allowed_hosts` | no | Token Status List policy |
| `subject_strategy` / `subject_acknowledge_weak_subject` | no | `ADMINISTRATIVE_NUMBER` (default), `ATTRIBUTE_TUPLE` (needs the acknowledgement) or `NONE` ([verify-only](../federation/eudi-wallet.md#verify-only-step-up)) |
| `verify_linked_provider` / `verify_released_claims` | with `NONE` | The wallet sign-in connection whose links prove whose wallet it is (required with `NONE`), and the claims released as `verified_claims` |
| `minimum_loa` / `clock_skew_seconds` / `allow_legacy_vc_sd_jwt_typ` | no | Defaults `http://eidas.europa.eu/LoA/high`, `60`, `false` |
| `limits_max_jwt_bytes` / `limits_cbor_max_depth` / `limits_cbor_max_items` / `limits_cbor_max_bytes` | no | Parser bounds |
| `id` | computed | The connection alias |

!!! warning "Keep state encrypted if you use `access_certificate_private_key`"
    `Sensitive` only redacts the value from CLI output and logs; the state file holds it in plaintext. Prefer `access_key_ref`, which never reaches state. If you do set the key, keep state in an encrypted remote backend.

### `helix_client_verified_claims`

*Preview.* Which wallet-verified claims an OIDC client receives as `verified_claims`, and where ([Verified data for applications](verified-claims.md)). The admin API refuses it unless the provider's service account is a realm admin.

```hcl
resource "helix_client_verified_claims" "portal" {
  client_id        = "citizen-portal"
  allowed_claims   = ["given_name", "age_over_18", "qualification.profession"]
  deliver_userinfo = true
  max_age_seconds  = 900
}
```

| Attribute | Required | Description |
| --- | --- | --- |
| `client_id` | yes, forces replace | The OIDC client's `client_id` |
| `allowed_claims` | yes | PID claims, `age_over_NN`, a credential id or `<credential>.<claim>` |
| `enabled` | no | The opt-in (default `true`) |
| `deliver_userinfo` / `deliver_id_token` / `deliver_access_token` | no | Channels (defaults `true`, `false`, `false`) |
| `max_age_seconds` | no | 60–2592000 |
| `api_verification` / `api_predicate_claims` | no | Allow the [verification API](verification-api.md), and the claims usable only in yes/no checks |
| `use_stored_attributes` | no | Also release [verified attributes kept on the profile](../federation/wallet-step-up-and-profile.md#keeping-verified-attributes) (default `false`) |

### `helix_client_verification_api` and `helix_verification_template`

*Preview.* A client's [verification API](verification-api.md) settings and templates. Realm admin only.

```hcl
resource "helix_client_verification_api" "shop" {
  client_id         = "shop"
  wallet_connection = "eudi-wallet"
  webhook_url       = "https://shop.example/hooks/wallet"
  webhook_secret    = var.webhook_secret # write-only; plaintext in state
  max_open          = 50
}

resource "helix_verification_template" "age" {
  client_id   = "shop"
  template_id = "age-check"
  name        = "Age check"
  purpose     = "To check you may rent a car"
  claims      = ["age_over_18"]
  predicates  = [{ id = "resident_utrecht", claim = "address.locality", values = ["Utrecht"] }]
}
```

`webhook_secret` is sensitive and write-only (16–256 characters); `webhook_secret_set` (computed) says whether one is stored. `max_open` is 1–1000, default 50.

### `helix_credential_issuer` and `helix_credential_template`

*Preview.* The realm's [credential issuer](../federation/credential-issuance.md). `helix_credential_issuer` holds the issuer settings (`enabled`, `display_name`, `logo_uri`, `locale`, `offer_ttl_seconds`, `wallet_provider_anchors`, `wallet_clients`, `publish_did_web`); `helix_credential_template` holds one template (`configuration_id`, `format`, `vct` or `doctype` / `namespace`, `claims` with `name`, `source`, `disclosable`, `mandatory`, display, `validity_days`, `max_batch`, `required_role`, `scope`, `key_attestation`, `wallet_attestation`, `signing_method`, `enabled`). The issuer key is not a Terraform resource: create it in the console or the admin API.

## See also

- [Manage applications](../manage/applications.md)
- [EU Digital Identity Wallet](../federation/eudi-wallet.md) · [Credential issuance](../federation/credential-issuance.md)
- [Verified data for applications](verified-claims.md) · [Verification API](verification-api.md)
- [Roles](../manage/roles.md)
- [Import / export & migration](import-export.md)
- [Integrate with standard OIDC](oidc-quickstart.md)
