Skip to content

may01 1024#9

Merged
mattbnz merged 12 commits into
mainfrom
may01-1024
May 1, 2026
Merged

may01 1024#9
mattbnz merged 12 commits into
mainfrom
may01-1024

Conversation

@mattbnz

@mattbnz mattbnz commented May 1, 2026

Copy link
Copy Markdown

mjbraun and others added 12 commits March 11, 2026 11:13
…tMatch (superfly#42)

When tokenizer runs behind a reverse proxy (e.g., fly-proxy), standard
proxy headers like X-Forwarded-For, Via, etc. get injected into requests.
These headers were being signed by the SigV4 processor, but could then be
modified by outbound proxies, causing AWS signature verification to fail.

Also adds a FIXME noting that region/service parsing from the credential
scope appears to be swapped (left unchanged to avoid breaking existing clients).
…in creds. (superfly#43)

There's a bug in the aws v4 processor. It reads the existing signature and then updates it with a new signature. It extracts the region and service fields from the old signature in the wrong order, so when it generates a new signature, they are swapped to service then region. Unfortunately we have active use cases that depend on this behavior. So this commit introduces an optional fix, with the default behavior being to preserve the field swapping bug behavior.

- Add new flag to the processor "no_swap", defaulting to false. When not present or fault the existing swapping behavior is preserved. When it is true, the fields are not swapped.

Co-authored-by: mjbraun <matt@fly.io>
* Add .DS_Store, binary, and scratch files to .gitignore

Also deduplicate the .vscode entry.

* Add JWTProcessorConfig for Google service account auth

Implements a stateless JWT-bearer token exchange flow:
1. Client sends sealed SA private key to tokenizer, proxied to Google's
   token endpoint
2. Tokenizer signs a JWT (RS256), builds the exchange request body,
   intercepts Google's response, seals the access token into a new
   InjectProcessorConfig, and returns the sealed blob to the caller
3. Caller uses the sealed access token for subsequent API requests via
   the existing InjectProcessor path

Key design decisions:
- Tokenizer is completely stateless (no cache). Multiple instances and
  blue/green deploys work without shared state.
- The caller never sees any plaintext credential. The private key, JWT,
  and access token only exist in tokenizer's process memory.
- Response body rewriting is a new capability, contained to this
  processor via the ResponseProcessorConfig interface.
- SealingContext passes the parent Secret's auth config and validators
  into the processor so the derived sealed token carries the same
  authorization requirements.
- Sub and scopes are overridable at request time via params.

Adds golang-jwt/jwt/v5 dependency.

* Document jwt_processor and SigV4 no_swap in README

Adds documentation for:
- jwt_processor: two-step sealed token exchange flow for Google SA auth
- sigv4_processor: documents the no_swap bug-compatibility field

* Add unsealtoken CLI tool for debugging sealed secrets

* Address PR review: ECDSA/EdDSA support, rename ctx to sctx, fly-src auth note

- Generalize parseKey() to return crypto.Signer, supporting RSA, ECDSA
  (P-256/P-384/P-521), and Ed25519 keys with auto-selected signing method
- Rename SealingContext parameter from ctx to sctx to avoid confusion
  with context.Context
- Add fly-src auth deployment note to README per reviewer suggestion
- Add unit tests for ECDSA (ES256) and Ed25519 (EdDSA) signing
- Add real-world e2e test (gated on GOOGLE_SA_KEY_FILE env var)
* Add OAuth2 client_credentials processor

Implements the client_credentials grant (RFC 6749 §4.4) following the
same two-step sealed pattern as JWTProcessorConfig - the caller never
sees the plaintext secret or access token.

Also extracts a shared sealTokenResponse helper from JWTProcessorConfig
so both processors share the response-sealing logic without duplication.

* gofmt
* Add more documentation about using tokenizer
* docuemnt more processors
* fix strip hazmat for the sigv4 processor to include the new "no_swap" field. Currently we leave it uninitialized, so its always logging as "false" even when provided as "true".
The tokenizer command will support subcommands for displaying its version, running the server, displaying the seal key, sealing tokens, and unsealing tokens. This will allow the deployed binary to be used directly for multiple purposes without shipping seperate helpers and will simplify instructions for setting up and using the tokenizer.
Previously we started logging sanitized decoded tokens, but we only captured one such decoded token per request. Requests can contain multiple tokens and optionally contain parameters that affect token injection. These changes capture all of the decoded tokens with their parameters and log them.

This needs to be done with care. I don't believe we're putting any sensitive data in tokenizer params, but if we are, they would land in the tokenizer's logs.
Go 1.24 reached end-of-life when 1.26 shipped; the Go security team is
no longer backporting stdlib fixes to that branch. govulncheck flagged
four call-reachable stdlib vulnerabilities against golang:1.24-alpine
(= go1.24.13) that have no 1.24 patch:

  GO-2026-4870  crypto/tls   TLS 1.3 KeyUpdate DoS (reachable from the
                             inbound proxy listener and the outbound
                             TLS dialer)
  GO-2026-4947  crypto/x509  chain-building DoS
  GO-2026-4946  crypto/x509  policy-validation DoS
  GO-2026-4601  net/url      IPv6 host literal misparse

Pin the Dockerfile to golang:1.25.9-alpine (which contains all four
fixes) and raise the go.mod directive to 1.25.0. That in turn unblocks
x/crypto v0.50.0 and the current x/exp snapshot, whose latest releases
already require Go 1.25. govulncheck on the result reports clean.

go mod tidy also promoted golang-jwt/jwt/v5 and icholy/replace from
indirect to direct to match how they're actually imported.
Patch-level updates to keep deps current:

- logrus 1.9.3 -> 1.9.4
- alecthomas/repr 0.4.0 -> 0.5.2
- google/uuid 1.3.0 -> 1.6.0
- vmihailenco/msgpack/v5 5.3.5 -> 5.4.1
GitHub Apps use a bespoke flow (JWT in the Authorization header, not an
OAuth2 form body) that JWTProcessorConfig can't express. This adds
GitHubAppProcessorConfig following the same sealed two-step pattern:
sign a 10-minute RS256 JWT, exchange it for an installation token, and
return a SealedTokenResponse whose inner InjectProcessorConfig is pinned
to api.github.com with a "token %s" Authorization format - so neither
the private key nor the installation token is ever plaintext outside
tokenizer's process memory.
Brings in 11 upstream commits including JWT/Google service account,
client credentials, and GitHub App processors; multi-command binary
refactor; and SigV4 bug fixes (superfly#42, superfly#43).

Conflict resolution:
- Dockerfile: took upstream 1.25.9-alpine pin
- go.mod: kept our higher aws-sdk-go-v2/goproxy/macaroon/smithy-go
  versions, took upstream x/crypto v0.50.0 and added golang-jwt/jwt/v5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mattbnz mattbnz enabled auto-merge (rebase) May 1, 2026 02:04
auto-merge was automatically disabled May 1, 2026 02:05

Rebase failed

@mattbnz mattbnz merged commit 0fad911 into main May 1, 2026
1 check passed
@mattbnz mattbnz deleted the may01-1024 branch May 1, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants