Documentation
¶
Overview ¶
Package auth provides tenant-aware API key authentication for cleat.
It implements Bearer token and header-based auth, tenant ID extraction from API keys via PostgreSQL lookup, and context propagation of tenant IDs.
Key types:
- TenantStore — PostgreSQL-backed API key to tenant ID resolution
- AuthMiddleware — HTTP middleware that validates and propagates tenant context
Package auth provides tenant-aware API key authentication for cleat's cleat execution framework.
It implements Bearer token and header-based auth, tenant ID extraction from API keys via PostgreSQL lookup, and context propagation of tenant IDs.
Index ¶
- func GenerateAPIKey() string
- func Middleware(store engine.WorkflowStore, requireAuth bool, publicPatterns ...string) func(http.Handler) http.Handler
- func TenantFromAPIKey(ctx context.Context, store engine.WorkflowStore, keyHash []byte) (uuid.UUID, error)
- func TenantIDFromContext(ctx context.Context) (uuid.UUID, bool)
- func WithTenantID(ctx context.Context, tenantID uuid.UUID) context.Context
- type TenantStore
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GenerateAPIKey ¶
func GenerateAPIKey() string
GenerateAPIKey generates a random API key string.
func Middleware ¶
func Middleware(store engine.WorkflowStore, requireAuth bool, publicPatterns ...string) func(http.Handler) http.Handler
Middleware authenticates requests using a cleat API key. Supports: Authorization: Bearer cleat_sk_<key> Also supports: X-Cleat-API-Key: <key> When requireAuth is true, requests without a valid API key are rejected with 401, except for public paths (/healthz, /metrics, and any additional patterns passed via publicPatterns).
publicPatterns is a hand-maintained allowlist, not a generic plugin-declared mechanism. It exists for endpoints that are meant to be called by parties who cannot present a cleat API key -- an inbound webhook receiver with its own HMAC check (plugins/webhookingest), a third-party IdP's OAuth redirect target (plugins/oauthprovider) -- and would otherwise 401 before that endpoint's own verification ever runs. Each entry is a Go 1.22+ http.ServeMux pattern ("POST /ingest/{source_id}"), matched with the exact same method+wildcard semantics the real mux uses, via a throwaway ServeMux built only for matching (see buildPublicMatcher) -- so "POST /ingest/{source_id}" does not also make "GET /ingest/sources" public.
A plugin-declared version of this (a PublicRoutes() method plugins implement themselves) would need changes to plugin/plugin.go and to each plugin, which are outside this package's ownership; wiring the list by hand in cmd/cleat-worker/main.go is the option available without those changes. Anyone adding a new externally-triggered plugin endpoint must add it here too -- nothing enforces that the two stay in sync.
func TenantFromAPIKey ¶
func TenantFromAPIKey(ctx context.Context, store engine.WorkflowStore, keyHash []byte) (uuid.UUID, error)
TenantFromAPIKey looks up a tenant by API key hash.
func TenantIDFromContext ¶
TenantIDFromContext extracts the tenant ID from the request context.
Types ¶
type TenantStore ¶
type TenantStore struct {
// contains filtered or unexported fields
}
TenantStore provides CRUD operations for tenants and their API keys.
func NewTenantStore ¶
func NewTenantStore(db *sql.DB) *TenantStore
NewTenantStore creates a new TenantStore.
func (*TenantStore) CreateAPIKey ¶
func (s *TenantStore) CreateAPIKey(ctx context.Context, tenantID uuid.UUID, description, rawKey string) error
CreateAPIKey creates an API key for a tenant. Returns the plaintext key (only returned once — caller must store it). The database stores sha256(key).
rawKey must be a high-entropy, randomly generated value — in practice always the output of GenerateAPIKey below (32 bytes from crypto/rand). SHA-256 is used, not a slow password KDF, because the only two production callers (cmd/cleat-worker/main.go) always pass a GenerateAPIKey() value: a 256-bit random token has no meaningful offline brute-force surface even hashed with a fast function, unlike a low-entropy user-chosen password. The hash exists so the plaintext key is never persisted and so lookups can use a DB equality index (ResolveTenantFromAPIKey does `WHERE key_hash = $1`); it is not a password-verification barrier. CodeQL go/weak-sensitive-data-hashing alert #12 flags this call; dismissed with that reasoning. If a caller is ever added that lets a tenant supply their own key text, that precondition breaks and this needs to move to bcrypt/scrypt/argon2 (golang.org/x/crypto is already a dependency) with a versioned-hash migration path for existing rows.
func (*TenantStore) CreateTenant ¶
func (s *TenantStore) CreateTenant(ctx context.Context, name, displayName string) (uuid.UUID, error)
CreateTenant creates a new tenant. Returns the tenant ID.
func (*TenantStore) RevokeAPIKey ¶
RevokeAPIKey revokes an API key.