admin

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2026 License: MIT Imports: 12 Imported by: 0

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

  1. Security (default-safe)
  2. Control (explicit enable/disable, explicit guards, fail-fast on assembly errors)
  3. Stability (paths and outputs aim to be stable)
  4. 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

Examples

Constants

View Source
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

func New(opts ...Option) http.Handler

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 BuildInfoSpec struct {
	Guard Guard
	Path  string // default "/buildinfo"

	IncludeDeps     bool // default false
	IncludeSettings bool // default false
}

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 AllowAll

func AllowAll() Guard

AllowAll returns a guard that allows all requests.

func Check

func Check(fn func(r *http.Request) bool) Guard

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 DenyAll

func DenyAll() Guard

DenyAll returns a guard that denies all requests with HTTP 403.

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

func IPAllowList(cidrsOrIPs ...string) Guard

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 HealthzSpec struct {
	Guard Guard
	Path  string // default "/healthz"
}

type LogLevelGetSpec

type LogLevelGetSpec struct {
	Guard Guard
	Path  string // default "/log/level"
	Var   *slog.LevelVar
}

type LogLevelSetSpec

type LogLevelSetSpec struct {
	Guard Guard
	Path  string // default "/log/level/set"
	Var   *slog.LevelVar
}

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 ProvidedSnapshotSpec struct {
	Guard Guard
	Path  string // default "/provided"

	Items    map[string]any
	MaxBytes int // optional; default conservative (aligned with ops)
}

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 ReportSpec struct {
	Guard Guard
	Path  string // default "/report"
}

type RuntimeSpec

type RuntimeSpec struct {
	Guard Guard
	Path  string // default "/runtime"
}

type TaskAccessSpec

type TaskAccessSpec struct {
	AllowPrefixes []string
	AllowNames    []string
	AllowFunc     func(name string) bool
}

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

type TokenSetLike interface {
	Contains(token string) bool
}

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 TuningAccessSpec struct {
	AllowPrefixes []string
	AllowKeys     []string
	AllowFunc     func(key string) bool
}

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
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL