Configuration model¶
HOG is configured entirely by YAML, in a Kubernetes-style resource shape.
Every document carries a kind, metadata (name plus optional labels),
and a spec — the module- or route-specific settings. There is no separate
schema file; each resource kind decodes its own spec into a typed Go struct
at load time.
${ENV} expansion¶
Before any document is parsed, config.Load expands ${VAR} and
${VAR:-default} references against the process environment, across the
entire raw file text — so a variable can appear anywhere, including inside a
quoted string. A referenced variable with no value and no default fails
config loading immediately; an explicitly-set empty value satisfies the
reference and skips the default.
Multi-document files and directories¶
config.Load accepts either a single file or a directory. For a directory,
every *.yaml/*.yml file is read in lexical filename order; within a
file, YAML's --- separates multiple documents. The concatenation of all
documents, in that order, is the config's document order — it decides two
things later: which RouteGroup field wins when several groups match the
same route (last match wins), and the default ordering of RequestPlugin and
ResponsePlugin instances that match the same route.
Resource kinds¶
The top-level kind values a config document can declare are:
| Kind | Cardinality | Purpose |
|---|---|---|
Gateway |
exactly one | gateway-wide settings and the plugin build manifest |
Route |
many | binds a path pattern to a terminal handler |
RouteGroup |
many | applies shared type/auth/projection/authorize to routes by selector |
Policy |
many | a named authorization rule, referenced by routes/groups |
IdP |
zero or one (today) | the OpenID Connect provider |
RequestPlugin |
many | a developer middleware in the pre-terminal slot |
ResponsePlugin |
many | a developer middleware in the post-terminal slot |
Telemetry |
zero or one | logging, tracing, and access-log settings (defaulted when absent) |
TerminalHandler and StateProvider are also names in HOG's kind namespace,
but they are not top-level document kinds you write yourself — they identify
modules selected from inside another resource: a route's
spec.handler.type picks a terminal handler, and the Gateway's
spec.stateProvider.type picks a state-provider module. See
extensibility for how the registry uses kind this way.
The Gateway resource¶
Exactly one Gateway resource is required; app.Parse rejects a config with
zero or with more than one. Its spec holds:
listen— the HTTP listen address (default:8080).trustedProxies— the load balancer/proxy addresses HOG runs behind.plugins— the build-time module manifest (import paths, optionally@version); consumed byhog-build, not read at runtime.session— cookie name, sealing key, TTL, fingerprint headers, the session info path, and the post-logout redirect.identity— how claims and groups are read out of the ID token/userinfo response (claims,groups.source|match|render|as,userInfo).auth— the login and logout endpoint paths.stateProvider— an optional server-side session store:type,refreshSkew,keyPrefix, and opaqueconfigpassed to that module.
Routes and route groups¶
A Route's spec sets match (the ServeMux pattern) and handler
(type plus handler-specific config). It may also set type (app or
service) and an access block: auth, authorize (a list of Policy
names to enforce), and projection.
A RouteGroup is not a parent container — it is a selector-based policy
object, exactly like a Kubernetes Service selecting Pods. Its spec.selector
(matchLabels and/or set-based matchExpressions) picks which routes it
applies to by their metadata.labels; an empty selector matches every route.
Its type and access block apply to every route it matches.
kind: Gateway
metadata: { name: hog }
spec:
listen: ":8080"
---
kind: Route
metadata: { name: dashboard, labels: { app: dash, tier: api } }
spec:
match: /api/dashboard
handler:
type: api
backends:
- { group: profile, upstream: ${PROFILE_SVC}, path: /me }
- { group: orders, upstream: ${ORDERS_SVC}, path: /list }
access: { authorize: [dash-users] }
---
kind: RouteGroup
metadata: { name: app-auth }
spec:
selector: { matchLabels: { app: dash } }
access: { auth: required }
---
kind: Policy
metadata: { name: dash-users }
spec:
require:
groups: [dashboard-users]
Resolving a route's effective access¶
route.Resolve computes what actually governs one route, combining the
route's own fields with every RouteGroup whose selector matches its labels
(document order; a later matching group overrides an earlier one field by
field):
- Type — the route's own
typewins if set; otherwise the last matching group'stype; otherwise it's inferred from the handler (reverse-proxyandapiinferservice; everything else infersapp). The result must beapporservice. - Auth — the route's own
access.authwins if set; otherwise the last matching group's; otherwise it defaults by type (service→required,app→public). The result must berequiredorpublic. - Projection — the route's own
access.projectionwins if set, otherwise the last matching group's. - Authorize — the union of the route's own
access.authorizelist and every matching group'saccess.authorizelist, de-duplicated, in that order. This is the set the authz stage evaluates.
An invalid type or auth value fails the build, even on a route that would
otherwise run without a session or IdP configured.
See the configuration reference for the full field-by-field listing.