diff --git a/config.go b/config.go index 040f68e29a..773c5d00b9 100644 --- a/config.go +++ b/config.go @@ -221,16 +221,17 @@ type autoPilotConfig struct { } type torConfig struct { - Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"` - SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"` - DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"` - StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` - Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"` - TargetIPAddress string `long:"targetipaddress" description:"IP address that Tor should use as the target of the hidden service"` - Password string `long:"password" description:"The password used to arrive at the HashedControlPassword for the control port. If provided, the HASHEDPASSWORD authentication method will be used instead of the SAFECOOKIE one."` - V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"` - V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"` - PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"` + Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"` + SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"` + DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"` + StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` + Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"` + TargetIPAddress string `long:"targetipaddress" description:"IP address that Tor should use as the target of the hidden service"` + Password string `long:"password" description:"The password used to arrive at the HashedControlPassword for the control port. If provided, the HASHEDPASSWORD authentication method will be used instead of the SAFECOOKIE one."` + V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"` + V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"` + PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"` + WatchtowerKeyPath string `long:"watchtowerkeypath" description:"The path to the private key of the watchtower onion service being created"` } // config defines the configuration options for lnd. @@ -567,6 +568,7 @@ func loadConfig() (*config, error) { cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir) cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir) cfg.Tor.PrivateKeyPath = cleanAndExpandPath(cfg.Tor.PrivateKeyPath) + cfg.Tor.WatchtowerKeyPath = cleanAndExpandPath(cfg.Tor.WatchtowerKeyPath) cfg.Watchtower.TowerDir = cleanAndExpandPath(cfg.Watchtower.TowerDir) // Ensure that the user didn't attempt to specify negative values for @@ -682,6 +684,19 @@ func loadConfig() (*config, error) { } } + if cfg.Tor.WatchtowerKeyPath == "" { + switch { + case cfg.Tor.V2: + cfg.Tor.WatchtowerKeyPath = filepath.Join( + cfg.Watchtower.TowerDir, defaultTorV2PrivateKeyFilename, + ) + case cfg.Tor.V3: + cfg.Tor.WatchtowerKeyPath = filepath.Join( + cfg.Watchtower.TowerDir, defaultTorV3PrivateKeyFilename, + ) + } + } + // Set up the network-related functions that will be used throughout // the daemon. We use the standard Go "net" package functions by // default. If we should be proxying all traffic through Tor, then diff --git a/docs/watchtower.md b/docs/watchtower.md index 772d456e29..870ec1e8ff 100644 --- a/docs/watchtower.md +++ b/docs/watchtower.md @@ -102,6 +102,24 @@ If the watchtower's clients will need remote access, be sure to either: - Use a proxy to direct traffic from an open port to the watchtower's listening address. +### Tor Hidden Services + +Watchtowers have tor hidden service support and can automatically generate a +hidden service on startup with the following flags: + +``` +🏔 lnd --tor.active --tor.v3 --watchtower.active +``` + +The onion address is then shown in the "uris" field when queried with `lncli tower info`: + +``` +... +"uris": [ + "03281d603b2c5e19b8893a484eb938d7377179a9ef1a6bca4c0bcbbfc291657b63@bn2kxggzjysvsd5o3uqe4h7655u7v2ydhxzy7ea2fx26duaixlwuguad.onion:9911" +] +``` + Note: *The watchtower’s public key is distinct from `lnd`’s node public key. For now this acts as a soft whitelist as it requires clients to know the tower’s public key in order to use it for backups before more advanced whitelisting diff --git a/lnd.go b/lnd.go index 3e6ed885b1..875f0e7377 100644 --- a/lnd.go +++ b/lnd.go @@ -45,6 +45,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/signal" + "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/watchtower/wtdb" @@ -473,6 +474,28 @@ func Main(lisCfg ListenerCfg) error { defer towerClientDB.Close() } + // If tor is active and either v2 or v3 onion services have been specified, + // make a tor controller and pass it into both the watchtower server and + // the regular lnd server. + var torController *tor.Controller + if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) { + torController = tor.NewController( + cfg.Tor.Control, cfg.Tor.TargetIPAddress, cfg.Tor.Password, + ) + + // Start the tor controller before giving it to any other subsystems. + if err := torController.Start(); err != nil { + err := fmt.Errorf("unable to initialize tor controller: %v", err) + ltndLog.Error(err) + return err + } + defer func() { + if err := torController.Stop(); err != nil { + ltndLog.Errorf("error stopping tor controller: %v", err) + } + }() + } + var tower *watchtower.Standalone if cfg.Watchtower.Active { // Segment the watchtower directory by chain and network. @@ -506,7 +529,7 @@ func Main(lisCfg ListenerCfg) error { return err } - wtConfig, err := cfg.Watchtower.Apply(&watchtower.Config{ + wtCfg := &watchtower.Config{ BlockFetcher: activeChainControl.chainIO, DB: towerDB, EpochRegistrar: activeChainControl.chainNotifier, @@ -519,7 +542,23 @@ func Main(lisCfg ListenerCfg) error { NodePrivKey: towerPrivKey, PublishTx: activeChainControl.wallet.PublishTransaction, ChainHash: *activeNetParams.GenesisHash, - }, lncfg.NormalizeAddresses) + } + + // If there is a tor controller (user wants auto hidden services), then + // store a pointer in the watchtower config. + if torController != nil { + wtCfg.TorController = torController + wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath + + switch { + case cfg.Tor.V2: + wtCfg.Type = tor.V2 + case cfg.Tor.V3: + wtCfg.Type = tor.V3 + } + } + + wtConfig, err := cfg.Watchtower.Apply(wtCfg, lncfg.NormalizeAddresses) if err != nil { err := fmt.Errorf("Unable to configure watchtower: %v", err) @@ -543,6 +582,7 @@ func Main(lisCfg ListenerCfg) error { server, err := newServer( cfg.Listeners, chanDB, towerClientDB, activeChainControl, idPrivKey, walletInitParams.ChansToRestore, chainedAcceptor, + torController, ) if err != nil { err := fmt.Errorf("Unable to create server: %v", err) diff --git a/server.go b/server.go index fe935f3c8e..a1dce6ec0d 100644 --- a/server.go +++ b/server.go @@ -322,7 +322,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, towerClientDB *wtdb.ClientDB, cc *chainControl, privKey *btcec.PrivateKey, chansToRestore walletunlocker.ChannelsToRecover, - chanPredicate chanacceptor.ChannelAcceptor) (*server, error) { + chanPredicate chanacceptor.ChannelAcceptor, + torController *tor.Controller) (*server, error) { var err error @@ -428,6 +429,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, // schedule sphinx: hop.NewOnionProcessor(sphinxRouter), + torController: torController, + persistentPeers: make(map[string]bool), persistentPeersBackoff: make(map[string]time.Duration), persistentConnReqs: make(map[string][]*connmgr.ConnReq), @@ -598,16 +601,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, selfAddrs = append(selfAddrs, ip) } - // If we were requested to route connections through Tor and to - // automatically create an onion service, we'll initiate our Tor - // controller and establish a connection to the Tor server. - if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) { - s.torController = tor.NewController( - cfg.Tor.Control, cfg.Tor.TargetIPAddress, - cfg.Tor.Password, - ) - } - chanGraph := chanDB.ChannelGraph() // We'll now reconstruct a node announcement based on our current @@ -1251,7 +1244,7 @@ func (s *server) Start() error { var startErr error s.start.Do(func() { if s.torController != nil { - if err := s.initTorController(); err != nil { + if err := s.createNewHiddenService(); err != nil { startErr = err return } @@ -1442,10 +1435,6 @@ func (s *server) Stop() error { close(s.quit) - if s.torController != nil { - s.torController.Stop() - } - // Shutdown the wallet, funding manager, and the rpc server. s.chanStatusMgr.Stop() s.cc.chainNotifier.Stop() @@ -1965,14 +1954,9 @@ func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, } } -// initTorController initiliazes the Tor controller backed by lnd and -// automatically sets up a v2 onion service in order to listen for inbound -// connections over Tor. -func (s *server) initTorController() error { - if err := s.torController.Start(); err != nil { - return err - } - +// createNewHiddenService automatically sets up a v2 or v3 onion service in +// order to listen for inbound connections over Tor. +func (s *server) createNewHiddenService() error { // Determine the different ports the server is listening on. The onion // service's virtual port will map to these ports and one will be picked // at random when the onion service is being accessed. diff --git a/watchtower/config.go b/watchtower/config.go index 26dfac75a6..a71bd0433c 100644 --- a/watchtower/config.go +++ b/watchtower/config.go @@ -34,8 +34,8 @@ var ( ) // Config defines the resources and parameters used to configure a Watchtower. -// All nil-able elements with the Config must be set in order for the Watchtower -// to function properly. +// All nil-able elements besides tor-related ones must be set in order for the +// Watchtower to function properly. type Config struct { // ChainHash identifies the chain that the watchtower will be monitoring // for breaches and that will be advertised in the server's Init message @@ -89,4 +89,16 @@ type Config struct { // message from the other end, if the connection has stopped buffering // the server's replies. WriteTimeout time.Duration + + // TorController allows the watchtower to optionally setup an onion hidden + // service. + TorController *tor.Controller + + // WatchtowerKeyPath allows the watchtower to specify where the private key + // for a watchtower hidden service should be stored. + WatchtowerKeyPath string + + // Type specifies the hidden service type (V2 or V3) that the watchtower + // will create. + Type tor.OnionType } diff --git a/watchtower/standalone.go b/watchtower/standalone.go index 8f50dc65a0..b1a36bb520 100644 --- a/watchtower/standalone.go +++ b/watchtower/standalone.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/brontide" + "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/watchtower/lookout" "github.com/lightningnetwork/lnd/watchtower/wtserver" ) @@ -112,6 +113,15 @@ func (w *Standalone) Start() error { log.Infof("Starting watchtower") + // If a tor controller exists in the config, then automatically create a + // hidden service for the watchtower to accept inbound connections from. + if w.cfg.TorController != nil { + log.Infof("Creating watchtower hidden service") + if err := w.createNewHiddenService(); err != nil { + return err + } + } + if err := w.lookout.Start(); err != nil { return err } @@ -142,6 +152,39 @@ func (w *Standalone) Stop() error { return nil } +// createNewHiddenService automatically sets up a v2 or v3 onion service in +// order to listen for inbound connections over Tor. +func (w *Standalone) createNewHiddenService() error { + // Get all the ports the watchtower is listening on. These will be used to + // map the hidden service's virtual port. + listenPorts := make([]int, 0, len(w.listeners)) + for _, listener := range w.listeners { + port := listener.Addr().(*net.TCPAddr).Port + listenPorts = append(listenPorts, port) + } + + // Once we've created the port mapping, we can automatically create the + // hidden service. The service's private key will be saved on disk in order + // to persistently have access to this hidden service across restarts. + onionCfg := tor.AddOnionConfig{ + VirtualPort: DefaultPeerPort, + TargetPorts: listenPorts, + Store: tor.NewOnionFile(w.cfg.WatchtowerKeyPath, 0600), + Type: w.cfg.Type, + } + + addr, err := w.cfg.TorController.AddOnion(onionCfg) + if err != nil { + return err + } + + // Append this address to ExternalIPs so that it will be exposed in + // tower info calls. + w.cfg.ExternalIPs = append(w.cfg.ExternalIPs, addr) + + return nil +} + // PubKey returns the public key for the watchtower used to authentication and // encrypt traffic with clients. //