diff --git a/config/autotls.go b/config/autotls.go index 67ada23abe8..638b205d840 100644 --- a/config/autotls.go +++ b/config/autotls.go @@ -6,9 +6,12 @@ import p2pforge "github.com/ipshipyard/p2p-forge/client" // for obtaining a domain and TLS certificate to improve connectivity for web // browser clients. More: https://github.com/ipshipyard/p2p-forge#readme type AutoTLS struct { - // Enables the p2p-forge feature + // Enables the p2p-forge feature and all related features. Enabled Flag `json:",omitempty"` + // Optional, controls if Kubo should add /tls/sni/.../ws listener to every /tcp port if no explicit /ws is defined in Addresses.Swarm + AutoWSS Flag `json:",omitempty"` + // Optional override of the parent domain that will be used DomainSuffix *OptionalString `json:",omitempty"` @@ -27,4 +30,5 @@ const ( DefaultDomainSuffix = p2pforge.DefaultForgeDomain DefaultRegistrationEndpoint = p2pforge.DefaultForgeEndpoint DefaultCAEndpoint = p2pforge.DefaultCAEndpoint + DefaultAutoWSS = true // requires AutoTLS.Enabled ) diff --git a/core/node/groups.go b/core/node/groups.go index 519cbb47d6e..b7c64fd3821 100644 --- a/core/node/groups.go +++ b/core/node/groups.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "regexp" "strings" "time" @@ -115,6 +116,8 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part enableRelayService := cfg.Swarm.RelayService.Enabled.WithDefault(enableRelayTransport) enableRelayClient := cfg.Swarm.RelayClient.Enabled.WithDefault(enableRelayTransport) enableAutoTLS := cfg.AutoTLS.Enabled.WithDefault(config.DefaultAutoTLSEnabled) + enableAutoWSS := cfg.AutoTLS.AutoWSS.WithDefault(config.DefaultAutoWSS) + atlsLog := log.Logger("autotls") // Log error when relay subsystem could not be initialized due to missing dependency if !enableRelayTransport { @@ -125,21 +128,59 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part logger.Fatal("Failed to enable `Swarm.RelayClient`, it requires `Swarm.Transports.Network.Relay` to be true.") } } + if enableAutoTLS { + if !cfg.Swarm.Transports.Network.TCP.WithDefault(true) { + logger.Fatal("Invalid configuration: AutoTLS.Enabled=true requires Swarm.Transports.Network.TCP to be true as well.") + } if !cfg.Swarm.Transports.Network.Websocket.WithDefault(true) { logger.Fatal("Invalid configuration: AutoTLS.Enabled=true requires Swarm.Transports.Network.Websocket to be true as well.") } + // AutoTLS for Secure WebSockets: ensure WSS listeners are in place (manual or automatic) wssWildcard := fmt.Sprintf("/tls/sni/*.%s/ws", cfg.AutoTLS.DomainSuffix.WithDefault(config.DefaultDomainSuffix)) wssWildcardPresent := false + customWsPresent := false + customWsRegex := regexp.MustCompile(`/wss?$`) + tcpRegex := regexp.MustCompile(`/tcp/\d+$`) + + // inspect listeners defined in config at Addresses.Swarm + var tcpListeners []string for _, listener := range cfg.Addresses.Swarm { + // detect if user manually added /tls/sni/.../ws listener matching AutoTLS.DomainSuffix if strings.Contains(listener, wssWildcard) { + atlsLog.Infof("found compatible wildcard listener in Addresses.Swarm. AutoTLS will be used on %s", listener) wssWildcardPresent = true break } + // detect if user manually added own /ws or /wss listener that is + // not related to AutoTLS feature + if customWsRegex.MatchString(listener) { + atlsLog.Infof("found custom /ws listener set by user in Addresses.Swarm. AutoTLS will not be used on %s.", listener) + customWsPresent = true + break + } + // else, remember /tcp listeners that can be reused for /tls/sni/../ws + if tcpRegex.MatchString(listener) { + tcpListeners = append(tcpListeners, listener) + } } - if !wssWildcardPresent { - logger.Fatal(fmt.Sprintf("Invalid configuration: AutoTLS.Enabled=true requires a catch-all Addresses.Swarm listener ending with %q to be present, see https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls", wssWildcard)) + + // Append AutoTLS's wildcard listener + // if no manual /ws listener was set by the user + if enableAutoWSS && !wssWildcardPresent && !customWsPresent { + if len(tcpListeners) == 0 { + logger.Fatal("Invalid configuration: AutoTLS.AutoWSS=true requires at least one /tcp listener present in Addresses.Swarm, see https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls") + } + for _, tcpListener := range tcpListeners { + wssListener := tcpListener + wssWildcard + cfg.Addresses.Swarm = append(cfg.Addresses.Swarm, wssListener) + atlsLog.Infof("appended AutoWSS listener: %s", wssListener) + } + } + + if !wssWildcardPresent && !enableAutoWSS { + logger.Fatal(fmt.Sprintf("Invalid configuration: AutoTLS.Enabled=true requires a /tcp listener ending with %q to be present in Addresses.Swarm or AutoTLS.AutoWSS=true, see https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls", wssWildcard)) } } @@ -152,7 +193,7 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part // Services (resource management) fx.Provide(libp2p.ResourceManager(bcfg.Repo.Path(), cfg.Swarm, userResourceOverrides)), - maybeProvide(libp2p.P2PForgeCertMgr(bcfg.Repo.Path(), cfg.AutoTLS), enableAutoTLS), + maybeProvide(libp2p.P2PForgeCertMgr(bcfg.Repo.Path(), cfg.AutoTLS, atlsLog), enableAutoTLS), maybeInvoke(libp2p.StartP2PAutoTLS, enableAutoTLS), fx.Provide(libp2p.AddrFilters(cfg.Swarm.AddrFilters)), fx.Provide(libp2p.AddrsFactory(cfg.Addresses.Announce, cfg.Addresses.AppendAnnounce, cfg.Addresses.NoAnnounce)), diff --git a/core/node/libp2p/addrs.go b/core/node/libp2p/addrs.go index 958d05bbbad..b2dc45c5ce6 100644 --- a/core/node/libp2p/addrs.go +++ b/core/node/libp2p/addrs.go @@ -133,21 +133,20 @@ func ListenOn(addresses []string) interface{} { } } -func P2PForgeCertMgr(repoPath string, cfg config.AutoTLS) interface{} { +func P2PForgeCertMgr(repoPath string, cfg config.AutoTLS, atlsLog *logging.ZapEventLogger) interface{} { return func() (*p2pforge.P2PForgeCertMgr, error) { storagePath := filepath.Join(repoPath, "p2p-forge-certs") - forgeLogger := logging.Logger("autotls").Desugar() - // TODO: this should not be necessary, but we do it to help tracking // down any race conditions causing // https://github.com/ipshipyard/p2p-forge/issues/8 - certmagic.Default.Logger = forgeLogger.Named("default_fixme") - certmagic.DefaultACME.Logger = forgeLogger.Named("default_acme_client_fixme") + rawLogger := atlsLog.Desugar() + certmagic.Default.Logger = rawLogger.Named("default_fixme") + certmagic.DefaultACME.Logger = rawLogger.Named("default_acme_client_fixme") certStorage := &certmagic.FileStorage{Path: storagePath} certMgr, err := p2pforge.NewP2PForgeCertMgr( - p2pforge.WithLogger(forgeLogger.Sugar()), + p2pforge.WithLogger(rawLogger.Sugar()), p2pforge.WithForgeDomain(cfg.DomainSuffix.WithDefault(config.DefaultDomainSuffix)), p2pforge.WithForgeRegistrationEndpoint(cfg.RegistrationEndpoint.WithDefault(config.DefaultRegistrationEndpoint)), p2pforge.WithCAEndpoint(cfg.CAEndpoint.WithDefault(config.DefaultCAEndpoint)), diff --git a/docs/config.md b/docs/config.md index ba99711b0f5..33c20e698cb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -29,6 +29,7 @@ config file at runtime. - [`AutoNAT.Throttle.Interval`](#autonatthrottleinterval) - [`AutoTLS`](#autotls) - [`AutoTLS.Enabled`](#autotlsenabled) + - [`AutoTLS.AutoWSS`](#autotlsautowss) - [`AutoTLS.DomainSuffix`](#autotlsdomainsuffix) - [`AutoTLS.RegistrationEndpoint`](#autotlsregistrationendpoint) - [`AutoTLS.RegistrationToken`](#autotlsregistrationtoken) @@ -492,30 +493,39 @@ Type: `object` > Feel free to enable it and [report issues](https://github.com/ipfs/kubo/issues/new/choose) if you want to help with testing. > Track progress in [kubo#10560](https://github.com/ipfs/kubo/issues/10560). -Enables AutoTLS feature to get DNS+TLS for [libp2p Secure WebSocket](https://github.com/libp2p/specs/blob/master/websockets/README.md) listeners defined in [`Addresses.Swarm`](#addressesswarm), such as `/ip4/0.0.0.0/tcp/4001/tls/sni/*.libp2p.direct/ws` and `/ip6/::/tcp/4001/tls/sni/*.libp2p.direct/ws`. +Enables AutoTLS feature to get DNS+TLS for [libp2p Secure WebSocket](https://github.com/libp2p/specs/blob/master/websockets/README.md) on `/tcp` port. -If `.../tls/sni/*.libp2p.direct/ws` [multiaddr] is present in [`Addresses.Swarm`](#addressesswarm) +If `AutoTLS.AutoWSS` is `true`, or `/tcp/../tls/sni/*.libp2p.direct/ws` [multiaddr] is present in [`Addresses.Swarm`](#addressesswarm) with SNI segment ending with [`AutoTLS.DomainSuffix`](#autotlsdomainsuffix), -Kubo will obtain and set up a trusted PKI TLS certificate for it, making it dialable from web browser's [Secure Contexts](https://w3c.github.io/webappsec-secure-contexts/). +Kubo will obtain and set up a trusted PKI TLS certificate for `*.peerid.libp2p.direct`, making it dialable from web browser's [Secure Contexts](https://w3c.github.io/webappsec-secure-contexts/). + +> [!TIP] +> - Most users don't need custom `/ws` config in `Addresses.Swarm`. Try running this with `AutoTLS.AutoWSS=true`: it will reuse preexisting catch-all `/tcp` ports that were already forwarded/safelisted on your firewall. +> - Debugging can be enabled by setting environment variable `GOLOG_LOG_LEVEL="error,autotls=debug,p2p-forge/client=debug"`. Less noisy `GOLOG_LOG_LEVEL="error,autotls=info` may be informative enough. +> - Certificates are stored in `$IPFS_PATH/p2p-forge-certs`. Removing directory and restarting daemon will trigger certificate rotation. > [!IMPORTANT] > Caveats: > - Requires your Kubo node to be publicly dialable. -> - If you want to test this with a node that is behind a NAT and uses manual port forwarding or UPnP (`Swarm.DisableNatPortMap=false`), +> - If you want to test this with a node that is behind a NAT and uses manual TCP port forwarding or UPnP (`Swarm.DisableNatPortMap=false`), use `AutoTLS.AutoWSS=true`, or manually > add catch-all `/ip4/0.0.0.0/tcp/4001/tls/sni/*.libp2p.direct/ws` and `/ip6/::/tcp/4001/tls/sni/*.libp2p.direct/ws` to [`Addresses.Swarm`](#addressesswarm) > and **wait 5-15 minutes** for libp2p node to set up and learn about own public addresses via [AutoNAT](#autonat). > - If your node is fresh and just started, the [p2p-forge] client may produce and log ERRORs during this time, but once a publicly dialable addresses are set up, a subsequent retry should be successful. > - The TLS certificate is used only for [libp2p WebSocket](https://github.com/libp2p/specs/blob/master/websockets/README.md) connections. > - Right now, this is NOT used for hosting a [Gateway](#gateway) over HTTPS (that use case still requires manual TLS setup on reverse proxy, and your own domain). -> [!TIP] -> - Debugging can be enabled by setting environment variable `GOLOG_LOG_LEVEL="error,autotls=debug,p2p-forge/client=debug"` -> - Certificates are stored in `$IPFS_PATH/p2p-forge-certs`. Removing directory and restarting daemon will trigger certificate rotation. - Default: `false` Type: `flag` +### `AutoTLS.AutoWSS` + +Optional. Controls if Kubo should add `/tls/sni/*.libp2p.direct/ws` listener to every pre-existing `/tcp` port IFF no explicit `/ws` is defined in [`Addresses.Swarm`](#addressesswarm) already. + +Default: `true` (active only if `AutoTLS.Enabled` is `true` as well) + +Type: `flag` + ### `AutoTLS.DomainSuffix` Optional override of the parent domain suffix that will be used in DNS+TLS+WebSockets multiaddrs generated by [p2p-forge] client.