Help us shape the future of Go! Take the 2026 Go Developer Survey

assertion

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package assertion mints the identity assertions the proxy passes to apps, and publishes the keys that verify them.

An assertion is the only thing an app can trust about who is calling. The convenience headers alongside it are explicitly unverified (R-053), so the signature here is what separates "Pando says this is Alice" from "a client typed Alice into a header".

Package assertion mints the identity assertions the proxy passes to apps, and publishes the JWKS that verifies them.

Ed25519, aud bound to the app ID to prevent cross-app replay, 120s lifetime, minted per request (R-051, R-055). The sub claim is users.id, stable across email change and independent of the identity adapter — apps key their data on it (R-054).

Index

Constants

View Source
const AnonymousSubject = "anonymous"

AnonymousSubject is the constant `sub` for an unauthenticated request (R-056).

A constant rather than an empty claim, so an app can tell "Pando says nobody is signed in" from "this request did not come through Pando" — the latter being the absence of the assertion header entirely.

View Source
const Header = "X-Pando-Assertion"

Header is where the assertion travels.

View Source
const Lifetime = 120 * time.Second

Lifetime is how long an assertion is valid (R-055).

It is also the interval at which long-lived connections are re-authorized (O-13) and the width of the revocation window (design 06 §3.1). One constant, referenced from all three places: two clocks measuring the same thing drift apart the first time someone tunes one of them.

Variables

This section is empty.

Functions

func KeyID

func KeyID(pub ed25519.PublicKey) string

KeyID is the kid of a public key: derived from the key, so every replica names a peer's key the same way without being told.

Types

type Claims

type Claims struct {
	// Sub is users.id, stable across email change and independent of the
	// identity adapter (R-054). Apps key their data on it, which makes it the
	// single most important stability guarantee in the system.
	Sub string `json:"sub"`

	Email  string   `json:"email,omitempty"`
	Name   string   `json:"name,omitempty"`
	Groups []string `json:"groups,omitempty"`

	// Aud is the app ID. It is what stops an assertion minted for one app being
	// replayed against another.
	Aud string `json:"aud"`

	Iat int64  `json:"iat"`
	Exp int64  `json:"exp"`
	Iss string `json:"iss"`
}

Claims is the assertion body.

type Key

type Key struct {
	ID     string
	Public ed25519.PublicKey

	// NotAfter is when this key stops being used for signing. It stays in the
	// JWKS past that point so assertions already issued still verify — which is
	// what makes rotation an overlap rather than a cliff (R-057).
	NotAfter time.Time
	// contains filtered or unexported fields
}

Key is an Ed25519 signing key with an identifier.

type Minter

type Minter struct {
	// contains filtered or unexported fields
}

Minter signs assertions and publishes the verifying keys.

func NewMinter

func NewMinter(issuer string, c clock.Clock) (*Minter, error)

NewMinter builds a minter with a freshly generated key.

func (*Minter) JWKS

func (m *Minter) JWKS(ctx context.Context) map[string]any

JWKS returns the public keys, for publication at /.well-known/jwks.json: this process's own, and every peer's.

Read from the peers per request rather than cached. An app that meets an unknown kid fetches the JWKS again, and a cached list would answer that fetch without the key it came for. If the peers cannot be read the process's own keys are still published: the alternative is publishing nothing, which fails every app rather than some.

func (*Minter) Mint

func (m *Minter) Mint(c Claims) (string, error)

Mint signs an assertion for one request.

Minted per request rather than cached: the lifetime is short enough that caching buys little, and a cached assertion would outlive a revocation by however long the cache held it.

func (*Minter) Rotate

func (m *Minter) Rotate() error

Rotate generates a new signing key and retires the current one.

The retired key keeps verifying until every assertion it signed has expired, which is at most Lifetime. Publishing both and switching the signer is the propagation window R-057 asks for.

func (*Minter) SigningKey

func (m *Minter) SigningKey() PublicKey

SigningKey is the public half of the key this process signs with, for publishing to the other replicas.

func (*Minter) SigningKeyID

func (m *Minter) SigningKeyID() string

SigningKeyID returns the key currently used to sign.

func (*Minter) Verify

func (m *Minter) Verify(token string) (Claims, error)

Verify checks an assertion and returns its claims.

Provided so Pando's own tests and tooling can verify what it mints. Apps verify independently using the published JWKS — that is the point of publishing it.

func (*Minter) WithPeers

func (m *Minter) WithPeers(p PeerKeys) *Minter

WithPeers sets where the other replicas' keys are read from.

type PeerKeys

type PeerKeys func(ctx context.Context) ([]PublicKey, error)

PeerKeys lists the verifying keys of the other Pando processes serving this install (issue #72).

Each replica signs with a key of its own that never leaves its memory, and publishes the public half where the others can read it. An app may fetch the JWKS from one replica and receive an assertion signed by another, so every replica's JWKS has to carry every key that may have signed something still valid — otherwise the app sees a kid nobody publishes and refuses a caller Pando vouched for (R-051).

type PublicKey

type PublicKey struct {
	ID     string
	Public ed25519.PublicKey
}

PublicKey is a verifying key, without the private half.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL