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 ¶
const DefaultCacheSize = 1024
DefaultCacheSize is the verified-token LRU capacity (docs/DESIGN.md §4.2).
Variables ¶
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 ¶
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 ¶
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 ¶
NewCache builds a Cache with the given capacity (values < 1 fall back to DefaultCacheSize).
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 ¶
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 ¶
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 ¶
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 ¶
TokenFromContext returns the verified token stored by the middleware, if the request passed through Require*.
func Verify ¶
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 ¶
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.