Merge pull request #4087 from Crypt-iQ/wt_hs_0310
watchtower: automatically create tor hidden service if enabled
This commit is contained in:
commit
90dfb97224
35
config.go
35
config.go
@ -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 watchtower’s public key is distinct from `lnd`’s node public key. For
|
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
|
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
|
public key in order to use it for backups before more advanced whitelisting
|
||||||
|
44
lnd.go
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)
|
||||||
|
32
server.go
32
server.go
@ -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.
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user