Documentation
¶
Overview ¶
Package admin assembles a secure, explicit, side-loadable admin subtree (http.Handler).
What is admin? ¶
admin is an *assembly layer* that wires together operational handlers provided by zkit (primarily package ops, plus a small set of httpx middlewares) into a single net/http subtree handler.
You mount the returned handler anywhere in your existing HTTP stack, without affecting your business routing/middlewares:
mux := http.NewServeMux()
mux.Handle("/-/", admin.New(...))
mux.Handle("/", yourBusinessHandler)
admin is designed for operators. It only outputs text/json (no UI / JS).
Design priorities ¶
- Security (default-safe)
- Control (explicit enable/disable, explicit guards, fail-fast on assembly errors)
- Stability (paths and outputs aim to be stable)
- Ease-of-use (reasonable defaults, low mental model)
Quick start ¶
A minimal setup is: pick a Guard, enable a few capabilities, and mount the handler:
tokenGuard := admin.Tokens([]string{"s3cr3t"})
h := admin.New(
admin.EnableHealthz(admin.HealthzSpec{Guard: tokenGuard}),
admin.EnableRuntime(admin.RuntimeSpec{Guard: tokenGuard}),
)
mux := http.NewServeMux()
mux.Handle("/-/", h)
Core rules (important) ¶
## Explicit enable + explicit guard
Nothing is mounted unless explicitly enabled via EnableXxx options. Every enabled capability must have a non-nil Guard (nil is an assembly error and will panic).
## Capability = Path
Each capability is identified by its path.
Read endpoints support GET and HEAD on the same path.
## Assembly errors are fail-fast
admin treats invalid configuration as a programming/assembly error and fails fast (panic), including (but not limited to):
- nil Guard
- invalid Path
- duplicated Path
Output format ¶
Most endpoints default to text output. Users can override per request with:
- ?format=json
- ?format=text
This behavior is aligned with package ops.
Capability list (EnableXxx) and defaults ¶
Each EnableXxx enables exactly one capability (or one read capability for GET+HEAD). Every Spec supports:
- Guard (required, non-nil)
- Path (optional; empty => default path)
Default paths (relative to the mounted admin subtree):
Report (human-oriented, GET/HEAD, text-only):
- EnableReport: "/report"
Basic read endpoints (GET/HEAD):
- EnableHealthz: "/healthz"
- EnableReadyz: "/readyz"
- EnableBuildInfo: "/buildinfo"
- EnableRuntime: "/runtime"
- EnableLogLevelGet: "/log/level"
- EnableTuningSnapshot: "/tuning/snapshot"
- EnableTuningOverrides: "/tuning/overrides"
- EnableTuningLookup: "/tuning/lookup" (?key=)
- EnableTasksSnapshot: "/tasks/snapshot"
- EnableProvidedSnapshot: "/provided"
Write endpoints (POST):
- EnableLogLevelSet: "/log/level/set" (?level=)
- EnableTuningSet: "/tuning/set" (?key=&value=)
- EnableTuningResetDefault: "/tuning/reset-default" (?key=)
- EnableTuningResetLast: "/tuning/reset-last" (?key=)
- EnableTaskTrigger: "/tasks/trigger" (?name=)
- EnableTaskTriggerAndWait: "/tasks/trigger-and-wait" (?name=&timeout=)
Notes on task write endpoints:
- Task control is name-based: the admin/ops layer looks up tasks via task.Manager.Lookup.
- Unnamed tasks are not indexed by task.Manager and therefore cannot be triggered by name.
Notes:
- If two enabled capabilities collide on the same path, admin panics (fail-fast).
- Multi tuning instances are supported by overriding Path per endpoint. If you want two tuning trees, you must explicitly set distinct paths for each endpoint to avoid collisions.
Security model: Guard ¶
admin exposes its own Guard interface so users do not need to import or understand httpx.
type Guard interface {
// Middleware returns a net/http middleware that enforces this guard.
// It must be fast and must not block; it must not do I/O.
Middleware() func(http.Handler) http.Handler
}
Denied requests always respond with HTTP 403 (Forbidden).
Guard helpers ¶
admin includes a small set of guard constructors:
- DenyAll(), AllowAll()
- Tokens / HotTokens (token from a header)
- IPAllowList (client IP allowlist; integrates with WithRealIP)
- TokensOrIPAllowList / TokensAndIPAllowList (token + IP composite guards)
- Check(fn) (custom fast predicate)
Notes:
- Static token/IP lists are fail-closed: empty/invalid inputs deny all.
- Token header can be customized via WithTokenHeader (applies to all token-based guards).
Real IP (for IP-based guards) ¶
If you use IP guards, correct behavior behind proxies requires real IP extraction. Configure it with WithRealIP:
admin.WithRealIP(admin.RealIPSpec{
TrustedProxies: []string{"10.0.0.0/8"}, // your LB/proxy CIDRs
})
Default-safe behavior:
- If TrustedProxies is empty, headers are not trusted, and IP checks fall back to RemoteAddr.
Report (/report) ¶
The report endpoint outputs a human-oriented overview of *already enabled* read capabilities (observation endpoints) in a single plain-text page.
Design notes:
- /report is text-only (no ?format= negotiation).
- /report includes only what is enabled in the same admin instance.
- /report is guarded by its own Guard and does not attempt per-capability re-authorization.
- The "provided" section is truncated to a conservative max size (reportProvidedMaxBytes).
Example: minimal admin ¶
This example shows a typical setup: protect everything with a static token, but restrict write endpoints to a separate (stronger) guard if desired.
tokenGuard := admin.Tokens([]string{"s3cr3t"})
h := admin.New(
admin.EnableReport(admin.ReportSpec{Guard: tokenGuard}),
admin.EnableHealthz(admin.HealthzSpec{Guard: tokenGuard}),
admin.EnableRuntime(admin.RuntimeSpec{Guard: tokenGuard}),
)
Example: tuning + write allowlist ¶
Write endpoints must specify Access; empty Access denies all writes (fail-closed).
t := tuning.New()
// ... register tunings ...
read := admin.Tokens([]string{"read-token"})
write := admin.Tokens([]string{"write-token"})
h := admin.New(
admin.EnableTuningSnapshot(admin.TuningSnapshotSpec{
Guard: read,
T: t,
Access: admin.TuningAccessSpec{}, // empty => no filtering for reads
}),
admin.EnableTuningSet(admin.TuningSetSpec{
Guard: write,
T: t,
Access: admin.TuningAccessSpec{
AllowPrefixes: []string{"feature.", "ops."},
},
}),
)
Index ¶
- Constants
- func New(opts ...Option) http.Handler
- type BuildInfoSpec
- type Builder
- type Guard
- func AllowAll() Guard
- func Check(fn func(r *http.Request) bool) Guard
- func DenyAll() Guard
- func HotTokens(set TokenSetLike, opts ...TokenOption) Guard
- func HotTokensAndIPAllowList(set TokenSetLike, cidrsOrIPs []string, opts ...TokenOption) Guard
- func HotTokensOrIPAllowList(set TokenSetLike, cidrsOrIPs []string, opts ...TokenOption) Guard
- func IPAllowList(cidrsOrIPs ...string) Guard
- func Tokens(tokens []string, opts ...TokenOption) Guard
- func TokensAndIPAllowList(tokens []string, cidrsOrIPs []string, opts ...TokenOption) Guard
- func TokensOrIPAllowList(tokens []string, cidrsOrIPs []string, opts ...TokenOption) Guard
- type HealthzSpec
- type LogLevelGetSpec
- type LogLevelSetSpec
- type Option
- func EnableBuildInfo(spec BuildInfoSpec) Option
- func EnableHealthz(spec HealthzSpec) Option
- func EnableLogLevelGet(spec LogLevelGetSpec) Option
- func EnableLogLevelSet(spec LogLevelSetSpec) Option
- func EnableProvidedSnapshot(spec ProvidedSnapshotSpec) Option
- func EnableReadyz(spec ReadyzSpec) Option
- func EnableReport(spec ReportSpec) Option
- func EnableRuntime(spec RuntimeSpec) Option
- func EnableTaskTrigger(spec TaskTriggerSpec) Option
- func EnableTaskTriggerAndWait(spec TaskTriggerAndWaitSpec) Option
- func EnableTasksSnapshot(spec TasksSnapshotSpec) Option
- func EnableTuningLookup(spec TuningLookupSpec) Option
- func EnableTuningOverrides(spec TuningOverridesSpec) Option
- func EnableTuningResetDefault(spec TuningResetDefaultSpec) Option
- func EnableTuningResetLast(spec TuningResetLastSpec) Option
- func EnableTuningSet(spec TuningSetSpec) Option
- func EnableTuningSnapshot(spec TuningSnapshotSpec) Option
- func WithRealIP(spec RealIPSpec) Option
- type ProvidedSnapshotSpec
- type ReadyCheck
- type ReadyzSpec
- type RealIPSpec
- type ReportSpec
- type RuntimeSpec
- type TaskAccessSpec
- type TaskTriggerAndWaitSpec
- type TaskTriggerSpec
- type TasksSnapshotSpec
- type TokenOption
- type TokenSetLike
- type TuningAccessSpec
- type TuningLookupSpec
- type TuningOverridesSpec
- type TuningResetDefaultSpec
- type TuningResetLastSpec
- type TuningSetSpec
- type TuningSnapshotSpec
Examples ¶
Constants ¶
const DefaultTokenHeader = "X-Access-Token"
DefaultTokenHeader is the default header used by token-based guards when not overridden.
Variables ¶
This section is empty.
Functions ¶
func New ¶
New assembles and returns the admin subtree handler.
Security & control:
- Nothing is mounted unless explicitly enabled via options.
- Every enabled capability must have a non-nil Guard (explicit).
Assembly errors are fail-fast and will panic.
Example (Healthz) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/evan-idocoding/zkit/admin"
)
func main() {
h := admin.New(
admin.EnableHealthz(admin.HealthzSpec{Guard: admin.AllowAll()}),
)
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "http://admin.test/healthz", nil)
h.ServeHTTP(rr, req)
fmt.Print(rr.Body.String())
}
Output: ok
Types ¶
type BuildInfoSpec ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder collects capabilities and builds the final admin handler.
It is intentionally not exposed; users configure admin via Options.
type Guard ¶
type Guard interface {
// Middleware returns a net/http middleware that enforces this guard.
//
// Denied requests must respond with HTTP 403.
Middleware() func(http.Handler) http.Handler
}
Guard enforces request admission for a capability.
Implementations must be fast and must not block; they must not do I/O.
func Check ¶
Check returns a guard backed by a custom fast predicate.
fn must be fast and must not block; it must not do I/O. fn == nil is an assembly error and will panic.
func HotTokens ¶
func HotTokens(set TokenSetLike, opts ...TokenOption) Guard
HotTokens returns a guard that validates requests using a hot-update token set.
set must be non-nil (nil is an assembly error and will panic).
func HotTokensAndIPAllowList ¶
func HotTokensAndIPAllowList(set TokenSetLike, cidrsOrIPs []string, opts ...TokenOption) Guard
HotTokensAndIPAllowList is like TokensAndIPAllowList, but token validation uses a hot-update set.
func HotTokensOrIPAllowList ¶
func HotTokensOrIPAllowList(set TokenSetLike, cidrsOrIPs []string, opts ...TokenOption) Guard
HotTokensOrIPAllowList is like TokensOrIPAllowList, but token validation uses a hot-update set.
func IPAllowList ¶
IPAllowList returns a guard backed by a static IP allowlist.
Entries may be CIDRs or single IPs. Empty/invalid inputs deny all (fail-closed).
func Tokens ¶
func Tokens(tokens []string, opts ...TokenOption) Guard
Tokens returns a guard that validates requests using a static token list.
Semantics are inherited from httpx.AccessGuard:
- nil/empty tokens => deny-all (fail-closed)
- blank tokens are ignored; if none remain => deny-all
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/evan-idocoding/zkit/admin"
)
func main() {
g := admin.Tokens([]string{"t"})
h := admin.New(
admin.EnableHealthz(admin.HealthzSpec{Guard: g}),
)
rr1 := httptest.NewRecorder()
req1 := httptest.NewRequest(http.MethodGet, "http://admin.test/healthz", nil)
h.ServeHTTP(rr1, req1)
rr2 := httptest.NewRecorder()
req2 := httptest.NewRequest(http.MethodGet, "http://admin.test/healthz", nil)
req2.Header.Set(admin.DefaultTokenHeader, "t")
h.ServeHTTP(rr2, req2)
fmt.Println(rr1.Code, rr2.Code)
}
Output: 403 200
func TokensAndIPAllowList ¶
func TokensAndIPAllowList(tokens []string, cidrsOrIPs []string, opts ...TokenOption) Guard
TokensAndIPAllowList returns a guard that allows a request when:
- token is allowed, AND
- client IP is allowlisted.
This is a thin wrapper around httpx.AccessGuard (default AND semantics).
func TokensOrIPAllowList ¶
func TokensOrIPAllowList(tokens []string, cidrsOrIPs []string, opts ...TokenOption) Guard
TokensOrIPAllowList returns a guard that allows a request when:
- token is allowed, OR
- client IP is allowlisted.
This is a thin wrapper around httpx.AccessGuard with WithOr().
type HealthzSpec ¶
type LogLevelGetSpec ¶
type LogLevelSetSpec ¶
type Option ¶
type Option func(*Builder)
Option configures admin assembly.
func EnableBuildInfo ¶
func EnableBuildInfo(spec BuildInfoSpec) Option
func EnableHealthz ¶
func EnableHealthz(spec HealthzSpec) Option
func EnableLogLevelGet ¶
func EnableLogLevelGet(spec LogLevelGetSpec) Option
func EnableLogLevelSet ¶
func EnableLogLevelSet(spec LogLevelSetSpec) Option
func EnableProvidedSnapshot ¶
func EnableProvidedSnapshot(spec ProvidedSnapshotSpec) Option
func EnableReadyz ¶
func EnableReadyz(spec ReadyzSpec) Option
func EnableReport ¶
func EnableReport(spec ReportSpec) Option
func EnableRuntime ¶
func EnableRuntime(spec RuntimeSpec) Option
func EnableTaskTrigger ¶
func EnableTaskTrigger(spec TaskTriggerSpec) Option
func EnableTaskTriggerAndWait ¶
func EnableTaskTriggerAndWait(spec TaskTriggerAndWaitSpec) Option
func EnableTasksSnapshot ¶
func EnableTasksSnapshot(spec TasksSnapshotSpec) Option
func EnableTuningLookup ¶
func EnableTuningLookup(spec TuningLookupSpec) Option
func EnableTuningOverrides ¶
func EnableTuningOverrides(spec TuningOverridesSpec) Option
func EnableTuningResetDefault ¶
func EnableTuningResetDefault(spec TuningResetDefaultSpec) Option
func EnableTuningResetLast ¶
func EnableTuningResetLast(spec TuningResetLastSpec) Option
func EnableTuningSet ¶
func EnableTuningSet(spec TuningSetSpec) Option
func EnableTuningSnapshot ¶
func EnableTuningSnapshot(spec TuningSnapshotSpec) Option
func WithRealIP ¶
func WithRealIP(spec RealIPSpec) Option
WithRealIP sets real IP extraction config for the admin subtree.
This affects IP-based guards (IPAllowList and token+IP composite guards).
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/evan-idocoding/zkit/admin"
)
func main() {
// Without configuring TrustedProxies, RealIP headers are ignored (default-safe),
// so IPAllowList checks fall back to RemoteAddr.
h0 := admin.New(
admin.EnableHealthz(admin.HealthzSpec{Guard: admin.IPAllowList("10.0.0.0/8")}),
)
// With TrustedProxies, RealIP headers can be trusted when the direct client is a trusted proxy.
h1 := admin.New(
admin.WithRealIP(admin.RealIPSpec{TrustedProxies: []string{"192.168.0.0/16"}}),
admin.EnableHealthz(admin.HealthzSpec{Guard: admin.IPAllowList("10.0.0.0/8")}),
)
// Simulate a request coming from a trusted proxy, forwarding a real client in 10.0.0.0/8.
req := httptest.NewRequest(http.MethodGet, "http://admin.test/healthz", nil)
req.RemoteAddr = "192.168.0.1:1234"
req.Header.Set("X-Forwarded-For", "10.1.2.3")
rr0 := httptest.NewRecorder()
h0.ServeHTTP(rr0, req)
rr1 := httptest.NewRecorder()
h1.ServeHTTP(rr1, req)
fmt.Println(rr0.Code, rr1.Code)
}
Output: 403 200
type ProvidedSnapshotSpec ¶
type ReadyCheck ¶
type ReadyCheck struct {
Name string
Func func(context.Context) error
Timeout time.Duration // <=0 means no extra timeout
}
ReadyCheck is a named readiness check.
type ReadyzSpec ¶
type ReadyzSpec struct {
Guard Guard
Path string // default "/readyz"
Checks []ReadyCheck
}
type RealIPSpec ¶
type RealIPSpec struct {
// TrustedProxies declares which direct client IP ranges are trusted proxies.
// Accepts CIDRs or single IPs (e.g. "10.0.0.0/8", "192.168.1.1").
TrustedProxies []string
// TrustedHeaders optionally overrides header priority. If empty, admin uses the
// standard order:
// - X-Forwarded-For
// - X-Real-IP
TrustedHeaders []string
}
RealIPSpec configures how admin extracts client IP for IP-based guards.
Default-safe: if TrustedProxies is empty, headers are not trusted and RemoteAddr is used.
type ReportSpec ¶
type RuntimeSpec ¶
type TaskAccessSpec ¶
type TaskTriggerAndWaitSpec ¶
type TaskTriggerAndWaitSpec struct {
Guard Guard
Path string // default "/tasks/trigger-and-wait"
// Mgr is the task manager whose named tasks can be triggered by this endpoint.
//
// Notes:
// - Task triggering is name-based and uses task.Manager.Lookup.
// - Unnamed tasks are not indexed by task.Manager and therefore cannot be triggered by name.
Mgr *task.Manager
Access TaskAccessSpec // required allowlist for writes; empty => deny-all
}
type TaskTriggerSpec ¶
type TaskTriggerSpec struct {
Guard Guard
Path string // default "/tasks/trigger"
// Mgr is the task manager whose named tasks can be triggered by this endpoint.
//
// Notes:
// - Task triggering is name-based and uses task.Manager.Lookup.
// - Unnamed tasks are not indexed by task.Manager and therefore cannot be triggered by name.
Mgr *task.Manager
Access TaskAccessSpec // required allowlist for writes; empty => deny-all
}
type TasksSnapshotSpec ¶
type TasksSnapshotSpec struct {
Guard Guard
Path string // default "/tasks/snapshot"
Mgr *task.Manager
Access TaskAccessSpec // optional filter for reads
}
type TokenOption ¶
type TokenOption func(*tokenConfig)
func WithTokenHeader ¶
func WithTokenHeader(name string) TokenOption
WithTokenHeader overrides the token header name for token-based guards.
Empty/blank names are ignored (default is DefaultTokenHeader).
type TokenSetLike ¶
TokenSetLike is a token set used by token-based guards.
Implementations must be safe for concurrent use. The request path must be fast and must not block.
type TuningAccessSpec ¶
type TuningLookupSpec ¶
type TuningLookupSpec struct {
Guard Guard
Path string // default "/tuning/lookup"
T *tuning.Tuning
Access TuningAccessSpec // optional filter for reads
}
type TuningOverridesSpec ¶
type TuningOverridesSpec struct {
Guard Guard
Path string // default "/tuning/overrides"
T *tuning.Tuning
Access TuningAccessSpec // optional filter for reads
}
type TuningResetDefaultSpec ¶
type TuningResetDefaultSpec struct {
Guard Guard
Path string // default "/tuning/reset-default"
T *tuning.Tuning
Access TuningAccessSpec // required allowlist for writes; empty => deny-all
}
type TuningResetLastSpec ¶
type TuningResetLastSpec struct {
Guard Guard
Path string // default "/tuning/reset-last"
T *tuning.Tuning
Access TuningAccessSpec // required allowlist for writes; empty => deny-all
}
type TuningSetSpec ¶
type TuningSetSpec struct {
Guard Guard
Path string // default "/tuning/set"
T *tuning.Tuning
Access TuningAccessSpec // required allowlist for writes; empty => deny-all
}
type TuningSnapshotSpec ¶
type TuningSnapshotSpec struct {
Guard Guard
Path string // default "/tuning/snapshot"
T *tuning.Tuning
Access TuningAccessSpec // optional filter for reads
}