feat(proxyfault): pass an interception CA so HTTP faults reach HTTPS dependencies - #502
Conversation
…deps An HTTPStatus fault could only be applied to cleartext HTTP, because the proxy never decrypted TLS. transparent-proxy can now terminate a matched HTTPS connection when given a CA, so thread that through: Opts.TLSInterceptCA emits --tls-ca-cert/--tls-ca-key on start. The paths are read inside the proxy's sidecar, whose rootfs is an overlay of the extension's own filesystem, so they refer to files in the extension (a mounted Secret) and the key is never written into the target. The flags are start-only: --revert reconstructs interception rules, which do not depend on the CA. Nil keeps the previous behaviour of never decrypting. Snapshot also carries tls_intercept_rejected, the count of connections whose client refused the minted certificate — the signal that the CA is missing from the target's truststore, and deliberately not counted as faulted.
|
Claude finished @achoimet's task in 2m 20s —— View job Claude's Review
Note: I wasn't able to run SummaryThis is a well-reasoned, carefully-scoped change. The stdin-handover design and the rationale in the PR description (overlay rootfs not carrying submounts) both check out against the code. I traced the pipe lifecycle in
No bugs, race conditions, or security issues found in the reviewed diff. One minor, non-blocking observation: the CA's raw key bytes ( The |
The previous commit passed --tls-ca-cert/--tls-ca-key, on the assumption that the sidecar could read a path in the extension's filesystem. That is wrong for the runc backend: the bundle rootfs is an overlay of the extension's "/", and an overlay does not carry the extension's submounts, so a CA mounted from a Kubernetes Secret is not visible by path inside the sidecar. Verified against the real mount options — the mount point appears as an empty directory. TLSInterceptCA now carries the PEM itself and it is written to the proxy's stdin, with only --tls-ca-stdin on the command line. That works identically for both backends, keeps the key out of argv, and never writes it to a disk the target could reach. startAndMonitor takes the stdin payload so both backends share one path; it is written and the pipe closed in the background, since the proxy reads stdin to EOF at startup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wB5XrsrNJjU9MH6yegmTA
Three review findings. The CA key was written from a deferred goroutine, which also ran on the StdoutPipe error path where cmd.Start had never been called. os/exec does not close the child end of the pipe on that path, so a failed start leaked a descriptor and left a copy of the private key resident for a process that never existed. The write now happens only after Start succeeds. --tls-ca-stdin was gated on the CA pointer being non-nil while the stdin write was gated on the payload being non-empty, so a half-populated CA told the proxy to read a stream that was never written — it would have hung reading an empty stdin and failed in a way that reads like a certificate problem rather than a configuration one. Both now share interceptCAPayload, which treats a missing half as no CA at all. Finally, an errant `git add` had committed go/.DS_Store; removed, and .DS_Store is now ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wB5XrsrNJjU9MH6yegmTA
Flagged by the PR review as unreachable in practice — StdoutPipe only fails when cmd.Stdout is already set, and cmd is always freshly constructed — but returning there after StdinPipe succeeded would strand its write end. Cheap to close, and the ordering is easy to change later without noticing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wB5XrsrNJjU9MH6yegmTA
TLSInterceptCA.LeafValidity emits --tls-leaf-validity, so an extension can expose how long minted per-SNI certificates live. Zero omits the flag and keeps the proxy's own default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wB5XrsrNJjU9MH6yegmTA
|



Bridge half of HTTPS response injection. Depends on steadybit/transparent-proxy#4.
What
Opts.TLSInterceptCA{CertPEM, KeyPEM}hands the proxy a certificate authority so anHTTPStatusfault can apply to an HTTPS dependency instead of only a cleartext one. Nil keeps today's behaviour: TLS is never decrypted and HTTPS is spliced through untouched.Snapshotalso gainstls_intercept_rejected— connections whose client refused the minted certificate. That is the canonical "the CA is not in the target's truststore (or the client pins certificates)" signal, deliberately not counted as faulted, so a failed interception is diagnosable rather than looking like a silent no-op.Why stdin, not a file path
I first passed
--tls-ca-cert/--tls-ca-key, assuming the sidecar could read a path in the extension's filesystem. That is wrong for the runc backend, and I verified it rather than shipping it:The bundle rootfs is created as an overlay with
lowerdir=/— the extension's root. An overlay does not carry the lower filesystem's submounts. A Kubernetes Secret is a separate mount, so inside the sidecar it appears as an empty directory. Reproduced with the exact mount options the real bundle uses:/transparent-proxyresolves)/etc/steadybit/tls-ca/Left as-is this would have failed only at runtime, and failed misleadingly: every handshake aborting, reported as "the client rejected our certificate", sending the operator after their truststore instead of a proxy that never had a usable CA. It would also have worked for extension-host (
ip netns execshares the extension's mount namespace) and silently failed for extension-container — the worst kind of asymmetry.So the PEM now goes over the process's stdin, with only
--tls-ca-stdinon the command line. That works identically for both backends, keeps the key out ofargv(visible in/proc), and never writes it to a disk the target could reach.Notes
startAndMonitortakes the stdin payload so both backends share one path; it is written and the pipe closed in the background, as the proxy reads stdin to EOF at startup.--revertreconstructs interception rules, which do not depend on the CA. Asserted against.Tests
12 pass under
-race. Covers: flags absent when unset,--tls-ca-stdinpresent and the PEM absent from argv when set, both halves present in the stdin payload, absent from revert args, the rejected counter surviving the stdout round-trip without inflatingfaulted, andstartAndMonitoractually writing stdin and closing it.Verified end to end under real iptables in minikube: CA loaded from stdin, trusted client gets the forged 503 over HTTP/2, untrusted client counted as rejected (not faulted), untargeted traffic passes through, and teardown leaves no rules.
🤖 Generated with Claude Code
https://claude.ai/code/session_019wB5XrsrNJjU9MH6yegmTA