config+lnd: add wallet-unlock-password-file option

In automated or unattended setups such as cluster/container
environments, unlocking the wallet through RPC presents a set of
challenges. Usually the password is present as a file somewhere in the
container already anyway so we might also just read it from there.
This commit is contained in:
Oliver Gugger 2021-04-16 14:28:49 +02:00
parent 8224de599b
commit 571d00b32c
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
3 changed files with 85 additions and 6 deletions

@ -293,6 +293,7 @@ type Config struct {
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
NoSeedBackup bool `long:"noseedbackup" description:"If true, NO SEED WILL BE EXPOSED -- EVER, AND THE WALLET WILL BE ENCRYPTED USING THE DEFAULT PASSPHRASE. THIS FLAG IS ONLY FOR TESTING AND SHOULD NEVER BE USED ON MAINNET."`
WalletUnlockPasswordFile string `long:"wallet-unlock-password-file" description:"The full path to a file (or pipe/device) that contains the password for unlocking the wallet; if set, no unlocking through RPC is possible and lnd will exit if no wallet exists or the password is incorrect"`
ResetWalletTransactions bool `long:"reset-wallet-transactions" description:"Removes all transaction history from the on-chain wallet on startup, forcing a full chain rescan starting at the wallet's birthday. Implements the same functionality as btcwallet's dropwtxmgr command. Should be set to false after successful execution to avoid rescanning on every restart of lnd."`
@ -687,6 +688,9 @@ func ValidateConfig(cfg Config, usageMessage string,
cfg.Tor.WatchtowerKeyPath = CleanAndExpandPath(cfg.Tor.WatchtowerKeyPath)
cfg.Watchtower.TowerDir = CleanAndExpandPath(cfg.Watchtower.TowerDir)
cfg.BackupFilePath = CleanAndExpandPath(cfg.BackupFilePath)
cfg.WalletUnlockPasswordFile = CleanAndExpandPath(
cfg.WalletUnlockPasswordFile,
)
// Create the lnd directory and all other sub directories if they don't
// already exist. This makes sure that directory trees are also created
@ -1278,6 +1282,20 @@ func ValidateConfig(cfg Config, usageMessage string,
return nil, err
}
switch {
// The no seed backup and auto unlock are mutually exclusive.
case cfg.NoSeedBackup && cfg.WalletUnlockPasswordFile != "":
return nil, fmt.Errorf("cannot set noseedbackup and " +
"wallet-unlock-password-file at the same time")
// If a password file was specified, we need it to exist.
case cfg.WalletUnlockPasswordFile != "" &&
!lnrpc.FileExists(cfg.WalletUnlockPasswordFile):
return nil, fmt.Errorf("wallet unlock password file %s does "+
"not exist", cfg.WalletUnlockPasswordFile)
}
// For each of the RPC listeners (REST+gRPC), we'll ensure that users
// have specified a safe combo for authentication. If not, we'll bail
// out with an error. Since we don't allow disabling TLS for gRPC

66
lnd.go

@ -5,6 +5,7 @@
package lnd
import (
"bytes"
"context"
"crypto/tls"
"fmt"
@ -278,7 +279,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error
var (
walletInitParams = WalletUnlockParams{
MacResponseChan: make(chan []byte),
// In case we do auto-unlock, we need to be able to send
// into the channel without blocking so we buffer it.
MacResponseChan: make(chan []byte, 1),
}
privateWalletPw = lnwallet.DefaultPrivatePassphrase
publicWalletPw = lnwallet.DefaultPublicPassphrase
@ -475,10 +478,63 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error
interceptorChain.SetWalletLocked()
}
// We wait until the user provides a password over RPC. In case lnd is
// started with the --noseedbackup flag, we use the default password
// for wallet encryption.
if !cfg.NoSeedBackup {
// If we've started in auto unlock mode, then a wallet _must_ already
// exist because we never want to enable the RPC unlocker in that case.
if cfg.WalletUnlockPasswordFile != "" && !walletExists {
return fmt.Errorf("wallet unlock password file was specified " +
"but wallet does not exist; initialize the wallet " +
"before using auto unlocking")
}
// What wallet mode are we running in? We've already made sure the no
// seed backup and auto unlock aren't both set during config parsing.
switch {
// No seed backup means we're also using the default password.
case cfg.NoSeedBackup:
// We continue normally, the default password has already been
// set above.
// A password for unlocking is provided in a file.
case cfg.WalletUnlockPasswordFile != "":
ltndLog.Infof("Attempting automatic wallet unlock with " +
"password provided in file")
pwBytes, err := ioutil.ReadFile(cfg.WalletUnlockPasswordFile)
if err != nil {
return fmt.Errorf("error reading password from file "+
"%s: %v", cfg.WalletUnlockPasswordFile, err)
}
// Remove any newlines at the end of the file. The lndinit tool
// won't ever write a newline but maybe the file was provisioned
// by another process or user.
pwBytes = bytes.TrimRight(pwBytes, "\r\n")
// We have the password now, we can ask the unlocker service to
// do the unlock for us.
unlockedWallet, unloadWalletFn, err := pwService.LoadAndUnlock(
pwBytes, 0,
)
if err != nil {
return fmt.Errorf("error unlocking wallet with "+
"password from file: %v", err)
}
defer func() {
if err := unloadWalletFn(); err != nil {
ltndLog.Errorf("Could not unload wallet: %v",
err)
}
}()
privateWalletPw = pwBytes
publicWalletPw = pwBytes
walletInitParams.Wallet = unlockedWallet
walletInitParams.UnloadWallet = unloadWalletFn
// If none of the automatic startup options are selected, we fall back
// to the default behavior of waiting for the wallet creation/unlocking
// over RPC.
default:
params, err := waitForWalletPassword(
cfg, pwService, []btcwallet.LoaderOption{loaderOpt},
interceptor.ShutdownChannel(),

@ -254,6 +254,11 @@
; BE USED ON MAINNET.
; noseedbackup=true
; The full path to a file (or pipe/device) that contains the password for
; unlocking the wallet; if set, no unlocking through RPC is possible and lnd
; will exit if no wallet exists or the password is incorrect
; wallet-unlock-password-file=/tmp/example.password
; Removes all transaction history from the on-chain wallet on startup, forcing a
; full chain rescan starting at the wallet's birthday. Implements the same
; functionality as btcwallet's dropwtxmgr command. Should be set to false after