frostmoln_workload_identity_binding (Resource)
Manages a Workload Identity Federation binding, mapping a managed Kubernetes (namespace, service account) to a least-privilege Frostmoln grant. A pod running as that service account can exchange its projected token for a short-lived, scoped Frostmoln credential. The grant is either flat scopes or an access policy attached with frostmoln_iam_policy_attachment — a policy expresses far narrower least privilege (per-resource targets, constraints, explicit denies), so prefer it for new bindings. The binding is owned by the provider's selected tenant (tenant_id, else the credential's default), and its cluster_id must belong to that same tenant.
~> Upgrade note. Before provider v0.37.9 this resource ignored tenant_id and always acted on the credential's home tenant — a binding for a non-home cluster failed at apply rather than landing in the wrong place. A configuration that sets a non-home tenant_id and previously applied has its bindings in the HOME tenant: the first plan after upgrading reads a 404, drops them from state and proposes a create in the selected tenant. terraform state rm + re-import against the selected tenant, or point tenant_id at the home tenant.
Authoritative scope — who owns what on this resource, declared in internal/scopedecl and machine-checked against the schema.
Enacted and reconciled — every configurable attribute not listed below: the platform applies it, and a refresh reads the truth back.
Create-immutable — cluster_id, namespace, service_account: the binding is identified by its cluster, namespace and service account; changing one is a different binding.
Example Usage
# A Workload Identity Federation binding: a pod running as the given
# Kubernetes service account can exchange its projected token for a short-lived,
# scoped Frostmoln credential — no long-lived secret in the pod.
resource "frostmoln_workload_identity_binding" "app" {
cluster_id = frostmoln_kubernetes_cluster.prod.id
namespace = "default"
service_account = "my-app"
# Least-privilege scopes only. Wildcards ("*" or "<resource>:*") are rejected.
scopes = [
"compute:read",
"storage:read",
]
}
# A POLICY-GRANTED binding: omit `scopes` entirely and let an attached access
# policy be the workload's sole authority. Prefer this — a flat scope grants a
# verb across every resource of a service, while a policy names individual
# targets, adds constraints and can deny explicitly.
#
# The binding is INERT until the attachment exists: with no grant at all the
# token exchange refuses it rather than minting a credential that grants nothing.
#
# TEARDOWN — read before you destroy. A granted binding is never left with zero
# grants, so detaching its ONLY policy is refused. `terraform destroy` removes
# the attachment first (it depends on the binding) and therefore fails. Destroy
# the BINDING first; that removes its attachments with it:
#
# terraform destroy -target=frostmoln_workload_identity_binding.reaper
# terraform destroy
#
# The same applies to any change that REPLACES the binding (cluster_id, namespace
# and service_account all force replacement).
resource "frostmoln_workload_identity_binding" "reaper" {
cluster_id = frostmoln_kubernetes_cluster.prod.id
namespace = "ops"
service_account = "instance-reaper"
}
data "frostmoln_iam_policy_document" "reaper" {
rule {
name = "read-and-delete-instances-only"
access = "allow"
operations = ["compute:instances:read", "compute:instances:list", "compute:instances:delete"]
targets = ["frn:compute:*:*:instances/*"]
}
}
resource "frostmoln_iam_policy" "reaper" {
name = "instance-reaper"
document = data.frostmoln_iam_policy_document.reaper.json
}
resource "frostmoln_iam_policy_attachment" "reaper" {
policy_id = frostmoln_iam_policy.reaper.id
attachee_type = "workload_identity"
attachee_id = frostmoln_workload_identity_binding.reaper.id
}Schema
Required
cluster_id(String) The managed cluster the binding applies to. The cluster must belong to the selected tenant (the provider's tenant_id).namespace(String) The Kubernetes namespace (DNS-1123 label).service_account(String) The Kubernetes service account name (DNS-1123 subdomain).
Optional
scopes(List of String) The least-privilege scopes granted to the workload (e.g.compute:read). The grantable set, with a description of each, is served by the platform — read it with thefrostmoln_api_key_scopesdata source orfm account api-key scopes. Wildcards (*or<resource>:*) are rejected. Changing the scopes updates the binding in place. Optional: omit it to author a policy-granted binding, whose authority comes entirely from an access policy attached withfrostmoln_iam_policy_attachment(directly, or through a group). Writenullor omit the attribute for no scopes; an empty list is not a valid spelling.Notes on the policy-granted path:
Until a policy is attached the binding is inert — the token exchange refuses it rather than minting a credential that grants nothing.
While a binding carries BOTH scopes and a policy, its scopes stay authoritative and the policy's additional authority does not take effect. It goes live only once
scopesis dropped, so verify after the drop, not after the attach.Dropping
scopesNARROWS the workload to whatever the policy allows, and the API only checks that some grant survives — never that the policy covers what the scopes covered. Enumerate the binding's effective access before dropping them.Removing the LAST grant is rejected outright. A binding whose only grant is an attached policy must therefore be destroyed BEFORE that attachment; a plain
terraform destroytries the attachment first and fails. Destroy the binding with-targetfirst — that removes its attachments with it.
Read-Only
created_at(String) The timestamp when the binding was created.id(String) The unique identifier of the binding.tenant_id(String) The owning tenant (server-set; the provider's tenant_id selects it).updated_at(String) The timestamp when the binding was last updated.
Import
Import is supported using the following syntax:
The terraform import command can be used, for example:
# Workload identity bindings are imported by their id.
terraform import frostmoln_workload_identity_binding.app <binding-id>