Documentation
¶
Overview ¶
Package gateway is the funcd catalog PEP proxy over the Quack/DuckLake serving path (ADR-0137, FEAT-0008/F102). It brings catalog under the same per-caller, per-query Cedar PEP as blob/kv/S3, for BOTH caller classes, by putting funcd on the query path via a proxy that fronts the engine — the S3 gateway shape (ADR-0085/0088): one proxy door, a per-caller credential resolved to a Cedar principal, a per-op PEP, the real backend credential (the shared engine token) used only after allow.
A Quack token is a BARE BEARER credential (unlike an S3 access key, which is inert without the SigV4 secret), so the internal per-function token must be unforgeable on its own. It is a standard HS256 JWT (go-jose) signed with the node master: DeriveCatalogToken signs it, PrincipalFor verifies the signature (alg pinned to HS256) before trusting its {ns, fn} claims. The external Identity gets a minted token: its public access key id (ADR-0135), ".", and a random, rotatable part. The prefix names the owner, so the lookup is two store reads (ADR-0153), the shape of the Identity's S3 credential lookup.
Index ¶
- func DeriveCatalogToken(master []byte, ns v1.NamespaceName, fn v1.ObjectName) (string, error)
- func IdentityCatalogToken(ns v1.NamespaceName, name v1.ObjectName, random string) string
- func NewCatalogProxy(keys CatalogKeys, pdp auth.Authorizer, engine EngineTarget, log *slog.Logger) http.Handler
- type CatalogKeys
- type EngineTarget
- type Manager
- func (m *Manager) Ensure(catalog auth.EntityRef, upstream, engineToken string) (string, error)
- func (m *Manager) Listen(ns v1.NamespaceName, name v1.ObjectName, port int) (bound int, moved bool, err error)
- func (m *Manager) ProxyURL(ns v1.NamespaceName, name v1.ObjectName) (string, bool)
- func (m *Manager) Release(ns v1.NamespaceName, name v1.ObjectName)
- func (m *Manager) Released(ns v1.NamespaceName) []v1.ObjectName
- func (m *Manager) Remove(ns v1.NamespaceName, name v1.ObjectName)
- func (m *Manager) Shutdown()
- func (m *Manager) Suspend(ns v1.NamespaceName, name v1.ObjectName)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DeriveCatalogToken ¶
func DeriveCatalogToken(master []byte, ns v1.NamespaceName, fn v1.ObjectName) (string, error)
DeriveCatalogToken mints the per-(namespace, function) catalog token as an HS256 JWT signed with the node master (ADR-0137). A Quack token is a bare bearer credential, so it must be unforgeable on its own: only the master holder can produce the signature, so a function cannot forge a peer's token. Deterministic — the same (master, ns, fn) always yields the same compact JWT (no time claims), so injection re-derives it without stored state.
func IdentityCatalogToken ¶ added in v0.4.0
func IdentityCatalogToken(ns v1.NamespaceName, name v1.ObjectName, random string) string
IdentityCatalogToken builds an Identity's catalog token (ADR-0153): its access key id, ".", random.
func NewCatalogProxy ¶
func NewCatalogProxy(keys CatalogKeys, pdp auth.Authorizer, engine EngineTarget, log *slog.Logger) http.Handler
NewCatalogProxy builds the catalog PEP proxy handler fronting one CatalogService engine (ADR-0137). It logs through log (nil means slog.Default()), as the Manager's proxies log through its logger (ADR-0002 §6).
Types ¶
type CatalogKeys ¶
type CatalogKeys interface {
// PrincipalFor resolves token to its principal. First: verify it as an HS256 JWT signed with the node
// master (a forged {ns, fn} fails the signature) ⇒ a Function principal. Else: decode the owner an
// Identity token's prefix names and read that Identity and its Secret (two Gets) ⇒ an Identity
// principal. Else: default-deny (ok=false).
PrincipalFor(token string) (auth.EntityRef, bool)
}
CatalogKeys resolves a presented catalog token to its Cedar principal (ADR-0137). It mirrors s3gateway.principalFor, with the JWT signature standing in for SigV4's per-request possession proof.
func NewCatalogKeys ¶
func NewCatalogKeys(master []byte, s store.Store) CatalogKeys
NewCatalogKeys builds the production CatalogKeys over the node master and the metastore.
type EngineTarget ¶
type EngineTarget struct {
Catalog auth.EntityRef // Type=KindCatalogService, Namespace/Name = the fronted CatalogService
Upstream string // the netns engine endpoint (an http URL)
EngineToken string // the shared engine token, never exposed to callers
}
EngineTarget is the single CatalogService this proxy endpoint fronts (ADR-0137): its Cedar resource ref (the catalog::query resource, namespace and name both fixed by the endpoint), its netns engine URL, and the shared engine token the caller token is swapped for on allow. One EngineTarget per listener endpoint, so the target catalog is fixed by the endpoint (never parsed from the opaque Quack body).
The ADR Contracts sketch typed Catalog as `v1.EntityRef`; there is no such type — the PDP resource is auth.EntityRef (a CatalogService ref), which is what this carries.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the per-CatalogService catalog PEP proxies (ADR-0137): it runs ONE node-private http.Server per catalog, each fronting that catalog's engine. The CatalogService reconciler calls Listen on every pass to bind the catalog's listener on its recorded port (ADR-0162), Ensure on its Ready branch to target the engine, Suspend while it is not Ready, and Release on delete; it calls Remove only once no Function binds a deleted catalog, so the URL internal functions are injected with (FUNCD_CATALOG_<ALIAS>_URL) never changes within a daemon run. One listener endpoint per catalog fixes the PEP's target catalog by the endpoint (the proxy takes the namespace from the resolved principal — see NewCatalogProxy).
func NewManager ¶
func NewManager(bindHost, publishHost string, keys CatalogKeys, pdp auth.Authorizer, log *slog.Logger) *Manager
NewManager builds the catalog proxy Manager over the shared token resolver + PDP (ADR-0137). The same CatalogKeys/Authorizer back every per-catalog proxy; each proxy differs only in its EngineTarget (the catalog ref + its netns engine URL + shared engine token). bindHost is the interface each proxy binds; publishHost is the host injected into functions (see Manager). Both empty ⇒ 127.0.0.1 (the process-runtime/dev default, shared loopback); under containerd pass bindHost "0.0.0.0" and publishHost the CNI bridge gateway IP so a worker in its own netns can reach the proxy.
func (*Manager) Ensure ¶
Ensure guarantees a node-private proxy is running for catalog, fronting upstream with engineToken, and returns the bare "<publishHost>:<port>" host:port the function injects as FUNCD_CATALOG_<ALIAS>_URL (its handler wraps it in quack://; the engine endpoint was bare too). Under containerd publishHost is the CNI bridge gateway IP (netns-reachable), NOT the bind interface; in dev both are 127.0.0.1. If a proxy already runs for the catalog it is reused (idempotent — a re-reconcile does not rebind); if the upstream or engineToken differs, the running proxy is retargeted in place and its URL stays the same. Thread-safe.
func (*Manager) Listen ¶ added in v0.6.0
func (m *Manager) Listen(ns v1.NamespaceName, name v1.ObjectName, port int) (bound int, moved bool, err error)
Listen binds the catalog's listener on port, its recorded status.proxyPort, unless one is bound in this daemon run (ADR-0162). When port is 0 or that bind fails it binds a new port; moved reports a failed non-zero bind. A new listener answers 503 until Ensure targets the engine; a bound one stops being released. A failed last bind is fault.Unavailable.
func (*Manager) ProxyURL ¶ added in v0.6.0
func (m *Manager) ProxyURL(ns v1.NamespaceName, name v1.ObjectName) (string, bool)
ProxyURL returns the bare "<publishHost>:<port>" of the catalog's listener when one is bound in this daemon run, and false when none is: a restarted daemon binds none until the catalog's first pass, so a URL stored before the restart may be dead (#662, ADR-0162).
func (*Manager) Release ¶ added in v0.6.0
func (m *Manager) Release(ns v1.NamespaceName, name v1.ObjectName)
Release suspends a deleted catalog's listener as Suspend does and marks it released: its URL stays bound for the Functions that bind the catalog until Remove closes it (ADR-0162). Releasing an unknown catalog is a no-op.
func (*Manager) Released ¶ added in v0.6.0
func (m *Manager) Released(ns v1.NamespaceName) []v1.ObjectName
Released returns the catalogs of ns whose listener is released, sorted.
func (*Manager) Remove ¶
func (m *Manager) Remove(ns v1.NamespaceName, name v1.ObjectName)
Remove stops and forgets the proxy for a catalog (ADR-0137 teardown path). Idempotent: removing an unknown catalog is a no-op.
func (*Manager) Shutdown ¶
func (m *Manager) Shutdown()
Shutdown stops every running proxy (daemon shutdown). Thread-safe and idempotent.
func (*Manager) Suspend ¶ added in v0.2.2
func (m *Manager) Suspend(ns v1.NamespaceName, name v1.ObjectName)
Suspend keeps a catalog's listener, so the URL consumers were injected with stays valid (#59), but answers every request 503 and drops the engine target and token until Ensure retargets it: a not-Ready catalog serves no caller (#372). Suspending an unknown catalog is a no-op.