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 ¶
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 ¶
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.
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 ¶
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 (*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 ¶
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 ¶
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.
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