All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- 🛠 - BREAKING CHANGE
- ✨ - Noteworthy change
- ✨
client: a libp2p host that listens on TCP port 443 can now get its TLS certificate for its own IP address, straight from the certificate authority, instead of registering a name with a forge broker. Enable it withWithIPCerts. The authority validates the address by connecting to it and running the ACME TLS-ALPN-01 challenge, which the WebSocket TLS listener already on that port answers, so no extra port is opened and nothing else is exposed. Such a host announces/ipX/<ip>/tcp/443/tls/wsand contacts no broker at all.- The listener decides which path a host takes and there is no crossing over: with one on port 443 a host only ever asks for a certificate for its own address, and a failure is logged as an error and retried rather than turning into a brokered name nobody asked for. Without one, a host registers a name as before.
WithIPCertPortandWithIPCertProfilecover the two things a non-public authority needs: the port it validates on, and turning off the short-lived profile that Let's Encrypt requires and a private CA may not offer.- Certificates for IP addresses are short-lived, about six days, so a host has to stay online to keep renewing. Requests are paced to stay inside the authority's rate limits, and the pace is written to storage so restarts cannot spend the budget.
- ✨ A node that comes back online after its certificate expired now gets a fresh certificate instead of staying stuck without one. Renewing an expired certificate cannot succeed: the renewal order references it through the ACME ARI
replacesfield, and the CA rejects orders that reference a certificate it no longer considers current (Let's Encrypt returns HTTP 404urn:ietf:params:acme:error:malformed), which certmagic retried forever. On startup the client now discards an expired certificate found in local storage and requests a new one from scratch, without thereplacesfield.1
- ✨ First-time certificate setup now confirms the registration broker is healthy (HTTP 204 from
/v1/health) before starting ACME issuance. While the broker keeps failing the check, the client logs a single ERROR and re-checks hourly instead of running doomed ACME flows that certmagic would retry with backoff for weeks; issuance starts automatically once the broker recovers. Nodes with a certificate already in storage are unaffected. The re-check interval respects aRetry-Afterheader sent by the broker when it is longer than the hourly default, capped at 24h. The probe is exposed asclient.CheckBrokerHealthtogether with theclient.HealthCheckPathconstant; see ipfs/kubo#11397 for an example of wiring this in a downstream node. (#91)
- ✨ Fixed AutoTLS registration failing with a
401when the forge endpoint is load-balanced (such asregistration.libp2p.direct). The DNS-01 PeerID-auth handshake makes two requests, and without a session-affinity cookie the second can reach a different backend than the first, which never issued the challenge and rejects the request with an additional401.client.SendChallengenow adds a cookie jar when the supplied*http.Clientlacks one, so the affinity cookie pins both requests to the same backend. A jar you set viaWithHTTPClientorWithChallengeHTTPClientstays in place. (#90)
- Bumped direct dependencies.
certmagicv0.21.6 → v0.25.3 hardens OCSP delegated-responder validation, andcorednsv1.14.2 → v1.14.3 builds against Go 1.26.2 to sweep in stdlib CVE fixes.fsnotifyv1.9.0 → v1.10.1 fixes the inotify sibling-path watch removal that affected thedenylistplugin. Also bumpedacmez/v3v3.0.0 → v3.1.6,pebble/v2v2.7.0 → v2.10.1,bartv0.26.0 → v0.28.0,go-datastorev0.8.2 → v0.9.1,go-multiaddrv0.16.0 → v0.16.1,go-multiaddr-dnsv0.4.1 → v0.5.0,go-multibasev0.2.0 → v0.3.0, andslok/go-http-metricsv0.12.0 → v0.13.0, plus patch bumps forgo-log/v2,prometheus/client_golang, andzap. The pebble bump required passingkeyAlg="rsa"topebbleCA.New,caaIdentities=niltopebbleWFE.New, and pointing pebble VA's DNS queries at CoreDNS's TCP listener, since pebble v2.10 forces TCP for ACME DNS lookups. - 🛠 Bumped the optional DynamoDB datastore (
database-type dynamo) togo-ds-dynamodbv0.3.0. The release switches to the current AWS SDK for Go and fixes several crashes and deadlocks. Operators should confirm their AWS credentials still work;AWS_REGION,AWS_ACCESS_KEY_ID, andAWS_SECRET_ACCESS_KEYstill apply. See the go-ds-dynamodb v0.3.0 release notes for details.
denylistplugin leaked the previous instance's feed tickers and fsnotify watcher on every Caddy reload. Cleanup now runs onOnShutdowninstead ofOnFinalShutdown, so reloads release these resources.
- ✨
client.WithHTTPClient(*http.Client)option onP2PForgeCertMgrand a matchingclient.WithChallengeHTTPClient(*http.Client)option forclient.SendChallenge. Lets callers supply a custom*http.Client(with a customTransport, resolver, or root CAs) for the DNS-01 challenge POST to the forge registration endpoint. Useful for test harnesses that run an in-process forge on a loopback address while the PeerID-auth signature must stay scoped to the production registration hostname.client.SendChallengegains a trailing variadicopts ...SendChallengeOptionparameter; existing positional-only callers compile unchanged. (#87)
- Bumped
google.golang.org/grpcto v1.79.3, clearing CVE-2026-33186 from SBOM scanners (not exploitable in p2p-forge at runtime; no gRPC listener is exposed) - Bumped
go-libp2pto v0.48.0 andcorednsto v1.14.2 to match the newquic-gov0.59.0 that ships with go-libp2p - Bumped
go-ds-dynamodbto v0.2.2 andgo-logto v2.9.1 - Pinned
coredns/caddyto the tagged v1.1.4 release in place of the pre-release master snapshot inherited from coredns - Wired the go-log slog bridge in
main.go, required by go-log v2.9 and go-libp2p v0.45+, so libp2p subsystem logs flow through go-log and respond togolog.SetLogLevel
- 🛠 IP denylist plugin (
denylist) supporting local files with fsnotify auto-reload and HTTP feeds (e.g. Spamhaus DROP, URLhaus) with periodic refresh. Integrates with theipparser(DNS) andacme(HTTP) plugins: denied IPs get NODATA on DNS and HTTP 403 on ACME requests. Allowlists are checked first and bypass denylists. Prometheus metrics expose blocked request counts, list sizes, and refresh status.
- 🛠 Bumped go.mod to Go 1.24
- Hardened
ipparser: extracted IP parsing intoparseIPFromPrefix()with query-type validation, removed deadANYquery handling (now returned as HINFO via theanyplugin per RFC 8482), and isolated tests to stop flakiness
- Metrics registry race conditions
- Datastore not closed on shutdown, leaking file handles on Windows
- Fixed addrs factory skipping logic in client/acme.go
- Added test coverage for addr skipping in client/acme_test.go
- Updated go-libp2p to v0.42
- Use autonatv2 event to begin cert management
- Filter out public /p2p-circuit addrs in client
- Updated go-libp2p to v0.41.1
Footnotes
-
RFC 9773, section 5: servers SHOULD validate the certificate referenced by
replacesand SHOULD reject the newOrder request when those checks fail. ↩