Submit an issue View all issues Source
MIR-1691

Token-enrolled clusters never anchor workload identity at cloud

Open public
evan evan Opened Aug 28, 2026 Updated Aug 28, 2026

Found while building the runtime half of unattended enrollment (exp/auto-enroll). Filed for a decision, not because the runtime code is wrong.

What happens

A cluster that registers with an enroll token always ends up with identity_anchor = cluster, even when cloud is serving workload-identity discovery. A cluster that registers the interactive way (browser approval) anchors at cloud in that same situation. So two clusters joining the same cloud end up on different anchors purely because of how they registered.

Verified live against a local cloud: the enrolled registration.json came back with identity_anchor: cluster.

Why it happens

The runtime picks the anchor from whether cloud sent an identity_issuer_url in the registration response (anchorForRegistration in cli/commands/server_register.go). The interactive responses (PollResponse, ApprovalResponse in cloud's services/clusters/registration.go) carry that field. The unattended response, InitiateRegisteredResponse (built by writeRegisteredResponse in services/clusters/enroll.go), does not. So on the enroll path the field decodes to empty and the runtime falls back to the cluster anchor.

Why it matters

anchorForRegistration's own reasoning is that a brand-new cluster should anchor at cloud when cloud offers discovery: it is the only option that works for a cluster that isn't reachable from the internet, and it keeps federation up while the cluster is down. An unattended, cloud-init-booted node is exactly that kind of cluster, yet it silently gets the weaker choice. The anchor is decided once at registration and then pinned (external trust config references the iss it produces), so this does not self-correct on a later restart.

The decision

Two ways to resolve it:

  1. Cloud sends the field. Add identity_issuer_url to InitiateRegisteredResponse / writeRegisteredResponse so enrolled clusters match interactive ones. Runtime already reads it; no runtime change needed. This is the option that makes the two paths consistent.
  2. It's intentional. If enrolled clusters should anchor at the cluster on purpose, say so in a comment at registerWithEnrollToken so the divergence is deliberate rather than an accident of the response shape.

Leaning toward option 1, but it's a cloud-side call.