Documentation
¶
Overview ¶
Package gateway — base_ha backend upstream.
Implements the `base_ha` upstream kind for Hanzo Base HA clusters (hanzoai/base-ha). One writer, N replicas. The gateway is the leader tracker — clients never see a 307.
Per-backend `extra_config`:
"github.com/hanzoai/gateway/base_ha": {
"service_dns": "foo-hs.hanzo.svc.cluster.local",
"port": 8090,
"leader_poll_interval": "1s",
"write_methods": ["POST","PUT","PATCH","DELETE"],
"read_your_writes_ttl": "5s"
}
The enclosing backend's single Host is the ClusterIP service (round-robin for reads). The base_ha factory overrides the transport:
- write methods (or X-Base-Writer: required header) → writer pod
- reads → round-robin via the ClusterIP service
- for 5s after a write, same client (X-Forwarded-For + X-Org-Id) pins to the writer for read-your-writes consistency
Leader discovery: a single goroutine per service_dns polls GET http://{service_dns}:{port}/_ha/leader every leader_poll_interval. The response is stored in an atomic.Pointer so the hot path is lock-free. On writer 5xx/connect-refused, the poller is force-refreshed and one retry is issued. Two consecutive 5xx = hard fail (no retry storm on OOM).
Package gateway — base-network backend upstream.
Implements `base-network://<service>` routing for base/network-enabled services (ATS, BD, TA, IAM, KMS, AML). See ~/work/hanzo/base/docs/NETWORK.md.
Per-backend `extra_config`:
"github.com/hanzoai/gateway/base-network": {
"shard_key": "user_id",
"shard_key_source": "jwt.sub" // | jwt.owner | header:X-Shard | cookie:sid
}
The enclosing backend's `host` names the headless Service whose members are polled at GET /-/base/members every 5 s. Writes route to a single rendezvous-hash owner pod; reads spread across the shard's member subset. A 307 from a pod we picked means the target isn't caught up — follow once.
build_app.go wires the gateway edge per HIP-0110.
The gateway is a pure ZAP→ZAP relay: ingress terminates TLS+HTTP and speaks ZAP to the gateway; the gateway authenticates on the Forward ENVELOPE, injects identity, and forwards to cloud/base over ZAP. The relay therefore lives on the ZAP node (RegisterRelay), not on an HTTP router — there is no per-request HTTP handling at the gateway anymore.
BuildApp still returns a tiny *zip.App so cmd/gateway/main can keep its :8080 listener as a process/liveness surface (ingress owns real client HTTP). It carries no forwarder and no auth middleware — those moved onto the node. RegisterRelay is the real entrypoint.
Package gateway is the HIP-0106 unified-binary mount surface for the Hanzo Gateway subsystem.
The mount logic ships under the `cloud` build tag so the standalone cmd/gateway binary (vendor-mode, KrakenD-based, ghcr.io/hanzoai/gateway) builds without pulling hanzoai/cloud + hanzoai/zip into its dependency tree. Build with `-tags=cloud` to compile the Mount.
See pkg/gateway/mount.go for the canonical
func Mount(app *zip.App, deps cloud.Deps) error
signature and the init() callsite that registers ("gateway", order=80) in cloud.Registry.
gate.go is the gateway relay's authn policy, run on every inbound Forward ENVELOPE per HIP-0110. It is the edge trust boundary: the gate validates the IAM JWT carried in the Forward's headers and stamps the resolved identity (TenantID/UserID/IsAdmin/Permissions) onto the envelope so the backend trusts the edge. forward.Relay then re-emits the Forward with that identity and ships it to the chosen backend.
One auth implementation: the gate reuses package iamauth (the same JWKS cache + JWT validation + token extraction shared with cmd/ingress and the gin/KrakenD middleware) and gateway's own permission bit-field math (computePermissionsBitField / permissionBits). No second copy.
Billing is NOT enforced here. The gateway is authn + identity injection only; cloud-api's own prepaid balance gate bills the bridged request. This split is deliberate (HIP-0110): the relay must not read f.Body and must not block on a per-request Commerce round-trip — it stamps identity and forwards. The gate therefore touches f.Path and f.Headers only.
Package gateway exposes the HIP-0106 unified-binary mount surface for the Hanzo Gateway. In the unified cloud binary the gateway acts as the trust boundary: it validates JWTs, strips client-supplied identity headers, and mints the gateway-authorized X-Org-Id / X-User-Id / X-User-Email / X-Roles / X-User-Permissions / X-User-IsAdmin / X-Phone-Number set documented in HIP-0026.
In split-deploy mode (legacy ghcr.io/hanzoai/gateway image) the gateway runs as its own KrakenD process — the standalone cmd/gateway binary keeps that path. The Mount path below is the SAME logic, reused inside the cloud binary so we do not maintain two trust boundaries.
peer_resolver.go decouples "where a backend lives" (its ZAP address) from "which connected NodeID forward.Relay must Call" (the handshake- learned peerID). HIP-0110's relay needs a peerID per request path, but a peerID is only knowable AFTER a successful ConnectDirectID handshake.
The original boot path braided three concerns into dialBackends: start the node, eagerly dial both backends, and treat any dial failure as a fatal os.Exit. That made the gateway un-bootable whenever cloud:9090 / base:9091 were absent — which is the steady state in environments where the ZAP backends aren't deployed yet, while HTTP serving (the part prod actually needs) would work fine.
peerResolver separates those concerns. The node is started once and unconditionally; dialing a backend is deferred to first use and retried on every Call until it succeeds. A backend being down degrades only the requests routed to it (they get a "peer not found" error that forward.Relay returns to the caller as a normal per-request failure), never the process. ConnectDirectID is idempotent — once a backend is reachable the first resolve caches its peerID and every subsequent resolve is a lock-free map read — so a healthy backend is dialed exactly once and ZAP behaves identically to the old eager path.
Package gateway rebrands every user-visible Lura/KrakenD identifier so that nothing a client or backend sees contains the string "krakend" or "KrakenD".
Lura (the upstream SDK we build on) emits several hard-coded brand strings:
- response header "X-KRAKEND"
- response header "X-Krakend-Completed"
- outbound backend User-Agent "KrakenD Version x.y"
- core.KrakendHeaderValue "Version x.y"
The response header name "X-KRAKEND" is a const in lura/core and cannot be reassigned at runtime; it is stripped and replaced by the BrandingMiddleware below. Everything else is a package-level var and is reassigned in init().
Index ¶
- Constants
- Variables
- func AddCheck(f func(context.Context, *cobra.Command, string, string) bool)
- func AssertGatewayMinted(c *zip.Ctx) bool
- func BaseHABackendFactory(logger logging.Logger, next proxy.BackendFactory) proxy.BackendFactory
- func BaseNetworkBackendFactory(logger logging.Logger, next proxy.BackendFactory) proxy.BackendFactory
- func BrandingMiddleware() gin.HandlerFunc
- func BuildApp(deps RouterDeps) (*zip.App, error)
- func InitZapListenerFromEnv()
- func LoadPlugins(folder, pattern string, logger logging.Logger)
- func LoadPluginsWithContext(ctx context.Context, folder, pattern string, logger logging.Logger)
- func LoadRoutes(cfg *RoutesConfig) error
- func LoadRoutesFromFile(path string) error
- func Mount(app *zip.App, deps cloud.Deps) error
- func NewAuthMiddleware(cfg AuthConfig) gin.HandlerFunc
- func NewBackendFactory(logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory
- func NewBackendFactoryWithContext(ctx context.Context, logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory
- func NewEngine(cfg config.ServiceConfig, opt luragin.EngineOptions) *gin.Engine
- func NewExecutor(ctx context.Context) cmd.Executor
- func NewHandlerFactory(logger logging.Logger, metricCollector *metrics.Metrics, ...) router.HandlerFactory
- func NewLazyPicker(node *zaplib.Node, logger luxlog.Logger, baseAddr, cloudAddr string) forward.PeerPicker
- func NewProxyFactory(logger logging.Logger, backendFactory proxy.BackendFactory, ...) proxy.Factory
- func NewTestPluginCmd() cmd.Command
- func NewTestPluginCmdWithArgs(flags ...cmd.FlagBuilder) cmd.Command
- func NewWidgetSecurityMiddleware(cfg WidgetSecurityConfig) gin.HandlerFunc
- func RegisterEncoders()
- func RegisterRelay(deps RelayDeps) error
- func RegisterSubscriberFactories(_ context.Context, _ config.ServiceConfig, _ logging.Logger) func(n string, p int)
- func SetGatewayMinted(c *zip.Ctx)
- func StartZapListener(cfg ZapListenerConfig) error
- func StopZapListener()
- func ZapBackendFactory(logger logging.Logger, next proxy.BackendFactory) proxy.BackendFactory
- type AgentStarter
- type AuthConfig
- type BackendFactory
- type BaseHAConfig
- type BaseNetworkConfig
- type BloomFilterJWT
- type DefaultRunServerFactory
- type EngineFactory
- type ExecutorBuilder
- type HandlerFactory
- type KMSResolver
- type LoggerBuilder
- type LoggerFactory
- type MetricsAndTraces
- type MetricsAndTracesRegister
- type PluginLoader
- type PluginLoaderWithContext
- type ProxyFactory
- type RelayDeps
- type RouteEntry
- type RouterDeps
- type RoutesConfig
- type RunServer
- type RunServerFactory
- type SubscriberFactoriesRegister
- type TokenRejecterFactory
- type WidgetSecurityConfig
- type ZapConfig
- type ZapListenerConfig
Constants ¶
const ( // ZapNamespace is the config key for ZAP backend configuration. ZapNamespace = "github.com/hanzoai/gateway/zap" // ZAP message types for gateway RPC MsgTypeHTTPRequest uint16 = 200 MsgTypeHTTPResponse uint16 = 201 )
const BaseHANamespace = "github.com/hanzoai/gateway/base_ha"
BaseHANamespace is the extra_config key for base_ha upstreams.
const BaseNetworkNamespace = "github.com/hanzoai/gateway/base-network"
const CompletedHeader = "X-Gateway-Completed"
CompletedHeader is the user-visible response header that replaces "X-Krakend-Completed". It keeps the same "true" / "false" semantic so any client depending on the completion flag only needs to change the header key.
const PoweredByHeader = "X-Powered-By"
PoweredByHeader is the user-visible response header that replaces "X-KRAKEND".
Variables ¶
var BrandName = "hanzoai/gateway"
BrandName is the canonical rebrand string emitted on responses and outbound backend calls. Overridable at build time via:
-ldflags "-X github.com/hanzoai/gateway.BrandName=..."
Functions ¶
func AssertGatewayMinted ¶
AssertGatewayMinted returns true iff the request flowed through gateway's auth middleware (i.e. X-Org-Id was minted by gateway, not supplied by the client).
Implementation: gateway middleware sets a per-request Locals key after JWT validation. AssertGatewayMinted reads that key.
In production, every cloud-mounted subsystem should reject any request where AssertGatewayMinted returns false (HTTP 502 with a clear message — it indicates a deployment misconfiguration, not a client problem).
func BaseHABackendFactory ¶
func BaseHABackendFactory(logger logging.Logger, next proxy.BackendFactory) proxy.BackendFactory
BaseHABackendFactory wraps the next BackendFactory with base_ha routing. Backends without BaseHANamespace in their extra_config fall through.
func BaseNetworkBackendFactory ¶
func BaseNetworkBackendFactory(logger logging.Logger, next proxy.BackendFactory) proxy.BackendFactory
BaseNetworkBackendFactory wraps `next` and intercepts backends carrying a BaseNetworkNamespace extra_config block. All others fall through.
func BrandingMiddleware ¶
func BrandingMiddleware() gin.HandlerFunc
BrandingMiddleware strips any residual lura-emitted branding headers that leak through because their names are compile-time consts in lura/core, and replaces them with canonical Hanzo equivalents. Must run before the lura endpoint handler so the wrapped writer is in place by the time lura calls c.Header().
func BuildApp ¶
func BuildApp(deps RouterDeps) (*zip.App, error)
BuildApp returns the gateway's tiny HTTP surface for cmd/gateway/main's :8080 listener. Real client traffic arrives over ZAP via RegisterRelay; this app exists only so the process exposes a liveness/readiness HTTP endpoint on the public port. It carries no forwarder.
func InitZapListenerFromEnv ¶
func InitZapListenerFromEnv()
InitZapListenerFromEnv initializes the ZAP listener from environment variables. Set ZAP_LISTENER_ENABLED=true to enable.
func LoadPlugins ¶
LoadPlugins loads and registers the plugins so they can be used if enabled at the configuration
func LoadPluginsWithContext ¶
func LoadRoutes ¶
func LoadRoutes(cfg *RoutesConfig) error
LoadRoutes loads routing config from YAML. Called at startup and on hot-reload.
func LoadRoutesFromFile ¶
LoadRoutesFromFile loads routes from a YAML file.
func Mount ¶
Mount registers the gateway subsystem on app per HIP-0106. The mount does three things:
Installs the canonical zip middleware chain (strip client-supplied identity headers, validate JWT, mint X-* headers) so every downstream subsystem on the same zip.App sees a clean trust boundary.
Loads the gateway routes table (KMS or local file) so any path not handled by a co-resident subsystem is proxied to the configured backend.
Exposes /_/gateway/healthz on the native zip surface so liveness probes work even with auth fully enabled.
Mount is idempotent — calling it twice on the same App will install the middleware twice; cloud.Registry guarantees one call per process.
func NewAuthMiddleware ¶
func NewAuthMiddleware(cfg AuthConfig) gin.HandlerFunc
NewAuthMiddleware creates a gin middleware that validates IAM JWT tokens, checks billing status, and injects identity headers for downstream services.
Canonical identity headers (the only ones downstream services should rely on):
- X-User-Id: user ID from JWT "sub" (fallback: preferred_username, name)
- X-Org-Id: org slug from JWT "owner" claim
- X-Roles: comma-joined role names from JWT "roles" claim
- X-User-Permissions: base-10 int64 bit-field derived from JWT permissions
- isAdmin (commerce treats absent/0 as no rights).
Auxiliary headers (derivatives of the JWT for convenience):
- X-User-Email: email from JWT "email" claim
- X-Phone-Number: phone from JWT "phone_number" or "phone" claim
- X-User-IsAdmin: "true" if the JWT asserts isAdmin
Trust boundary: all of the above are stripped on ingress (see stripIdentityHeaders) and only re-set after the JWT is validated. A client-supplied X-User-Permissions can NEVER reach a downstream service — Red P0-1 (2026-04-27).
Billing:
- Checks commerce service for positive balance
- Fail-open: if billing service is unreachable, request proceeds
- If balance <= 0: returns 402 Payment Required
Public endpoints (configurable allowlist) bypass all auth checks.
func NewBackendFactory ¶
func NewBackendFactory(logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory
NewBackendFactory creates a BackendFactory by stacking all the available middlewares: - oauth2 client credentials - http cache - martian - pubsub - amqp - cel - lua - rate-limit - circuit breaker - metrics collector - opencensus collector
func NewBackendFactoryWithContext ¶
func NewBackendFactoryWithContext(ctx context.Context, logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory
NewBackendFactoryWithContext creates a BackendFactory by stacking all the available middlewares and injecting the received context
func NewEngine ¶
func NewEngine(cfg config.ServiceConfig, opt luragin.EngineOptions) *gin.Engine
NewEngine creates a new gin engine with middlewares and routing.
func NewExecutor ¶
NewExecutor returns an executor for the cmd package. The executor initalizes the entire gateway by registering the components and composing a RouterFactory wrapping all the middlewares.
func NewHandlerFactory ¶
func NewHandlerFactory(logger logging.Logger, metricCollector *metrics.Metrics, rejecter jose.RejecterFactory) router.HandlerFactory
NewHandlerFactory returns a HandlerFactory with a rate-limit and a metrics collector middleware injected
func NewLazyPicker ¶
func NewLazyPicker(node *zaplib.Node, logger luxlog.Logger, baseAddr, cloudAddr string) forward.PeerPicker
NewLazyPicker is the production seam: it builds a lazy resolver over an already-started node, best-effort warms the base and cloud backends (a failed warm is logged at WARN and ignored — the picker will retry on first use), and returns the forward.PeerPicker for RegisterRelay. The gateway boots regardless of backend reachability; healthy backends are dialed once here and served from cache thereafter.
func NewProxyFactory ¶
func NewProxyFactory(logger logging.Logger, backendFactory proxy.BackendFactory, metricCollector *metrics.Metrics) proxy.Factory
NewProxyFactory returns a new ProxyFactory wrapping the injected BackendFactory with the default proxy stack and a metrics collector
func NewTestPluginCmd ¶
func NewTestPluginCmdWithArgs ¶
func NewTestPluginCmdWithArgs(flags ...cmd.FlagBuilder) cmd.Command
func NewWidgetSecurityMiddleware ¶
func NewWidgetSecurityMiddleware(cfg WidgetSecurityConfig) gin.HandlerFunc
NewWidgetSecurityMiddleware creates a gin middleware that enforces:
Per-IP rate limiting for widget keys (hz_*): prevents any single IP from abusing the free widget endpoint.
Global rate limiting across all widget requests: prevents distributed attacks from exhausting model API budget.
Origin validation: widget keys are only accepted from approved Hanzo domains (Origin or Referer header). Non-browser clients (curl, scripts) that omit Origin headers are rejected for widget keys.
This middleware MUST run after NewAuthMiddleware. It only acts on requests with hz_ bearer tokens; all other requests pass through untouched.
func RegisterEncoders ¶
func RegisterEncoders()
RegisterEncoders registers all the available encoders
func RegisterRelay ¶
RegisterRelay installs the canonical HIP-0110 relay on deps.Node:
- forward.Relay(node, gate, pick): decodes each inbound Forward envelope, runs the auth gate on it (JWT validate + identity inject, NO body read, NO billing — cloud-api bills the bridged request), re-emits the Forward with identity, and forwards to the picked peer, streaming the backend's Response back verbatim.
- forward.RegisterReversePushHandler(node): routes backend→gateway Push frames (SSE / WebSocket) back to the originating client conn.
The gate reuses package iamauth + gateway's permission bit-field math; there is exactly one auth implementation. pick routes /v1/base/* to the base peer, everything else to cloud.
func RegisterSubscriberFactories ¶
func RegisterSubscriberFactories(_ context.Context, _ config.ServiceConfig, _ logging.Logger) func(n string, p int)
RegisterSubscriberFactories registers all the available sd adaptors
func SetGatewayMinted ¶
SetGatewayMinted marks the current request as gateway-minted. Called by gateway's auth middleware after successful JWT validation (or trusted-headers pass-through). Subsystems must NEVER call this — it is the trust boundary itself.
func StartZapListener ¶
func StartZapListener(cfg ZapListenerConfig) error
StartZapListener starts a TLS 1.3+PQ listener on the given port. External clients (e.g. dev CLI) connect here with TLS-wrapped ZAP binary. Each accepted TLS connection is transparently proxied to the internal ZAP node (started by the ZapBackendFactory pool on the internal port), which handles the ZAP handshake, message dispatch, and forwarding to cloud-api.
func StopZapListener ¶
func StopZapListener()
StopZapListener gracefully shuts down the ZAP TLS listener.
func ZapBackendFactory ¶
func ZapBackendFactory(logger logging.Logger, next proxy.BackendFactory) proxy.BackendFactory
ZapBackendFactory wraps a standard BackendFactory and adds ZAP transport support. Backends with "github.com/hanzoai/gateway/zap" in their extra_config will use ZAP binary transport instead of HTTP.
Types ¶
type AgentStarter ¶
type AgentStarter interface {
Start(
context.Context,
[]*config.AsyncAgent,
logging.Logger,
chan<- string,
proxy.Factory,
) func() error
}
AgentStarter defines a type that starts a set of agents
type AuthConfig ¶
type AuthConfig struct {
// Enabled controls whether the auth middleware is active.
// Default: true. Set to false via AUTH_ENABLED=false to disable
// all auth checks (useful for integration tests and development).
Enabled bool
// JWKS URL to fetch signing keys (default: https://hanzo.id/.well-known/jwks)
JWKSURL string
// Expected JWT issuer (default: https://hanzo.id)
Issuer string
// Audiences is the allowlist of acceptable JWT `aud` values. A token
// passes when its audience matches ANY entry. IAM stamps user tokens with
// aud=<client_id>, so a single fixed audience rejects every user JWT; the
// allowlist (iamauth.AudiencesFromEnv) is the fix. Override entirely with
// GATEWAY_ALLOWED_AUDIENCES.
Audiences []string
// Billing check endpoint (default: http://commerce.hanzo.svc.cluster.local:8001)
BillingURL string
// BillingToken is the COMMERCE_SERVICE_TOKEN for authenticating with Commerce.
BillingToken string
// BillingEnabled controls whether billing checks are performed.
// Default: true (checks enabled). Set to false to disable.
BillingEnabled bool
// BillingPaths scopes balance enforcement to request paths matching one
// of these prefixes. This is the one knob that keeps billing OFF for the
// AI/validated (per-token billed downstream) and public routes while
// ON for the metered must-gate platform surface (cloud/tasks/insights/
// o11y/mpc/evals/licensing/product/provisioning/…). The /v1/commerce
// funding surface is hard-excluded in code (billingPathMatch) regardless
// of this list. Empty list enforces on every non-funding, non-public
// route. Set BILLING_PATHS to the must-gate prefixes. One scope.
BillingPaths []string
// Paths that bypass auth entirely (exact prefix match)
PublicPaths []string
// Hosts that bypass auth entirely (e.g. hanzo.id for login)
PublicHosts []string
// If true, requests without a token are rejected (402/401).
// If false (default), requests without a token pass through without headers.
RequireAuth bool
}
AuthConfig holds configuration for the auth middleware.
func DefaultAuthConfig ¶
func DefaultAuthConfig() AuthConfig
DefaultAuthConfig returns the default auth configuration from environment variables.
func (AuthConfig) Validate ¶ added in v2.15.0
func (c AuthConfig) Validate() error
Validate reports a fatal misconfiguration: billing enabled without the Commerce endpoint and service token it depends on. Enforced so enabling billing can never silently fail open (empty URL → checkBalance allows) nor 503-storm the whole metered surface (empty token → Commerce 401). The one caller that can return an error — gateway.Mount — refuses to start in this state; NewAuthMiddleware additionally fails the balance gate closed.
type BackendFactory ¶
type BackendFactory interface {
NewBackendFactory(context.Context, logging.Logger, *metrics.Metrics) proxy.BackendFactory
}
BackendFactory returns a Gateway backend factory, ready to be passed to the Gateway proxy factory
type BaseHAConfig ¶
type BaseHAConfig struct {
// ServiceDNS is the headless or ClusterIP DNS name of the base-ha
// service. Used only for the /_ha/leader poll — the actual read path
// uses the enclosing backend Host (round-robin).
ServiceDNS string `json:"service_dns"`
// Port is the HTTP port the base-ha pods listen on.
Port int `json:"port"`
// LeaderPollInterval is parsed by time.ParseDuration. Default 1s.
LeaderPollInterval string `json:"leader_poll_interval"`
// WriteMethods lists HTTP methods that require writer pinning.
// Default: POST, PUT, PATCH, DELETE.
WriteMethods []string `json:"write_methods"`
// ReadYourWritesTTL is how long after a write the same client pins
// to the writer for reads. Default 5s. Set to "0s" to disable.
ReadYourWritesTTL string `json:"read_your_writes_ttl"`
}
BaseHAConfig mirrors the JSON schema documented above. Zero-value defaults are applied at factory time.
type BaseNetworkConfig ¶
type BloomFilterJWT ¶
type BloomFilterJWT struct{}
BloomFilterJWT is the default TokenRejecterFactory implementation.
func (BloomFilterJWT) NewTokenRejecter ¶
func (BloomFilterJWT) NewTokenRejecter(ctx context.Context, cfg config.ServiceConfig, l logging.Logger, reg func(n string, p int)) (jose.ChainedRejecterFactory, error)
NewTokenRejecter registers the bloomfilter component and links it to a token rejecter. Then it returns a chained rejecter factory with the created token rejecter and other based on the CEL component.
type DefaultRunServerFactory ¶
type DefaultRunServerFactory struct{}
DefaultRunServerFactory creates the default RunServer by wrapping the injected RunServer with the plugin loader and the CORS module
func (*DefaultRunServerFactory) NewRunServer ¶
func (*DefaultRunServerFactory) NewRunServer(l logging.Logger, next router.RunServerFunc) RunServer
type EngineFactory ¶
type EngineFactory interface {
NewEngine(config.ServiceConfig, router.EngineOptions) *gin.Engine
}
EngineFactory returns a gin engine, ready to be passed to the Gateway RouterFactory
type ExecutorBuilder ¶
type ExecutorBuilder struct {
// PluginLoader is deprecated: Use PluginLoaderWithContext
PluginLoader PluginLoader
PluginLoaderWithContext PluginLoaderWithContext
LoggerFactory LoggerFactory
SubscriberFactoriesRegister SubscriberFactoriesRegister
TokenRejecterFactory TokenRejecterFactory
MetricsAndTracesRegister MetricsAndTracesRegister
EngineFactory EngineFactory
ProxyFactory ProxyFactory
BackendFactory BackendFactory
HandlerFactory HandlerFactory
RunServerFactory RunServerFactory
AgentStarterFactory AgentStarter
Middlewares []gin.HandlerFunc
}
ExecutorBuilder is a composable builder. Every injected property is used by the NewCmdExecutor method.
func (*ExecutorBuilder) NewCmdExecutor ¶
func (e *ExecutorBuilder) NewCmdExecutor(ctx context.Context) cmd.Executor
NewCmdExecutor returns an executor for the cmd package. The executor initializes the entire gateway by delegating most of the tasks to the injected collaborators. They register the components and compose a RouterFactory wrapping all the middlewares. Every nil collaborator is replaced by the default one offered by this package.
type HandlerFactory ¶
type HandlerFactory interface {
NewHandlerFactory(logging.Logger, *metrics.Metrics, jose.RejecterFactory) router.HandlerFactory
}
HandlerFactory returns a Gateway router handler factory, ready to be passed to the Gateway RouterFactory
type KMSResolver ¶
type KMSResolver interface {
// FetchRoutes returns the raw YAML payload at the given KMS path.
// An empty path is a programmer error; resolvers should return an error.
FetchRoutes(path string) ([]byte, error)
}
KMSResolver fetches a routing-config secret payload from a Hanzo KMS path. The default implementation is a noop; activated implementations are wired in by setting GATEWAY_ROUTES_KMS_PATH (and the resolver-specific env vars documented on each implementation).
Implementations MUST return the raw YAML bytes for the routes config; the caller (loadRoutesFromEnv) is responsible for parsing them via yaml.Unmarshal.
func SetKMSResolver ¶
func SetKMSResolver(r KMSResolver) KMSResolver
SetKMSResolver swaps the package-level KMS resolver. Intended for tests and for embedders that want to plug in a non-HTTP secret backend. Returns the previous resolver so callers can restore it.
type LoggerBuilder ¶
type LoggerBuilder struct{}
LoggerBuilder is the default BuilderFactory implementation.
func (LoggerBuilder) NewLogger ¶
func (LoggerBuilder) NewLogger(cfg config.ServiceConfig) (logging.Logger, io.Writer, error)
NewLogger sets up the logging components as defined at the configuration.
type LoggerFactory ¶
LoggerFactory returns a Gateway Logger factory, ready to be passed to the Gateway RouterFactory
type MetricsAndTraces ¶
type MetricsAndTraces struct {
// contains filtered or unexported fields
}
MetricsAndTraces is the default implementation of the MetricsAndTracesRegister interface.
func (*MetricsAndTraces) Close ¶
func (m *MetricsAndTraces) Close()
type MetricsAndTracesRegister ¶
type MetricsAndTracesRegister interface {
Register(context.Context, config.ServiceConfig, logging.Logger) *metrics.Metrics
}
MetricsAndTracesRegister registers the defined observability components and returns a metrics collector, if required.
type PluginLoader ¶
PluginLoader defines the interface for the collaborator responsible of starting the plugin loaders Deprecated: Use PluginLoaderWithContext
type PluginLoaderWithContext ¶
type PluginLoaderWithContext interface {
LoadWithContext(ctx context.Context, folder, pattern string, logger logging.Logger)
}
PluginLoaderWithContext defines the interface for the collaborator responsible of starting the plugin loaders
type ProxyFactory ¶
type ProxyFactory interface {
NewProxyFactory(logging.Logger, proxy.BackendFactory, *metrics.Metrics) proxy.Factory
}
ProxyFactory returns a Gateway proxy factory, ready to be passed to the Gateway RouterFactory
type RelayDeps ¶
type RelayDeps struct {
Logger luxlog.Logger
Node *zaplib.Node
// Pick chooses the backend peer for a request path. Production passes a
// lazy resolver-backed picker (peerResolver.picker) that dials backends
// on first use, so the gateway boots even when the backends are absent.
// When nil, RegisterRelay falls back to the static pickPeer over the
// pre-resolved BasePeerID / CloudPeerID below.
Pick forward.PeerPicker
// BasePeerID / CloudPeerID are the handshake-learned NodeIDs of the
// already-connected base and cloud peers (from ConnectDirectID at dial
// time). Used only when Pick is nil (e.g. tests that dial up front).
BasePeerID string
CloudPeerID string
// Auth is the IAM JWT validation config for the relay gate. Use
// DefaultAuthConfig() to read it from the environment.
Auth AuthConfig
}
RelayDeps is what RegisterRelay needs to stand up the ZAP relay on a node.
type RouteEntry ¶
type RouteEntry struct {
Prefix string `yaml:"prefix" json:"prefix"`
Backend string `yaml:"backend" json:"backend"`
Rewrite string `yaml:"rewrite,omitempty" json:"rewrite,omitempty"` // optional: rewrite prefix
}
RouteEntry maps a path prefix to a backend URL.
type RouterDeps ¶
type RouterDeps struct {
Logger luxlog.Logger
ZAPNode *zaplib.Node
CloudAddr string
BaseAddr string
}
RouterDeps is the set of dependencies BuildApp needs.
type RoutesConfig ¶
type RoutesConfig struct {
Redirects map[string]string `yaml:"redirects" json:"redirects"`
Routes map[string][]RouteEntry `yaml:"routes" json:"routes"`
Subdomains map[string]string `yaml:"subdomains" json:"subdomains"`
}
RoutesConfig is the YAML structure for gateway routing. Loaded from KMS (GATEWAY_ROUTES_KMS_PATH) or local file (GATEWAY_ROUTES_FILE).
type RunServer ¶
RunServer defines the interface of a function used by the Gateway router to start the service
type RunServerFactory ¶
type RunServerFactory interface {
NewRunServer(logging.Logger, router.RunServerFunc) RunServer
}
RunServerFactory returns a RunServer with several wraps around the injected one
type SubscriberFactoriesRegister ¶
type SubscriberFactoriesRegister interface {
Register(context.Context, config.ServiceConfig, logging.Logger) func(string, int)
}
SubscriberFactoriesRegister registers all the required subscriber factories from the available service discover components and adapters and returns a service register function. The service register function will register the service by the given name and port to all the available service discover clients
type TokenRejecterFactory ¶
type TokenRejecterFactory interface {
NewTokenRejecter(context.Context, config.ServiceConfig, logging.Logger, func(string, int)) (jose.ChainedRejecterFactory, error)
}
TokenRejecterFactory returns a jose.ChainedRejecterFactory containing all the required jose.RejecterFactory. It also should setup and manage any service related to the management of the revocation process, if required.
type WidgetSecurityConfig ¶
type WidgetSecurityConfig struct {
// MaxRequestsPerIP is the maximum number of widget requests per IP
// within the rate limit window. Default: 10.
MaxRequestsPerIP int
// Window is the sliding window duration for per-IP rate limiting.
// Default: 1 minute.
Window time.Duration
// GlobalMaxRequests is the maximum total widget requests across all
// IPs within the window. Protects against distributed abuse.
// Default: 600.
GlobalMaxRequests int
// AllowedOrigins is the set of origin domains allowed for widget
// requests. If empty, origin checking is disabled.
AllowedOrigins []string
// CleanupInterval controls how often stale entries are evicted
// from the per-IP rate limit map. Default: 5 minutes.
CleanupInterval time.Duration
}
WidgetSecurityConfig holds configuration for widget key rate limiting and origin validation.
func DefaultWidgetSecurityConfig ¶
func DefaultWidgetSecurityConfig() WidgetSecurityConfig
DefaultWidgetSecurityConfig returns safe defaults.
AllowedOrigins can be overridden via WIDGET_ALLOWED_ORIGINS env var (comma-separated list of bare hostnames, no scheme/port). Subdomain matches are automatic: "hanzo.ai" also allows "*.hanzo.ai".
type ZapConfig ¶
type ZapConfig struct {
// NodeID is the ZAP node ID for this service
NodeID string `json:"node_id"`
// PeerID is the ZAP node ID of the target backend
PeerID string `json:"peer_id"`
// PeerAddr is the direct address (host:port) if mDNS is disabled
PeerAddr string `json:"peer_addr"`
// ServiceType is the mDNS service type (e.g., "_hanzo._tcp")
ServiceType string `json:"service_type"`
// Port is the local ZAP port
Port int `json:"port"`
// Timeout is the request timeout in milliseconds
Timeout int `json:"timeout_ms"`
// NoDiscovery disables mDNS and uses direct connection
NoDiscovery bool `json:"no_discovery"`
}
ZapConfig holds the ZAP backend configuration extracted from gateway.json.
Source Files
¶
- auth_compat.go
- auth_middleware.go
- backend_factory.go
- base_ha_backend.go
- base_network_backend.go
- build_app.go
- doc.go
- encoding.go
- executor.go
- gate.go
- handler_factory.go
- mount.go
- peer_resolver.go
- plugin.go
- proxy_factory.go
- rebrand.go
- router_engine.go
- router_kms.go
- sd.go
- trust.go
- widget_security.go
- zap_backend.go
- zap_listener.go
- zaphttp_listener.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
admin-api
command
admin-api is the god-mode backend for the Hanzo Operator console (admin.hanzo.ai).
|
admin-api is the god-mode backend for the Hanzo Operator console (admin.hanzo.ai). |
|
admin-guard
command
admin-guard is the single forward-auth gate that restricts Hanzo's RAW global-admin surfaces (platform.hanzo.ai, studio, commerce-admin, the raw KMS admin UI, the IAM management UI) to GLOBAL ADMINS ONLY — an IAM user whose org (`owner`) is the admin org (IAM `IsGlobalAdmin`: owner == AdminOrg).
|
admin-guard is the single forward-auth gate that restricts Hanzo's RAW global-admin surfaces (platform.hanzo.ai, studio, commerce-admin, the raw KMS admin UI, the IAM management UI) to GLOBAL ADMINS ONLY — an IAM user whose org (`owner`) is the admin org (IAM `IsGlobalAdmin`: owner == AdminOrg). |
|
gateway
command
cmd/gateway is the standalone Hanzo Gateway edge process per HIP-0110.
|
cmd/gateway is the standalone Hanzo Gateway edge process per HIP-0110. |
|
gateway-integration
command
|
|
|
ingress
command
Hanzo Ingress — lightweight host-based reverse proxy Replaces nginx-ingress with a minimal, config-driven proxy.
|
Hanzo Ingress — lightweight host-based reverse proxy Replaces nginx-ingress with a minimal, config-driven proxy. |
|
Package iamauth is the single source of truth for Hanzo IAM JWT validation at the edge.
|
Package iamauth is the single source of truth for Hanzo IAM JWT validation at the edge. |
|
loadgen
|
|
|
conn_holder
command
Package main holds N idle keep-alive HTTP/1.1 connections against a target URL.
|
Package main holds N idle keep-alive HTTP/1.1 connections against a target URL. |
|
Package middleware ships gateway-owned middleware for the zip web framework.
|
Package middleware ships gateway-owned middleware for the zip web framework. |
|
Package tests implements utility functions to help with API Gateway testing.
|
Package tests implements utility functions to help with API Gateway testing. |