Blog

Passwordless Workload Authentication: Closing the NHI Gap

In workload-identity literature — SPIFFE, AWS IRSA, GKE Workload Identity, Azure Workload Identity — "passwordless" never means "no credential material on the wire." Credentials always exist: a projected JWT, an instance identity document, an SVID. What passwordless means is no pre-shared, human-distributed, long-lived secret. The credential is minted and rotated by the platform, not provisioned to the workload by a human or a CI/CD pipeline.

For Kubernetes operators, AWS Lambdas, Azure Container Apps, and GCP Cloud Run services, that definition matters. These workloads run in environments that already have an identity — the kubelet projects a service-account JWT, the EC2 instance metadata service signs an instance identity document, the GCE metadata server issues identity tokens. The question is not how to create more credentials, but how to verify the ones the platform already gives you.

Platform attestation replaces the pre-shared secret The platform mints a short-lived token for the workload and rotates it automatically. The workload presents that token instead of a pre-shared secret. The verifier checks the signature against the issuer's published key set, confirms issuer, audience and expiry, matches the claims against a workload template, and returns a short-lived session. No human ever distributes a credential. Platform kubelet / IMDS / metadata server mints + rotates Workload pod / lambda / container app holds no secret presents token Verifier 1. signature vs pinned JWKS 2. issuer + audience 3. expiry and skew 4. claims vs template 5. auto-register NHI 6. issue short-lived session X-NHI-Session, minutes not months What is gone No bootstrap token, no env var of secret material, no human in the credential path.
The credential still exists — it is just minted and rotated by the platform rather than handed out by a person. The verifier's job is to check evidence, not to issue secrets.

What the platform actually mints

Before any of this is useful, it helps to be concrete about what each platform hands a workload, because the verification logic differs in the details even though the shape is identical.

The common thread is that none of these are secrets you store. They are short-lived assertions the platform will happily mint again in a few minutes, which is precisely why leaking one matters far less than leaking a static API key.

How verification works

A verifier's job is narrow and worth stating precisely, because most of the security lives in steps people skip.

  1. Signature against a pinned key set. Fetch the issuer's JWKS and verify the token was signed by a key it publishes. Pinning the issuer, not just trusting any valid JWT, is what stops a token minted by an unrelated cluster from being accepted.
  2. Issuer and audience. The issuer must match the binding exactly. The audience must match a value you chose — never a default. An audience check is the difference between "this token was minted for us" and "this token was minted for something else and replayed at us."
  3. Expiry, not-before, and bounded skew. These tokens are short-lived by design, so a generous clock-skew allowance quietly extends their life. Keep the leeway tight enough that it does not become the real TTL.
  4. Expected claims. Match the platform-specific claims against the binding: namespace and service account for Kubernetes, tenant and resource ID for Azure, project and zone for GCP.
  5. Template match, then registration. Only once the evidence checks out does a workload template decide whether this identity is allowed to exist, what scopes it gets, and whether the per-template cap has room.

Skip step two and you have built something that accepts any well-formed token from a trusted issuer, which is a surprisingly common failure and an entirely silent one.

The four gaps we closed

CoreLink's non-human identity (NHI) subsystem shipped with Kubernetes and AWS workload attestation working end-to-end. Admin creates an NHI once, binds it to a trusted issuer and expected claims, and the workload hits /api/v1/nhi-agent/connect with its platform-minted JWT. No pre-shared secret. No bootstrap token. Just evidence the platform can verify against a pinned JWKS.

Four gaps remained between that and a complete cross-cloud story. We closed them over the last month:

1. JIT NHI auto-registration via workload templates

Even with attestation working, admins had to pre-create an NHI record and configure its binding before the workload could connect. That's one remaining manual step.

Workload templates close it. An admin defines a template like "any pod in namespace prod with service account myapp-* auto-registers as an NHI with scopes secrets:read and sessions:read." The first attested Connect from a matching workload auto-provisions the NHI. Claim matchers support equals, prefix, and glob patterns. Caps are enforced via MaxNHIs, and default scopes flow to the new NHI's metadata.

2. Azure and GCP attestation at parity

Azure Managed Identity tokens (v1 sts.windows.net, v2 login.microsoftonline.com) and GCP identity tokens (accounts.google.com) needed dedicated verifiers. Each carries platform-specific claims — Azure's tid, oid, xms_mirid; GCP's nested google.compute_engine.{project_id, instance_id, zone}. The verifiers extract these into the binding's expected-claims map so a workload template can match on "resource group X in tenant Y" or "instance in project Z."

3. Multi-cluster SPIFFE trust-bundle federation

Accepting external SVIDs from peer clusters needed JWKS plumbing, peer management, and a middleware routing layer that distinguishes own-domain from federated-domain SPIFFE URIs at mTLS. Federation peers are managed via a new admin UI; each peer has an immutable trust domain and a JWKS that's refreshed from a bundle endpoint (manual refresh for now, automatic refresh is on the follow-up list).

4. RFC 8693 inbound token exchange

The platform could already issue JWT-SVIDs to exchange outbound against AWS STS, Azure workload federation, and GCP workload pool. What it couldn't do was accept an inbound OIDC token from a peer identity platform and return a native CoreLink session. A new POST /oauth/token grant type implements RFC 8693 for SPIFFE subject tokens: external JWT → resolve peer by trust domain → verify signature + issuer + audience against the peer's JWKS → assert subject stability between preview and verification (anti-forgery) → match claims against a workload template → auto-register the NHI → narrow scopes → issue platform session.

What this unlocks

For a Kubernetes operator, the full deploy-to-first-request flow is now:

  1. Platform admin defines a workload template once (one namespace + service-account pattern).
  2. Operator creates a pod with the matching service account.
  3. Kubelet projects a SA token at /var/run/secrets/kubernetes.io/serviceaccount/token — rotated automatically, never human-distributed.
  4. Pod POSTs the token to CoreLink's Connect endpoint. CoreLink fetches the cluster's JWKS, verifies the JWT against the bound issuer and audience, matches the claims against the template, auto-registers the NHI, returns a short-lived X-NHI-Session.
  5. Every subsequent API call uses the session header. No credential material in the pod's environment.

For AWS, Azure, and GCP workloads, the only thing that changes is which attestation verifier runs. The rest — template match, NHI auto-registration, session — is identical.

Failure modes worth designing for

Attestation removes the static credential, but it introduces its own set of things that go wrong quietly. These are the ones worth building tests around before you rely on any of it.

What's next

Near-term: automatic SPIFFE bundle-endpoint refresh, nonce-based replay protection on Connect, and a policy language for claim matchers richer than equals/prefix/glob. Longer-term: WebAssembly-based custom attestation verifiers so customers can plug in platforms we haven't built native support for.

If you're running a cloud workload today with a bootstrap token or an environment variable full of secret material, the fix is on the other side of the workload template + matching attestation verifier. The passwordless part isn't a rewrite; it's a deploy config change.