tokenex is a Go library that exchanges an identity token your workload already has, such as a SPIFFE JWT-SVID or a GitHub Actions OIDC token, for short-lived credentials from cloud providers, secret stores and OAuth2 servers. It refreshes the credentials before they expire and delivers each new one over a Go channel, so there are no long-lived keys to store or rotate.
| Providers | |
|---|---|
| Identity token sources | Static JWT · GitHub Actions OIDC · SPIFFE / SPIRE JWT-SVID |
| Exchanged for | AWS · GCP · Azure · OCI · Vault / OpenBao · CyberArk Conjur · RFC 7523 incl. Anthropic WIF · RFC 8693 incl. OpenAI WIF · Generic |
| Other refreshed credentials | Kubernetes Secret · OAuth2 authorization code · OAuth2 client credentials · GitHub App |
Any identity token source works with any provider in the second row.
go get go.riptides.io/tokenexExchange the workload's SPIRE JWT-SVID for AWS credentials and keep them fresh:
package main
import (
"context"
"log"
awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/go-logr/logr"
"go.riptides.io/tokenex/pkg/aws"
"go.riptides.io/tokenex/pkg/credential"
"go.riptides.io/tokenex/pkg/token/spiffe"
)
func main() {
ctx := context.Background()
// JWT-SVIDs from the SPIRE agent at SPIFFE_ENDPOINT_SOCKET.
svids, err := spiffe.NewIdentityTokenProvider(spiffe.WithAudience("sts.amazonaws.com"))
if err != nil {
log.Fatal(err)
}
provider, err := aws.NewCredentialsProvider(ctx, logr.Discard(), &awssdk.Config{Region: "us-east-1"})
if err != nil {
log.Fatal(err)
}
creds, err := provider.GetCredentials(ctx, svids,
aws.WithRoleArn("arn:aws:iam::123456789012:role/spire-demo"),
aws.WithRoleSessionName("spire-demo"),
)
if err != nil {
log.Fatal(err)
}
// A fresh result arrives before the previous credentials expire.
for res := range creds {
if res.Err != nil {
log.Fatal(res.Err)
}
c := res.Credential.(*credential.AWSCreds)
log.Printf("access key %s, expires %s", c.AccessKeyID, c.Expires)
}
}AWS needs an IAM OIDC identity provider that trusts SPIRE's OIDC Discovery Provider; examples/spire-aws has the setup steps and a runnable version. In CI, swap spiffe for githubactions; for another target, swap aws for any provider below.
Each provider has its own page with a full example.
- Static (
pkg/token): holds a JWT you already have;SetTokenreplaces it. - GitHub Actions (
pkg/token/githubactions): fetches OIDC ID tokens from the GitHub Actions runtime. - SPIFFE (
pkg/token/spiffe): fetches JWT-SVIDs from the SPIFFE Workload API, e.g. a SPIRE agent.
To bring your own source, implement token.IdentityTokenProvider or wrap a function with token.IdentityTokenProviderFunc.
- AWS (
pkg/aws): exchanges ID tokens for AWS temporary session credentials using AWS's Workload Identity Federation. - GCP (
pkg/gcp): exchanges ID tokens for GCP access tokens using GCP's Workload Identity Federation. - Azure (
pkg/azure): exchanges ID tokens for Azure access tokens using Microsoft Entra ID's Workload Identity Federation. - OCI (
pkg/oci): exchanges ID tokens for OCI User Principal Session Tokens (UPST) using OCI's Workload Identity Federation. - Vault (
pkg/vault): exchanges ID tokens for secrets from Vault or OpenBao using JWT authentication. - Conjur (
pkg/conjur): exchanges ID tokens for secrets from CyberArk Conjur (Secrets Manager Self-Hosted and SaaS) using Conjur's JWT authenticator, and polls for rotated values. - RFC 7523 (
pkg/rfc7523): exchanges ID tokens for OAuth2 access tokens with thejwt-bearergrant, including Anthropic Workload Identity Federation. - RFC 8693 (
pkg/rfc8693): exchanges ID tokens for OAuth2 access tokens with token exchange, including OpenAI Workload Identity Federation. - Generic (
pkg/generic): returns the identity token itself and refreshes it before expiration. - K8sSecret (
pkg/k8ssecret): watches a Kubernetes secret which contains a token and publishes updates when the secret changes. - OAuth2AC (
pkg/oauth2ac): obtains access tokens through the OAuth2 authorization code flow and refreshes them before expiration. - OAuth2CC (
pkg/oauth2cc): obtains access tokens through the OAuth2 client credentials flow and refreshes them before expiration. - GitHub App (
pkg/githubapp): mints GitHub App installation access tokens by signing a JWT with the App's private key and refreshes them before expiration.
All credential providers in this library follow a consistent pattern for credential delivery:
- The
GetCredentialsmethod returns a channel that receives credential updates - For the first credential and each refresh, an
Updateevent is sent - If credentials are removed, a
Removeevent is sent - In case of errors, the
Errfield is populated,Credentialis nil, and the refresh loop exits - When the refresh loop exits, the channel is closed
Important: Since these channels continuously provide credential updates (including automatic refreshes), they should be processed in a goroutine to avoid blocking the main execution flow, as shown in the provider docs.
When processing credentials, you can check the event type to determine what action to take:
switch result.Event {
case credential.UpdateEventType:
// Use the updated credentials
log.Printf("Received new/refreshed credentials")
// Use result.Credential for API calls
case credential.RemoveEventType:
// Handle credential removal
log.Printf("Credentials were removed")
}For proper application shutdown, always:
- Cancel the context when your application is terminating
- Wait for all credential handling goroutines to complete using a wait group
- Handle channel closure and context cancellation in your credential processing loops
This ensures that all resources are properly cleaned up and prevents goroutine leaks.
- Introducing tokenex: an open source Go library for fetching and refreshing credentials
- tokenex adds Vault & OpenBao support: Exchanging ID tokens (JWTs) for secrets without static credentials
- Supplying short-lived OpenAI API keys to AI agents with Riptides
- Secretless Azure access with tokenex: Federated Identity via User-Assigned Managed Identity
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.
For suspected vulnerabilities, follow the private reporting instructions in SECURITY.md.