callerctx

package
v0.23.1 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package callerctx carries the request-scoped caller identity, policy inputs and correlation metadata that every apic-generated surface — REST, WebSocket and MCP — hands to business logic.

It depends on the standard library ONLY. securex, mcpx, wsx and every generated tree import it, so any other dependency would create a cycle.

Defensive copy at store time, shared and read-only after that

ContextWithCaller takes a defensive copy of the Caller it is given: once stored, mutating the ORIGINAL struct (or its slices, its map, its nested Workload or Delegation chain) has no further effect on what the context carries. That copy is made exactly ONCE, at store time. Every later CallerFromContext or MustCaller call — on that context, or on any context derived from it (context.WithCancel, context.WithValue, ...) — returns the SAME *Caller pointer to every reader. A retrieved Caller is therefore shared and MUST be treated as read-only: mutating one of its fields races with every other goroutine that holds the same (or a derived) context, and mutating Attributes — a map — that way is a fatal concurrent write, not merely a race. A caller that needs to change a field must call Clone() first and mutate its own, independent copy.

ContextWithOwnedCaller: the one exception to "always cloned at store time"

ContextWithOwnedCaller skips that defensive copy: it stores the exact *Caller pointer it is given. It exists for a caller that just constructed a Caller (e.g. a fresh value from securex.CallerFromRequest) and holds no other reference to it — skipping the clone avoids copying Roles/Scopes/AMR/Attributes/Delegation a second time for a value nothing else could have mutated anyway. Everything downstream of the store (CallerFromContext, MustCaller, the shared-pointer/read-only contract above) behaves identically either way; the only difference is which *Caller value ends up shared, and therefore who else must never mutate it. Using ContextWithOwnedCaller with a Caller the installer still holds a reference to — and might read or write again — reintroduces exactly the race ContextWithCaller's clone exists to prevent, since neither the installer's copy nor any reader's now alias a defensive copy; they alias the SAME value. Use ContextWithCaller whenever there is any doubt.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContextWithCaller

func ContextWithCaller(ctx context.Context, c *Caller) context.Context

ContextWithCaller returns ctx carrying a defensive copy of c, cloned once at this call. A nil c returns ctx unchanged so a caller-less path stays caller-less rather than acquiring an empty-but-present principal. The clone made here — not c itself — is what every later CallerFromContext / MustCaller call returns, and it returns the SAME pointer to every reader; see the package doc for the resulting shared, read-only contract.

func ContextWithOwnedCaller

func ContextWithOwnedCaller(ctx context.Context, c *Caller) context.Context

ContextWithOwnedCaller returns ctx carrying c directly, with NO defensive copy. This is the fast path for a caller that just built c (e.g. a fresh value from a constructor) and holds no other reference to it: skipping the clone avoids copying Roles/Scopes/AMR/Attributes/Delegation a second time when the constructor already produced an isolated value nothing else can mutate.

The tradeoff is the same shared/read-only contract ContextWithCaller documents, PLUS an extra obligation on the caller of this function: once c is handed to ContextWithOwnedCaller, the caller must not retain a reference to c and mutate it later — that mutation would be visible through ctx (and every context derived from it) exactly like mutating a value retrieved via CallerFromContext, which the package doc already forbids. Use ContextWithCaller instead whenever c is retained, reused, or came from anywhere this function can't prove is exclusively owned by the call site (including a value read back from another context).

A nil c returns ctx unchanged, matching ContextWithCaller.

Types

type Caller

type Caller struct {
	Subject   string
	Issuer    string
	Tenant    string
	Namespace string
	ClientID  string
	Kind      Kind

	Roles      []string
	Scopes     []string
	Attributes map[string]string
	AMR        []string
	ACR        string

	Delegation *Delegation

	CorrelationID string
	RequestID     string
	TraceID       string
	SpanID        string

	AuthMode string
	Workload *Workload
}

Caller is the verified principal plus everything a handler needs to make an authorization decision, emit correlated telemetry and honour a deadline. Every field is server-derived from a verified credential — never from a request body, query parameter or unverified header.

func CallerFromContext

func CallerFromContext(ctx context.Context) (*Caller, bool)

CallerFromContext returns the caller carried by ctx: the SAME *Caller pointer for every call against this ctx, or against any context derived from it, shared across every reader. Treat the result as read-only — call Clone() before mutating any field, including writing to Attributes (see the package doc).

func MustCaller

func MustCaller(ctx context.Context) *Caller

MustCaller returns the caller carried by ctx, or an unknown caller with no roles and no scopes. It never returns nil, so authorization code can be written without nil checks and still fails closed. Like CallerFromContext, the returned pointer is shared with every other reader of ctx (or of a context derived from it); treat it as read-only and call Clone() before mutating any field.

func (*Caller) Clone

func (c *Caller) Clone() *Caller

Clone deep-copies every mutable field, producing an independent *Caller that is safe to mutate. This is the supported way to get a private, writable copy of a value retrieved from a context (see CallerFromContext).

func (*Caller) DelegationChain

func (c *Caller) DelegationChain() []string

DelegationChain returns the actor subjects from the innermost actor outward. A bounded walk: a malformed or cyclic chain cannot spin here.

func (*Caller) HasRole

func (c *Caller) HasRole(role string) bool

HasRole reports exact membership in Roles.

func (*Caller) HasScope

func (c *Caller) HasScope(scope string) bool

HasScope reports exact membership in Scopes. Matching is exact: scope strings are case-sensitive per RFC 6749 §3.3.

func (*Caller) LogValue

func (c *Caller) LogValue() slog.Value

LogValue renders a caller for structured logs WITHOUT tenant, namespace, attributes or the scope/role strings themselves. Tenant and namespace are high-cardinality and are forbidden as telemetry labels (ARCHITECTURE §9); scope and role names describe the authorization topology. Counts and the correlation identifiers are enough to trace a request.

type Delegation

type Delegation struct {
	Subject  string
	Issuer   string
	ClientID string
	Parent   *Delegation
}

Delegation is one link of an RFC 8693 token-exchange actor chain. Parent is the actor that delegated to this one, or nil at the root.

type Kind

type Kind string

Kind classifies the principal behind a request. It is derived from verified token claims, never from request data.

const (
	KindUnknown  Kind = "unknown"
	KindHuman    Kind = "human"
	KindService  Kind = "service"
	KindWorkload Kind = "workload"
	KindDevice   Kind = "device"
)

The Kind values. KindUnknown is the fail-closed default -- what MustCaller returns when no caller is installed, and what a request whose credentials identified no principal is classified as. The others name the kind of verified principal (securex.CallerFromRequest documents which credential maps to which).

type Workload

type Workload struct {
	SPIFFEID       string
	Subject        string
	Issuer         string
	Classification string
}

Workload is a non-bearer machine identity: an mTLS peer certificate or a SPIFFE SVID.

Jump to

Keyboard shortcuts

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