Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

watchtower: automatically create tor hidden service if enabled #4087

Merged
merged 3 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
wpaulino marked this conversation as resolved.
Show resolved Hide resolved
}
}

// 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
Expand Down
18 changes: 18 additions & 0 deletions docs/watchtower.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

```
...
Crypt-iQ marked this conversation as resolved.
Show resolved Hide resolved
"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
Expand Down
44 changes: 42 additions & 2 deletions lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Crypt-iQ marked this conversation as resolved.
Show resolved Hide resolved
}
}()
}

var tower *watchtower.Standalone
if cfg.Watchtower.Active {
// Segment the watchtower directory by chain and network.
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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)
Expand Down
32 changes: 8 additions & 24 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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.
Expand Down
16 changes: 14 additions & 2 deletions watchtower/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
43 changes: 43 additions & 0 deletions watchtower/standalone.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Crypt-iQ marked this conversation as resolved.
Show resolved Hide resolved
TargetPorts: listenPorts,
Store: tor.NewOnionFile(w.cfg.WatchtowerKeyPath, 0600),
Type: w.cfg.Type,
}

addr, err := w.cfg.TorController.AddOnion(onionCfg)
wpaulino marked this conversation as resolved.
Show resolved Hide resolved
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)
cfromknecht marked this conversation as resolved.
Show resolved Hide resolved

return nil
}

// PubKey returns the public key for the watchtower used to authentication and
// encrypt traffic with clients.
//
Expand Down