lncli: add flags for stateless init to wallet unlocker commands

This commit is contained in:
Oliver Gugger 2020-10-06 17:23:27 +02:00
parent 4c8d374007
commit 10673b9dfa
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -4,16 +4,31 @@ import (
"bufio"
"bytes"
"context"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/urfave/cli"
)
var (
statelessInitFlag = cli.BoolFlag{
Name: "stateless_init",
Usage: "do not create any macaroon files in the file " +
"system of the daemon",
}
saveToFlag = cli.StringFlag{
Name: "save_to",
Usage: "save returned admin macaroon to this file",
}
)
var createCommand = cli.Command{
Name: "create",
Category: "Startup",
@ -37,6 +52,14 @@ var createCommand = cli.Command{
to potentially recover all on-chain funds, and most off-chain funds as
well.
If the --stateless_init flag is set, no macaroon files are created by
the daemon. Instead, the binary serialized admin macaroon is returned
in the answer. This answer MUST be stored somewhere, otherwise all
access to the RPC server will be lost and the wallet must be recreated
to re-gain access.
If the --save_to parameter is set, the macaroon is saved to this file,
otherwise it is printed to standard out.
Finally, it's also possible to use this command and a set of static
channel backups to trigger a recover attempt for the provided Static
Channel Backups. Only one of the three parameters will be accepted. See
@ -58,6 +81,8 @@ var createCommand = cli.Command{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
},
statelessInitFlag,
saveToFlag,
},
Action: actionDecorator(create),
}
@ -171,7 +196,15 @@ func create(ctx *cli.Context) error {
}
}
}
}
// Should the daemon be initialized stateless? Then we expect an answer
// with the admin macaroon later. Because the --save_to is related to
// stateless init, it doesn't make sense to be set on its own.
statelessInit := ctx.Bool(statelessInitFlag.Name)
if !statelessInit && ctx.IsSet(saveToFlag.Name) {
return fmt.Errorf("cannot set save_to parameter without " +
"stateless_init")
}
walletPassword, err := capturePassword(
@ -349,13 +382,19 @@ mnemonicCheck:
AezeedPassphrase: aezeedPass,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
StatelessInit: statelessInit,
}
if _, err := client.InitWallet(ctxb, req); err != nil {
response, err := client.InitWallet(ctxb, req)
if err != nil {
return err
}
fmt.Println("\nlnd successfully initialized!")
if statelessInit {
return storeOrPrintAdminMac(ctx, response.AdminMacaroon)
}
return nil
}
@ -410,6 +449,12 @@ var unlockCommand = cli.Command{
start up. This command MUST be run after booting up lnd before it's
able to carry out its duties. An exception is if a user is running with
--noseedbackup, then a default passphrase will be used.
If the --stateless_init flag is set, no macaroon files are created by
the daemon. This should be set for every unlock if the daemon was
initially initialized stateless. Otherwise the daemon will create
unencrypted macaroon files which could leak information to the system
that the daemon runs on.
`,
Flags: []cli.Flag{
cli.IntFlag{
@ -430,6 +475,7 @@ var unlockCommand = cli.Command{
"combination with some sort of password " +
"manager or secrets vault.",
},
statelessInitFlag,
},
Action: actionDecorator(unlock),
}
@ -485,6 +531,7 @@ func unlock(ctx *cli.Context) error {
req := &lnrpc.UnlockWalletRequest{
WalletPassword: pw,
RecoveryWindow: recoveryWindow,
StatelessInit: ctx.Bool(statelessInitFlag.Name),
}
_, err = client.UnlockWallet(ctxb, req)
if err != nil {
@ -511,7 +558,35 @@ var changePasswordCommand = cli.Command{
--noseedbackup), one must restart their daemon without
--noseedbackup and use this command. The "current password" field
should be left empty.
If the daemon was originally initialized stateless, then the
--stateless_init flag needs to be set for the change password request
as well! Otherwise the daemon will generate unencrypted macaroon files
in its file system again and possibly leak sensitive information.
Changing the password will by default not change the macaroon root key
(just re-encrypt the macaroon database with the new password). So all
macaroons will still be valid.
If one wants to make sure that all previously created macaroons are
invalidated, a new macaroon root key can be generated by using the
--new_mac_root_key flag.
After a successful password change with the --stateless_init flag set,
the current or new admin macaroon is returned binary serialized in the
answer. This answer MUST then be stored somewhere, otherwise
all access to the RPC server will be lost and the wallet must be re-
created to re-gain access. If the --save_to parameter is set, the
macaroon is saved to this file, otherwise it is printed to standard out.
`,
Flags: []cli.Flag{
statelessInitFlag,
saveToFlag,
cli.BoolFlag{
Name: "new_mac_root_key",
Usage: "rotate the macaroon root key resulting in " +
"all previously created macaroons to be " +
"invalidated",
},
},
Action: actionDecorator(changePassword),
}
@ -539,15 +614,53 @@ func changePassword(ctx *cli.Context) error {
return fmt.Errorf("passwords don't match")
}
// Should the daemon be initialized stateless? Then we expect an answer
// with the admin macaroon later. Because the --save_to is related to
// stateless init, it doesn't make sense to be set on its own.
statelessInit := ctx.Bool(statelessInitFlag.Name)
if !statelessInit && ctx.IsSet(saveToFlag.Name) {
return fmt.Errorf("cannot set save_to parameter without " +
"stateless_init")
}
req := &lnrpc.ChangePasswordRequest{
CurrentPassword: currentPw,
NewPassword: newPw,
StatelessInit: statelessInit,
NewMacaroonRootKey: ctx.Bool("new_mac_root_key"),
}
_, err = client.ChangePassword(ctxb, req)
response, err := client.ChangePassword(ctxb, req)
if err != nil {
return err
}
if statelessInit {
return storeOrPrintAdminMac(ctx, response.AdminMacaroon)
}
return nil
}
// storeOrPrintAdminMac either stores the admin macaroon to a file specified or
// prints it to standard out, depending on the user flags set.
func storeOrPrintAdminMac(ctx *cli.Context, adminMac []byte) error {
// The user specified the optional --save_to parameter. We'll save the
// macaroon to that file.
if ctx.IsSet("save_to") {
macSavePath := lncfg.CleanAndExpandPath(ctx.String("save_to"))
err := ioutil.WriteFile(macSavePath, adminMac, 0644)
if err != nil {
_ = os.Remove(macSavePath)
return err
}
fmt.Printf("Admin macaroon saved to %s\n", macSavePath)
return nil
}
// Otherwise we just print it. The user MUST store this macaroon
// somewhere so we either save it to a provided file path or just print
// it to standard output.
fmt.Printf("Admin macaroon: %s\n", hex.EncodeToString(adminMac))
return nil
}