diff --git a/src/specification/margo-management-interface/resources/index.md.jinja2 b/src/specification/margo-management-interface/resources/index.md.jinja2 index c9c6a74c..43f47ff4 100644 --- a/src/specification/margo-management-interface/resources/index.md.jinja2 +++ b/src/specification/margo-management-interface/resources/index.md.jinja2 @@ -116,7 +116,7 @@ GET /api/v1/deployments/{deploymentId}/{digest} | ---- | ----------- | | 200 OK | The response body contains the raw `ApplicationDeployment` YAML (`Content-Type: application/yaml`). Server MUST set `ETag` to the quoted digest and MUST return `Cache-Control: private, max-age=31536000, immutable` (see [Protocol: ETag and Caching](#protocol-etag-and-caching)). | | 403 Forbidden | The request is not authorized by the WFM's local policy (for example, the client relationship has been retired; see [Authorization](../identity/wfm-identity-profile.md#authorization)). | -| 404 Not Found | The referenced digest does not exist on the server. `404 Not Found` indicates only that this specific digest is unavailable. It MUST NOT be interpreted as a deletion signal by a client; deletion of workloads is determined solely by absence from the state manifest. | +| 404 Not Found | The referenced digest is not available to this client. The WFM MUST return the same response for a digest that does not exist and for a digest that exists outside the caller's scope (see [caller scoping](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication)). `404 Not Found` indicates only that this specific digest is unavailable. It MUST NOT be interpreted as a deletion signal by a client; deletion of workloads is determined solely by absence from the state manifest. | > **Note:** Servers MAY apply HTTP `Content-Encoding` (e.g., gzip, br). The client advertises support via `Accept-Encoding`. Digests and ETags always refer to the decoded representation (i.e., the exact bytes of the response body after decompressing any HTTP `Content-Encoding` such as gzip). Servers SHOULD include `Vary: Accept-Encoding` if compression is used. @@ -142,7 +142,7 @@ GET /api/v1/bundles/{digest} | ---- | ----------- | | 200 OK | The bundle was successfully retrieved. The server MUST set `Content-Type` to the manifest-declared `bundle.mediaType`, `ETag` to the quoted digest, and MUST return `Cache-Control: private, max-age=31536000, immutable` (see [Protocol: ETag and Caching](#protocol-etag-and-caching)). | | 403 Forbidden | The request is not authorized by the WFM's local policy (for example, the client relationship has been retired; see [Authorization](../identity/wfm-identity-profile.md#authorization)). | -| 404 Not Found | The referenced digest does not exist on the server. `404 Not Found` indicates only that this specific digest is unavailable. It MUST NOT be interpreted as a deletion signal by a client; deletion of workloads is determined solely by absence from the state manifest. | +| 404 Not Found | The referenced digest is not available to this client. The WFM MUST return the same response for a digest that does not exist and for a digest that exists outside the caller's scope (see [caller scoping](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication)). `404 Not Found` indicates only that this specific digest is unavailable. It MUST NOT be interpreted as a deletion signal by a client; deletion of workloads is determined solely by absence from the state manifest. | > **Note:** Servers MAY apply `Content-Encoding` (e.g., gzip, br) and SHOULD include `Vary: Accept-Encoding` if they do. @@ -183,7 +183,7 @@ sha256:a1b2c3d4e5f67890123456789abcdef0123456789abcdef0123456789abcdef All Desired State endpoints implement standard HTTP caching semantics to optimize synchronization between the Workload Fleet Manager (WFM) and the Workload Fleet Management Client. ETags are used to detect content changes and avoid redundant data transfers. Two caching models are defined: one for the mutable State Manifest, and one for immutable, content-addressable resources such as individual deployments and bundles. -Every response is scoped to the authenticated client, so all Desired State responses are marked `Cache-Control: private`, which keeps a response in that client's own cache rather than a shared one. Because mTLS carries no `Authorization` header (whose presence would normally keep a response out of shared caches per [RFC 9111 §3.5](https://datatracker.ietf.org/doc/html/rfc9111#section-3.5)), servers set this explicitly rather than rely on default cache behavior. Under end-to-end mTLS no shared cache sees these responses at all; the marking matters only where an operator terminates mTLS at a TLS-offloading proxy and cleartext reaches an internal hop (see the MIAF [traffic-inspecting proxies](../identity/tls-requirements.md#traffic-inspecting-proxies) rules), where `private` keeps a cache on that hop from serving one client's response to another. +Every response is scoped to the authenticated client, so all Desired State responses are marked `Cache-Control: private`, which keeps a response in that client's own cache rather than a shared one. Because mTLS carries no `Authorization` header (whose presence would normally keep a response out of shared caches per [RFC 9111 §3.5](https://datatracker.ietf.org/doc/html/rfc9111#section-3.5)), servers set this explicitly rather than rely on default cache behavior. Under end-to-end mTLS no shared cache sees these responses at all; the marking matters only where an operator terminates mTLS at a TLS-offloading proxy and an HTTP cache runs behind it (see the MIAF [traffic-inspecting proxies](../identity/tls-requirements.md#traffic-inspecting-proxies) rules), where `private` keeps a cache on that hop from serving one client's response to another. ### State Manifest Endpoint @@ -219,7 +219,7 @@ This section defines the end-to-end workflow followed by a client to retrieve, v - If the manifest is unchanged, the WFM responds with `304 Not Modified`. - If the manifest has changed (`200 OK`), the client: - - Verifies the `manifestVersion` is strictly greater than the stored version. If not, the update MUST be rejected and SHOULD be logged as a security event. The specific requirements for logging security events are not currently defined and will be addressed in a future version of the specification. + - Verifies the `manifestVersion` is strictly greater than the stored version. If not, the update MUST be rejected and SHOULD be logged as a security event. The MIAF [security event logging](../identity/identity-security-considerations.md#security-event-logging) rules define the content of such a log entry. - Parses the manifest and decides whether to fetch the bundle or individual deployments. - Downloads and verifies digests for all referenced `ApplicationDeployment` YAMLs. - The client reconciles its local workloads: diff --git a/system-design/specification/identity/identity-framework.md b/system-design/specification/identity/identity-framework.md index 4eeb2f09..9c425773 100644 --- a/system-design/specification/identity/identity-framework.md +++ b/system-design/specification/identity/identity-framework.md @@ -75,10 +75,18 @@ MIAF references the current published text of each SPIFFE specification instead | Bundle endpoint | [SPIFFE Federation](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Federation.md), bundle-endpoint model adopted by reference | The Trust Bundle retrieval endpoint follows the bundle-endpoint model (one URL per Trust Domain). The `https_web` and `https_spiffe` endpoint profiles are candidates for future adoption; this release authenticates retrieval per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap). | | Discovery document | Margo | Not part of SPIFFE; defined in [Trust Bundle and Discovery Endpoints](./trust-bundle-and-discovery.md#discovery-document-endpoint). | +MIAF does not adopt the SPIFFE Workload API, and it does not adopt the node and workload attestation of SPIFFE implementations such as SPIRE. In SPIRE, attestation authenticates each issuance, and automatic issuance makes lifetimes of a few hours practical. Under MIAF, the operator authorizes each issuance through the provisioning channel (see [operator provisioning playbook](./identity-lifecycle.md#operator-provisioning-playbook)). Unlike in a SPIRE deployment, under MIAF: + +- MIAF does not require the MIS to authenticate the device or the process that holds a key. The only required check at issuance is the operator's approval. +- SVID lifetimes are not short by default, so expiry alone is not always an adequate revocation mechanism (see [SVID lifetime guidance](./identity-lifecycle.md#svid-lifetime-guidance)). +- MIAF requires no attestation that binds a key to the hardware of a device. + +SPIFFE itself specifies no attestation: the SPIFFE Workload API leaves the authentication of the caller to the implementation. MIAF also does not claim SPIFFE Federation conformance. SPIFFE Federation requires a client to support the `https_web` and `https_spiffe` endpoint profiles. MIAF uses neither and authenticates retrieval by its own [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap), so a SPIFFE Federation client does not necessarily work against an MIS. + ## Identity Model - **Identity representation.** An identity is named by a **SPIFFE ID** and represented by an **SVID** issued under the Trust Domain's MIS. -- **Path namespace.** A SPIFFE ID issued under a MIAF identity profile MUST have a path beginning with `/margo/`. Each identity profile claims a non-conflicting sub-prefix and defines its structure (the path conventions for WFMs and WFM Clients are in the [WFM Identity Profile](./wfm-identity-profile.md)). So that `/margo/` remains a reliable signal of MIAF provenance, a non-MIAF SVID in the same Trust Domain MUST NOT use it. +- **Path namespace.** A SPIFFE ID issued under a MIAF identity profile MUST have a path beginning with `/margo/`. Each identity profile claims a non-conflicting sub-prefix and defines its structure (the path conventions for WFMs and WFM Clients are in the [WFM Identity Profile](./wfm-identity-profile.md)). So that `/margo/` remains a reliable signal of MIAF provenance, a non-MIAF SVID in the same Trust Domain MUST NOT use it. The MIS MUST NOT issue an SVID whose path begins with `/margo/` unless the path conforms to a MIAF identity profile. An MIS built on a stock CA can apply this check in the operator's workflow, when the operator chooses the SPIFFE ID, before issuance. - **Uniqueness.** Each SPIFFE ID names a single identity within its Trust Domain. - **Lifecycle.** All identities follow the [lifecycle vocabulary](./identity-lifecycle.md#lifecycle-vocabulary). - **Extensibility.** The MIS, Trust Domain, SVID, and Trust Bundle concepts are generic; further profiles may be added for new principal classes without redefining the framework. @@ -93,6 +101,12 @@ The **Margo Identity Service (MIS)** is a role, not a specific service. Within a Anything that meets these responsibilities can fill the role: [SPIRE](https://spiffe.io/docs/latest/spire-about/), a CA configured for a MIAF profile, an operator's provisioning workflow, or something else. The only wire contract MIAF fixes for the MIS is the two HTTPS trust endpoints above; it does not standardize how the MIS issues SVIDs, and beyond those endpoints conformance is judged by behavior rather than by API surface. +A conformance test can check that behavior on the SVIDs an MIS issues, without an issuance API (informative). The test can check that each issued SVID: + +- conforms to the [X.509-SVID profile](./svids.md#x509-svid-profile), including its leaf constraints; +- conforms to the [cryptographic requirements](./svids.md#cryptographic-requirements); and +- carries a SPIFFE ID in the Trust Domain that conforms to the [path namespace](#identity-model) rules and to the path rules of its identity profile. + ### Deployment Patterns (informative) Three common ways to fulfil the MIS role. The framework requirements above apply equally to all of them. diff --git a/system-design/specification/identity/identity-lifecycle.md b/system-design/specification/identity/identity-lifecycle.md index afdfc86e..d78c2e2e 100644 --- a/system-design/specification/identity/identity-lifecycle.md +++ b/system-design/specification/identity/identity-lifecycle.md @@ -58,4 +58,6 @@ Where the MIS operates as an intermediate CA beneath an enterprise or offline ro MIAF favours short SVID lifetimes: short lifetimes keep the blast radius low and let expiry double as revocation. The right value depends on the principal. A workload identity in a connected service can renew hourly; a device with intermittent or air-gapped connectivity needs enough margin to renew before a realistic offline window ends. As a rough orientation, automated SPIRE-style workload SVIDs are commonly 1-24 hours, while long-lived device SVIDs are often weeks to a few months. +Validity-period evaluation depends on the verifier's clock (see [certificate validation](./tls-requirements.md#certificate-validation)). Operators SHOULD account for devices with weak clocks when choosing SVID lifetimes. A deployment that cannot assure clock integrity SHOULD NOT rely on expiry alone for revocation. + Manual provisioning makes short lifetimes operationally expensive, so an operator relying on it MAY use longer lifetimes than automated renewal would allow. Treat any such extension as a bridge: once automated renewal is available, an operator SHOULD reduce lifetimes to the shortest value compatible with their principals' connectivity. diff --git a/system-design/specification/identity/identity-security-considerations.md b/system-design/specification/identity/identity-security-considerations.md index a44a2932..cd1bdf9e 100644 --- a/system-design/specification/identity/identity-security-considerations.md +++ b/system-design/specification/identity/identity-security-considerations.md @@ -2,28 +2,32 @@ MIAF assumes an adversarial network and the possibility of a compromised individual device or service. Its goals are to preserve identity integrity and minimize blast radius; protecting the private keys those identities rest on is the responsibility of the deployment rather than provided by the framework. -This section is scoped to identity. For each threat, it describes the mitigation that MIAF relies on, along with links to the specific rules. Some mitigations are normative framework requirements; others are residual risks carried by the operator's deployment. +This section is scoped to identity. For each threat, it describes the mitigation that MIAF relies on, along with links to the specific rules. Some mitigations are normative framework requirements; others are residual risks carried by the operator's deployment. The threat tables state no requirements of their own. Each normative mitigation is stated on the page the table links to. ## Framework Threats | Threat | Description | Mitigation | | :----- | :---------- | :---------- | -| **Theft and misuse of a credential** | An attacker who obtains a principal's SVID together with its private key authenticates as that principal anywhere its SPIFFE ID is accepted; an X.509-SVID carries no audience restriction confining it to one verifier. | A principal MUST protect its private key (see the private-key-compromise row below); SVIDs SHOULD be short-lived per [SVID lifetime guidance](./identity-lifecycle.md#svid-lifetime-guidance) to bound the exposure window; and each verifier MUST validate the SVID against the Trust Bundle and authorize the specific SPIFFE ID locally, so a stolen credential reaches only what that identity is granted (see [X.509-SVID validation](./svids.md#x509-svid-validation)). | +| **Theft and misuse of a credential** | An attacker who obtains a principal's SVID together with its private key authenticates as that principal anywhere its SPIFFE ID is accepted; an X.509-SVID carries no audience restriction confining it to one verifier. | The protection of the private key is covered in the private-key-compromise row below. Short SVID lifetimes limit the exposure window (see [SVID lifetime guidance](./identity-lifecycle.md#svid-lifetime-guidance)). Each verifier validates the SVID against the Trust Bundle and authorizes the specific SPIFFE ID locally, so a stolen credential reaches only what that identity is granted (see [X.509-SVID validation](./svids.md#x509-svid-validation)). | | **Private key compromise** | An attacker exfiltrates a principal's private key and can then authenticate as that principal until the credential is withdrawn. | A principal protects its private key per deployment policy; MIAF specifies no key-protection mechanism of its own. The [operator provisioning playbook](./identity-lifecycle.md#operator-provisioning-playbook)'s preferred path issues by CSR, which keeps the key on the principal and can bind it to hardware (TPM, secure element, or HSM) where available. Once compromise is known, access is withdrawn via the [operator revocation playbook](./identity-lifecycle.md#operator-revocation-playbook), bounded by short [SVID lifetimes](./identity-lifecycle.md#svid-lifetime-guidance). | | **Central key-custody concentration** | Where a principal cannot generate its own key pair, the operator generates it centrally (see [operator provisioning playbook](./identity-lifecycle.md#operator-provisioning-playbook)), concentrating many principals' private keys in one store whose breach exposes all of them at once. | The provisioning playbook's preferred path generates the key on the principal (issuance by CSR), leaving no central copy; where a principal cannot generate its own key pair, the operator generates it centrally and accepts the resulting key-custody concentration risk, protecting the store per deployment policy and treating its breach as private-key compromise (row above). | | **Compromised issuance authority (MIS)** | A compromised [MIS](./identity-framework.md#the-mis-role) can mint a valid SVID for any SPIFFE ID in the Trust Domain, impersonating any WFM or client, or issue under a WFM namespace at will. | MIAF concentrates issuance in the MIS by design, so protecting it is an operator PKI-governance responsibility: scope the issuing authority (for example an intermediate CA per the [deployment patterns](./identity-framework.md#deployment-patterns-informative)) and protect its signing key per deployment policy. Recovery is [Trust Bundle rotation](./identity-lifecycle.md#trust-anchor-rotation-playbook) to retire the compromised authority. | -| **Initial trust anchor confusion** | An attacker causes a principal to trust the wrong HTTPS authority and serves a malicious discovery document or Trust Bundle. | A principal MUST authenticate the first HTTPS retrieval of the discovery document and `trustBundleUri` per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap). | -| **Trust Bundle substitution or anchor injection** | An attacker who compromises a Trust Bundle origin (or its server certificate) injects a trust anchor. Because the SPIFFE bundle is authenticated only by the transport and is not itself signed, every SVID chaining to the injected anchor is then accepted. This applies on every refresh, not only at bootstrap. | A client retrieves the Trust Bundle only over HTTPS validated against its configured initial-trust anchors or pins, on each refresh, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap) and [certificate validation](./tls-requirements.md#certificate-validation), and MUST fail closed on an empty or unusable bundle per [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle). | -| **Trust Bundle rollback** | A stale, cached, or replayed Trust Bundle is served to reverse a completed rotation, re-admitting a trust anchor that was removed to revoke a compromise. | A client authenticates every Trust Bundle retrieval, not only the first, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap), so a third party cannot substitute an older bundle. The refresh interval overrides HTTP cache freshness, so a cached copy cannot outlive that interval (see [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle)). | -| **Blocked Trust Bundle refresh** | A client cannot refresh its Trust Bundle, because an attacker denies it network access or because the device is intermittently connected or offline by design. The client keeps validating SVIDs against its cached anchors and never learns that an anchor was retired, so a compromised issuing authority under that anchor can keep minting SVIDs the client accepts. Short SVID lifetimes do not help, because the compromised authority can mint fresh ones. | MIAF sets no upper bound on bundle staleness and accepts this residual risk. A client caches its bundle to validate SVIDs while offline (see [bundle contents and distribution](./trust-bundle-and-discovery.md#bundle-contents-and-distribution)), so a client that failed closed on a stale bundle would stop validating its WFM during any connectivity outage. The exposure requires the attacker to reach the client and to hold an SVID issued under the retired anchor, and it ends at the next successful refresh. An operator that needs a staleness bound sets one in deployment policy. | +| **Initial trust anchor confusion** | An attacker causes a principal to trust the wrong HTTPS authority and serves a malicious discovery document or Trust Bundle. | A principal authenticates the first HTTPS retrieval of the discovery document and `trustBundleUri`, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap). | +| **Trust Bundle substitution or anchor injection** | An attacker who compromises a Trust Bundle origin (or its server certificate) injects a trust anchor. Because the SPIFFE bundle is authenticated only by the transport and is not itself signed, every SVID chaining to the injected anchor is then accepted. This applies on every refresh, not only at bootstrap. | A client retrieves the Trust Bundle only over HTTPS validated against its configured initial-trust anchors or pins, on each refresh, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap) and [certificate validation](./tls-requirements.md#certificate-validation), and fails closed on an empty or unusable bundle, per [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle). A client anchored on a public PKI accepts a `trustBundleUri` from the discovery document only at the origin of that document, per the [Trust Bundle retrieval endpoint](./trust-bundle-and-discovery.md#trust-bundle-retrieval-endpoint) rules. A valid certificate for another public name is therefore not sufficient. Integrity of the cached copy is covered in the local-tampering row below. | +| **Trust Bundle rollback** | A stale, cached, or replayed Trust Bundle is served to reverse a completed rotation, re-admitting a trust anchor that was removed to revoke a compromise. | A client authenticates every Trust Bundle retrieval, not only the first, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap), so a third party cannot substitute an older bundle. The refresh interval overrides HTTP cache freshness, so a cached copy cannot outlive that interval (see [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle)). These controls do not stop an authenticated origin, a cache, or a proxy in front of the MIS from serving an older bundle. A decrease of `spiffe_sequence` is logged and signaled, but the bundle is not rejected, per [bundle ordering](./trust-bundle-and-discovery.md#bundle-ordering). | +| **Blocked Trust Bundle refresh** | A client cannot refresh its Trust Bundle, because an attacker denies it network access or because the device is intermittently connected or offline by design. The client keeps validating SVIDs against its cached anchors and never learns that an anchor was retired, so a compromised issuing authority under that anchor can keep minting SVIDs the client accepts. Short SVID lifetimes do not help, because the compromised authority can mint fresh ones. | MIAF sets no upper bound on bundle staleness and accepts this residual risk. A client caches its bundle to validate SVIDs while offline (see [bundle contents and distribution](./trust-bundle-and-discovery.md#bundle-contents-and-distribution)), so a client that failed closed on a stale bundle would stop validating its WFM during any connectivity outage. The exposure requires the attacker to reach the client and to hold an SVID issued under the retired anchor, and it ends at the next successful refresh. An operator that needs a staleness bound sets one in deployment policy. A client signals a bundle older than an operator-configured threshold, per [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle), so the condition is visible to the operator. | | **Reused SPIFFE ID after principal replacement** | A principal is decommissioned rather than compromised, so nobody withdraws its SVID, and its SPIFFE ID is reused for the replacement. The retired SVID stays valid until it expires, and its private key may be recoverable from decommissioned or resold hardware, so the retired principal can still authenticate as the shared identity. | An operator assigns a fresh SPIFFE ID to the replacement unless the retired private key is known to be destroyed, per the [operator provisioning playbook](./identity-lifecycle.md#operator-provisioning-playbook). While the two share an identity, allowlist removal cannot separate them, so the exposure otherwise lasts until the retired SVID expires (see [SVID lifetime guidance](./identity-lifecycle.md#svid-lifetime-guidance)). | -| **Certificate revocation lag** | A revocation event is not propagated promptly. | A deployment relies on the [operator revocation playbook](./identity-lifecycle.md#operator-revocation-playbook) and short SVID lifetimes; a verifier SHOULD limit connection lifetime so that a long-lived mTLS session does not extend the lag indefinitely (see [session lifetime and re-validation](./tls-requirements.md#session-lifetime-and-re-validation)). | -| **Service impersonation / MITM** | An adversary attempts to impersonate the MIS or another service. | All endpoints MUST use HTTPS with strict certificate validation per the [TLS requirements](./tls-requirements.md). A client authenticates the MIS trust endpoints against its configured initial-trust anchors or pins, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap), since the MIS presents no SVID. For mTLS between principals, a peer MUST verify the presented SVID against the configured Trust Domain and Trust Bundle. | -| **Cross-domain trust confusion** | A component accepts identities from an unintended Trust Domain. | A verifier MUST determine the Trust Domain from the SPIFFE ID and MUST NOT trust an SVID whose Trust Domain is not the verifier's own. | -| **Inadvertent inspection-proxy MITM** | An operator-deployed traffic-inspecting proxy intercepts and re-signs Margo mTLS traffic, presenting a substitute certificate to either endpoint. | An operator MUST exempt Margo mTLS endpoints from inspection per [traffic-inspecting proxies](./tls-requirements.md#traffic-inspecting-proxies). Both endpoints MUST validate peer SVIDs against the Trust Bundle and reject a substituted certificate. | -| **Forwarded-identity header injection** | In a TLS-offload topology, an attacker injects a forged `Client-Cert` header to impersonate an authenticated caller at the backend. | The proxy MUST remove or overwrite any `Client-Cert` or `Client-Cert-Chain` header on incoming requests, and the backend MUST accept a forwarded identity only over the trusted proxy boundary, per [traffic-inspecting proxies](./tls-requirements.md#traffic-inspecting-proxies). | -| **Unreliable or manipulated verifier clock** | A principal mis-evaluates certificate validity periods because its clock is skewed or unset (a dead RTC battery, no time source at first boot) or because an attacker rewinds it, for example by spoofing an unauthenticated time source on the adversarial network. Rewinding the clock extends the validity window of an expired SVID, defeating the use of expiry as revocation. | SVID validation depends on a trustworthy local time source. A principal SHOULD synchronize time from a trustworthy source before validating certificates, and operators SHOULD account for devices with weak clocks when choosing SVID lifetimes (see [SVID lifetime guidance](./identity-lifecycle.md#svid-lifetime-guidance)). A deployment that cannot assure clock integrity SHOULD NOT rely on expiry alone for revocation. | -| **Handshake identity disclosure on TLS 1.2 fallback** | Where an operator enables the optional TLS 1.2 fallback, the mTLS handshake sends the client X.509-SVID in cleartext, so a passive on-path observer can read the peer's SPIFFE ID, and with it its Trust Domain and client relationship. | Default to TLS 1.3, which encrypts the certificate messages, per the [TLS requirements](./tls-requirements.md#minimum-tls-baseline). Enable TLS 1.2 only where a deployment requires it, accepting this passive-disclosure risk; when used, TLS 1.2 MUST follow modern security guidance ([RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325)). | +| **Certificate revocation lag** | A revocation event is not propagated promptly. | A deployment relies on the [operator revocation playbook](./identity-lifecycle.md#operator-revocation-playbook) and short SVID lifetimes. A verifier limits connection lifetime, so that a long-lived mTLS session does not extend the lag indefinitely (see [session lifetime and re-validation](./tls-requirements.md#session-lifetime-and-re-validation)). | +| **Service impersonation / MITM** | An adversary attempts to impersonate the MIS or another service. | All endpoints use HTTPS with strict certificate validation, per the [TLS requirements](./tls-requirements.md). A client authenticates the MIS trust endpoints against its configured initial-trust anchors or pins, per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap), since the MIS presents no SVID. For mTLS between principals, a peer verifies the presented SVID against its Trust Domain and Trust Bundle, per [X.509-SVID validation](./svids.md#x509-svid-validation). | +| **Cross-domain trust confusion** | A component accepts identities from an unintended Trust Domain. | A verifier determines the Trust Domain from the SPIFFE ID and rejects an SVID whose Trust Domain is not the verifier's own, per [X.509-SVID validation](./svids.md#x509-svid-validation). | +| **Inadvertent inspection-proxy MITM** | An operator-deployed traffic-inspecting proxy intercepts and re-signs Margo mTLS traffic, presenting a substitute certificate to either endpoint. | An operator exempts Margo mTLS endpoints from inspection, per [traffic-inspecting proxies](./tls-requirements.md#traffic-inspecting-proxies). Both endpoints validate peer SVIDs against the Trust Bundle, per [X.509-SVID validation](./svids.md#x509-svid-validation), and so reject a substituted certificate. The same section gives deployment guidance for a conduit that requires a control point. | +| **Forwarded-identity header injection** | In a TLS-offload topology, an attacker injects a forged `Client-Cert` header to impersonate an authenticated caller at the backend. | The proxy removes or overwrites any `Client-Cert` or `Client-Cert-Chain` header on incoming requests, and the backend accepts a forwarded identity only over the authenticated connection from the proxy, per [traffic-inspecting proxies](./tls-requirements.md#traffic-inspecting-proxies). | +| **Internal-hop tampering** | In a TLS-offload topology, an attacker with access to the connection between the proxy and the backend forges or changes requests, and claims any client identity. MIAF has no application-layer signature, so the backend has no end-to-end check of a request. | The connection between the proxy and the backend is protected against eavesdropping and modification, by means the operator chooses, per [traffic-inspecting proxies](./tls-requirements.md#traffic-inspecting-proxies). From the proxy onwards, the integrity of each request is the operator's responsibility. | +| **Replay of early data** | An on-path attacker records a TLS 1.3 early-data request and sends it again. Examples are a `DELETE` that removes a device registration, and an old `PUT` that restores superseded capability data. | Early data is excluded on every MIAF connection, per the [TLS requirements](./tls-requirements.md#minimum-tls-baseline). TLS 1.3 protects data sent after the handshake completes against replay. Replay protection on the connection between a TLS-offloading proxy and the backend is part of the operator's responsibility for that connection (internal-hop row above). | +| **Local tampering with cached trust material** | An attacker with write access to a device's storage changes the stored trust material. An added anchor in the cached Trust Bundle makes the client accept SVIDs the attacker issues, for example a forged WFM SVID, until the next accepted refresh replaces the bundle. A changed initial-trust anchor, pin, or directly supplied `trustBundleUri` makes the client accept a bundle from the attacker at the next refresh. A replaced SVID and key make the principal authenticate with an identity that the attacker controls. | A client protects the integrity of its cached trust material at rest, per [bundle contents and distribution](./trust-bundle-and-discovery.md#bundle-contents-and-distribution). Where the device offers no such protection, the operator carries the residual risk and relies on the physical security of the device. | +| **Trust endpoint denial of service and refresh stampede** | The bundle endpoint is unauthenticated and every principal depends on it. An attacker floods it, or clients that share one `spiffe_refresh_hint` send their requests at the same time, for example after an outage. | Clients randomize the refresh time and back off on failure, and the MIS rate-limits both trust endpoints, per [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle). A client that cannot refresh keeps its cached bundle, so an outage of the endpoint does not stop validation. | +| **Unreliable or manipulated verifier clock** | A principal mis-evaluates certificate validity periods because its clock is skewed or unset (a dead RTC battery, no time source at first boot) or because an attacker rewinds it, for example by spoofing an unauthenticated time source on the adversarial network. Rewinding the clock extends the validity window of an expired SVID, defeating the use of expiry as revocation. | SVID validation depends on a trustworthy local time source. A principal uses an authenticated time source where one is available, per [certificate validation](./tls-requirements.md#certificate-validation). The [SVID lifetime guidance](./identity-lifecycle.md#svid-lifetime-guidance) covers devices with weak clocks, and deployments that cannot assure clock integrity. | +| **Handshake identity disclosure on TLS 1.2 fallback** | Where an operator enables the optional TLS 1.2 fallback, the mTLS handshake sends the client X.509-SVID in cleartext, so a passive on-path observer can read the peer's SPIFFE ID, and with it its Trust Domain and client relationship. | Default to TLS 1.3, which encrypts the certificate messages, per the [TLS requirements](./tls-requirements.md#minimum-tls-baseline). The fallback is disabled by default on both ends and needs explicit operator configuration, so a single misconfigured end cannot downgrade its peers. An operator enables TLS 1.2 only where a deployment requires it, and accepts this passive-disclosure risk. When used, TLS 1.2 follows [RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325), per the same requirements. | ## WFM Identity Profile Threats @@ -31,9 +35,33 @@ WFM and WFM Client identities inherit the framework threats above. The threats b | Threat | Description | Mitigation | | :--- | :--- | :--- | -| **Wrong-WFM impersonation** | A WFM Client connects to a WFM whose SVID is validly issued by the Trust Domain but whose `wfm-id` is not the one that issued the client, so the client authenticates to the wrong WFM. | The WFM Client MUST verify the `wfm-id` in the WFM's SPIFFE path against the `wfm-id` in its own SVID before authenticating (see [Recognition by the WFM Client](./wfm-identity-profile.md#recognition-by-the-wfm-client)). | -| **Wrong-WFM-Client acceptance** | A WFM accepts an SVID whose `wfm-id` belongs to a different WFM's namespace, treating the bearer as a client of itself. | The WFM MUST verify the `wfm-id` in the SPIFFE path against its own namespace before authorizing (see [Recognition by the WFM](./wfm-identity-profile.md#recognition-by-the-wfm)). | -| **Unauthorized client within the namespace** | A validly issued SVID under a WFM's own namespace (`.../wfm//client/...`) is presented by a party the WFM's operator never authorized; for instance, the MIS issues under the namespace to the wrong principal. | Namespace match alone does not grant access: a WFM MUST admit a caller only if its identity is accepted by the WFM's accepted-client policy (see [Authorization](./wfm-identity-profile.md#authorization)). Operators SHOULD also constrain the MIS to issue under a WFM's namespace only to that WFM's authorized clients. | -| **Stale accepted-client policy** | An entry remains in a WFM's accepted-client policy after the relationship is retired, letting a still-valid credential keep accessing the API. | An operator removes `wfm-client-id` entries promptly per the revocation row of the [lifecycle table](./wfm-identity-profile.md#lifecycle). The WFM MAY also deny the request by local policy, surfaced as described in [API Requirements and Security](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication), per [Authorization](./wfm-identity-profile.md#authorization). | +| **Wrong-WFM impersonation** | A WFM Client connects to a WFM whose SVID is validly issued by the Trust Domain but whose `wfm-id` is not the one that issued the client, so the client authenticates to the wrong WFM. | The WFM Client verifies the `wfm-id` in the WFM's SPIFFE path against the `wfm-id` in its own SVID before authenticating (see [Recognition by the WFM Client](./wfm-identity-profile.md#recognition-by-the-wfm-client)). | +| **Wrong-WFM-Client acceptance** | A WFM accepts an SVID whose `wfm-id` belongs to a different WFM's namespace, treating the bearer as a client of itself. | The WFM verifies the `wfm-id` in the SPIFFE path against its own namespace before authorizing (see [Recognition by the WFM](./wfm-identity-profile.md#recognition-by-the-wfm)). | +| **Unauthorized client within the namespace** | A validly issued SVID under a WFM's own namespace (`.../wfm//client/...`) is presented by a party the WFM's operator never authorized; for instance, the MIS issues under the namespace to the wrong principal. | Namespace match alone does not grant access. A WFM admits a caller only if the WFM's accepted-client policy accepts its identity, and the operator constrains the MIS to issue under a WFM's namespace only to that WFM's authorized clients (see [Authorization](./wfm-identity-profile.md#authorization)). | +| **Stale accepted-client policy** | An entry remains in a WFM's accepted-client policy after the relationship is retired, letting a still-valid credential keep accessing the API. | An operator removes `wfm-client-id` entries promptly per the revocation row of the [lifecycle table](./wfm-identity-profile.md#lifecycle). The WFM can also deny the request by local policy, surfaced as described in [API Requirements and Security](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication), per [Authorization](./wfm-identity-profile.md#authorization). | | **No precise revocation of a compromised WFM** | A WFM has no client-side accepted-server allowlist comparable to the WFM's accepted-client policy, so a compromised WFM SVID cannot be revoked precisely: only fleet-wide Trust Bundle rotation or expiry withdraws it, so both the blast radius and the revocation lag are large. | Keep WFM SVIDs short-lived and reissue with the same SPIFFE ID before expiry; use [Trust Bundle rotation](./identity-lifecycle.md#trust-anchor-rotation-playbook) as the cryptographically enforced revocation path, accepting its fleet-wide scope (see the WFM [lifecycle](./wfm-identity-profile.md#lifecycle)). | -| **Non-conformant authentication paths** | A WFM exposes an authentication path outside the mTLS model (an unauthenticated onboarding endpoint or an application-layer signature scheme) that an attacker uses to bypass SVID validation. | A WFM MUST authenticate every Management Interface request with mTLS and an X.509-SVID, and MUST reject requests that do not conform per [API Requirements and Security](../margo-management-interface/api-requirements-and-security.md). | +| **Compromised see-thru gateway** | A see-thru gateway holds the only MIAF identity for itself and for every child device behind it. It reports the existence and capabilities of each child device, receives the desired state for all of them in one state manifest, and routes each deployment by `metadata.deviceId`. A compromised gateway can report child devices that do not exist, report false capabilities to receive workloads, or deliver a deployment to a device other than the one it names. | Child devices are not principals in this release. The link between the gateway and a child device is outside MIAF, and the deployment secures it by its own means. The WFM Client, not the WFM, enforces `metadata.deviceId` targeting. A WFM can also constrain which `deviceId`s a client reports (see [API Requirements and Security](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication)). A future device identity profile is expected to give child devices their own identities. | +| **Non-conformant authentication paths** | A WFM exposes an authentication path outside the mTLS model (an unauthenticated onboarding endpoint or an application-layer signature scheme) that an attacker uses to bypass SVID validation. | A WFM authenticates every Management Interface request with mTLS and an X.509-SVID, and rejects requests that do not conform, per [API Requirements and Security](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication). | + +## Security Event Logging + +A principal SHOULD log the security events below that have no linked rule. A row with a linked rule points to the page that states the requirement for that event. Each log entry SHOULD carry a timestamp from the principal's clock (see [certificate validation](./tls-requirements.md#certificate-validation)). + +| Event | Content of the entry | +| :---- | :------------------- | +| Authentication failure | the reason, and the SPIFFE ID of the peer where it presented one | +| Authorization denial | the SPIFFE ID and the policy decision | +| Trust Bundle accepted or rejected | the reason for a rejection | +| Change of the trust anchor set | the anchors added and removed | +| TLS 1.2 negotiated | the peer, per the [TLS requirements](./tls-requirements.md#minimum-tls-baseline) | +| `spiffe_sequence` regression | per [bundle ordering](./trust-bundle-and-discovery.md#bundle-ordering) | +| Trust Bundle older than the operator threshold | the time of the last accepted bundle, per [selecting and refreshing the bundle](./trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle) | +| Discovery document rejected | the reason: a `trustDomain` mismatch, or a `trustBundleUri` on an origin the client does not accept | +| Identity failure of a WFM Client | the cause, per [identity failure and deployed workloads](./wfm-identity-profile.md#identity-failure-and-deployed-workloads) | +| Own SVID expired, or its renewal failed | the SPIFFE ID and the `notAfter` time | + +The table maps to the auditable events of IEC 62443-3-3 SR 2.8 and IEC 62443-4-2 CR 2.8. MIAF does not specify log storage, log transport, or retention. + +## Non-Repudiation + +mTLS authenticates a peer for the duration of a connection. It gives no evidence that a third party can verify after the connection ends, so MIAF provides no non-repudiation of requests. A use case that requires non-repudiation needs a signed-message profile, which is not defined in this release. diff --git a/system-design/specification/identity/svids.md b/system-design/specification/identity/svids.md index cd18505a..f9c974c2 100644 --- a/system-design/specification/identity/svids.md +++ b/system-design/specification/identity/svids.md @@ -23,8 +23,11 @@ This section constrains the signature algorithms and key parameters used for SVI - **ECDSA P-256 with SHA-256 is mandatory to implement.** Every MIAF component MUST implement it, for both presenting and validating SVIDs, so that any two components always share at least one algorithm. - A component MAY additionally implement EdDSA (Ed25519), RSA, or both. Whether a peer can accept a presented SVID depends on two kinds of algorithm choice. The signature algorithm of each certificate in the chain is chosen by its issuing CA, and the peer needs it to validate the chain. The public-key algorithm of the leaf key is chosen when the key pair is generated, and the peer needs it to verify the TLS handshake signature. A peer that does not implement every algorithm involved fails to authenticate the presenter. Selecting algorithms supported across the Trust Domain, for the principal's key and for every CA in the chain, is therefore the operator's responsibility at issuance (see [operator provisioning playbook](./identity-lifecycle.md#operator-provisioning-playbook)). ECDSA P-256 with SHA-256 throughout is always a safe choice. - A component that validates SVIDs MUST validate every algorithm it accepts from peers. For RSA, this includes accepting both PSS and PKCS#1 v1.5 signatures on certificates. +- A verifier MUST reject an RSA public key in a presented SVID chain whose public exponent is less than 65537. [FIPS 186-5](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf) already requires an exponent greater than 2^16 at key generation, and a small exponent makes forged PKCS#1 v1.5 signatures practical against a lax verifier. -> **Crypto-agility (informative):** MIAF names its algorithms explicitly so the permitted set can evolve. The set above is classical; post-quantum signature suites are expected to arrive as additional permitted algorithms rather than a redesign of the framework. Defaulting the transport to TLS 1.3 (see [TLS requirements](./tls-requirements.md)) supports this: it is the version track on which post-quantum key exchange and authentication are being standardized. +> **Crypto-agility (informative):** MIAF names its algorithms explicitly so the permitted set can evolve. The set above is classical; post-quantum signature suites are expected to arrive as additional permitted algorithms rather than a redesign of the framework. Defaulting the transport to TLS 1.3 (see [TLS requirements](./tls-requirements.md)) supports this: it is the version track on which post-quantum key exchange and authentication are being standardized. Hybrid post-quantum key exchange is already permitted (see [key exchange](./tls-requirements.md#minimum-tls-baseline)). +> +> Post-quantum signatures and public keys are much larger than classical ones, so a revision that adds post-quantum algorithms also revisits the [chain limits](#x509-svid-validation) below. Devices provisioned under this specification can stay in service for 10 to 20 years, so a device provisioned today can meet post-quantum SVIDs during its service life. Keys MUST be generated with a cryptographically secure random number generator seeded from an entropy source carrying enough genuine entropy for the key size ([RFC 4086](https://datatracker.ietf.org/doc/html/rfc4086)). @@ -35,7 +38,7 @@ These requirements apply to MIAF-generated artifacts and to the keys used in SVI A verifier authenticates a peer by validating the presented X.509-SVID against the peer's Trust Domain and, on success, treating the SPIFFE ID it carries as the peer's identity. A verifier MUST, in order: - read the SPIFFE ID from the leaf certificate's **URI SAN** to determine the peer's Trust Domain, and reject the SVID unless that Trust Domain is the verifier's own. DNS hostname matching does not apply to SVID identity and MUST NOT override the SPIFFE ID. -- validate the presented chain against that Trust Domain's [Trust Bundle](./trust-bundle-and-discovery.md), accepting an SVID that chains to any anchor in the current bundle. A bundle MAY contain more than one anchor (for example, during a [trust anchor rotation](./identity-lifecycle.md#trust-anchor-rotation-playbook) overlap), and every anchor in it is equally authoritative. Reject any certificate outside its validity period. A verifier MUST NOT rely on AIA fetching or other out-of-band intermediate retrieval; the presenter supplies the intermediates the chain needs. +- validate the presented chain against that Trust Domain's [Trust Bundle](./trust-bundle-and-discovery.md), accepting an SVID that chains to any anchor in the current bundle. A bundle MAY contain more than one anchor (for example, during a [trust anchor rotation](./identity-lifecycle.md#trust-anchor-rotation-playbook) overlap), and every anchor in it is equally authoritative. Reject any certificate outside its validity period. A verifier MUST NOT rely on AIA fetching or other out-of-band intermediate retrieval; the presenter supplies the intermediates the chain needs. A verifier MUST reject a presented chain of more than six certificates, before it builds the certification path. Six certificates cover a leaf, four intermediate CAs, and a trust anchor that the presenter includes, as in nested SPIRE servers under an enterprise PKI. A verifier SHOULD reject a presented chain whose encoded size exceeds 64 KiB. This limit is far above the 2048-byte SPIFFE ID length that [SPIFFE ID, Section 2.3](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#23-maximum-spiffe-id-length) requires implementations to support. These limits stop a peer from forcing a long path validation on a constrained device. - enforce the SPIFFE X.509-SVID leaf constraints and reject any SVID that violates them: basic-constraints `cA` MUST be `false`; `keyCertSign` and `cRLSign` MUST NOT be set in key usage; the SPIFFE ID MUST use the `spiffe` scheme with a non-root path; and the certificate MUST carry exactly one URI SAN. These leaf constraints, and the other structural rules of the SPIFFE X.509-SVID specification, apply during validation as well as issuance. diff --git a/system-design/specification/identity/tls-requirements.md b/system-design/specification/identity/tls-requirements.md index 1e35cfca..4cc37a9b 100644 --- a/system-design/specification/identity/tls-requirements.md +++ b/system-design/specification/identity/tls-requirements.md @@ -9,10 +9,16 @@ MIAF follows [RFC 9852](https://datatracker.ietf.org/doc/html/rfc9852), which re | Requirement | Normative directive | Reference | | :---------- | :------------------ | :-------- | | **Default protocol version** | An implementation MUST use **TLS 1.3** as its default. | [RFC 8446](https://datatracker.ietf.org/doc/html/rfc8446), [RFC 9852](https://datatracker.ietf.org/doc/html/rfc9852) | -| **TLS 1.2 fallback** | TLS 1.2 MAY be supported as a non-default fallback where a deployment requires it. When supported, it MUST conform to [RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325). | [RFC 9852](https://datatracker.ietf.org/doc/html/rfc9852), [RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325) | +| **TLS 1.2 fallback** | TLS 1.2 MAY be supported as a non-default fallback where a deployment requires it. When supported, it MUST conform to [RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325). A client, and a server of an mTLS endpoint, MUST disable the fallback by default and MUST enable it only by explicit operator configuration. They SHOULD log each connection that negotiates TLS 1.2. | [RFC 9852](https://datatracker.ietf.org/doc/html/rfc9852), [RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325) | | **Deprecated versions** | SSL v2, SSL v3, TLS 1.0, and TLS 1.1 MUST NOT be used. | [RFC 6176](https://datatracker.ietf.org/doc/html/rfc6176) (SSL v2), [RFC 7568](https://datatracker.ietf.org/doc/html/rfc7568) (SSL v3), [RFC 8996](https://datatracker.ietf.org/doc/html/rfc8996) (TLS 1.0, TLS 1.1) | +| **Early data (0-RTT)** | A client MUST NOT send TLS 1.3 early data. A server of an mTLS endpoint MUST NOT accept it. | [RFC 8446, Section 8](https://datatracker.ietf.org/doc/html/rfc8446#section-8), [RFC 9325](https://datatracker.ietf.org/doc/html/rfc9325) | +| **Key exchange** | An implementation MAY offer or select a hybrid key-exchange group that combines a classical and a post-quantum algorithm (for example, `X25519MLKEM768`), where its TLS stack supports one. | [RFC 8446](https://datatracker.ietf.org/doc/html/rfc8446), [IANA TLS Supported Groups](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8) | -The TLS 1.2 fallback carries a confidentiality cost specific to MIAF. A MIAF client certificate is an X.509-SVID, and TLS 1.2 sends the certificate messages in cleartext during the handshake (TLS 1.3 encrypts them), so a passive on-path observer can read the peer's SPIFFE ID, and with it the peer's Trust Domain and client relationship, from any TLS 1.2 mTLS handshake. Defaulting to TLS 1.3 avoids this. An operator that enables the fallback accepts the exposure (see [handshake identity disclosure on TLS 1.2 fallback](./identity-security-considerations.md#framework-threats)). +The TLS 1.2 fallback carries a confidentiality cost specific to MIAF. A MIAF client certificate is an X.509-SVID, and TLS 1.2 sends the certificate messages in cleartext during the handshake (TLS 1.3 encrypts them), so a passive on-path observer can read the peer's SPIFFE ID, and with it the peer's Trust Domain and client relationship, from any TLS 1.2 mTLS handshake. Defaulting to TLS 1.3 avoids this. An operator that enables the fallback accepts the exposure (see [handshake identity disclosure on TLS 1.2 fallback](./identity-security-considerations.md#framework-threats)). On an mTLS connection, both ends apply the default independently, so the connection cannot negotiate TLS 1.2 unless the operator enabled the fallback on both ends. The server rules do not apply to the MIS trust endpoints. The client rules already exclude TLS 1.2 and early data on those connections by default, and a stock HTTPS server can then serve them unchanged. Against an active downgrade, the TLS 1.3 downgrade protection of RFC 8446, Section 4.1.3 applies. + +TLS 1.3 early data is not protected against replay (RFC 8446, Section 8). A replayed request that changes state can, for example, remove a device registration again, or restore superseded capability data. A client that polls gains nothing from 0-RTT, so MIAF excludes it on every connection it governs. + +A hybrid key-exchange group protects the confidentiality of recorded traffic against a later quantum attack. Margo devices often stay in service for 10 to 20 years (see the [crypto-agility note](./svids.md#cryptographic-requirements)), so this protection is relevant to traffic recorded today. The same note describes the authentication side of the post-quantum transition. ## Initial Trust Bootstrap @@ -33,7 +39,9 @@ Whichever path is used, a client MUST NOT accept trust material from an unauthen Each endpoint MUST validate the peer's TLS certificate chain and identity in accordance with [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280). The verifier MUST check that the presented certificate chain is within its validity period and MUST reject an expired certificate. MIAF does not use RFC 5280 online revocation checking (CRL or OCSP); a compromised credential is withdrawn by removing its trust anchor from the Trust Bundle and by short SVID lifetimes (see [operator revocation playbook](./identity-lifecycle.md#operator-revocation-playbook) and [Session Lifetime and Re-validation](#session-lifetime-and-re-validation)). -Validity-period evaluation depends on a trustworthy local clock (see [unreliable verifier clock](./identity-security-considerations.md#framework-threats)). A verifier MAY apply a small, bounded clock-skew tolerance consistent with its time-synchronization assumptions. +Validity-period evaluation depends on a trustworthy local clock (see [unreliable verifier clock](./identity-security-considerations.md#framework-threats)). A verifier MAY apply a clock-skew tolerance consistent with its time-synchronization assumptions. The tolerance SHOULD NOT exceed 5 minutes. + +A principal SHOULD obtain time from an authenticated time source, such as Network Time Security ([RFC 8915](https://datatracker.ietf.org/doc/html/rfc8915)), where one is available. An unauthenticated time source lets an on-path attacker set the clock, and a rewound clock makes an expired SVID appear valid. **Server identity for MIAF HTTPS endpoints (discovery and Trust Bundle retrieval):** @@ -55,6 +63,14 @@ These limits are measured from the full TLS handshake that validated the peer's ## Traffic-Inspecting Proxies -MIAF specifies end-to-end mTLS between a principal and a verifier. Terminating that mTLS at a proxy inside the verifier's trust boundary, instead of in the verifier's own process, is a deployment choice. A proxy that terminates the mTLS, validates the peer's SVID, and forwards the authenticated identity to the backend satisfies the MIAF authentication requirement on the backend's behalf. Where such a proxy forwards the identity over an application-layer header (for example, the [RFC 9440](https://datatracker.ietf.org/doc/html/rfc9440) `Client-Cert` header), that header is the caller's identity and MUST be spoof-proof: the proxy MUST remove or overwrite any `Client-Cert` or `Client-Cert-Chain` header present on an incoming request, and the backend MUST accept a forwarded identity header only on requests arriving over the trusted proxy boundary (see [RFC 9440 §4](https://datatracker.ietf.org/doc/html/rfc9440#section-4)). +MIAF specifies end-to-end mTLS between a principal and a verifier. Terminating that mTLS at a proxy inside the verifier's trust boundary, instead of in the verifier's own process, is a deployment choice. A proxy that terminates the mTLS, validates the peer's SVID, and forwards the authenticated identity to the backend satisfies the MIAF authentication requirement on the backend's behalf. Where such a proxy forwards the identity over an application-layer header (for example, the [RFC 9440](https://datatracker.ietf.org/doc/html/rfc9440) `Client-Cert` header), that header is the caller's identity and MUST be spoof-proof: the proxy MUST remove or overwrite any `Client-Cert` or `Client-Cert-Chain` header present on an incoming request, and the backend MUST accept a forwarded identity header only on requests arriving over the trusted proxy boundary, that is, over the protected connection from the proxy described below (see [RFC 9440 §4](https://datatracker.ietf.org/doc/html/rfc9440#section-4)). + +The connection between the proxy and the backend MUST be protected against eavesdropping and modification by parties other than the proxy and the backend (see [RFC 9440 §4](https://datatracker.ietf.org/doc/html/rfc9440#section-4)). The operator chooses the means. Examples are mTLS with a credential that the backend accepts only from the proxy, and a network that the operator isolates so that only the proxy can reach the backend. mTLS is the stronger choice, because the backend then relies on a credential and not on the network path. Terminating the mTLS at a proxy ends the end-to-end authentication at that proxy. From there, the operator is responsible for the integrity of each request and of the identity the proxy forwards (see [internal-hop tampering](./identity-security-considerations.md#framework-threats)). **Traffic-inspecting proxies** (NGFW, SWG, SASE products) in the inline path between a principal and a verifier are not supported. Traffic inspection requires the proxy to MITM TLS for content inspection, which is architecturally incompatible with mTLS. An operator MUST exempt Margo mTLS endpoints from inspection. Other B2B mTLS APIs follow the same pattern, and inspection-proxy products commonly expose it as an explicit configuration option. + +**Deployment guidance (informative).** Many industrial operators require inspection at the IT/OT boundary as part of an IEC 62443 zone-and-conduit design. For them, the exemption is a deviation from an assessed policy. The following points support that deviation: + +- Margo mTLS traffic is authenticated and integrity-protected from end to end. An inline proxy cannot see these flows without breaking the peer authentication that MIAF depends on. +- Where policy requires a control point on the conduit, the operator can limit the conduit to the WFM and MIS endpoints, by destination and TLS SNI, and log each connection. These controls need no decryption. +- An operator can record the exemption as a documented, risk-assessed deviation, with the points above as its justification. diff --git a/system-design/specification/identity/trust-bundle-and-discovery.md b/system-design/specification/identity/trust-bundle-and-discovery.md index 97501f66..5e394af8 100644 --- a/system-design/specification/identity/trust-bundle-and-discovery.md +++ b/system-design/specification/identity/trust-bundle-and-discovery.md @@ -46,7 +46,7 @@ The path above is the default convention; an origin serving several Trust Domain A client MUST ignore unknown fields in the discovery document. -A client that already holds an SVID SHOULD verify that the document's `trustDomain` matches the trust domain of its own SPIFFE ID and treat a mismatch as a configuration error; on an origin serving several Trust Domains, this check is what catches a client pointed at the wrong Trust Domain's document. +A client that already holds an SVID MUST verify that the document's `trustDomain` matches the trust domain of its own SPIFFE ID, and MUST reject the document on a mismatch. On an origin serving several Trust Domains, this check catches a client pointed at the wrong Trust Domain's document. The check does not authenticate the document: an attacker who controls the document can copy the expected `trustDomain` into it. ### Example Discovery Document Response @@ -72,6 +72,8 @@ The resource identified by `trustBundleUri` returns the Trust Domain's SPIFFE bu The endpoint requires no authentication at the application layer; the transport is authenticated per [initial trust bootstrap](./tls-requirements.md#initial-trust-bootstrap). A client cannot yet validate MIAF-issued SVIDs when it first retrieves trust material, so this connection relies on an initial trust mechanism established outside MIAF, not on a MIAF SVID. When `trustBundleUri` names a different origin than the discovery document, the client's initial-trust material (configured PKI anchors or operator-provisioned pins) MUST cover that origin. +A CA that issues certificates for public names covers every public origin, so the rule above does not limit the origins that a client accepts under such a CA. This case applies to a client that uses its system trust store for these connections, and to a client whose operator-configured anchors or pins include a CA that issues for public names. Such a client MUST reject a `trustBundleUri` from the discovery document that has another origin than the discovery document. When the client retrieves the bundle, an HTTP redirect to another origin counts as another origin. An operator who serves the bundle on another origin supplies `trustBundleUri` to the client directly. A pin on the MIS server key or on a private CA, and an enterprise CA that does not issue for public names, already limit the origins that the client accepts, so this rule does not apply to them. + ### Route and HTTP Methods ```https @@ -124,12 +126,28 @@ A client uses the retrieved bundle as the authoritative source when validating S A client MUST NOT validate SVIDs against a retrieved bundle it rejects (see the checks above). A client MUST keep validating SVIDs against its current bundle until it accepts a retrieved bundle. A failed or rejected retrieval therefore does not change the trust material the client uses. When a client accepts a retrieved bundle, the client MUST NOT combine trust anchors from its current bundle with trust anchors from the retrieved bundle. The trust anchors in the retrieved bundle replace all trust anchors the client used before. An anchor the operator removed from the published bundle to revoke a compromise would otherwise stay in use (see step 4 of the [trust anchor rotation playbook](./identity-lifecycle.md#trust-anchor-rotation-playbook)). -A client SHOULD refresh its cached bundle at the interval given by the bundle's `spiffe_refresh_hint`, when present, and otherwise at an operator-configured interval. This refresh cadence is authoritative: HTTP cache revalidation (`If-None-Match`/`304`, and any `Cache-Control` freshness) is an efficiency optimization within it and MUST NOT defer a refresh the interval requires. The refresh interval bounds how quickly a Trust Bundle rotation reaches the fleet; the [trust anchor rotation playbook](./identity-lifecycle.md#trust-anchor-rotation-playbook) depends on it. A client that cannot reach the bundle endpoint keeps using its cached bundle. MIAF sets no upper bound on bundle staleness (see [blocked Trust Bundle refresh](./identity-security-considerations.md#framework-threats)). +A client SHOULD refresh its cached bundle at the interval given by the bundle's `spiffe_refresh_hint`, when present, and otherwise at an operator-configured interval. A client SHOULD apply an operator-configured maximum interval, and then refreshes at the smaller of that maximum and `spiffe_refresh_hint`. A substituted bundle can otherwise carry a very large hint, and the client then does not refresh for that period. SPIFFE treats the hint as a hint only, so the maximum stays compatible with SPIFFE. This refresh cadence is authoritative: HTTP cache revalidation (`If-None-Match`/`304`, and any `Cache-Control` freshness) is an efficiency optimization within it and MUST NOT defer a refresh the interval requires. The refresh interval bounds how quickly a Trust Bundle rotation reaches the fleet; the [trust anchor rotation playbook](./identity-lifecycle.md#trust-anchor-rotation-playbook) depends on it. + +A client SHOULD select each refresh time at random between 90% and 100% of the interval. A client SHOULD retry a failed retrieval after a random delay, and SHOULD increase the maximum delay exponentially with each consecutive failure, up to the refresh interval. Without these rules, clients that share one `spiffe_refresh_hint` send their requests to the bundle endpoint at the same time, and again at the same time after an outage. + +The MIS SHOULD rate-limit requests to both trust endpoints. + +A client that cannot reach the bundle endpoint keeps using its cached bundle. MIAF sets no upper bound on bundle staleness (see [blocked Trust Bundle refresh](./identity-security-considerations.md#framework-threats)). A client SHOULD record when it last accepted a bundle, and SHOULD raise an operator-visible signal when the age of its bundle exceeds an operator-configured threshold. The signal does not by itself stop the client from validating SVIDs against the cached bundle. + +### Bundle Ordering + +SPIFFE uses `spiffe_sequence` to order bundles. The [SPIFFE bundle format](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#411-sequence-number) recommends that the publisher sets it. [SPIFFE Federation](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Federation.md#42-managing-fetched-bundles) recommends that a client uses it to compare the freshness of two bundles, and considers the most recently retrieved bundle to be up to date when the field is absent. MIAF does not add requirements on the publisher, so an MIS built on an existing SPIFFE deployment stays conformant. A client uses the sequence to detect a regression, but it does not reject a bundle because of it. A client therefore accepts every bundle that a SPIFFE client following these recommendations accepts. + +When the held bundle and a retrieved bundle both carry `spiffe_sequence`, and the retrieved value is lower, the client MUST log the regression and SHOULD raise an operator-visible signal. When the held bundle carries `spiffe_sequence` and the retrieved bundle does not, the client MUST log the change. The client then applies the rules above to the retrieved bundle as to any other. A regression is expected after some operator actions, such as the restore of an MIS database from a backup. It can also mean that an origin, a cache, or a proxy serves an older bundle (see [Trust Bundle rollback](./identity-security-considerations.md#framework-threats)). ## Bundle Contents and Distribution A Trust Bundle is distributed as a SPIFFE [bundle](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md), MAY additionally be delivered through deployment tooling or provisioning flows, and SHOULD be cached locally by a client to support offline validation. +A client SHOULD protect the integrity of its stored trust material at rest: the cached bundle, its initial-trust anchors or pins, a directly supplied `trustBundleUri`, and its own SVID and private key. A platform keystore, a signed configuration store, or a MAC under a hardware-bound key are examples. A cached bundle is the client's authoritative anchor set between refreshes. An attacker who adds an anchor to it can make the client accept SVIDs that the attacker issues (see [local tampering with cached trust material](./identity-security-considerations.md#framework-threats)). + The bundle contains the Trust Domain's X.509 trust anchors only; intermediate CA certificates travel with the presented SVID chain, not in the bundle (see [chain delivery](./svids.md#x509-svid-profile)). +Each `x509-svid` entry carries one CA certificate in `x5c`, per [SPIFFE X.509-SVID, Section 6](https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md#6-representation-in-the-spiffe-bundle). That certificate is the trust anchor, and a client takes it from the first `x5c` value, as that section specifies. A client MUST ignore an entry whose certificate does not carry basic constraints with `cA` set to `true`, or whose key usage, where present, does not include `keyCertSign`. RFC 5280 path validation does not check these properties for a trust anchor. A bundle in which no entry remains is empty, and the rule in [selecting and refreshing the bundle](#selecting-and-refreshing-the-bundle) applies. + A SPIFFE bundle is a JWK Set that MAY also carry JWT-SVID signing keys (`"use": "jwt-svid"`). MIAF uses only `x509-svid` entries; an implementation MUST ignore any `jwt-svid` or other non-`x509-svid` key material found in the bundle. diff --git a/system-design/specification/identity/wfm-identity-profile.md b/system-design/specification/identity/wfm-identity-profile.md index 4e0d130a..1880c896 100644 --- a/system-design/specification/identity/wfm-identity-profile.md +++ b/system-design/specification/identity/wfm-identity-profile.md @@ -25,6 +25,8 @@ The `wfm-id` segment: An operator MAY assign one shared `wfm-id` across several WFM instances to present a single logical identity, or distinct `wfm-id`s for each instance for finer-grained lifecycle management. +The two options have different security costs. A WFM Client takes its expected `wfm-id` from its own SVID (see [Recognition by the WFM Client](#recognition-by-the-wfm-client)), so it can fail over only to instances that share that `wfm-id`. A shared `wfm-id` is therefore the usual choice for high availability. All instances then present one SPIFFE ID, so no verifier can tell them apart, and the operator cannot revoke one instance alone. Trust Bundle rotation or expiry are then the only ways to withdraw a compromised instance (see [no precise revocation of a compromised WFM](./identity-security-considerations.md#wfm-identity-profile-threats)). Where the instances share one private key, each instance holds a copy of the key that can leak (see the theft and key-custody rows of the [framework threats](./identity-security-considerations.md#framework-threats)). SPIFFE permits several key pairs under one SPIFFE ID. Whether an MIS issues them depends on the MIS, and a stock CA does not prevent it. + A WFM participating in this profile: - MUST hold a valid WFM X.509-SVID; and @@ -102,10 +104,16 @@ WFM revocation is heavier-handed than WFM Client revocation because there is no Removing a `wfm-client-id` withdraws one client only where the accepted-client policy lists clients individually. Where a WFM instead accepts any client within its namespace (see [Authorization](#authorization)), there is no individual entry to remove, so withdrawing a single client requires narrowing the policy to explicit entries or rotating the Trust Bundle. +### Identity Failure and Deployed Workloads + +A WFM Client can lose the ability to authenticate: its SVID expires, it holds no usable Trust Bundle, or it cannot validate the WFM's SVID (for example, because its clock is wrong). The mTLS rules then stop its Management Interface calls, so the management plane fails closed. The WFM Client MUST NOT stop, remove, or change workloads already deployed because of such a failure. It SHOULD raise an operator-visible signal while the failure lasts. + +In industrial deployments, availability often has priority over other security objectives (see also IEC 62443, FR 7, resource availability). After an identity failure, the last applied desired state therefore stays in effect, and the workloads keep running. + ## Authorization A WFM MUST authorize each request using local policy keyed on the authenticated WFM Client identity. Recognizing the SPIFFE ID (see [Recognition by the WFM](#recognition-by-the-wfm)) establishes only that the caller is a validly issued client within this WFM's namespace; it does not by itself grant access. -A WFM MUST maintain an accepted-client policy and admit a caller only when its identity is accepted by that policy; a matching `wfm-id` namespace is necessary but not sufficient. The policy MAY accept named `wfm-client-id`s (or full SPIFFE IDs) individually, and MAY accept any client within this WFM's namespace where the operator trusts the [MIS](./identity-framework.md#the-mis-role) to issue identities under `spiffe:///margo/wfm//client/` only to authorized clients. How the policy is expressed is implementation-specific; the requirement is that acceptance is an explicit local decision, not an automatic consequence of holding a valid SVID. Policy MAY further consider deployment-specific `wfm-client-id` metadata, and a WFM MAY deny a request from a still-valid credential; for example, once a client relationship has been retired. +A WFM MUST maintain an accepted-client policy and admit a caller only when its identity is accepted by that policy; a matching `wfm-id` namespace is necessary but not sufficient. The policy MAY accept named `wfm-client-id`s (or full SPIFFE IDs) individually, and MAY accept any client within this WFM's namespace where the operator trusts the [MIS](./identity-framework.md#the-mis-role) to issue identities under `spiffe:///margo/wfm//client/` only to authorized clients. An operator SHOULD constrain the MIS to issue SVIDs under a WFM's namespace only to that WFM's authorized clients. How the policy is expressed is implementation-specific; the requirement is that acceptance is an explicit local decision, not an automatic consequence of holding a valid SVID. Policy MAY further consider deployment-specific `wfm-client-id` metadata, and a WFM MAY deny a request from a still-valid credential; for example, once a client relationship has been retired. How this profile applies to the Margo Management Interface is specified in [API Requirements and Security](../margo-management-interface/api-requirements-and-security.md#identity-and-authentication): the endpoints served, the mTLS authentication of each call, the handling of caller identity, and how an authorization denial is surfaced (HTTP status and response body). diff --git a/system-design/specification/margo-management-interface/api-requirements-and-security.md b/system-design/specification/margo-management-interface/api-requirements-and-security.md index 2534f196..1539335a 100644 --- a/system-design/specification/margo-management-interface/api-requirements-and-security.md +++ b/system-design/specification/margo-management-interface/api-requirements-and-security.md @@ -34,14 +34,14 @@ Every Management Interface endpoint is scoped to the authenticated caller. A WFM A `deviceId` is not a global name: it identifies a device only within the scope of one WFM Client. The binding between a `deviceId` and the caller's identity is established by the client itself, when it first reports capabilities for that device (see [Device Capabilities](../margo-management-interface/device-capabilities.md)), and every later reference to that `deviceId` is resolved within the reporting client's scope. Because the scope is derived from the authenticated SPIFFE ID, a client cannot register, read, or mutate a device in another client's scope: two clients reporting the same `deviceId` string address two unrelated device records. A WFM MAY additionally constrain, by local policy, which `deviceId`s a given client is allowed to report; such policy is deployment-specific and out of scope for this specification. -The WFM authorizes each request using local policy keyed on the authenticated WFM Client identity, and MAY deny a request from a still-valid credential, per [Authorization](../identity/wfm-identity-profile.md#authorization). When a WFM denies a request by local policy (for example, a retired client relationship), it SHOULD respond `403 Forbidden` with an [RFC 9457](https://datatracker.ietf.org/doc/html/rfc9457) Problem Details body (`Content-Type: application/problem+json`) using the `wfm-client-relationship-retired` type: +The WFM authorizes each request using local policy keyed on the authenticated WFM Client identity, and MAY deny a request from a still-valid credential, per [Authorization](../identity/wfm-identity-profile.md#authorization). When a WFM denies a request by local policy (for example, a retired client relationship), it SHOULD respond `403 Forbidden` with an [RFC 9457](https://datatracker.ietf.org/doc/html/rfc9457) Problem Details body (`Content-Type: application/problem+json`). The body uses the [`not-authorized`](../problem-types.md#not-authorized) problem type: ```json { "type": "https://docs.margo.org/specification/problem-types#not-authorized", - "title": "Client Relationship Retired", + "title": "Not Authorized", "status": 403, - "detail": "The WFM Client relationship has been retired by local policy." + "detail": "The WFM Client relationship has been retired by local policy." } ``` @@ -49,8 +49,6 @@ Both parties represent themselves with an X.509-SVID, an X.509 certificate carry The Management Interface follows the MIAF [traffic-inspecting proxies](../identity/tls-requirements.md#traffic-inspecting-proxies) rules: a TLS-offloading proxy that forwards the validated client identity to the backend is supported, and an operator MUST exempt Margo mTLS endpoints from inline traffic inspection. - - ## Error Responses All API error responses conform to [RFC 9457 Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc9457). Error responses are returned with `Content-Type: application/problem+json` and include a stable `type` URI that clients MUST use for programmatic error handling. diff --git a/system-design/specification/margo-management-interface/device-capabilities.md b/system-design/specification/margo-management-interface/device-capabilities.md index 132e39cf..30df547e 100644 --- a/system-design/specification/margo-management-interface/device-capabilities.md +++ b/system-design/specification/margo-management-interface/device-capabilities.md @@ -28,7 +28,7 @@ DELETE /api/v1/capabilities/{deviceId} | 204 No Content | The device capabilities document was deleted successfully. | | 400 Bad Request | PUT: Malformed request body. | | 403 Forbidden | The request is not authorized by the WFM's local policy (for example, the client relationship has been retired; see [Authorization](../identity/wfm-identity-profile.md#authorization)). | -| 404 Not Found | PUT: No gateway was found for the given child-device `deviceId` (see [Gateways considerations](#gateways-considerations) for more details).
DELETE: No device with the given `deviceId` was found for the client. | +| 404 Not Found | PUT: No gateway was found for the client for the given child-device `deviceId` (see [Gateways considerations](#gateways-considerations) for more details).
DELETE: No device with the given `deviceId` was found for the client. | | 422 Unprocessable Content | Request body includes a semantic error. | ## Request Body Attributes diff --git a/system-design/specification/problem-types.md b/system-design/specification/problem-types.md index 1353eec6..53a28a83 100644 --- a/system-design/specification/problem-types.md +++ b/system-design/specification/problem-types.md @@ -13,7 +13,7 @@ These values are used in the `type` field of RFC 9457 `application/problem+json` | [#invalid-request](#invalid-request) | 400 | Malformed request body. | | [#semantic-error](#semantic-error) | 422 | Request body includes a semantic error. | | [#not-authorized](#not-authorized) | 403 | The request is not authorized by the WFM's local policy (for example, the client relationship has been retired). | -| [#gateway-not-found](#gateway-not-found) | 404 | No gateway was found for the given child-device deviceId. | +| [#gateway-not-found](#gateway-not-found) | 404 | No gateway was found for the client for the given child-device deviceId. | | [#device-not-found](#device-not-found) | 404 | No device with the given deviceId was found for the client. | | [#invalid-bundle](#invalid-bundle) | 404 | Bundle not found for the given digest. | | [#deployment-not-found](#deployment-not-found) | 404 | Deployment not found for the given digest. | @@ -149,7 +149,7 @@ This problem type identifies requests that are denied by the WFM's local authori - **Type URI:** `https://docs.margo.org/specification/problem-types#gateway-not-found` - **HTTP status:** 404 Not Found -- **Summary:** No gateway was found for the given child-device deviceId. +- **Summary:** No gateway was found for the client for the given child-device deviceId. This problem type indicates that the server cannot find a gateway for the child-device identified by the `deviceId` path parameter. This applies when a child `deviceId` is used and no parent gateway is registered for it. @@ -158,7 +158,7 @@ This problem type indicates that the server cannot find a gateway for the child- "type": "https://docs.margo.org/specification/problem-types#gateway-not-found", "title": "Gateway not found", "status": 404, - "detail": "No gateway was found for the given child-device deviceId.", + "detail": "No gateway was found for the client for the given child-device deviceId.", "instance": "/api/v1/capabilities/gateway-1/child-device-2" } ``` @@ -271,7 +271,7 @@ This problem type is returned when the Trust Domain discovery document is not av - **HTTP status:** 404 Not Found - **Summary:** SPIFFE bundle unavailable. -This problem type is returned when the SPIFFE Trust Bundle cannot be retrieved from the Margo Identity Service (MIS). Clients MUST NOT validate SVIDs when the Trust Bundle is unavailable and SHOULD retry using the backoff strategy indicated in the response. +This problem type is returned when the SPIFFE Trust Bundle cannot be retrieved from the Margo Identity Service (MIS). A client keeps validating SVIDs against its cached bundle, and retries per the [refresh rules](./identity/trust-bundle-and-discovery.md#selecting-and-refreshing-the-bundle). A client that holds no bundle cannot validate SVIDs. ```json {