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.
Before you begin
Section titled “Before you begin”- Terraform 1.11 or later.
- An install whose Admin API you can reach as
HOST:PORTover 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.
Install the provider
Section titled “Install the provider”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.
Configure the provider
Section titled “Configure the provider”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:
| Argument | Environment variable | Value |
|---|---|---|
endpoint | AUTHWISE_ENDPOINT | The Admin API, as HOST:PORT. gRPC over TLS. |
token_url | AUTHWISE_TOKEN_URL | The admin issuer’s token endpoint. |
client_id | AUTHWISE_CLIENT_ID | The client’s AWID. |
client_secret | AUTHWISE_CLIENT_SECRET | The client secret, passed verbatim. |
audience | AUTHWISE_AUDIENCE | The 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:
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 first configuration
Section titled “A first configuration”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}Identifiers, names, and import
Section titled “Identifiers, names, and import”Every resource exposes two identifiers:
nameis the full resource name, such astenants/t-01/realms/r-01. It is the Terraform ID and the import ID.TYPE_idis the last segment of the name, such asrealm_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:
terraform import authwise_realm.employees tenants/TENANT_ID/realms/REALM_IDAssociation 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.
Configs built by data sources
Section titled “Configs built by data sources”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.
Working notes
Section titled “Working notes”- Parents are immutable. Changing
tenant_id,issuer_id,realm_id,audience_id, orclient_idon a resource replaces it. - Authoritative sets.
authwise_access_role_access_permissionsandauthwise_scope_access_permissionsown 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 referrerslists them for an endpoint. - Secrets.
authwise_secret.payload_wois write-only: never in plan or state. Rotate by bumpingpayload_wo_version. Setting a secret reference on another resource needs theidentity.secrets.usepermission 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
rulesare ordered and first match wins, and the server refuses a partial update beneath it. Reference other resources inside thejsonencodeso Terraform orders creates and destroys; a literal name string needsdepends_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.
Resource reference
Section titled “Resource reference”| Area | Resources |
|---|---|
| Structure | authwise_issuer, authwise_domain, authwise_realm, authwise_audience |
| Clients | authwise_client, authwise_client_secret |
| Users | authwise_user |
| Access | authwise_access_permission, authwise_access_role, authwise_access_role_access_permissions, authwise_access_condition, authwise_access_binding, authwise_scope, authwise_scope_access_permissions |
| Sign-in | authwise_provider, authwise_factor, authwise_realm_authentication_policy |
| Branding | authwise_theme, authwise_appearance_profile, authwise_asset, authwise_asset_content |
| Credentials and integrations | authwise_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 aforexpression.authwise_client_secretslists secrets without their values, andauthwise_secretreads metadata only. - Config builders.
authwise_interactive_client_config,authwise_saml_relying_party_config, oneauthwise_factor_TYPEper factor type, and oneauthwise_provider_TYPEper sign-in provider type. They make no API call. - Introspection.
authwise_realm_authentication_context_schemaexposes the policy language’s variables and the factor types the install runs, for acheckblock.
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.