diff --git a/reference/http/api.md b/reference/http/api.md index 4d064621..ec2a7c41 100644 --- a/reference/http/api.md +++ b/reference/http/api.md @@ -78,18 +78,20 @@ A `Request` object is passed to HTTP middleware handlers and direct static REST ### Properties -| Property | Type | Description | -| ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `url` | string | The request target (path + query string), e.g. `/path?query=string` | -| `method` | string | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, etc. | -| `headers` | [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) | Request headers | -| `pathname` | string | Path portion of the URL, without query string | -| `protocol` | string | `http` or `https`. On the Node runtime, behind a TLS-terminating proxy that forwards the PROXY v2 SSL TLV, this reflects the client's original scheme even though the proxy-to-Harper hop is plaintext; a proxy forwarding only address info (no SSL TLV) still reads as `http`. Bun and uWebSockets derive this from `X-Forwarded-Proto`/the listener's secure flag instead, independent of the SSL TLV. | -| `data` | any | Deserialized body, based on `Content-Type` header | -| `ip` | string | Remote IP address of the client (or last proxy) | -| `host` | string | Host from the request headers | -| `session` | object | Current cookie-based session (a `Table` record instance). Update with `request.session.update({ key: value })`. A cookie is set automatically the first time a session is updated or a login occurs. | -| `connectionInfo` | object \| undefined | TLS facts a fronting proxy forwarded over PROXY protocol v2 — negotiated ALPN, SNI, TLS version/cipher, the client's JA3/JA4 fingerprint, and the client's mTLS certificate chain (presented, not necessarily verified — see below). `undefined` for a direct connection or one without a v2 header. See [Connection info](#connection-info). | +| Property | Type | Description | +| ----------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `url` | string | The request target (path + query string), e.g. `/path?query=string` | +| `method` | string | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, etc. | +| `headers` | [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) | Request headers | +| `pathname` | string | Path portion of the URL, without query string | +| `protocol` | string | `http` or `https`. On the Node runtime, behind a TLS-terminating proxy that forwards the PROXY v2 SSL TLV, this reflects the client's original scheme even though the proxy-to-Harper hop is plaintext; a proxy forwarding only address info (no SSL TLV) still reads as `http`. Bun and uWebSockets derive this from `X-Forwarded-Proto`/the listener's secure flag instead, independent of the SSL TLV. | +| `data` | any | Deserialized body, based on `Content-Type` header | +| `ip` | string | Remote IP address of the client (or last proxy) | +| `host` | string | Host from the request headers | +| `session` | object | Current cookie-based session (a `Table` record instance). Update with `request.session.update({ key: value })`. A cookie is set automatically the first time a session is updated or a login occurs. | +| `connectionInfo` | object \| undefined | TLS facts a fronting proxy forwarded over PROXY protocol v2 — negotiated ALPN, SNI, TLS version/cipher, the client's JA3/JA4 fingerprint, and the client's mTLS certificate chain (presented, not necessarily verified — see below). `undefined` for a direct connection or one without a v2 header. See [Connection info](#connection-info). | +| `authorized` | boolean \| undefined | Whether the client's certificate passed TLS verification — Node's `socket.authorized`. On a directly-terminated connection this is verification against Harper's configured certificate authorities (`tls.certificateAuthority`, or a certificate record marked as an authority — see [Certificate Verification](../security/certificate-verification.md)); behind Harper Fabric's proxy-protocol UDS mirror it instead reflects Symphony's own, separately configured client CA, forwarded as a single trust bit (see [Trust and mTLS](#connection-info)). `false` for an unverified or absent client certificate on either path, including a mirrored connection where the proxy verified the client but omitted its oversized chain. `undefined` on a plain (non-TLS, non-mirrored) connection, and always `undefined` on Bun and uWebSockets. | +| `peerCertificate` | object \| null | The client's TLS peer certificate — Node's `socket.getPeerCertificate(true)`, cached after the first read. On a direct connection this is Node's full certificate object. `issuerCertificate` is present only when Node can resolve the issuer, so it's absent on a resumed TLS session, on Node.js 26.8.0/26.8.1, and when the issuer is in neither the client's sent chain nor Harper's trusted CAs (for example, an unverified client when mTLS is optional) — check it before reading its fields; behind the Fabric UDS mirror it's a smaller object parsed from the forwarded chain (subject, issuer, subjectaltname, validity, serial number, fingerprints, `raw`, `issuerCertificate` — no `pubkey`, `bits`, `modulus`, `ca`, or `ext_key_usage`, though `raw` can be re-parsed for those). An empty object (`{}`) if no certificate was presented, if the proxy verified the certificate but omitted the chain, or if the forwarded leaf fails to parse (rare, but possible even when `authorized` is `true`); `null` on a connection with no TLS/mTLS support at this layer, and always `null` on Bun and uWebSockets. | ### Connection info @@ -124,7 +126,7 @@ class Origin { } ``` -**Trust and mTLS.** `connectionInfo` is populated only from the trusted proxy-protocol channel (Harper Fabric's per-worker Unix domain socket, writable only by the local proxy) — never from a request header, so the channel itself cannot be forged by a client (its _contents_ are client-controlled, per above). `clientCertChain` and `tls.verified` are independent signals, not one another's proof: a chain is attached whenever the client presented a certificate, whether or not the proxy verified it, and `tls.verified` can be `true` while `clientCertChain` is absent (a proxy may verify a certificate but omit an oversized chain). Treating chain presence alone as proof of a verified client accepts unverified certificates. For gating on a verified mTLS client, use `request.authorized` — a chain-gated boolean the proxy layer populates with the same semantics as a directly-terminated TLS connection — rather than `connectionInfo.tls.verified` or `clientCertChain` presence alone. Like `connectionInfo`, this is Node-runtime only: `request.authorized` is always `undefined` on Bun and uWebSockets. +**Trust and mTLS.** `connectionInfo` is populated only from the trusted proxy-protocol channel (Harper Fabric's per-worker Unix domain socket, writable only by the local proxy) — never from a request header, so the channel itself cannot be forged by a client (its _contents_ are client-controlled, per above). `clientCertChain` and `tls.verified` are independent signals, not one another's proof: a chain is attached whenever the client presented a certificate, whether or not the proxy verified it, and `tls.verified` can be `true` while `clientCertChain` is absent (a proxy may verify a certificate but omit an oversized chain). Treating chain presence alone as proof of a verified client accepts unverified certificates. For gating on a verified mTLS client, use [`request.authorized`](#properties) rather than `connectionInfo.tls.verified` or `clientCertChain` presence alone — it's `false` (not just absent) for an unverified or absent client certificate on either path, including a mirrored chain the proxy verified but omitted. `authorized` reflects chain trust only. Revocation is checked separately, by Harper's mTLS authentication and only when `http.mtls` is configured (see [Certificate Verification](../security/certificate-verification.md)), so in your own middleware a revoked certificate can still read `authorized: true`. The proxy path verifies against Symphony's own, separately configured client CA, not Harper's own configured certificate authorities. A directly-terminated `authorized: true` and a mirrored `authorized: true` therefore aren't guaranteed to share a trust root, even though both gate the same way. To read the certificate itself, [`request.peerCertificate`](#properties) is populated the same way, but the mirror's version carries fewer fields than a direct connection's (see the table). Like `connectionInfo`, both are Node-runtime only: `request.authorized` and `request.peerCertificate` are always `undefined`/`null` on Bun and uWebSockets. ### Methods diff --git a/reference/http/configuration.md b/reference/http/configuration.md index 1f8cab5b..1119ba6e 100644 --- a/reference/http/configuration.md +++ b/reference/http/configuration.md @@ -197,7 +197,7 @@ Type: `boolean | object` Default: `false` -Enables mutual TLS (mTLS) authentication for HTTP connections. When set to `true`, client certificates are verified against the CA specified in `tls.certificateAuthority`. Authenticated connections use the `CN` (common name) from the certificate subject as the Harper username. +Enables mutual TLS (mTLS) authentication for HTTP connections. When set to `true`, client certificates are verified against Harper's configured CAs (`tls.certificateAuthority`, or a certificate record marked as an authority). Authenticated connections use the `CN` (common name) from the certificate subject as the Harper username. ```yaml http: diff --git a/reference/security/mtls-authentication.md b/reference/security/mtls-authentication.md index 12cd8457..78d70711 100644 --- a/reference/security/mtls-authentication.md +++ b/reference/security/mtls-authentication.md @@ -13,7 +13,7 @@ Harper supports Mutual TLS (mTLS) authentication for incoming HTTP connections. ## How It Works 1. The client presents a TLS certificate during the handshake. -2. Harper validates the certificate against the configured CA (`tls.certificateAuthority`). +2. Harper validates the certificate against its configured CAs (`tls.certificateAuthority`, or a certificate record marked as an authority). 3. If valid, Harper extracts the `CN` from the certificate subject and uses it as the username for the request. 1. Or it is configurable via the `http.mtls.user` option in the relevant configuration object. 4. Optionally, Harper checks whether the certificate has been revoked (see [Certificate Verification](./certificate-verification.md)). @@ -69,7 +69,7 @@ The username for the mTLS-authenticated request is derived from the `CN` field o To use mTLS you need: -1. A Certificate Authority (CA) certificate configured in `tls.certificateAuthority`. +1. A Certificate Authority (CA) certificate configured in `tls.certificateAuthority`, or stored as a certificate record marked as an authority. 2. Client certificates signed by that CA, with a `CN` matching a Harper username. 3. The `http.mtls` configuration enabled.