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 and its OIDC/SAML client |
helix_realm_role |
A realm role |
helix_wallet_identity_provider |
An EU Digital Identity Wallet (OpenID4VP verifier) identity-provider connection (Beta) |
helix_client_verified_claims |
A client's verified-claims release policy (Preview) |
helix_client_verification_api · helix_verification_template |
A client's verification API settings and templates (Preview) |
helix_wallet_profile_settings |
A verify-only wallet connection's step-up and profile settings (Preview) |
helix_credential_issuer · helix_credential_template |
The realm's credential issuer settings and templates (Preview) |
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, or move them between environments with import / export. The provider is not published to the Terraform Registry; it is available to licensed customers from KubeDNA B.V. ([email protected]), 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.
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
}
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.
# 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"
}
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); 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.
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) 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 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 (server default off; signed, unsigned or multisigned; expected origins) |
dc_api_additional_key_ref |
no | The second realm key of a multi-signed request (Preview) |
verifier_info_source / verifier_info_format / verifier_info_json |
no | OpenID4VP verifier_info (Preview) |
transaction_data_types |
no | Verify-only: the transaction types the connection may confirm (Preview) |
credentials / credential_sets |
no | JSON strings (jsonencode([...])): credentials beyond the PID 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 (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) |
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 |
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). The admin API refuses it unless the provider's service account is a realm admin.
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, and the claims usable only in yes/no checks |
use_stored_attributes |
no | Also release verified attributes kept on the profile (default false) |
helix_client_verification_api and helix_verification_template¶
Preview. A client's verification API settings and templates. Realm admin only.
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. 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.