feat: HTTPProvider with AutoTLS - #11333
Draft
lidel wants to merge 41 commits into
Draft
Conversation
Picks up websocket.WithFallbackHTTPHandler so a /ws or /tls/ws listener can serve a caller-supplied http.Handler for non-upgrade requests. Required by the HTTPProvider feature in subsequent commits.
… flag
Renames the lazy fallback handler from WSSFallback to HTTPProvider across
the kubo tree. The new name reflects the role this peer plays on the
network: an HTTP-native source for trustless-gateway block retrieval,
discoverable through identify/DHT alongside libp2p transports.
- new HTTPProvider config struct with Enabled/Libp2p/Cleartext/
AnnounceMultiaddrs flags and matching Default* constants
- IpfsNode.WSSFallback field, FX param, file/struct/test renames; the
libp2p-stream gateway function becomes serveHTTPProviderOverLibp2p
- Experimental.GatewayOverLibp2p marked deprecated; daemon refuses to
start when it is set, with a one-line migration error pointing at
HTTPProvider.Enabled and HTTPProvider.Libp2p
- libp2p-stream gateway gating switches from the deprecated flag to
HTTPProvider.{Enabled,Libp2p}
- test/cli/http_gateway_over_libp2p_test.go renamed to
http_provider_over_libp2p_test.go, subtests + config keys updated to
match the new naming
Mounts the same trustless-gateway handler that already serves the libp2p-stream transport on the AutoTLS WebSocket port via the new go-libp2p WithFallbackHTTPHandler hook, so any HTTP/2 client can fetch blocks directly with the AutoTLS-issued cert and no libp2p stack. Two pieces: - RequireHTTP2OverTLS wraps the handler so HTTPS requests must speak HTTP/2; HTTP/1.1 over TLS gets 426 Upgrade Required, with an Upgrade: h2,websocket hint. HTTP/1.1 stays reserved for the WebSocket upgrade dance, and bitswap-httpnet gets the multiplexing it needs. Cleartext requests pass through regardless of HTTP version, so reverse-proxy deployments are unaffected. - The HTTPS mux also serves /.well-known/libp2p/protocols (libp2p Gateway spec) via a fresh WellKnownHandler, mirroring what p2phttp.Host already auto-mounts on the libp2p-stream path. A bitswap-httpnet client hitting the AutoTLS hostname can discover the /ipfs/gateway protocol with one HTTPS GET, no libp2p stream needed. The serveHTTPProviderOverLibp2p doc comment now points at https://specs.ipfs.tech/http-gateways/libp2p-gateway/ and replaces the stale FIXME on gatewayProtocolID.
…xt /ws Two changes that turn this peer into a discoverable HTTP source for any client following the libp2p+HTTP discovery model. Announcement: HTTPProvider.AnnounceMultiaddrs (default true) derives an HTTP-flavored sibling for every announced WebSocket multiaddr: /ws becomes /http, /tls/ws becomes /tls/http, /tls/sni/<host>/ws becomes /tls/sni/<host>/http. The /http entries share the same TCP port and TLS cert as their /ws siblings, so this is purely an announcement; no extra socket is opened. The derivation runs after p2p-forge resolves the wildcard SNI and before the Addresses.NoAnnounce filter, so derived addresses inherit the same filtering as everything else. boxo/bitswap/network/httpnet picks them up through identify, the DHT, and IPNI. Cleartext: HTTPProvider.Cleartext (default false) auto-appends a plaintext /ws listener to each /tcp/N already in Addresses.Swarm, unless one is configured. Intended for reverse-proxy deployments where TLS is terminated upstream and the proxy forwards either HTTP/1.1 or h2c to kubo. Off by default because a node running AutoTLS does not need a public cleartext path. The check excludes /wss and /tls/ws (both TLS forms), so flipping it on alongside AutoTLS still adds cleartext where the operator wants it. The FX gate for the HTTPProviderHandler relaxes from "AutoTLS && HTTPProvider" to just "HTTPProvider", because cleartext /ws (manual or Cleartext-derived) needs the handler too.
This was referenced May 16, 2026
Picks up client.WithHTTPClient and client.WithResolver, used by the new ?dial=/?dns= AutoTLS.RegistrationEndpoint overrides in a follow-up commit.
Three knobs that make end-to-end tests against a private ACME/forge stack practical without polluting production paths: - AutoTLS.TrustedCARootsPEM: trust a private CA bundle for the ACME directory HTTPS call. Already documented as accepting a Pebble or self-hosted root. - AutoTLS.AllowPrivateForgeAddrs: lift the public-IP gate on cert requests so a loopback-only test node can complete a forge handshake. - AutoTLS.SelfSignedForTests: skip the forge handshake entirely and hand the WebSocket transport an in-memory self-signed cert. Pure test escape hatch; off by default. The SelfSignedForTests path lives in core/node/libp2p/autotls_selfsigned.go behind a constructor wired into the libp2p host setup only when the flag is on. Production builds with the flag off are unaffected.
AutoTLS.RegistrationEndpoint now accepts two optional query parameters: - ?dial=host:port reroutes the TCP dial of the registration POST while leaving the URL Kubo advertises (and signs over for PeerID-auth) unchanged. Lets a local p2p-forge instance on a non-privileged loopback port answer requests whose Host header must still match the production registration hostname. - ?dns=host:port points the DNS-01 propagation pre-flight check at this DNS server instead of the system resolver. Needed when the forge's domain suffix is not in public DNS, e.g. a private .test suffix used in end-to-end tests. The daemon strips both parameters before handing the URL to p2p-forge; unrelated query parameters are preserved. Parsing and dispatch live in parseForgeOverrides + forgeDialOverrideClient + forgeDNSOverrideResolver. Covered by TestParseForgeOverrides; documented under AutoTLS.RegistrationEndpoint.
Adds test/autotls/ as a dedicated Go sub-module with its own go.mod, so the heavy test dependencies (Pebble, CoreDNS, p2p-forge) do not leak into kubo's main module or vulnerability-scanning surface. The canary brings up an in-process Pebble (ACME test server) and the full p2p-forge service (CoreDNS-based DNS + HTTP registration endpoint), points a real kubo daemon at them via the new AutoTLS knobs and the ?dial=/?dns= overrides, and walks the chain end-to-end: registration POST, DNS-01 validation, cert install, /tls/http announcement, HTTP/2 fetch of a real block. One canary covers every link. Run with: make test_autotls (or `cd test/autotls && go test ./...`). Wired into CI as the autotls-tests job in gotest.yml.
Adds two CLI test files exercising the HTTPProvider transports: - TestHTTPProvider covers the plain-HTTP and h2c paths on the swarm port (HTTP/1.1, h2c, WSS upgrade still works, well-known/libp2p/protocols, refusal to serve deserialized responses). - TestHTTPProviderAutoTLS covers the AutoTLS HTTPS path: h2 over TLS, rejection of h1 over TLS, well-known served, /tls/http multiaddr announced. Also migrates the two pre-existing tests that still set the removed Experimental.GatewayOverLibp2p flag (which the daemon now refuses to start with) over to HTTPProvider.Enabled + HTTPProvider.Libp2p, and renames the GatewayOverLibp2p subtest in content_blocking_test.go to HTTPProviderLibp2p.
serveHTTPProviderOverLibp2p early-returned when HTTPProvider.Libp2p was off and so skipped installing the trustless gateway handler into the AutoWSS-port placeholder. Any node with HTTPProvider.Enabled=true and Libp2p=false (typical for HTTPProvider.Cleartext-only setups) ended up serving permanent 503 Service Unavailable on /ws and /tls/ws even though the listener was up and the placeholder was wired. Restructure so the function does two independent jobs: 1. Always install the handler on the AutoWSS-port placeholder when HTTPProvider.Enabled=true. Covers AutoTLS, manually configured /ws, and HTTPProvider.Cleartext-derived /ws. 2. Then optionally start the libp2p-stream transport when HTTPProvider.Libp2p=true. Also renames the local error channel from p2pGwErrc to httpProviderErrc and replaces the now-stale "add trustless gateway over libp2p" comment.
Documents HTTPProvider in docs/config.md (it had no section before) and adds a v0.43 changelog highlight. Two related cleanups: - Spell out the HTTPProvider vs Gateway mental model: server side (raw blocks via ?format=raw, NoFetch, swarm port, no recursion) versus the recursive loopback Gateway on 127.0.0.1:8080. Mirror "client side" framing onto HTTPRetrieval. - Replace stale "raw blocks + CARs only" doc strings in config/httpprovider.go, core/corehttp/gateway.go, and core/node/libp2p/httpprovider.go with "raw blocks via ?format=raw only". HTTPProvider does not serve CARs. Adds the gateway-conformance-http-provider-cleartext CI job and migrates the existing gateway-conformance-libp2p-experiment job off the removed Experimental.GatewayOverLibp2p flag. Updates docs/experimental-features.md to point at HTTPProvider.Libp2p (the GraphSync section's cross-reference too).
lidel
force-pushed
the
poc/wss-fallback-trustless-gateway
branch
from
May 16, 2026 20:02
f7864a1 to
04b7c9f
Compare
Three small CI cleanups surfaced by the first PR run: - mk/golang.mk: collapse the test_autotls recipe to a single line. The backslash line continuations worked locally with bash-as-sh but failed under dash on the GHA runner, which executed each line separately and tried to run the trailing backslashes as commands. Matches the single-line style already used by test_examples. - .github/workflows/gateway-conformance.yml: drop Gateway.PublicGateways from the libp2p-experiment and cleartext jobs. Both run only the trustless-gateway conformance subset, which does not exercise subdomain routing, so the example.com / localhost public gateway config was unused. - .gitignore: ignore test/autotls/autotls-tests.json (gotestsum jsonfile output), matching the existing rules for the cli and fuse test artifacts.
Each of the three conformance jobs now sets Addresses.Swarm, Gateway, and API explicitly to ports unique across the matrix: - full gateway suite: 14001 / 18080 / 15001 - libp2p (gateway node): 24001 / 28080 / 25001 - libp2p (proxy node): 24002 / 28081 / 25002, http-forward 28092 - cleartext HTTPProvider: 34001 / 38080 / 35001 Leading 1/2/3 identifies the job at a glance. No job inherits the kubo defaults, so every port in a CI log unambiguously names the job it belongs to and the jobs can safely share a runner if matrix scheduling ever puts them there.
Three follow-ups to the first PR run: - test/autotls/zones/libp2p.test was used by the canary locally but never committed; the p2p-forge ipparser plugin reads it at CoreDNS startup. Without the file CI's canary failed before any test ran. The root .gitignore's *.test rule (intended for Go test binaries) matched the zone file, hence the per-directory .gitignore exception. - gofmt: config/autotls.go const block alignment, trailing blank line at the end of test/cli/http_provider_test.go, harness.go const block, canary_test.go trailing blank line. - stylecheck ST1005: the GatewayOverLibp2p migration error string ended with a period; reworded so it no longer does.
Pulls in pebble v2.10 API changes. The autotls harness now mirrors what p2p-forge's own e2e suite did for v0.9.0: pebble VA dials CoreDNS's TCP listener (pebble v2.10 forces TCP for ACME DNS lookups), and pebbleCA.New uses keyAlg="rsa" since pebble's GetRootKey only handles RSA.
HTTPProvider.Enabled stays off by default; once enabled, the libp2p-stream transport comes up automatically (it is the in-band path .well-known already advertises) while Cleartext and the /http multiaddr announcement stay off until the operator opts in explicitly. The const block carries the default rationale; field godocs no longer restate default values.
Pulls in Boxo v0.40.0, go-libp2p-kad-dht v0.40.0, cheggaaa/pb v3, and the dag --local-only / migration fetcher work from master. Dep conflicts resolved by keeping our HTTPProvider-required pins on top of master's baseline: - certmagic v0.25.3 (pulled in by p2p-forge v0.9.0) - p2p-forge v0.9.0 - go-libp2p v0.48.1-0.20260515215300-a72c0588b088 (pin for libp2p/go-libp2p#3509, still open; needed for websocket.WithFallbackHTTPHandler) Other transitive bumps follow from `make mod_tidy`. AutoTLS canary + HTTPProvider CLI tests pass on the merged tree.
Bump go-libp2p to the build that adds websocket.WithHTTPServerConfig, and configure the http.Server that serves the trustless gateway on the shared /ws and /tls/ws port. - go.mod, go.sum (all modules): bump go-libp2p to bb289fcf25f2, which renames the fallback option to WithHTTPHandler and adds WithHTTPServerConfig - core/node/libp2p, core/node: set ReadHeaderTimeout, IdleTimeout, and HTTP/2 stream limits on the fallback server; derive WriteByteTimeout and SendPingTimeout from Gateway.RetrievalTimeout so the gateway's 504 fires before the connection-level guard. WriteTimeout and ReadTimeout stay unset to avoid truncating large CAR streams - docs/config.md: note Gateway.RetrievalTimeout also covers slow reads from a non-local datastore such as NFS or S3
…ustless-gateway # Conflicts: # docs/changelogs/v0.43.md # docs/examples/kubo-as-a-library/go.mod # docs/examples/kubo-as-a-library/go.sum # go.mod # go.sum
lidel
force-pushed
the
poc/wss-fallback-trustless-gateway
branch
from
June 2, 2026 18:38
01cfc07 to
c7175e1
Compare
Track the released go-libp2p instead of the pre-merge PR commit now that libp2p/go-libp2p#3509 has landed on master. The HTTPProvider fallback handler comment also drops the old WithFallbackHTTPHandler name in favor of the merged WithHTTPHandler API.
Pull the HTTPProvider HTTP/2 stream cap and the three streaming timeouts out of the inline server config into a documented const block, so each value carries its rationale. The stream-cap godoc cross-references Gateway.MaxConcurrentRequests to keep the per-connection transport limit from being confused with the global application-level cap.
…ustless-gateway # Conflicts: # core/node/groups.go
Belt-and-suspenders coverage that Kubo verifies blocks fetched over HTTP from /routing/v1 providers against the requested CID, as the HTTPProvider feature expands the set of HTTP providers nodes fetch from. - reject: when the only provider serves bytes that do not hash to the CID, ipfs cat fails (bounded by --timeout) and never emits them - failover: a broken provider alongside a valid one still yields the correct bytes
Conflicts resolved: - core/node/groups.go: take master's toggleable MonitorDeadListeners and its new MonitorCGNAT invoke; keep this branch's 4-arg AddrsFactory that passes the HTTPProvider announce flag. - go.mod: keep the go-libp2p pseudo-version this branch pins for the websocket fallback handler (newer than master's v0.48.0); take master's other dependency updates. go mod tidy promoted gorilla/websocket and golang.org/x/net to direct. - docs/changelogs/v0.43.md: keep both highlights, HTTPProvider and master's CGNAT and IPNS notes.
HTTPProvider now targets v0.44, so its highlight moves from v0.43 to a new v0.44.md, with v0.44 added to the changelog index.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
7 tasks
…ustless-gateway # Conflicts: # docs/changelogs/v0.44.md # docs/examples/kubo-as-a-library/go.mod # docs/examples/kubo-as-a-library/go.sum # go.mod # go.sum # test/dependencies/go.mod # test/dependencies/go.sum
Master's v0.44.md is the fresh dev-cycle template. Highlights on this branch have no version assigned yet, so they live in vTBD.md until one is.
A node with a publicly dialable /tcp/443 listener now gets its TLS certificate for its own IP address, straight from Let's Encrypt, instead of registering a name with the libp2p.direct broker. Browsers reach it at /ip4/<ip>/tcp/443/tls/ws with no DNS lookup and nobody else in the path, and HTTPProvider serves the trustless gateway behind the same certificate on the same port. The only thing to configure is the listener. The ACME challenge that proves an address is defined on port 443 and nowhere else, so a node on any other port, or one the authority cannot reach, keeps using the broker exactly as before. - AutoTLS.IPCerts, on by default, opts back out to the broker path - AutoTLS.IPCertsPort is an advanced knob for a local ACME server; a public authority connects to 443 and ignores anything else p2p-forge and boxo are pinned to unreleased commits from ipshipyard/p2p-forge#94 and ipfs/boxo#1196 until those land.
The counterpart of the AutoTLS canary for the broker-free path: a daemon listening on the port the authority validates on certifies its own address against an in-process Pebble, announces it, serves it on a handshake carrying no SNI, and answers a block fetch over HTTP/2 on the same certificate. It also asserts that nothing under the broker's domain is announced, so "no broker" is checked rather than assumed. The CLI test asserts the daemon reaches an authority for its own address, not merely that it stays away from the broker: with the registration delay in play, staying away is true for an hour whether or not any of this works. Pebble's validation authority is pointed at the port the node did get, since binding 443 needs privileges no test has. The CoreDNS directive list is now assigned once, so the two parallel end-to-end tests stop racing on it.
…ustless-gateway # Conflicts: # docs/examples/kubo-as-a-library/go.mod # docs/examples/kubo-as-a-library/go.sum # go.mod # go.sum # test/dependencies/go.mod # test/dependencies/go.sum
A node listening on port 443 certifies its own address and never registers a name with the broker, whether or not that works out. A node without such a listener uses the broker, as before. Failure on the direct path is an error in the log and a retry, not a quiet switch to a name nobody asked for, so what a node is doing stays something an operator can read off its config. - AutoTLS.IPCerts documents the rule, the upgrade behaviour for a node that already holds a libp2p.direct certificate, and that a private authority set through AutoTLS.CAEndpoint has to offer the short-lived profile or issue nothing - test/cli covers both directions: a node on the port goes to the authority for its own address and leaves the broker alone even when issuance fails, and one with IPCerts off registers a name instead
Picks up the review round on ipshipyard/p2p-forge#94: an address the address factory holds back is still offered for certification, the error for a node that cannot be validated says whether the port is unreachable or merely the wrong one, and a named TLS handshake is left to certmagic as it was before this feature existed. Also brings ipshipyard/p2p-forge#95 through that branch, so an expired certificate is discarded rather than renewed, on the address path as well as the brokered one.
lidel
force-pushed
the
poc/wss-fallback-trustless-gateway
branch
from
July 28, 2026 23:44
fe22339 to
fed8d39
Compare
Gateway.RetrievalTimeout=0 documents the gateway timeout as disabled, but the HTTPProvider transport derived a 30s WriteByteTimeout and SendPingTimeout from it, turning the guard into a stricter limit than the one the operator turned off. Leave both unset when the timeout is 0.
HTTPProvider.Cleartext and AnnounceMultiaddrs ride on the WebSocket fallback handler, so with Swarm.Transports.Network.Websocket disabled they can never exist; the daemon still started and logged that the gateway was exposed on /ws and /tls/ws. Refuse startup when a surface that needs the transport is requested (or none remains), keep the libp2p-stream-only combination working, and log the reduced surface instead of the false exposure line. CLI test covers all four combinations.
Extract the /ws auto-append logic into cleartextWSListeners so the suppression and matching rules can be table-tested.
Log an error-level warning at construction so a production config with the test escape hatch enabled is impossible to miss. Correct the godoc and comments: the flag only swaps the WebSocket transport's cert, it does not stop the AutoTLS/ACME pipeline; tests pair it with AutoTLS.Enabled=false.
Document TrustedCARootsPEM and AllowPrivateForgeAddrs in docs/config.md and add them to the TOC (SkipDNSLookup was missing there too). The TrustedCARootsPEM godoc now says the bundle becomes the only trust anchor for ACME connections; certmagic does not fall back to the system store. Add DefaultAutoTLSAllowPrivateForgeAddrs and use it instead of a literal false.
Enabling HTTPProvider installs the trustless gateway behind every /ws and /tls/ws listener, so an AutoTLS node serves it over public HTTPS even while AnnounceMultiaddrs is off; the godoc and config.md said only the libp2p-stream transport was involved. Spell out the h2-only policy over TLS, tie the /http announcements to AnnounceMultiaddrs instead of claiming they are automatic, and note the WebSocket transport requirement. Also explain the swallowed NewComponent error, fix a stale test filename reference, and correct 'diallable' to 'dialable'.
The hand-rolled env save/restore leaked PEBBLE_VA_NOSLEEP and the forge token after both canaries ran (each saved the other's value as the previous one) and left a window where one test's cleanup could unset the auth token while the other's CoreDNS was starting. t.Setenv handles restore and forbids t.Parallel, so the two canaries now run serially.
…ustless-gateway # Conflicts: # docs/examples/kubo-as-a-library/go.mod # docs/examples/kubo-as-a-library/go.sum # go.mod # go.sum # test/dependencies/go.mod # test/dependencies/go.sum
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
Depends on ipshipyard/p2p-forge#94, which has to land and ship in a tagged release before this can merge. Ready for early review.
Two changes, one goal: a browser can fetch verifiable blocks straight from a Kubo node over ordinary HTTPS.
Today that path is awkward. A browser needs a name from the
libp2p.directbroker, a DNS lookup, and a WebSocket handshake. WebSocket does not speak HTTP/2, so a modern browser opens h2, gets refused, and retries as HTTP/1.1, paying for a second handshake. Every step is something that can be slow, or down.HTTPProvider: the trustless gateway over plain HTTP/2Experimental and opt-in. Turn it on alongside
AutoTLSand the node's local trustless gateway becomes reachable over plain HTTPS on the same TCP swarm port the/tls/wslibp2p listener already uses, on the certificate AutoTLS already holds. Any HTTP client works: a browser,curl, afetch()call, no libp2p stack anywhere.It is read-only and narrow on purpose. Raw blocks (
?format=raw) from the local blockstore, no network fetch for missing data. This is the server side of HTTP retrieval;HTTPRetrievalis the client side, and neither is the loopbackGatewayyou browse with.Sharing one port between libp2p, WebSocket and plain HTTPS comes from libp2p/go-libp2p#3509, shipped in go-libp2p v0.49.0. Settings and the mental model:
HTTPProvider.AutoTLS.IPCerts: a certificate for your own IP, no brokerSame port, second use. A node with a publicly dialable
/tcp/443listener gets its certificate for its own IP address, straight from Let's Encrypt1, and never registers a name.Let's Encrypt proves the address by connecting back to port 443 and starting a TLS handshake, which the
/tls/wslistener sitting there already answers. Nothing extra is exposed and no second port is opened. The node then announces/ip4/<your-ip>/tcp/443/tls/wsand the matching/tls/http, and a browser dials the address itself: no name to register, no DNS to resolve, nobody else in the path.The only thing to configure is the listener, and there is no crossing over between the two paths. A node on 443 asks only for its own address, and when that fails it says so and retries rather than quietly registering a name you did not ask for. A node on any other port keeps using the broker exactly as before. Off-switch and the port knob:
AutoTLS.IPCerts.Certificates covering an IP address last about six days, so a node has to stay online to keep renewing.
User-facing writeup for both: changelog highlights (likely to be pushed to a later release).
TODO
Experimental.GatewayOverLibp2ppaths and rename anything that still carries the old name.boxo/gatewayover the last two years; wire the ones that apply to the HTTPProvider handler (today it inherits whatever the existing trustless-gateway path uses, which may be incomplete)./tls/http), or opt-in for now.inbrowser.linkcan hit over HTTP/2 and not/tls/ws, because/wsin libp2p is janky, it does not work over http/2 and modern browser hitting it as h2 fails, and has to retry as 1.1. If the browser hit HTTP handler as h2, we would not waste time for second handshake./tls/httpendpoint in routing-system agnostic way that can be returned by/routing/v1endpointslibp2p.directname as a fallback for clients that cannot dial an address, or drop it once its own cert is in place (today it drops it).Refs
HTTPProvider.*UX (as opt-in)/tls/httpannounced on the DHT, trustless gateway behind itFootnotes
Let's Encrypt 6-day and IP address certificates, generally available since January 2026. ↩