Workload Identity
Workload Identity lets a pod in your managed Kubernetes cluster call the Frostmoln API using its Kubernetes ServiceAccount — with no long-lived API key stored in the pod. It is the Frostmoln equivalent of AWS IAM Roles for Service Accounts (IRSA), GCP Workload Identity Federation, and Azure Workload Identity.
A short-lived, scoped Frostmoln credential is minted for the pod on demand and refreshed automatically. Nothing secret is baked into your image or a Secret you manage.
Pilot — access is granted per tenant
Workload Identity is rolled out as a pilot. It is available only for tenants that hold the workload-identity entitlement, and only on managed Kubernetes clusters in such a tenant. If you don't see the Workload Identity tab on a cluster, it isn't enabled for your tenant yet — contact support to request access.
How it works
- You create a binding that maps a
(namespace, serviceAccount)in one of your clusters to a set of least-privilege Frostmoln scopes. - You annotate that Kubernetes ServiceAccount to opt it in.
- When a pod runs as that ServiceAccount, the cluster automatically:
- projects a short-lived Kubernetes ServiceAccount token into the pod,
- runs a small credential-helper sidecar that exchanges it for a scoped Frostmoln token, and
- writes that token to a file the app reads.
- Your app calls the Frostmoln API with the token. It is refreshed automatically before it expires (roughly hourly), so the app never handles a long-lived secret.
The exchange is one-way and outbound only: the platform never reaches into your cluster, and the pod trades a token it already holds for a Frostmoln token it is entitled to.
Step 1 — Create a binding
A binding grants a specific ServiceAccount a specific set of permissions. Those come either from scopes on the binding itself, or from an access policy attached to it — see Grant with an access policy below. Create the binding in the portal, with the fm CLI, or with Terraform.
Portal
On the cluster's detail page, open the Workload Identity tab and choose Add binding. Enter the namespace and service account name, pick the scopes to grant, and save.
fm CLI
fm kubernetes workload-identity binding create \
--cluster <cluster-id> \
--namespace default \
--service-account my-app \
--scope compute:read --scope storage:read
# List and delete
fm kubernetes workload-identity binding list --cluster <cluster-id>
fm kubernetes workload-identity binding delete <binding-id>Terraform
resource "frostmoln_workload_identity_binding" "my_app" {
cluster_id = frostmoln_kubernetes_cluster.prod.id
namespace = "default"
service_account = "my-app"
scopes = [
"compute:read",
"storage:read",
]
}Step 2 — Annotate the ServiceAccount
Opt the ServiceAccount in by adding the annotation frostmoln.cloud/workload-identity: "true":
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: default
annotations:
frostmoln.cloud/workload-identity: 'true'or imperatively:
kubectl -n default annotate serviceaccount my-app \
frostmoln.cloud/workload-identity=trueOnly ServiceAccounts carrying this annotation are wired up — it is opt-in per ServiceAccount.
Step 3 — Use the credential in your pod
Run your pod as the annotated ServiceAccount. The platform injects everything automatically; your app only needs to read the token from the file named by the FROSTMOLN_WORKLOAD_CREDENTIALS_FILE environment variable and send it as a bearer token:
curl -H "Authorization: Bearer $(cat "$FROSTMOLN_WORKLOAD_CREDENTIALS_FILE")" \
https://api.frostmoln.cloud/api/v1/...token, _ := os.ReadFile(os.Getenv("FROSTMOLN_WORKLOAD_CREDENTIALS_FILE"))
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(string(token)))The file is refreshed in place before the token expires, so re-read it (rather than caching its contents) for long-running processes.
Scopes
Workload identities are least-privilege by design. A binding's scopes must be explicit and concrete — the * (all) and <resource>:* (e.g. compute:*) wildcards are rejected. Grant only the scopes the workload actually needs, for example:
compute:read— read instances, images, flavorsstorage:read— read volumes and bucketsnetwork:read— read VPCs, subnets, security groups
To broaden or narrow access later, update the binding's scopes — the change takes effect on the next token refresh.
Grant with an access policy
Scopes are coarse: compute:read grants reads across every instance, image and flavor. An access policy can name individual resources, add conditions such as a source-IP range, and deny explicitly. For a workload that should touch one bucket from one network, a policy expresses what a scope cannot.
To use one, create the binding without scopes and attach a policy to it.
# Create a binding with no scopes
fm kubernetes workload-identity binding create \
--cluster <cluster-id> --namespace ops --service-account reaper
# Attach a policy to it
fm iam policy attach <policy-id> --type workload_identity --id <binding-id>In the portal, leave the scope picker empty when adding the binding, then attach a policy under Settings → Access Policies. In Terraform, omit scopes and add a frostmoln_iam_policy_attachment.
A binding with no scopes lists as no scopes — neither the portal nor fm can tell one granted by an attached policy from one granting nothing, so neither claims it does. Three things are worth knowing:
- It is inert until a policy is attached. With nothing granting it, the token exchange refuses to mint rather than issuing a credential that grants nothing — the pod's credential helper reports an error instead of getting a dead token.
- A binding can never be left granting nothing. Removing its last remaining grant — its scopes, or its last policy — is rejected. Delete the binding instead.
- While a binding has both scopes and a policy, the scopes are what apply. The policy's additional permissions take effect only once the scopes are removed, so verify after removing them, not after attaching the policy.
Access policies require the IAM access policies feature on your tenant.
Token lifetime
The minted Frostmoln token is short-lived (about one hour) and refreshed automatically by the injected credential helper. There is no long-lived secret to rotate: revoking access is a matter of deleting the binding (or removing the annotation), after which no new tokens are minted for that ServiceAccount.
Removing access
- Delete the binding (portal,
fm kubernetes workload-identity binding delete, orterraform destroy) to revoke its access. - Remove the annotation from the ServiceAccount to stop injecting credentials into new pods.
Deleting the binding is also how you remove a workload's access entirely — a binding cannot be emptied down to granting nothing.
Terraform: destroy a policy-granted binding before its attachment
Because a binding can never be left with no grant, detaching its only policy is rejected. 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.my_app
terraform destroyThe same applies to any change that replaces the binding — cluster_id, namespace and service_account all force replacement.