Skip to content

MaxAnderson95/jotsmith

Repository files navigation

jotsmith

Stand up a personal OIDC issuer in Azure and mint JWTs of arbitrary shape against it.

jotsmith is a single-user CLI for testing OIDC / workload-identity federation flows — octo-sts, HashiCorp Vault JWT auth, AWS IAM OIDC, GCP Workload Identity Federation, or anything else that trusts a JWKS-publishing issuer. Instead of exfiltrating a real token from a GitHub Actions runner or a Kubernetes pod, you publish a discovery document + JWKS to an Azure Storage static website and mint short-lived tokens with exactly the claims you want.

The signing key lives in Azure Key Vault and never leaves it — signing happens inside Key Vault, so no private key material ever touches your machine.

How it works

  jotsmith (local CLI)
          │
          │  data plane
          ├────────────────────────────┐
          │ write blobs                │ sign(digest) ⇄ signature
          ▼                            ▼
  ┌───────────────┐            ┌────────────────┐
  │ Storage       │            │ Key Vault      │
  │  $web/        │            │  signing-key   │
  │   .well-known/│            │  (RSA 2048)    │
  │    openid-…   │            └────────────────┘
  │    jwks.json  │
  └───────┬───────┘
          │ HTTPS (public)
          ▼
   Consumer fetches discovery + JWKS to verify
   tokens that jotsmith minted.
  • Issuer URL is the raw Azure static-website endpoint (https://<account>.z<n>.web.core.windows.net). No custom domain in v1.
  • Algorithm is RS256 only, with one active RSA 2048 signing key.
  • The tool never provisions Azure resources. Your Storage Account and Key Vault must already exist; jotsmith setup only configures them.

Requirements

  • An Azure subscription with two existing resources:

    • A Storage Account (general-purpose v2).
    • A Key Vault in Azure RBAC mode (not legacy access-policy mode).
  • An Azure identity (CLI login, managed identity, etc.) resolvable by DefaultAzureCredential, holding these roles:

    Resource Built-in role Why
    Storage Account Storage Account Contributor Enable static website hosting (only if not already enabled)
    Storage Account Storage Blob Data Contributor Read/write the published documents
    Key Vault Key Vault Crypto Officer Create, read, and sign with the key

    If static website hosting is already enabled, you don't need Storage Account Contributor — setup detects and accepts that.

Installation

Download a release archive for your platform from the Releases page, extract it, and put the jotsmith binary somewhere on your PATH.

# macOS (Apple Silicon) example
tar -xzf jotsmith_*_darwin_arm64.tar.gz
install -m 0755 jotsmith ~/.local/bin/jotsmith
jotsmith --version

Prebuilt archives are available for macOS (amd64/arm64), Linux (amd64/arm64), and Windows (amd64/arm64).

Or build from source (Go 1.25+):

go build -o jotsmith ./cmd/jotsmith

Getting started

1. Authenticate to Azure

jotsmith uses DefaultAzureCredential. The simplest path is the Azure CLI:

az login

2. Set up the issuer

Point jotsmith at your existing Storage Account and Key Vault. This enables static website hosting (if needed), creates the signing key, publishes the discovery document and JWKS, and writes your local config file.

jotsmith setup \
  --subscription <subscription-id> \
  --storage-account <storage-account-name> \
  --key-vault <key-vault-name>

setup is idempotent — re-running it with the same arguments refreshes the published documents without recreating the key. The config file is written to ${XDG_CONFIG_HOME:-$HOME/.config}/jotsmith/config.json.

3. Mint a token

jotsmith token mint --sub octo-sts --aud sigstore --exp 5m

The compact JWT is written to stdout and nothing else, so it's safe to pipe or capture:

TOKEN=$(jotsmith token mint --sub ci --claim repo=acme/app --claim-json admin=true)

Claim flags:

  • --sub (required), --aud (repeatable), --exp, --iat, --nbf, --jti — the standard claims.
  • --exp takes a duration relative to iat (15m, 1h, 24h) or an RFC3339 timestamp. Default 15m. Tokens longer than 24h require --allow-long-lived.
  • --claim key=value — a custom string claim (repeatable).
  • --claim-json key=<json> — a custom claim parsed as JSON, for numbers, booleans, arrays, and objects (repeatable).
  • --claims-file <path> — merge claims from a JSON file.
  • iss always comes from your config and cannot be overridden.

Add --verbose to print the decoded header and payload to stderr after minting.

4. Verify a token

Round-trips through the live discovery document and JWKS over HTTPS:

jotsmith token verify "$(jotsmith token mint --sub me)" --aud sigstore

To inspect any token without verifying it (including tokens from other issuers):

jotsmith token decode "$TOKEN"

Command reference

Command What it does
jotsmith setup Configure an existing Storage Account + Key Vault as an OIDC issuer and write the config
jotsmith token mint Mint a signed JWT (writes the token to stdout only)
jotsmith token verify <jwt> Verify a token against the live issuer over HTTPS
jotsmith token decode <jwt> Decode a token's header and payload without verifying
jotsmith doctor Audit Azure state against your config (read-only)
jotsmith doctor --repair Fix drift it knows how to fix in place
jotsmith key rotate Rotate the signing key (snap-cutover)
jotsmith discovery show Print the discovery document as it would be published
jotsmith jwks show Print the JWKS as it would be published
jotsmith config show Print the resolved config (or its path with --path)
jotsmith destroy Tear down the issuer-shaped state (keeps the Azure resources)
jotsmith completion <shell> Emit a shell completion script (bash, zsh, fish, powershell)

Global flags accepted on every command:

Flag Env Default Purpose
--config JOTSMITH_CONFIG XDG config path Path to the config file
--log-level JOTSMITH_LOG_LEVEL info error, warn, info, debug, or trace
--no-color NO_COLOR off Disable ANSI color in stderr output

Everything except a minted token is written to stderr, so token mint output stays clean for piping.

Troubleshooting

Start with doctor. It audits every part of the issuer and reports PASS / WARN / FAIL per check:

jotsmith doctor          # read-only audit
jotsmith doctor --e2e    # also mint + verify a throwaway token end to end
jotsmith doctor --repair # fix what it can in place
jotsmith doctor --json   # machine-readable output

doctor --repair can re-enable static website hosting and re-upload the discovery document and JWKS when they're missing, malformed, or stale. Problems that need human action (see below) are reported but not changed.

Common issues:

  • "Key Vault is in legacy access-policy mode." jotsmith only supports Key Vaults in Azure RBAC mode. Migrate the vault to RBAC and re-run setup.
  • mint fails with a permissions / signing error. Confirm the resolved identity still holds Key Vault Crypto Officer on the named vault. Run with --log-level debug to see which credential and tenant resolved.
  • setup fails uploading the documents with 403 AuthorizationPermissionMismatch. The identity can reach the Storage Account but lacks the data-plane role. Grant it Storage Blob Data Contributor on the account — this is a separate assignment from the control-plane access that lets setup enable static website hosting. RBAC role assignments can take a few minutes to propagate.
  • Wrong Azure identity resolved. DefaultAzureCredential checks several sources in order. Run with --log-level debug to see which one won, and az login (or set the appropriate environment) to select the right one.
  • verify rejects a freshly minted token. Check the published documents are current with jotsmith doctor; a stale JWKS after a manual change or a key rotation is the usual cause. doctor --repair re-publishes them.
  • Storage web endpoint changed (e.g. region migration). If the static-website URL no longer matches your config's issuer, doctor flags it but won't rewrite it silently — that would break every consumer's trust policy. Re-run jotsmith setup --force-issuer-rewrite deliberately if the change is real.
  • A consumer complains about a missing endpoint. The discovery document intentionally omits authorization_endpoint, token_endpoint, etc. — there's nothing to point them at. This matches what real workload-identity issuers (GitHub Actions, etc.) publish. Strict OIDC clients may object; that's expected for this testing tool.

Rotating the signing key

jotsmith key rotate

This is a snap-cutover: the moment rotation completes, every token minted under the previous key stops verifying. There is no overlap window. It prompts for confirmation unless you pass --yes.

Cleanup

Tear down the issuer-shaped state while keeping your Azure resources:

jotsmith destroy

This soft-deletes the signing key in Key Vault and deletes the published /.well-known/ documents. It does not delete the Storage Account or Key Vault themselves. The local config file is kept unless you pass --all:

jotsmith destroy --all   # also remove the local config file

destroy prompts for confirmation unless you pass --yes, and re-running it against an already-destroyed issuer is a safe no-op.

To fully remove everything afterward, delete the Storage Account and Key Vault through Azure yourself (and purge the soft-deleted key if you don't want it recoverable). jotsmith never deletes those resources for you.

About

Stand up a personal OIDC issuer in Azure and mint JWTs against it

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages