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:
parent
8224de599b
commit
571d00b32c
18
config.go
18
config.go
@ -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
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
|
||||
|
Loading…
Reference in New Issue
Block a user