Merge pull request #4087 from Crypt-iQ/wt_hs_0310

watchtower: automatically create tor hidden service if enabled
This commit is contained in:
Olaoluwa Osuntokun 2020-03-30 16:42:35 -07:00 committed by GitHub
commit 90dfb97224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 38 deletions

@ -221,16 +221,17 @@ type autoPilotConfig struct {
} }
type torConfig struct { type torConfig struct {
Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"` 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"` 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"` 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."` 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"` 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"` 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."` 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"` 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"` 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"` 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. // config defines the configuration options for lnd.
@ -567,6 +568,7 @@ func loadConfig() (*config, error) {
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir) cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir) cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir)
cfg.Tor.PrivateKeyPath = cleanAndExpandPath(cfg.Tor.PrivateKeyPath) cfg.Tor.PrivateKeyPath = cleanAndExpandPath(cfg.Tor.PrivateKeyPath)
cfg.Tor.WatchtowerKeyPath = cleanAndExpandPath(cfg.Tor.WatchtowerKeyPath)
cfg.Watchtower.TowerDir = cleanAndExpandPath(cfg.Watchtower.TowerDir) cfg.Watchtower.TowerDir = cleanAndExpandPath(cfg.Watchtower.TowerDir)
// Ensure that the user didn't attempt to specify negative values for // 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 // Set up the network-related functions that will be used throughout
// the daemon. We use the standard Go "net" package functions by // the daemon. We use the standard Go "net" package functions by
// default. If we should be proxying all traffic through Tor, then // default. If we should be proxying all traffic through Tor, then

@ -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 - Use a proxy to direct traffic from an open port to the watchtower's listening
address. 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 watchtowers public key is distinct from `lnd`s node public key. For Note: *The watchtowers 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 towers now this acts as a soft whitelist as it requires clients to know the towers
public key in order to use it for backups before more advanced whitelisting public key in order to use it for backups before more advanced whitelisting

44
lnd.go

@ -45,6 +45,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/walletunlocker"
"github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
@ -473,6 +474,28 @@ func Main(lisCfg ListenerCfg) error {
defer towerClientDB.Close() 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 var tower *watchtower.Standalone
if cfg.Watchtower.Active { if cfg.Watchtower.Active {
// Segment the watchtower directory by chain and network. // Segment the watchtower directory by chain and network.
@ -506,7 +529,7 @@ func Main(lisCfg ListenerCfg) error {
return err return err
} }
wtConfig, err := cfg.Watchtower.Apply(&watchtower.Config{ wtCfg := &watchtower.Config{
BlockFetcher: activeChainControl.chainIO, BlockFetcher: activeChainControl.chainIO,
DB: towerDB, DB: towerDB,
EpochRegistrar: activeChainControl.chainNotifier, EpochRegistrar: activeChainControl.chainNotifier,
@ -519,7 +542,23 @@ func Main(lisCfg ListenerCfg) error {
NodePrivKey: towerPrivKey, NodePrivKey: towerPrivKey,
PublishTx: activeChainControl.wallet.PublishTransaction, PublishTx: activeChainControl.wallet.PublishTransaction,
ChainHash: *activeNetParams.GenesisHash, 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 { if err != nil {
err := fmt.Errorf("Unable to configure watchtower: %v", err := fmt.Errorf("Unable to configure watchtower: %v",
err) err)
@ -543,6 +582,7 @@ func Main(lisCfg ListenerCfg) error {
server, err := newServer( server, err := newServer(
cfg.Listeners, chanDB, towerClientDB, activeChainControl, cfg.Listeners, chanDB, towerClientDB, activeChainControl,
idPrivKey, walletInitParams.ChansToRestore, chainedAcceptor, idPrivKey, walletInitParams.ChansToRestore, chainedAcceptor,
torController,
) )
if err != nil { if err != nil {
err := fmt.Errorf("Unable to create server: %v", err) err := fmt.Errorf("Unable to create server: %v", err)

@ -322,7 +322,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
towerClientDB *wtdb.ClientDB, cc *chainControl, towerClientDB *wtdb.ClientDB, cc *chainControl,
privKey *btcec.PrivateKey, privKey *btcec.PrivateKey,
chansToRestore walletunlocker.ChannelsToRecover, chansToRestore walletunlocker.ChannelsToRecover,
chanPredicate chanacceptor.ChannelAcceptor) (*server, error) { chanPredicate chanacceptor.ChannelAcceptor,
torController *tor.Controller) (*server, error) {
var err error var err error
@ -428,6 +429,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
// schedule // schedule
sphinx: hop.NewOnionProcessor(sphinxRouter), sphinx: hop.NewOnionProcessor(sphinxRouter),
torController: torController,
persistentPeers: make(map[string]bool), persistentPeers: make(map[string]bool),
persistentPeersBackoff: make(map[string]time.Duration), persistentPeersBackoff: make(map[string]time.Duration),
persistentConnReqs: make(map[string][]*connmgr.ConnReq), persistentConnReqs: make(map[string][]*connmgr.ConnReq),
@ -598,16 +601,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
selfAddrs = append(selfAddrs, ip) 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() chanGraph := chanDB.ChannelGraph()
// We'll now reconstruct a node announcement based on our current // We'll now reconstruct a node announcement based on our current
@ -1251,7 +1244,7 @@ func (s *server) Start() error {
var startErr error var startErr error
s.start.Do(func() { s.start.Do(func() {
if s.torController != nil { if s.torController != nil {
if err := s.initTorController(); err != nil { if err := s.createNewHiddenService(); err != nil {
startErr = err startErr = err
return return
} }
@ -1442,10 +1435,6 @@ func (s *server) Stop() error {
close(s.quit) close(s.quit)
if s.torController != nil {
s.torController.Stop()
}
// Shutdown the wallet, funding manager, and the rpc server. // Shutdown the wallet, funding manager, and the rpc server.
s.chanStatusMgr.Stop() s.chanStatusMgr.Stop()
s.cc.chainNotifier.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 // createNewHiddenService automatically sets up a v2 or v3 onion service in
// automatically sets up a v2 onion service in order to listen for inbound // order to listen for inbound connections over Tor.
// connections over Tor. func (s *server) createNewHiddenService() error {
func (s *server) initTorController() error {
if err := s.torController.Start(); err != nil {
return err
}
// Determine the different ports the server is listening on. The onion // 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 // service's virtual port will map to these ports and one will be picked
// at random when the onion service is being accessed. // at random when the onion service is being accessed.

@ -34,8 +34,8 @@ var (
) )
// Config defines the resources and parameters used to configure a Watchtower. // 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 // All nil-able elements besides tor-related ones must be set in order for the
// to function properly. // Watchtower to function properly.
type Config struct { type Config struct {
// ChainHash identifies the chain that the watchtower will be monitoring // ChainHash identifies the chain that the watchtower will be monitoring
// for breaches and that will be advertised in the server's Init message // 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 // message from the other end, if the connection has stopped buffering
// the server's replies. // the server's replies.
WriteTimeout time.Duration 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
} }

@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/lookout" "github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtserver" "github.com/lightningnetwork/lnd/watchtower/wtserver"
) )
@ -112,6 +113,15 @@ func (w *Standalone) Start() error {
log.Infof("Starting watchtower") 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 { if err := w.lookout.Start(); err != nil {
return err return err
} }
@ -142,6 +152,39 @@ func (w *Standalone) Stop() error {
return nil 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 // PubKey returns the public key for the watchtower used to authentication and
// encrypt traffic with clients. // encrypt traffic with clients.
// //