feat(keycardai-oauth): add WorkloadIdentity credential with pluggable subject token sources#191
Merged
Merged
Conversation
… subject token sources
One generic WorkloadIdentity credential owns the exchange contract
(jwt-bearer client assertion, no basic auth, fresh fetch per exchange,
no caching). A one-method SubjectTokenSource protocol is the only
per-platform code: FileTokenSource (EKS, AKS, Kubernetes projected
tokens), GCPMetadataTokenSource (GKE, GCE, Cloud Run), FlyTokenSource
(Fly Machines), or any bare callable.
The optional client_id constructor argument is sent as the client_id
form parameter alongside the assertion; token-federation application
credentials (KEP 108) are resolved by it.
EKSWorkloadIdentity becomes a deprecated subclass wrapping its existing
discovery and read logic as a source, with unchanged signature,
EKS-only env discovery, and unchanged error types, which are now
subclasses of the new WorkloadIdentity{Configuration,Runtime}Error.
Implements the workload-identity spec (keycard-sdk-spec#39). ECO-110.
Contributor
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.20.0",
"next_version": "0.21.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
…enSource The source returns the platform-issued OIDC identity token, which the credential attaches as the client assertion. Naming it a subject token collided with TokenExchangeRequest.subject_token, which carries the inbound user token on the RFC 8693 exchange.
Contributor
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.20.0",
"next_version": "0.21.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
Contributor
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.20.0",
"next_version": "0.21.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
Larry-Osakwe
marked this pull request as ready for review
July 15, 2026 21:57
carldunham
approved these changes
Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Python implementation of the workload-identity spec (status: Proposed, accepted in keycard-sdk-spec#39). Tracks ECO-110. Mirrors the Go implementation (go-sdk#31) with the same conformance table.
One generic
WorkloadIdentitycredential owns the invariant exchange contract: jwt-bearer client assertion, no Basic auth, fresh fetch on every exchange, no caching. The only per-platform code is a one-method protocol:Built-in sources (named by mechanism, not platform), the spec's complete v1 set:
FileTokenSource: projected token files. Covers EKS, AKS (AZURE_FEDERATED_TOKEN_FILEis in the default discovery list), and any Kubernetes projected service-account token.GCPMetadataTokenSource: GCP metadata identity endpoint (?audience=&format=full+Metadata-Flavor: Google). Covers GKE, GCE, Cloud Run.FlyTokenSource: POST/v1/tokens/oidcover the/.fly/apiUnix socket via httpx uds transport, audience in the JSON body.client_id (KEP 108): optional
client_idconstructor argument, sent as theclient_idform parameter alongside the assertion (the field already existed onTokenExchangeRequestand serializes automatically). Token-federation application credentials are resolved by it; legacy token credentials don't need it.Back-compat:
EKSWorkloadIdentitybecomes a deprecated subclass ofWorkloadIdentitythat wraps its existing discovery/validation/read logic as a source: unchanged signature, EKS-only env discovery (a test pins that it does NOT discover the Azure variable), and unchanged error types.EKSWorkloadIdentity{Configuration,Runtime}Errorare now subclasses of the newWorkloadIdentity{Configuration,Runtime}Error(which carry asourceattribute), so existingexceptclauses keep working and new code can catch the generic types. All 30 pre-existing credential tests pass unchanged.Divergence note vs Go, deliberate: Go's error aliasing makes EKS and generic errors the same type; Python uses subclassing because the EKS errors carry a richer message shape. Both satisfy the spec's "aliases or subclasses" contract.
Testing
Conformance tests per the spec's Testing table: fake/callable sources asserting the exact exchange-request shape, no-caching via call counter, cause chaining (
__cause__), tmp files + monkeypatched env for every discovery variable,httpx.MockTransportfor the GCP and Fly request shapes and failure paths, plus one real Unix-socket server test proving the uds wiring end to end.ruff checkclean, full package suite 332 passed.🤖 Generated with Claude Code