Documentation
¶
Overview ¶
Package agentauth is the auth.md agent-identity layer (api-v1-redesign §5 / Slice 5b). This file is the signing foundation (5b-1): an RS256 JWT signer loaded from a PEM private key, plus the public JWKS published at /.well-known/jwks.json that agents verify e2a-minted tokens against.
Key management mirrors the sibling agentdrive deployment: the private key is supplied as a PEM string via config/env (E2A_OAUTH_SIGNING_KEY), never generated or persisted by e2a, with a `kid` (E2A_OAUTH_SIGNING_KID, default "v1") advertised in the JWKS and stamped on every token. An empty key leaves the agent-auth surface DISABLED: the JWKS serves an empty key set and any attempt to sign returns ErrSigningDisabled, so dev/self-host deployments that don't use agent identity run unchanged. Rotation = advertise a new kid, then retire the old after the longest token TTL.
Index ¶
- Constants
- Variables
- type Signer
- func (s *Signer) Enabled() bool
- func (s *Signer) KeyID() string
- func (s *Signer) PublicJWKS() jose.JSONWebKeySet
- func (s *Signer) Sign(claims jwt.Claims, private map[string]interface{}) (string, error)
- func (s *Signer) SignAccessToken(sub, scope string, assertionVersion int, issuer string) (string, time.Time, error)
- func (s *Signer) SignIdentityAssertion(sub, scope string, assertionVersion int, issuer string) (string, time.Time, error)
- func (s *Signer) VerifyToken(token, expectedType, issuer string) (*VerifiedClaims, error)
- type VerifiedClaims
Constants ¶
const ( TypIdentityAssertion = "identity_assertion" TypAccessToken = "access_token" // IdentityAssertionTTL bounds the long-lived credential. Re-mintable any // time from the agent's e2a_agt_ key, so it need not be long; 30 days // matches the auth.md reference's anonymous assertion. IdentityAssertionTTL = 30 * 24 * time.Hour // AccessTokenTTL keeps the bearer short so a leak self-heals quickly. The // agent re-exchanges its identity_assertion when it expires. AccessTokenTTL = 15 * time.Minute )
auth.md token model (Slice 5b-2). e2a issues two server-signed RS256 JWTs:
- identity_assertion — the long-lived credential an agent holds (typ "identity_assertion"). Minted at POST /agent/identity once the agent has proven who it is (Slice 5b-2 bootstraps this with the agent's e2a_agt_ key). Re-presented at the token endpoint to mint access tokens.
- access_token — the short-lived bearer (typ "access_token") the agent actually calls the API with. Minted at POST /oauth2/token via grant_type=jwt-bearer.
Both carry the agent's email as `sub`, the granted `scope` (always "agent" on this path), and the `assertion_version` of the agent row — the kill switch: a bump invalidates every assertion/token mintable from the old version.
const SigningAlg = jose.RS256
SigningAlg is the only algorithm e2a issues — RS256, what auth.md's reference implementation emits and what every JWKS consumer expects.
Variables ¶
var ErrSigningDisabled = errors.New("agentauth: signing key not configured")
ErrSigningDisabled is returned by Sign when no signing key is configured. Handlers translate it into a 501/"agent auth not enabled" response rather than a 500, so an unconfigured deployment fails cleanly.
var ErrTokenInvalid = errors.New("agentauth: token invalid")
ErrTokenInvalid wraps every verification failure so callers can map the whole class to a 401/invalid_grant without leaking which check failed.
Functions ¶
This section is empty.
Types ¶
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer holds the parsed RSA private key and its key id. The zero value (and a Signer built from an empty PEM) is a valid, DISABLED signer.
func NewSigner ¶
NewSigner builds a Signer from a PEM-encoded RSA private key (PKCS#1 or PKCS#8) and a kid. An empty pemKey yields a disabled signer (no error) so the surface can be off by default; a non-empty but malformed key is a hard error so a misconfigured deployment fails fast at startup rather than at first sign.
func (*Signer) PublicJWKS ¶
func (s *Signer) PublicJWKS() jose.JSONWebKeySet
PublicJWKS returns the public half as a JWK set for /.well-known/jwks.json. When disabled it returns an empty (non-nil) set so the endpoint always serves valid JSON ({"keys":[]}).
func (*Signer) Sign ¶
Sign issues an RS256 JWT carrying the given claims, with the kid in the header so verifiers can select the right JWKS entry. Returns ErrSigningDisabled when no key is configured.
func (*Signer) SignAccessToken ¶
func (s *Signer) SignAccessToken(sub, scope string, assertionVersion int, issuer string) (string, time.Time, error)
SignAccessToken mints a short-lived access_token for sub. Same issuer/aud binding as the assertion. Returns the token and its absolute expiry.
func (*Signer) SignIdentityAssertion ¶
func (s *Signer) SignIdentityAssertion(sub, scope string, assertionVersion int, issuer string) (string, time.Time, error)
SignIdentityAssertion mints a long-lived identity_assertion for sub (the agent email), bound to issuer (= the AS public URL, used as both iss and aud). Returns the token and its absolute expiry.
func (*Signer) VerifyToken ¶
func (s *Signer) VerifyToken(token, expectedType, issuer string) (*VerifiedClaims, error)
VerifyToken validates an e2a-minted JWT: RS256 signature against the local public key, iss == issuer, aud contains issuer, not expired (with skew), and typ == expectedType. On success returns the typed claims; every failure wraps ErrTokenInvalid. assertion_version freshness is the caller's job (it needs the live agent row).