Skip to content

Terraform provider

The Terraform provider manages everything inside a tenant: issuers, domains, realms, audiences, clients and their secrets, users, the Access catalog, sign-in providers and factors, branding, secrets, certificates, and endpoints. It talks to the Admin API, so it works against any install you can reach and hold a credential for. It does not create tenants or run the install itself; the operator does that.

  • Terraform 1.11 or later.
  • An install whose Admin API you can reach as HOST:PORT over gRPC. See Reach the Admin API.
  • A client credential for the install’s admin issuer. The operator mints one on the first install and stores it in a Kubernetes Secret; see Read the machine credential. The credential’s permissions bound what Terraform can do.

The provider is published on the Terraform Registry as authwisecom/authwise. Declare it and run terraform init:

terraform {
required_version = ">= 1.11"
required_providers {
authwise = {
source = "authwisecom/authwise"
version = "~> 0.4"
}
}
}

Pin the provider to the release that matches your install. Each release’s changelog names the identity server release it was built against; running a provider ahead of its server fails on fields the server does not know.

The provider authenticates with the OAuth 2.0 client_credentials grant. Each argument falls back to an environment variable, so CI can configure it without any credential in the configuration:

ArgumentEnvironment variableValue
endpointAUTHWISE_ENDPOINTThe Admin API, as HOST:PORT. gRPC over TLS.
token_urlAUTHWISE_TOKEN_URLThe admin issuer’s token endpoint.
client_idAUTHWISE_CLIENT_IDThe client’s AWID.
client_secretAUTHWISE_CLIENT_SECRETThe client secret, passed verbatim.
audienceAUTHWISE_AUDIENCEThe admin audience’s AWID. An identifier, not a URL; the token grant rejects anything else.

The machine credential the operator mints carries all five values under the same names, so the shortest configuration exports them and leaves the provider block to scope defaults:

Terminal window
CREDENTIAL="$(kubectl -n NAMESPACE get secret CR_NAME-admin-credential \
-o jsonpath='{.data.credential\.json}' | base64 -d)"
export AUTHWISE_TOKEN_URL="$(jq -r .token_url <<<"$CREDENTIAL")"
export AUTHWISE_CLIENT_ID="$(jq -r .client_id <<<"$CREDENTIAL")"
export AUTHWISE_CLIENT_SECRET="$(jq -r .client_secret <<<"$CREDENTIAL")"
export AUTHWISE_AUDIENCE="$(jq -r .audience <<<"$CREDENTIAL")"
export AUTHWISE_ENDPOINT="API_HOST:443"

Replace NAMESPACE and CR_NAME with the install’s namespace and the name of its AuthwiseIdentityServer, and API_HOST with a host from the install’s networking.api.hosts. The endpoint in the credential file is the in-cluster Service address, which suits a runner inside the cluster; from outside, use the Admin API host.

provider "authwise" {
# Scope defaults. Every resource inherits these unless it sets its own
# tenant_id, issuer_id, realm_id, or audience_id.
tenant_id = "TENANT_ID"
issuer_id = "ISSUER_ID"
}

TENANT_ID and ISSUER_ID are the AWIDs of the tenant and issuer you manage, for example the admin tenant and its issuer. Read them with awctl identity issuers list --tenant-id TENANT_ID, or from the admin console.

Setting any one of token_url, client_id, or client_secret without the others is an error that names the missing argument. With none of them set, the provider uses the credential that awctl auth login stored, which suits interactive use on a workstation.

A realm for a population of users, an audience for an API, and a browser client that signs those users in and calls that API:

resource "authwise_realm" "employees" {
display_name = "Employees"
config = jsonencode({ defaultLocale = "en" })
}
resource "authwise_audience" "api" {
display_name = "https://api.example.com"
}
data "authwise_interactive_client_config" "console" {
allowed_redirect_uris = ["https://console.example.com/callback"]
}
resource "authwise_client" "console" {
display_name = "Console"
audience_id = authwise_audience.api.audience_id
grant_type = "authorization_code"
config = data.authwise_interactive_client_config.console.any
}
output "console_client_id" {
value = authwise_client.console.client_id
}

Replace the display names and the redirect URI with your own. The realm takes the provider’s tenant_id; the audience and client take its issuer_id.

A machine client needs a secret, which the server mints and returns once. Terraform keeps it in state and hands it on from there, so treat the state as sensitive:

resource "authwise_client" "ci" {
display_name = "CI"
grant_type = "client_credentials"
}
resource "authwise_client_secret" "ci" {
client_id = authwise_client.ci.client_id
expires_at = "2027-01-01T00:00:00Z"
# There is no rotate operation. Change a keeper to mint a successor; with
# create_before_destroy the new secret exists before the old one goes.
keepers = {
rotation = "2026-09"
}
lifecycle {
create_before_destroy = true
}
}
output "ci_client_secret" {
value = authwise_client_secret.ci.secret
sensitive = true
}

Every resource exposes two identifiers:

  • name is the full resource name, such as tenants/t-01/realms/r-01. It is the Terraform ID and the import ID.
  • TYPE_id is the last segment of the name, such as realm_id = "r-01".

The rule for references: an attribute named *_id takes another resource’s TYPE_id, never its name. A *_ref = { name = ... } block and the association resources take name. The server refuses an identifier that belongs to a different scope, so a mistake fails at apply rather than being stored.

Most objects get a server-assigned identifier. Four take one you choose, as a required TYPE_id attribute, and changing it replaces the resource: authwise_access_permission, authwise_access_role, authwise_domain, and authwise_scope.

To import an existing object, use its resource name:

Terminal window
terraform import authwise_realm.employees tenants/TENANT_ID/realms/REALM_ID

Association resources import by the owning object’s name, and authwise_realm_authentication_policy imports by the realm’s name. Input-only attributes, such as a certificate’s PEM or a write-only secret payload, are not recoverable on import.

Providers, factors, and clients carry a type-specific config. Each type has a data source that builds it from typed, validated attributes and makes no API call; reference its any output, as authwise_interactive_client_config does above. Other structured configs, such as authwise_issuer.config, are written as their JSON encoding with jsonencode. Keys are lowerCamel. Leave zero values out rather than writing them, because the server omits zero values when it returns them and a written zero shows as a perpetual diff.

  • Parents are immutable. Changing tenant_id, issuer_id, realm_id, audience_id, or client_id on a resource replaces it.
  • Authoritative sets. authwise_access_role_access_permissions and authwise_scope_access_permissions own the whole membership of their role or scope. A member added out of band is removed on the next apply.
  • Deletion is refused while referenced. The server refuses to delete a secret, certificate, endpoint, or issuer that something still references. Remove the reference first; awctl identity endpoints referrers lists them for an endpoint.
  • Secrets. authwise_secret.payload_wo is write-only: never in plan or state. Rotate by bumping payload_wo_version. Setting a secret reference on another resource needs the identity.secrets.use permission on the credential.
  • Disabling a factor is not deleting it. Enrolled authenticators survive a disable; only a destroy removes them.
  • Issuer routing is written whole. The realm selector’s rules are ordered and first match wins, and the server refuses a partial update beneath it. Reference other resources inside the jsonencode so Terraform orders creates and destroys; a literal name string needs depends_on.
  • Server warnings surface as Terraform warnings on realm, factor, and policy writes. Read them; they name what the server accepted but will not enforce.
AreaResources
Structureauthwise_issuer, authwise_domain, authwise_realm, authwise_audience
Clientsauthwise_client, authwise_client_secret
Usersauthwise_user
Accessauthwise_access_permission, authwise_access_role, authwise_access_role_access_permissions, authwise_access_condition, authwise_access_binding, authwise_scope, authwise_scope_access_permissions
Sign-inauthwise_provider, authwise_factor, authwise_realm_authentication_policy
Brandingauthwise_theme, authwise_appearance_profile, authwise_asset, authwise_asset_content
Credentials and integrationsauthwise_secret, authwise_certificate, authwise_endpoint

Data sources come in three kinds:

  • Readers. One singular data source per resource type, by resource name, and one plural lister per type (authwise_realms, authwise_clients, and so on) that reads every object under a parent. Listers have no server-side filter; narrow them with a for expression. authwise_client_secrets lists secrets without their values, and authwise_secret reads metadata only.
  • Config builders. authwise_interactive_client_config, authwise_saml_relying_party_config, one authwise_factor_TYPE per factor type, and one authwise_provider_TYPE per sign-in provider type. They make no API call.
  • Introspection. authwise_realm_authentication_context_schema exposes the policy language’s variables and the factor types the install runs, for a check block.

The full schema of every resource and data source is on the provider’s page on the Terraform Registry.

There is no tenant resource. Tenants are created on the operator-only tenancy surface, not through the Admin API, and the provider works inside one.