cloudflare / cloudflare/cloudflared

💡 Blind TLS pass-through for HTTP origins over Tunnel (browser-direct)

Open
#1,654 0 comments 0 reactions 0 assignees View on GitHub
Priority: Normal Type: Feature Request
Dominant language
Go
Stars
15.6k
Forks
1.4k
PR merge metrics
No merged PRs in 30d

Description

**Describe the feature you'd like**

***Topology / motivation***
We run a *single* `cloudflared` connector on a shared edge/bastion host, deliberately **not** on every origin:

- no direct inbound path to the origins (they're only reachable from the connector over the internal network)
- we don't want to deploy/maintain the `cloudflared` binary on every service host
- a single connector is a **centralized chokepoint for traffic monitoring and egress control** -- connection/flow logs, destinations, traffic volumes, and tunnel/connector health observed in one place, instead of scattered across every origin host

The connector reaches internal services over HTTPS; those services carry their own self-signed / internally-issued certificates.

***Problem***
For a public hostname with `service: https://internal-host`, `cloudflared` acts as an **L7 reverse proxy**: it terminates the request, sees the full plaintext, then re-encrypts to the origin. So the connector host holds cleartext for all proxied traffic. If that shared host is compromised, passive capture and active MITM are trivial -- which is exactly the blast radius we were trying to avoid by *not* co-locating the connector with each origin.

We are fine with Cloudflare's edge terminating the public TLS (we *want* edge WAF/Access/cache). What we want is to **stop extending that plaintext trust to the connector VM we operate**.

`service: tcp://` makes the connector blind, but the hostname is then no longer reachable directly from a browser (it needs `cloudflared access`/WARP on the client side), which breaks the "just open the URL" UX for a public hostname.

***Desired outcome***
A mode where:

1. the public HTTPS hostname stays **browser-accessible** (TLS terminated at the CF edge, as today -- edge L7 features still apply)
2. the **edge->origin** leg is end-to-end TLS that transits the connector as **opaque ciphertext** -- `cloudflared` forwards bytes to the configured internal destination *without decrypting*
3. the origin presents a **self-signed or Cloudflare-issued cert** (a la Cloudflare Origin CA), and certificate validation for that leg happens **at the CF edge** (Full / Full-strict semantics), not at the connector

Net effect: a compromised connector host can no longer read or MITM application payloads (edge-side cert validation also prevents redirect-MITM), while origins stay non-internet-exposed **and** browser-direct access is preserved.

This stays compatible with the centralized-monitoring goal above: as a blind relay the connector still sees connection-level metadata (source flow, destination `host:port`, byte counts, timing) for flow logging and egress policy -- only the payload bytes become opaque, not the visibility into *which* flows go *where*.

***One possible mechanism*** (just one option -- the right design is Cloudflare's to make):

- a per-rule `originRequest` mode, e.g. `tlsPassthrough: true`, or a `service: https+passthrough://internal-host:443` scheme -- the hostname->destination mapping from ingress is preserved
- data plane: instead of the cloudflared-facing edge sending a decoded HTTP request over the tunnel, it **originates the origin-side TLS itself** (validating the origin cert via an uploaded CA / CF Origin CA) and relays that TLS stream over the tunnel as an opaque stream (reusing the existing TCP/stream path). `cloudflared` dials the rule's destination and pipes bytes -- no `RoundTrip`, no TLS termination at the connector
- conceptually: **"Full (Strict) SSL for Tunnel origins, with the connector as a transparent relay"** -- bring the existing Origin CA / Full-strict trust model to a *tunneled* origin while taking the connector out of the payload trust boundary

***Honest trade-offs / open questions:***

- **This is not a connector-only change -- it needs Cloudflare Edge support.** Today the connector receives structured HTTP over the tunnel; making it blind requires the edge to originate origin-side TLS and relay opaque bytes. A change in `cloudflared` alone is insufficient.
- **Connector-side L7 features are lost for such hostnames:** per-path ingress routing within a hostname, HTTP-level Access middleware applied at the connector, header injection, connector request logs. Edge-side WAF/cache/Access/edge-logs are unaffected (the edge still terminates the public TLS). Per-*hostname* routing is fine (known at registration); per-*path* is not.
- **Trust boundary:** origin-cert validation must happen at the edge for the guarantee to hold (the connector is blind and can't validate) -- operators configure the origin CA exactly as with classic Origin CA / Full-strict today.
- **Residual risk:** this protects payload confidentiality/integrity against a compromised connector host and blocks redirect-MITM, but a compromised connector can still observe metadata (timing, byte counts, destination/SNI), cause DoS, or attempt credential/registration abuse. It reduces blast radius; it is not full isolation.

**Describe alternatives you've considered**
- **`service: tcp://` + `cloudflared access` / WARP-to-Tunnel (private network routing):** blind, end-to-end encrypted, supports self-signed origins -- but requires a client (WARP or `cloudflared access`) on the consumer side. Not browser-direct for a public hostname. Fails the "just open the URL, no client" requirement
- **Classic Full (Strict) + Cloudflare Origin CA:** gives exactly the desired edge<->origin trust model, but requires the origin to be **directly reachable** from Cloudflare's edge -- which defeats the "no direct inbound to origin, connector-only ingress" requirement
- **Run `cloudflared` on each origin host:** keeps plaintext local to the origin, but defeats the "one connector, don't deploy the binary everywhere" operational goal and enlarges the maintenance/attack surface
- **`--no-tls-verify` / `caPool` / `originServerName`:** only affect the connector->origin leg; they don't change the fact that the connector still sees plaintext

**Additional context**
Related / adjacent issues:

- #673 *Honour SSL/TLS encryption mode setting* -- closest neighbor; today satisfied only up to "connector re-encrypts", which still **terminates** at the connector
- #421 *Use Origin Server Certificate Instead Of Cloudflare!* -- the origin-cert / "CF is a MITM" concern (about the public-facing cert)
- #1649 *--no-tls-verify Allows MITM* -- same threat model, connector->origin leg
- #1257 *Is an arbitrary TCP tunnel encrypted end to end?* -- community asking where encryption happens
- #407 *Authenticated origin pull* -- Full-strict / authenticated origin pull don't apply to tunneled origins today

Prior art inside Cloudflare: **Spectrum** already relays opaque TLS for L4 apps; this asks for an analogous *edge-validated, connector-blind* relay for **HTTP** apps served via Tunnel, while keeping edge-side L7 intact.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.