auth

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package auth implements the JWT validation and Astarte authorization-claim layer shared by every Astrate REST surface (docs/DESIGN.md §4.2). It reproduces upstream Astarte's token semantics exactly — asymmetric keys only (RSA/ECDSA, `none` and HMAC hard-rejected), per-realm multi-key rotation, and `"<verb-regex>:<opts>:<path-regex>"` authorization strings matched with implicit anchoring against the request method and the path relative to the realm base — so tokens minted by astartectl and existing operator tooling work unmodified.

The package is pure (no database): key material is injected through the KeySource interface, which *store.Store already satisfies.

Index

Constants

View Source
const DefaultCacheSize = 1024

DefaultCacheSize is the verified-token LRU capacity (docs/DESIGN.md §4.2).

Variables

View Source
var (
	// ErrNoRealmKeys reports that the realm has no JWT public keys
	// configured, so no token can possibly verify.
	ErrNoRealmKeys = errors.New("auth: realm has no JWT public keys")
	// ErrNoKeyMatched reports that the token signature verified against
	// none of the realm's keys (wrong key, tampered token, or disallowed
	// algorithm).
	ErrNoKeyMatched = errors.New("auth: token matches none of the realm keys")
	// ErrUnsupportedKey reports PEM key material that is neither an RSA nor
	// an ECDSA public key.
	ErrUnsupportedKey = errors.New("auth: unsupported public key type")
)

Sentinel errors returned by token verification. All of them map to 401 at the HTTP layer; they are distinct so logs and tests can tell causes apart.

Functions

func ParsePublicKeysPEM

func ParsePublicKeysPEM(pems []string) ([]crypto.PublicKey, error)

ParsePublicKeysPEM parses a realm's JWT public key set. Each entry may carry one or more PEM blocks; supported block types are PKIX "PUBLIC KEY" and PKCS#1 "RSA PUBLIC KEY", and the decoded keys must be RSA or ECDSA (matching the signing-method allowlist). An entry with no usable key is an error: silently dropping keys would turn a key-set typo into a hard 401 for every token holder.

func RelativePath

func RelativePath(urlPath, base string) (string, bool)

RelativePath computes the authorization path with upstream parity (Astarte's GuardianAuthorizePath plug): split the URL path into segments, drop everything up to and including the first segment equal to base, and join the rest with "/". ok is false when base does not appear in the path.

Example: RelativePath("/pairing/v1/test/agent/devices", "test") returns ("agent/devices", true).

Types

type Cache

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

Cache memoizes verified tokens so the signature check, claim validation, and regex compilation run once per (token, key set) instead of once per request. Entries are keyed by SHA-256 of the token *and* of the key set, so rotating a realm's keys naturally invalidates its cached tokens.

func NewCache

func NewCache(size int) *Cache

NewCache builds a Cache with the given capacity (values < 1 fall back to DefaultCacheSize).

func (*Cache) Verify

func (c *Cache) Verify(tokenString string, keysPEM []string) (*Token, error)

Verify returns the verified Token for tokenString against the realm's PEM key set, from cache when possible. Cache hits still honour `exp`: a token that expired while cached is evicted and rejected. Failed verifications are not cached.

type Claim

type Claim string

Claim names an Astarte authorization claim: the JWT key under which a list of authorization strings is carried (docs/DESIGN.md §4.2).

const (
	// ClaimAppEngine authorizes the AppEngine API (a_aea).
	ClaimAppEngine Claim = "a_aea"
	// ClaimChannels authorizes Astarte Channels; Astrate honours it on the
	// live stream socket (a_ch).
	ClaimChannels Claim = "a_ch"
	// ClaimHousekeeping authorizes the Housekeeping API (a_ha).
	ClaimHousekeeping Claim = "a_ha"
	// ClaimPairing authorizes the Pairing agent API (a_pa).
	ClaimPairing Claim = "a_pa"
	// ClaimRealmManagement authorizes the Realm Management API (a_rma).
	ClaimRealmManagement Claim = "a_rma"
)

The Astarte claim set. Each claim authorizes one API surface; the values are the exact JWT keys upstream tooling (astartectl, astarte-go) emits.

type KeySource

type KeySource interface {
	GetRealmByName(ctx context.Context, name string) (*store.Realm, error)
}

KeySource supplies per-realm key material. *store.Store satisfies it; the middleware only reads JWTPublicKeysPEM from the returned realm.

type Middleware

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

Middleware authenticates and authorizes REST requests with realm JWTs (docs/DESIGN.md §4.2). Status mapping is upstream parity: missing or unverifiable token → 401, verified token whose claims do not authorize the request → 403, both with the canonical envelopes.

func NewMiddleware

func NewMiddleware(keys KeySource) *Middleware

NewMiddleware builds a Middleware over the given key source with a DefaultCacheSize token cache.

func (*Middleware) RequireRealm

func (m *Middleware) RequireRealm(claim Claim) func(http.Handler) http.Handler

RequireRealm guards a realm-scoped route (path pattern must carry a {realm} segment): it resolves the realm's JWT public keys, verifies the bearer token, and matches the claim's authorization strings against the method and the path relative to the realm base.

func (*Middleware) RequireStatic

func (m *Middleware) RequireStatic(claim Claim, keysPEM []string) func(http.Handler) http.Handler

RequireStatic guards an instance-level route (Housekeeping) with a fixed key set instead of per-realm keys. The authorization path is the request path relative to the service base (the segment after "v1").

type Token

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

Token is a verified JWT: its expiry and its compiled authorization grants. Tokens are immutable and safe for concurrent use, which is what allows the LRU cache to hand the same *Token to many requests.

func TokenFromContext

func TokenFromContext(ctx context.Context) (*Token, bool)

TokenFromContext returns the verified token stored by the middleware, if the request passed through Require*.

func Verify

func Verify(tokenString string, keys []crypto.PublicKey, now func() time.Time) (*Token, error)

Verify parses tokenString, verifies its signature against the key set, and validates its registered claims (`exp` and `nbf` are honoured when present; `iat` is not required — upstream parity, docs/DESIGN.md §4.2). The token verifies if *any* key in the set matches, which is what makes zero-downtime key rotation work. now supplies the validation clock.

func (*Token) Authorizes

func (t *Token) Authorizes(claim Claim, verb, authPath string) bool

Authorizes reports whether the token grants `claim` for the given verb (HTTP method, or JOIN/WATCH on the stream socket) and authorization path (the request path relative to the realm base, e.g. "agent/devices"). Multiple authorization strings within a claim are OR-ed; a token without the claim authorizes nothing on that surface.

func (*Token) ExpiresAt

func (t *Token) ExpiresAt() (time.Time, bool)

ExpiresAt returns the token expiry and whether one is set (`exp` is optional, upstream parity).

Jump to

Keyboard shortcuts

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