Authentication

The provider gets an access token from the token endpoint of one realm, {url}/realms/{realm}/protocol/openid-connect/token, and sends it as a bearer token on every API call. What the token allows is what the roles of the signed-in user, or of the client’s service account, allow. See roles and permissions.

Choosing the grant

The provider picks the grant from the credentials it finds:

Credentials setGrantUse it for
client_id, client_secretclient_credentialsEvery day: CI, pipelines, a workstation
client_id, username, passwordpasswordOnce, to bootstrap the service account

Setting both password and client_secret is an error, and so is setting neither. The password grant sends no secret, so its client_id has to be a public client with Direct access grants on. The client credentials grant needs a confidential client with service_account_enabled.

The password grant is deprecated, and it fails for an admin who owes a required action or signs in with a passkey or a federated identity provider. Keep it for the bootstrap.

Provider arguments

ArgumentEnvironment variableDescription
urlFERRISKEY_URLBase URL of the API, with no /realms suffix. Required
realmFERRISKEY_REALMRealm the provider signs in to, often master. Required
client_idFERRISKEY_CLIENT_IDClient the token is requested for. Required
client_secretFERRISKEY_CLIENT_SECRETSelects the client credentials grant. Sensitive
usernameFERRISKEY_USERNAMEUser for the password grant
passwordFERRISKEY_PASSWORDSelects the password grant. Sensitive
scopeFERRISKEY_SCOPEScope sent with the token request. Optional
ca_certFERRISKEY_CA_CERTPEM certificate or bundle to trust, for an instance behind a private CA
tls_insecure_skip_verifyFERRISKEY_TLS_INSECURESkip certificate verification. Development only

An argument set in the configuration wins over its environment variable.

url is where the API answers, not where the console is. With the Helm chart, the API is served under api.server.rootPath, /api by default, so url is https://auth.example.com/api. A local server started on port 3333 with no root path is http://localhost:3333.

realm is only where the provider signs in. Resources name their own realm, so one provider signed in to master can manage every realm of the instance.

Environment variables

Keep credentials out of .tf files. A CI job sets the variables and the provider block only names what is not secret:

provider "ferriskey" {
  url       = "https://auth.example.com/api"
  realm     = "master"
  client_id = "terraform-runner"
}
export FERRISKEY_CLIENT_SECRET='<secret>'
terraform plan

All of the provider block can come from the environment, in which case provider "ferriskey" {} is enough.

Several instances

Use an alias per instance, and pass the alias to the resources that belong to it:

provider "ferriskey" {
  alias         = "staging"
  url           = "https://auth.staging.example.com/api"
  realm         = "master"
  client_id     = "terraform-runner"
  client_secret = var.staging_runner_secret
}

resource "ferriskey_realm" "acme_staging" {
  provider = ferriskey.staging
  name     = "acme"
}

TLS

For an instance whose certificate comes from a private CA, pass the CA:

provider "ferriskey" {
  url     = "https://auth.internal.example/api"
  realm   = "master"
  ca_cert = file("${path.module}/ca.pem")
}

tls_insecure_skip_verify = true turns verification off. It sends the client secret to whoever answers, so use it only against a local instance with a self-signed certificate.

Tokens and retries

The provider requests a token on the first API call, keeps it, and gets a new one shortly before it expires: with the refresh token when it has one, with a new grant otherwise. A 401 makes it drop the token and retry the call once. A long apply does not need longer token lifetimes.

Throttled and failing calls are retried up to three times with exponential backoff, honoring Retry-After:

FailureRetried for
429 Too Many RequestsEvery method
5xx, connection errorGET, HEAD, PUT and DELETE only

A POST or PATCH that failed with a 5xx is not retried, because the change may have been applied. Run terraform plan to see where the server stands, then apply again.

Set TF_LOG=DEBUG to see token requests and retries in the output.