octo-sts-rs (GitHub Token Service)
Overview
octo-sts-rs is a Rust implementation of a GitHub App Short-Lived Token Service (STS). It provides secure, scoped GitHub authentication for CI/CD workflows by exchanging GitHub OIDC tokens for short-lived GitHub App tokens.
This service eliminates the need for long-lived GitHub credentials (such as Personal Access Tokens or SSH keys) in CI/CD pipelines, replacing them with ephemeral, scoped tokens that follow the principle of least privilege.
Architecture
| Property | Value |
|---|---|
| Runtime | Google Cloud Run |
| Region | northamerica-northeast2 |
| Non-production domain | sts.dev.badal.io |
| Key management | Google Cloud KMS (RSA 2048-bit asymmetric signing) |
| Service account | octo-sts-rust@{project}.iam.gserviceaccount.com |
| IAM role | roles/cloudkms.signerVerifier only |
How It Works
The token exchange flow follows these steps:
GitHub Actions octo-sts-rs Cloud KMS GitHub API
| | | |
| 1. Request OIDC token | | |
| (GitHub built-in) | | |
| | | |
| 2. Send OIDC token | | |
| + scope + identity | | |
|--------------------------->| | |
| | 3. Validate OIDC | |
| | token signature | |
| | | |
| | 4. Check trust | |
| | policy | |
| | | |
| | 5. Sign JWT | |
| |---------------------->| |
| | <-- signed JWT ------| |
| | | |
| | 6. Exchange JWT for | |
| | installation token| |
| |-------------------------------------->|
| | <-- scoped token ----| |
| | | |
| 7. Return scoped token | | |
|<---------------------------| | |
Detailed Steps
-
GitHub Actions workflow requests a GitHub OIDC token -- This is a built-in capability of GitHub Actions; no external service is needed for this step.
-
Workflow sends the OIDC token to the exchange endpoint:
https://sts.dev.badal.io/sts/exchange?scope={scope}&identity={identity} -
octo-sts-rs validates the OIDC token signature -- Confirms the token was issued by GitHub and has not been tampered with.
-
Trust policy check -- Verifies that the requesting identity (repository, workflow, branch) is authorized to request a token with the given scope.
-
JWT signing via Cloud KMS -- The service signs a JWT using the GitHub App private key stored in Cloud KMS. The private key never leaves KMS; the service only has permission to sign.
-
Token exchange -- The signed JWT is exchanged with the GitHub API for a scoped, short-lived GitHub App installation token.
-
Return token -- The scoped token is returned to the calling workflow.
Infrastructure Components
| Component | Details |
|---|---|
| KMS Key Ring | octo-sts-keyring |
| Crypto Key | octo-sts-github-app-key (RSA 2048-bit asymmetric signing) |
| Cloud Run | 1 CPU, 512MB memory, 0-10 instances |
| Artifact Registry | octo-sts-rust-non-production (cleanup: 10 recent, untagged after 7 days) |
| Image Source | ghcr.io/epiphytic/octo-sts-rust mirrored to Google Artifact Registry |
GitHub Actions Integration
A custom GitHub Action wraps the token exchange for easy use in workflows.
Action Location
devex-reusable-workflows/.github/actions/octo-sts/token/action.yml
Usage
- uses: badal-io/devex-reusable-workflows/.github/actions/octo-sts/token@main
with:
scope: "badal-io" # Target org or org/repo
identity: "my-identity" # Trust policy identity name
domain: "sts.dev.badal.io" # octo-sts domain
Inputs
| Input | Description | Required |
|---|---|---|
scope | Target organization or org/repo for the token | Yes |
identity | Trust policy identity name | Yes |
domain | octo-sts-rs domain | Yes |
Outputs
| Output | Description |
|---|---|
token | Scoped, short-lived GitHub App token |
Example Workflow
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC token
steps:
- name: Get GitHub token
id: octo-sts
uses: badal-io/devex-reusable-workflows/.github/actions/octo-sts/token@main
with:
scope: "badal-io"
identity: "deploy-identity"
domain: "sts.dev.badal.io"
- name: Use the token
env:
GITHUB_TOKEN: ${{ steps.octo-sts.outputs.token }}
run: |
gh repo list badal-io --limit 5
Trust Policies
Trust policies define which workflow identities can request tokens and what permissions those tokens will have.
Policy Location
Trust policies are stored as YAML files in repositories:
.github/chainguard/*.sts.yaml
Policy Structure
A trust policy specifies:
- Which workflow identities can request tokens (repository, branch, workflow path)
- What scope the token covers (entire organization or specific repositories)
- What permissions the resulting token will have (read, write, admin on specific resources)
Security Features
No Long-Lived Credentials
The entire authentication chain uses ephemeral credentials:
- GitHub OIDC tokens are short-lived and scoped to the running workflow
- Workload Identity Federation provides GCP access without service account keys
- The resulting GitHub App tokens expire automatically
KMS-Protected Keys
The GitHub App private key is stored exclusively in Google Cloud KMS:
- The key material never leaves KMS
- The
octo-sts-rustservice account only hasroles/cloudkms.signerVerifier-- it can sign payloads but cannot export or read the key - All signing operations are logged in Cloud Audit Logs
Scoped Tokens
Tokens are scoped based on trust policies:
- Minimal permissions following the principle of least privilege
- Tokens can be scoped to an organization or a specific repository
- Permission sets match only what the consuming workflow needs
Short-Lived Tokens
- GitHub App installation tokens typically expire in 1 hour
- No token refresh or caching -- each workflow run gets a fresh token
Audit Trail
- All token exchange requests are logged in Cloud Run logs
- KMS signing operations are logged in Cloud Audit Logs
- GitHub token usage is tracked in the GitHub audit log
Terraform Configuration
The infrastructure for octo-sts-rs is managed via Terraform in the devex-backstage-mly9n repository:
| Path | Purpose |
|---|---|
terraform/shared-octo-sts-rust/ | Shared Terraform module for octo-sts-rs resources |
terraform/non-production/main.tf | Non-production environment configuration |
.github/workflows/octo-sts-rust-deploy.yml | Deployment workflow |
Deployment Flow
The deployment process follows these steps:
- Pull image from the upstream source:
ghcr.io/epiphytic/octo-sts-rust - Tag and push the image to Google Artifact Registry
- Deploy to Cloud Run with the following environment variables:
| Variable | Description |
|---|---|
GITHUB_APP_ID | The GitHub App ID for token generation |
KMS_KEY_NAME | Full resource path to the KMS crypto key version |
DOMAIN | The service domain (e.g., sts.dev.badal.io) |
GITHUB_WEBHOOK_SECRET | Secret for validating GitHub webhook payloads |
Repositories
| Repository | Purpose |
|---|---|
| github.com/epiphytic/octo-sts-rust | Upstream source code for octo-sts-rs |
| badal-io/devex-backstage-mly9n | Deployment configuration and Terraform infrastructure |
| badal-io/devex-reusable-workflows | GitHub Action for token exchange |