authz

package
v2.0.24 Latest Latest
Warning

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

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

Documentation

Overview

Package authz authorizes each upstream call at the moment it is sent.

The alternative — predicting from a tool's arguments which upstream endpoint the call will hit, and authorizing that prediction — needs the prediction and the dispatch to agree forever. They are two copies of one decision, and a disagreement means a call authorized against one route is made against another. Here the request being authorized IS the request being sent: the check happens inside the http.RoundTripper the upstream client was built with, where the method and full URL are facts rather than forecasts.

Index

Constants

View Source
const OAuthRouteAuthPath = "/api/v3/oauth/auth"

OAuthRouteAuthPath is proxy-auth's OAuth route-authz endpoint (introspection + Casbin enforcement). Distinct from first-party /api/v3/auth, which rejects OAuth tokens.

Variables

This section is empty.

Functions

func CallerFrom

func CallerFrom(ctx context.Context) string

CallerFrom returns the bearer WithCaller stored, or "". An empty one is passed to proxy-auth, which refuses it: failing closed at the authorizer keeps "every upstream call is authorized" from depending on a check here.

func WithCaller

func WithCaller(ctx context.Context, bearer string) context.Context

WithCaller returns ctx carrying the end user's bearer token. Set once per tools/call (middleware Auth), read on every upstream request it makes.

Types

type Authorizer

type Authorizer interface {
	Allow(ctx context.Context, bearer, route, method string) (bool, error)
}

Authorizer decides one (route, method) for one caller. route is the service-prefixed path proxy-auth's RBAC is keyed by, e.g. "/core-metadata/api/v3/device/all".

type DeniedError

type DeniedError struct {
	Route  string
	Method string
}

DeniedError reports that proxy-auth refused this route for this caller. It carries the route so the refusal can be reported without re-deriving it.

Denial and outage MUST be distinguishable by type, not by EdgeX error kind: a refused upstream call surfaces as KindServiceUnavailable, the same kind a proxy-auth outage produces, so kind-based handling would let a denial be retried as a transient fault.

func (*DeniedError) Error

func (e *DeniedError) Error() string

type Injector

type Injector struct {
	// contains filtered or unexported fields
}

Injector is the AuthenticationInjector an upstream client is built with. Through NewInjector, never as a literal: the transport is assembled once at construction, so a field set afterwards would be ignored.

Both halves are shared: one instance per upstream service, serving every caller. That is safe for exactly one reason — nothing about the caller is stored on either. Anything per-caller rides the request's context.

func NewInjector

func NewInjector(delegate restinterfaces.AuthenticationInjector, authorizer Authorizer, servicePrefix string) *Injector

NewInjector builds one service's injector. A nil delegate is usable; a nil authorizer is not — it panics on the first RoundTrip. The fail-closed answer is an authorizer that refuses every route, which is what the production caller supplies.

func (*Injector) AddAuthenticationData

func (i *Injector) AddAuthenticationData(req *http.Request) error

func (*Injector) RoundTripper

func (i *Injector) RoundTripper() http.RoundTripper

RoundTripper returns the authorizing transport — never the base one unwrapped, which would send the request unauthorized.

type OutageError

type OutageError struct {
	Route  string
	Method string
	Err    error
}

OutageError reports that proxy-auth could not be reached or did not answer. Not a decision: the call is refused, but nothing is known about the caller's permissions.

func (*OutageError) Error

func (e *OutageError) Error() string

func (*OutageError) Unwrap

func (e *OutageError) Unwrap() error

type ProxyAuth

type ProxyAuth struct {
	// BaseURL is proxy-auth's base URL.
	BaseURL string
	// Injector decorates the authorization request itself. It must NOT add the
	// MCP service's own JWT: proxy-auth reads the end user's bearer from the
	// Authorization header, and a second Authorization header would be sent.
	Injector restinterfaces.AuthenticationInjector
	// Resource is forwarded as X-Forwarded-Resource so proxy-auth confines the
	// token to its bound audience.
	Resource string
}

ProxyAuth asks security-proxy-auth whether one caller may take one route.

func (ProxyAuth) Allow

func (p ProxyAuth) Allow(ctx context.Context, bearer, route, method string) (bool, error)

Allow reports whether the caller may take route with method. Only an explicit 204 allows; a 403 denies; a 401 for a rejected token is surfaced as a sentinel. Anything else — an unexpected 2xx, a 5xx, or a transport failure — is returned as an error, so no call is ever allowed without an explicit 204 (fail closed).

type Transport

type Transport struct {
	// Next carries the request once it is allowed.
	Next http.RoundTripper
	// Authorizer is asked about every request.
	Authorizer Authorizer
	// ServicePrefix is the "/core-metadata"-style prefix identifying the
	// upstream service. It comes from the client's configuration, never from
	// the request URL: the URL a client sends is service-relative
	// ("/api/v3/device/all"), so the service identity lives only in the host,
	// and proxy-auth's routes are prefixed by service.
	ServicePrefix string
}

Transport authorizes every request it carries before letting it out, and is the only place an upstream call can be authorized from.

It holds nothing about any caller: the bearer and the caller's context both ride the request that RoundTrip is handed, read at the moment it is sent. That is what lets one Transport — and so one upstream client — serve every caller. ⚠ A caller-bound field would be set once for whoever built the object and then used for every request through it, which no single-threaded test would show.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip authorizes req and sends it only if allowed. On refusal or on an authorization failure nothing is sent — the error is returned before Next is reached, so no data can leave on an unauthorized call.

type UnauthenticatedError

type UnauthenticatedError struct {
	Route  string
	Method string
	// Err is proxy-auth's own answer. The message returned to the caller is a
	// fixed sentence, so this is the only record of WHY the token was refused —
	// expired, wrong audience, bad signature, revoked — and the middleware logs
	// it. Carried for the same reason OutageError carries its cause.
	Err error
}

UnauthenticatedError reports that proxy-auth rejected the caller's token itself — expired, revoked, or not valid for this resource — rather than refusing a route to an authenticated caller.

It is a third outcome, not a flavour of OutageError: a client that re-runs its OAuth flow on an authentication failure must be able to tell "your token is stale, get a new one" from "the authorization service is down, try later". Collapsing the two makes a bad token look like a transient fault and hides the one error the client can actually act on.

func (*UnauthenticatedError) Error

func (e *UnauthenticatedError) Error() string

func (*UnauthenticatedError) Unwrap

func (e *UnauthenticatedError) Unwrap() error

Jump to

Keyboard shortcuts

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