first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# `trust`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Encapsulates the trusted-source enforcement consulted by the header tenant
|
||||
resolver before it acts on a header value. The check exists because an
|
||||
HTTP header is end-user-controllable on public traffic; without a
|
||||
trusted-source gate, any browser could spoof the tenant by sending
|
||||
`X-Tenant-Id: competitor`.
|
||||
|
||||
Upstream middleware (a service-mesh adapter, an mTLS-verified ingress,
|
||||
internal-network admission gate) stamps a `source` value on
|
||||
`request.metadata`. The resolver consults that stamp via `isTrustedSource`;
|
||||
when the source is missing or absent from the configured allow-list, the
|
||||
header is ignored (strict-mode raises `TENANT_HEADER.UNTRUSTED_SOURCE`,
|
||||
non-strict-mode returns `null` so the chain advances).
|
||||
|
||||
## Features
|
||||
|
||||
- `RequestTrustMetadata` shape — `{ source?: string }` minimal contract.
|
||||
- `RequestMetadataExtractor` injectable extractor with a fail-closed
|
||||
default that reads `request.metadata`.
|
||||
- `isTrustedSource(metadata, trustedSources)` — fail-closed boolean.
|
||||
Missing metadata, missing `source`, or a `source` not in the allow-list
|
||||
all yield `false`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
defaultMetadataExtractor,
|
||||
isTrustedSource,
|
||||
} from "@suaveplan/api-tenant-resolver-header/trust";
|
||||
|
||||
const metadata = defaultMetadataExtractor(request);
|
||||
if (isTrustedSource(metadata, ["service-mesh"])) {
|
||||
// header may be consulted
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import type {
|
||||
RequestMetadataExtractor,
|
||||
RequestTrustMetadata,
|
||||
} from "@suaveplan/api-tenant-resolver-header/trust";
|
||||
|
||||
const sidecar = new WeakMap<Request, RequestTrustMetadata>();
|
||||
|
||||
const fromSidecar: RequestMetadataExtractor = (req) =>
|
||||
sidecar.get(req) ?? null;
|
||||
|
||||
// ingress middleware stamps a frozen Request via a WeakMap rather than
|
||||
// mutating the request itself:
|
||||
sidecar.set(request, { source: "service-mesh" });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface RequestTrustMetadata` — `{ readonly source?: string }`.
|
||||
- `type RequestMetadataExtractor = (request: Request)
|
||||
=> RequestTrustMetadata | null | undefined`.
|
||||
- `const defaultMetadataExtractor: RequestMetadataExtractor` — reads
|
||||
`request.metadata`.
|
||||
- `function isTrustedSource(metadata, trustedSources): boolean` —
|
||||
fail-closed allow-list check.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`isTrustedSource` returns `false` for absent metadata so the policy is
|
||||
fail-closed by default. The alternative — treating "no metadata" as
|
||||
"trust by default" — would mean a single misconfigured ingress (one that
|
||||
forgets to set `source`) opens the entire fleet to header spoofing. The
|
||||
trade-off is that hosts MUST wire an ingress adapter that stamps `source`
|
||||
before the resolver runs; the resolver's contract is explicit about this
|
||||
expectation.
|
||||
|
||||
The default extractor reads a mutable property because that is the simplest
|
||||
host wiring. Frozen-Request runtimes (Workers, certain edge platforms) pass
|
||||
an explicit extractor backed by a `WeakMap`. Either way, the trust check is
|
||||
identical — only the I/O changes.
|
||||
Reference in New Issue
Block a user