policy

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package policy defines the policy document an administrator writes: its types, its strict JSON decoding and the limits and identifier formats every policy must meet.

It decodes and checks the document's shape only; deciding what a policy means is the compiler's job, and the package never reads storage, the network or the clock. It imports no other internal package, so any gateway package may use it.

Index

Constants

View Source
const (
	DefaultCORSMaxAge = 600
	MaxCORSMaxAge     = 86_400
	MaxCORSOrigins    = 32
	MaxCORSHeaders    = 32
	MaxCORSExpose     = 16
	MaxRemoveHeaders  = 16
	MaxAllowAddresses = 256
)

Limits of the per-API settings.

View Source
const (
	ForwardExtension         = "x-apiward-forward"
	CORSExtension            = "x-apiward-cors"
	ResponseHeadersExtension = "x-apiward-response-headers"
	AddressesExtension       = "x-apiward-addresses"
)

OpenAPI root members carrying an API's settings; one API per description.

View Source
const (
	DefaultPolicyBytes  = 64 << 10
	DefaultAPIs         = 64
	DefaultOperations   = 256
	DefaultRoles        = 64
	DefaultPermits      = 4096
	DefaultRateCounters = 262144
	DefaultWindow       = 10000

	MaxPolicyBytes  = 64 << 10
	MaxAPIs         = 256
	MaxOperations   = 4096
	MaxRoles        = 256
	MaxPermits      = 65536
	MaxRateCounters = 1 << 20
	MaxWindow       = 100000
)

Defaults of the capacity ceilings, which a deployment may raise up to the maxima below. A rate counter keeps 257 bytes of heap, so 1,048,576 counters need about 257 MiB. MaxPolicyBytes is the store's own bound on a stored document (its CHECK constraint), so a policy is never accepted that the database would refuse to keep.

View Source
const (
	MaxJSONDepth    = 16
	MaxJSONValues   = 4096
	MaxNumberLength = 32

	MaxSchemaProperties = 64
	MaxEnumValues       = 64
	MaxSchemaLength     = 1 << 20
	MaxHeaderName       = 64
)
View Source
const (
	SchemaVersion       = 4
	LatestSchemaVersion = 5
)

SchemaVersion is the policy document version written when nothing newer is needed; LatestSchemaVersion adds upstream URLs, TLS profiles and the Host header choice. The compiler accepts both, and compiles a version 4 document exactly as before version 5 existed.

View Source
const (
	MaxPolicyDepth   = 16
	MaxTokenRoles    = 64
	MaxIDLength      = 64
	MaxSubjectLength = 128
	MaxTargetLength  = 256
	MaxUpstreamURL   = 2048
	MaxBodyLimit     = 1 << 20
)

MaxTokenRoles bounds the roles one token may carry, whatever the policy's role ceiling: it is part of the token size bound.

View Source
const (
	MaxWhenHosts   = 16
	MaxWhenHeaders = 8
)
View Source
const ProfileCore = "core"

ProfileCore is the only enforcement profile that can be activated. Under it the gateway enforces routing, the per-operation access flag, bearer JWT authentication and role permits on every operation declared auth "required", the operation's rate rule, its body and header schemas, and the mandatory audit record before forwarding. Any other profile is rejected rather than activated with controls it cannot run.

View Source
const WhenExtension = "x-apiward-when"

WhenExtension is the OpenAPI operation member that carries a When.

Variables

Functions

func CheckID

func CheckID(id, field string, args ...any) error

CheckID accepts short identifiers made of letters, digits, '.', '_' and '-', starting with a letter or digit, so identifiers are safe to log and to compare byte for byte.

func CheckMembers

func CheckMembers(data []byte, maxDepth int) error

CheckMembers rejects JSON nested deeper than maxDepth, and objects with a repeated member name or a name outside [a-z0-9_]. encoding/json keeps the last of repeated names and matches names case-insensitively, so without this check two readers of the same document could disagree about it.

Values of members named "schema" or "headers" are JSON Schemas, whose keywords and property names are case-sensitive and not limited to [a-z0-9_]; the "headers" lists of "when" and "cors" objects are not. They are decoded as raw JSON, never into Go structs, so only the repeated-name and depth rules apply inside them.

func Clip

func Clip(text string) string

Clip shortens untrusted text to 256 bytes, without splitting a UTF-8 sequence, before it is echoed in a report or an error.

func DecodeBounded

func DecodeBounded(data []byte, maxDepth, maxValues int) (any, string)

DecodeBounded is DecodeJSON with the caller's nesting and value limits.

func DecodeJSON

func DecodeJSON(data []byte) (any, string)

DecodeJSON decodes exactly one JSON value into the form the schema library validates (maps, slices, json.Number, strings, booleans and nil). It rejects invalid UTF-8, repeated member names, data after the value, nesting deeper than MaxJSONDepth, more than MaxJSONValues values and number literals the library could not compare cheaply. encoding/json alone would keep the last repeated member and replace invalid UTF-8, so the gateway and an upstream could read different documents. It returns the value, or the rejection reason.

func DecodeStrict

func DecodeStrict(data []byte, value any) error

DecodeStrict decodes exactly one JSON value, rejecting unknown fields and anything after the value.

func Encode

func Encode(p Policy) ([]byte, error)

Encode returns the canonical form of p: 2-space JSON with members in struct order and a trailing newline; upstreams, APIs and roles sorted by id; operations by API, target and method; permits by role and operation. Schemas have their members sorted and each "required" list sorted; condition hosts are sorted, header conditions sorted by canonical name; an API's origins, methods and address ranges are sorted and its header names sorted in canonical form. The input is not changed. It fails only on a schema that is not valid JSON.

func IsSubjectID

func IsSubjectID(id string) bool

IsSubjectID accepts 1 to 128 printable ASCII characters without spaces. The same rule applies to the "sub" claim, so a subject identifier is always safe to log and to forward in a header.

func ParseRange

func ParseRange(text string) (netip.Prefix, bool)

ParseRange parses a CIDR range whose host bits are zero. An IPv4-mapped IPv6 range becomes the IPv4 range it covers; callers that need the canonical text compare it with the returned prefix's String.

Types

type API

type API struct {
	ID              string           `json:"id"`
	Upstream        string           `json:"upstream"`
	Forward         *Forward         `json:"forward,omitempty"`
	CORS            *CORS            `json:"cors,omitempty"`
	ResponseHeaders *ResponseHeaders `json:"response_headers,omitempty"`
	// AllowAddresses, when present, lists the only client address ranges
	// (CIDR) whose requests reach the API's operations.
	AllowAddresses []string `json:"allow_addresses,omitempty"`
}

API registers one managed API and the single upstream that serves it.

type BodyRule

type BodyRule struct {
	MaxBytes int64           `json:"max_bytes"`
	Schema   json.RawMessage `json:"schema"`
}

BodyRule declares a JSON request body, its size ceiling and the JSON Schema it must satisfy. An operation without a BodyRule accepts no request body.

type CORS

type CORS struct {
	Origins     []string `json:"origins"`
	Methods     []string `json:"methods"`
	Headers     []string `json:"headers,omitempty"`
	Expose      []string `json:"expose,omitempty"`
	Credentials bool     `json:"credentials,omitempty"`
	// MaxAgeSeconds is how long browsers may cache a preflight answer;
	// absent means DefaultCORSMaxAge.
	MaxAgeSeconds *int64 `json:"max_age_seconds,omitempty"`
}

CORS is the API's cross-origin browser access. The gateway answers preflights itself and adds the allowed origin to responses; it never decides access with it.

type Forward

type Forward struct {
	StripPrefix string `json:"strip_prefix,omitempty"`
	AddPrefix   string `json:"add_prefix,omitempty"`
}

Forward rewrites the path an admitted request is forwarded with: the inbound path without StripPrefix, after AddPrefix. Every decision is made on the inbound path; the rewrite applies only to the outbound request.

In an OpenAPI description the rule is the root member named ForwardExtension.

type HeaderCondition

type HeaderCondition struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

HeaderCondition holds when the request carries the named header exactly once, with exactly this value. Names compare without regard to case.

type Limits added in v0.2.0

type Limits struct {
	PolicyBytes  int
	APIs         int
	Operations   int
	Roles        int
	Permits      int
	RateCounters int
	Window       int
}

Limits are the capacity ceilings a deployment runs with. A zero member takes its default. APIs bounds the upstreams too; Window is the number of recent requests kept for live views.

func (Limits) Resolve added in v0.2.0

func (l Limits) Resolve() (Limits, error)

Resolve returns l with every zero member set to its default, or an error naming the first member outside 1 to its maximum.

type Operation

type Operation struct {
	ID      string          `json:"id"`
	API     string          `json:"api"`
	Method  string          `json:"method"`
	Target  string          `json:"target"`
	Access  string          `json:"access"`
	Auth    string          `json:"auth"`
	Body    *BodyRule       `json:"body,omitempty"`
	Headers json.RawMessage `json:"headers,omitempty"` // JSON Schema for declared request headers
	Rate    *RateRule       `json:"rate,omitempty"`
	When    *When           `json:"when,omitempty"`
	// Modules holds the members named by compiled-in modules other than the
	// built-ins, keyed by module ID; it is encoded as those members.
	Modules map[string]json.RawMessage `json:"-"`
}

Operation binds a method and target of an API to an access decision and the controls that apply to it.

func (Operation) MarshalJSON

func (o Operation) MarshalJSON() ([]byte, error)

MarshalJSON writes the operation's own members and then its module members in ID order. Without module members the output is exactly the default encoding.

type Permit

type Permit struct {
	Role      string `json:"role"`
	Operation string `json:"operation"`
}

Permit grants a role an operation. Roles have no hierarchy: a permit applies to the named role only.

type Policy

type Policy struct {
	SchemaVersion int         `json:"schema_version"`
	Profile       string      `json:"profile"`
	Upstreams     []Upstream  `json:"upstreams"`
	APIs          []API       `json:"apis"`
	Operations    []Operation `json:"operations"`
	Roles         []Role      `json:"roles"`
	Permits       []Permit    `json:"permits"`
}

Policy is the complete permanent policy document. Every publication carries a whole document; there is no partial update.

func DecodePolicy

func DecodePolicy(document []byte, limits Limits) (Policy, error)

DecodePolicy parses a policy document. It rejects input larger than limits.PolicyBytes (zero: the default) or deeply nested, repeated or non-lowercase member names, unknown fields, unsupported schema versions and trailing data. Limits out of range are refused as LIMIT_EXCEEDED.

type PolicyError

type PolicyError struct {
	Code  string
	Field string
}

PolicyError is a typed candidate rejection. Field is the JSON path of the offending value when one applies.

func Invalid

func Invalid(code, field string, args ...any) *PolicyError

func (*PolicyError) Error

func (e *PolicyError) Error() string

type RateRule

type RateRule struct {
	Algorithm     string `json:"algorithm"`
	Count         int64  `json:"count"`
	WindowSeconds int64  `json:"window_seconds"`
	Key           string `json:"key"`
}

RateRule limits requests to an operation by verified subject or direct client address within a sliding window.

type ResponseHeaders

type ResponseHeaders struct {
	HSTS     bool     `json:"hsts,omitempty"`
	Nosniff  bool     `json:"nosniff,omitempty"`
	Frame    string   `json:"frame,omitempty"`    // "deny" or "sameorigin"
	Referrer string   `json:"referrer,omitempty"` // "no-referrer" or "strict-origin-when-cross-origin"
	Remove   []string `json:"remove,omitempty"`
}

ResponseHeaders adds security headers to the API's responses when the upstream did not set them, and removes the named upstream headers.

type Role

type Role struct {
	ID string `json:"id"`
}

Role is a flat role identifier.

type Upstream

type Upstream struct {
	ID   string `json:"id"`
	Dial string `json:"dial,omitempty"`
	URL  string `json:"url,omitempty"`
	TLS  string `json:"tls,omitempty"`
	Host string `json:"host,omitempty"`
}

Upstream is one entry of the closed destination inventory. It holds exactly one of Dial (a loopback address, plain HTTP) or URL (version 5: an http or https origin); TLS names a TLS profile and Host chooses the Host header ("inbound" or "upstream"), both only with URL.

type When

type When struct {
	Hosts   []string          `json:"hosts,omitempty"`
	Headers []HeaderCondition `json:"headers,omitempty"`
}

When lists the conditions an operation's requests must meet: the request host matches one of Hosts (exact, or "*." followed by a domain, which matches any name ending in "."+domain) and every header condition holds. Conditions only select an operation; they never grant access.

In an OpenAPI description the conditions are the operation member named WhenExtension.

Jump to

Keyboard shortcuts

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