Skip to content

Proxy builder: clientCertificate(cert, key[, passphrase]) for mTLS upstreams (rift#1609) - #353

Merged
EtaCassiopeia merged 1 commit into
masterfrom
feat/rift-342-proxy-client-certificate
Oct 28, 2026
Merged

EtaCassiopeia merged 1 commit into
masterfrom
feat/rift-342-proxy-client-certificate

Conversation

@EtaCassiopeia

Copy link
Copy Markdown
Collaborator

Closes #342

Why

From rift 0.24.0 (rift#1609), a proxy response's cert, key and passphrase are presented to an upstream that requests a client certificate, as in Mountebank. The typed builder could not set them, and raw JSON had two gaps:

  • nothing enforced the pair;
  • an older engine drops all three silently, so the mTLS handshake fails at request time with no clue why.

The design decision (typed setters, following the ImposterSpec.https(certPem, keyPem) precedent) is recorded in the triage comment on #342.

What

  • ProxySpec.clientCertificate(certPem, keyPem) and clientCertificate(certPem, keyPem, passphrase):

    • write cert, key and passphrase through ProxyResponse.extra, so there's no model or binary change, and read-back already round-trips;
    • make the pair mandatory by construction (a private record);
    • check up front for a BEGIN CERTIFICATE block and a BEGIN … PRIVATE KEY block (PKCS#8, encrypted PKCS#8, PKCS#1 or SEC1), and refuse an empty passphrase and nulls;
    • a later call replaces an earlier one.

    Key-to-cert matching and decryption stay with the engine (400).

  • Version gate: PROXY_CLIENT_CERT_SINCE = "0.24.0". create and replaceAll of a definition whose proxy carries any of the three wire keys fail with InvalidDefinition on an older engine. The gate reads the wire keys, so a hand-built proxy is covered as well as the DSL setter. This is the same mechanism and the same modes as the requireClientCertificate gate.

  • Javadoc: the engine echoes these keys in GET /imposters and --datadir (protect the admin API). Upstream trust is separate (upstreamTrust).

  • Docs: docs/design/sdk-api.md, ProxySpec block.

  • Fixtures: tls/client.pem, tls/client-key.pem and tls/client-key-encrypted.pem (passphrase changeit). These are the PEM forms of the existing throwaway client.p12; the commands are in tls/README.md.

Tests

  • ProxyClientCertificateTest (9): literal wire JSON, key order, the passphrase, replacement, other chainers keep the certificate, PEM/passphrase/null refusals.
  • ProxyClientCertificateGateTest (5): 0.23.0 refused before sending; the raw JSON path refused; replaceAll checked; 0.24.0 accepted; a proxy without a certificate never asks for the version.
  • ProxyClientCertificateIT (4, live, all lanes). The upstream is an https imposter requiring a client certificate from tls/ca.pem, and the engine trusts that CA:
    • without clientCertificate → not 200 (handshake refused);
    • cert + key → 200;
    • encrypted key + passphrase → 200;
    • the engine echoes cert/key back into the model.
  • Local runs: full verify on engine v0.24.0 passed on SPAWN and EMBEDDED.

Downstream

rift-scala's FacadeCoverage will need entries for both ProxySpec.clientCertificate arities when it bumps.

🤖 Generated with Claude Code

rift 0.24.0 (rift#1609) sends a proxy's cert, key and passphrase to an
upstream that asks for a client certificate, as Mountebank does. The
typed builder had no way to set them; raw JSON could, but nothing
enforced the pair, and an older engine drops all three silently, so the
handshake fails at request time with no clue why.

ProxySpec.clientCertificate(cert, key[, passphrase]) writes them as a
pair, checks the PEM blocks up front, and creating such an imposter on
an engine older than 0.24.0 is refused, for the raw wire keys too. The
DSL already takes private keys this way (ImposterSpec.https).

Closes #342
@EtaCassiopeia
EtaCassiopeia merged commit 0ea65be into master Oct 28, 2026
18 checks passed
@EtaCassiopeia
EtaCassiopeia deleted the feat/rift-342-proxy-client-certificate branch October 28, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proxy builder: clientCertificate(cert, key[, passphrase]) for mTLS upstreams (rift#1609)

1 participant