peerticket

package
v0.14.2-dev Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package peerticket verifies short-lived JWTs ("peer tickets") cloudbox issues at `POST /api/v1/ssh/peer-ticket`. The local outpost trades its matrix_elev cookie for one of these tickets, then presents it to a peer outpost on the LAN-direct path. The receiving outpost calls Verify with its stored CloudboxTicketPubkey and, on success, treats the connection as cloudbox-vouched without the X-Periscope-Role header that cloudbox stamps on the matrix-tunnel proxied path.

The cookie itself never leaves the original client ↔ cloudbox channel — only the derived ticket traverses the LAN.

Replay defense is a process-memory LRU keyed by `jti`. 60s TTLs keep the replay window naturally bounded; restart-blast-radius is smaller than the TTL window so persisting jti would be pointless.

Index

Constants

View Source
const DefaultJTICap = 4096

DefaultJTICap is the LRU cap. 4096 entries × ~60s TTL bounds memory at ~256KiB worst case — trivial. Lower this if the receiver runs in a heavily memory-constrained environment.

Variables

View Source
var (
	ErrMalformed     = errors.New("peerticket: malformed")
	ErrBadSignature  = errors.New("peerticket: signature invalid")
	ErrExpired       = errors.New("peerticket: expired")
	ErrNotYetValid   = errors.New("peerticket: not yet valid")
	ErrWrongAudience = errors.New("peerticket: wrong audience")
	ErrScopeMissing  = errors.New("peerticket: required scope missing")
	ErrReplayed      = errors.New("peerticket: jti already consumed")
	ErrNoPubkey      = errors.New("peerticket: no verification key configured")
)

Sentinel errors so callers (sshHandler, future capability handlers) can distinguish "this ticket is malformed" from "this ticket is expired" from "this ticket was already consumed."

Functions

func LoadPubkey

func LoadPubkey(pemStr string) (ed25519.PublicKey, error)

LoadPubkey parses a PEM-encoded ed25519 public key (the format cloudbox publishes via /api/register/exchange). Empty input returns (nil, nil) so callers can distinguish "no key configured yet" from "configured but malformed."

Types

type Claims

type Claims struct {
	Issuer    string    // "cloudbox" — informational; not gated on
	Audience  string    // "outpost:<peer agent_name>" — must equal receiver
	Subject   string    // OS user the peer should impersonate
	Role      string    // "user" or "admin" — feeds the X-Periscope-Role-equivalent decision
	Scope     []string  // capability strings the ticket grants ("ssh", "sftp", "backup", …)
	ExpiresAt time.Time // hard cutoff (cloudbox issues 60s tickets)
	NotBefore time.Time // optional; honored if set
	IssuedAt  time.Time // informational; not gated on
	JTI       string    // unique-per-issuance; replay-protected by Verifier
}

Claims is the verified peer-ticket payload, projected onto the fields the outpost actually uses. Anything cloudbox adds in the future that we don't read here is silently ignored — fine for additive evolution.

type Verifier

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

Verifier holds receiver-side state — currently just the replay- protection LRU. One Verifier per process; safe for concurrent use.

func NewVerifier

func NewVerifier(cap int) *Verifier

NewVerifier returns a Verifier ready to use. cap ≤ 0 means use DefaultJTICap.

func (*Verifier) Verify

func (v *Verifier) Verify(token string, opts VerifyOptions) (*Claims, error)

Verify parses the JWT, verifies its signature against Pubkey, validates every claim against opts, and consumes the jti against the replay LRU. Returns the projected Claims on success.

type VerifyOptions

type VerifyOptions struct {
	// Pubkey is the cloudbox ed25519 verification key, loaded from
	// FileConfig.CloudboxTicketPubkey via LoadPubkey.
	Pubkey ed25519.PublicKey

	// ExpectedAudience must match the ticket's `aud` claim exactly.
	// The receiver passes its own identity ("outpost:" + AgentName)
	// — never a value taken from the request. Prevents an attacker
	// who captured a ticket scoped to peer-A from replaying it to
	// peer-B.
	ExpectedAudience string

	// RequiredScope is the capability the receiver is gating right
	// now ("ssh" for the WS-SSH handler, "sftp" or "backup" for
	// future per-route gates). The ticket's `scope` claim MUST
	// contain this value.
	RequiredScope string

	// ClockSkew widens both the `nbf` and `exp` windows by this
	// amount to absorb modest clock drift between cloudbox and the
	// receiver. Default 30s when zero.
	ClockSkew time.Duration

	// Now overrides time.Now() for tests. Zero-valued = real clock.
	Now time.Time
}

VerifyOptions threads the receiver-side invariants into Verify. ExpectedAudience and RequiredScope are mandatory — fail-closed on empty values so a misconfigured caller can't accidentally widen trust.

Jump to

Keyboard shortcuts

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