diff --git a/cmd/lncli/cmd_bake_macaroon.go b/cmd/lncli/cmd_macaroon.go similarity index 67% rename from cmd/lncli/cmd_bake_macaroon.go rename to cmd/lncli/cmd_macaroon.go index 5929a536d..fe1ce36fe 100644 --- a/cmd/lncli/cmd_bake_macaroon.go +++ b/cmd/lncli/cmd_macaroon.go @@ -1,11 +1,13 @@ package main import ( + "bytes" "context" "encoding/hex" "fmt" "io/ioutil" "net" + "strconv" "strings" "github.com/lightningnetwork/lnd/lnrpc" @@ -18,7 +20,7 @@ var bakeMacaroonCommand = cli.Command{ Name: "bakemacaroon", Category: "Macaroons", Usage: "Bakes a new macaroon with the provided list of permissions " + - "and restrictions", + "and restrictions.", ArgsUsage: "[--save_to=] [--timeout=] [--ip_address=] permissions...", Description: ` Bake a new macaroon that grants the provided permissions and @@ -48,6 +50,10 @@ var bakeMacaroonCommand = cli.Command{ Name: "ip_address", Usage: "the IP address the macaroon will be bound to", }, + cli.Uint64Flag{ + Name: "root_key_id", + Usage: "the numerical root key ID used to create the macaroon", + }, }, Action: actionDecorator(bakeMacaroon), } @@ -66,6 +72,7 @@ func bakeMacaroon(ctx *cli.Context) error { savePath string timeout int64 ipAddress net.IP + rootKeyID uint64 parsedPermissions []*lnrpc.MacaroonPermission err error ) @@ -89,6 +96,10 @@ func bakeMacaroon(ctx *cli.Context) error { } } + if ctx.IsSet("root_key_id") { + rootKeyID = ctx.Uint64("root_key_id") + } + // A command line argument can't be an empty string. So we'll check each // entry if it's a valid entity:action tuple. The content itself is // validated server side. We just make sure we can parse it correctly. @@ -122,6 +133,7 @@ func bakeMacaroon(ctx *cli.Context) error { // RPC call. req := &lnrpc.BakeMacaroonRequest{ Permissions: parsedPermissions, + RootKeyId: rootKeyID, } resp, err := client.BakeMacaroon(context.Background(), req) if err != nil { @@ -180,3 +192,80 @@ func bakeMacaroon(ctx *cli.Context) error { return nil } + +var listMacaroonIDsCommand = cli.Command{ + Name: "listmacaroonids", + Category: "Macaroons", + Usage: "List all macaroons root key IDs in use.", + Action: actionDecorator(listMacaroonIDs), +} + +func listMacaroonIDs(ctx *cli.Context) error { + client, cleanUp := getClient(ctx) + defer cleanUp() + + req := &lnrpc.ListMacaroonIDsRequest{} + resp, err := client.ListMacaroonIDs(context.Background(), req) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} + +var deleteMacaroonIDCommand = cli.Command{ + Name: "deletemacaroonid", + Category: "Macaroons", + Usage: "Delete a specific macaroon ID.", + ArgsUsage: "root_key_id", + Description: ` + Remove a macaroon ID using the specified root key ID. For example: + + lncli deletemacaroonid 1 + + WARNING + When the ID is deleted, all macaroons created from that root key will + be invalidated. + + Note that the default root key ID 0 cannot be deleted. + `, + Action: actionDecorator(deleteMacaroonID), +} + +func deleteMacaroonID(ctx *cli.Context) error { + client, cleanUp := getClient(ctx) + defer cleanUp() + + // Validate args length. Only one argument is allowed. + if ctx.NArg() != 1 { + return cli.ShowCommandHelp(ctx, "deletemacaroonid") + } + + rootKeyIDString := ctx.Args().First() + + // Convert string into uint64. + rootKeyID, err := strconv.ParseUint(rootKeyIDString, 10, 64) + if err != nil { + return fmt.Errorf("root key ID must be a positive integer") + } + + // Check that the value is not equal to DefaultRootKeyID. Note that the + // server also validates the root key ID when removing it. However, we check + // it here too so that we can give users a nice warning. + if bytes.Equal([]byte(rootKeyIDString), macaroons.DefaultRootKeyID) { + return fmt.Errorf("deleting the default root key ID 0 is not allowed") + } + + // Make the actual RPC call. + req := &lnrpc.DeleteMacaroonIDRequest{ + RootKeyId: rootKeyID, + } + resp, err := client.DeleteMacaroonID(context.Background(), req) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 86e637644..796110861 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -301,6 +301,8 @@ func main() { verifyChanBackupCommand, restoreChanBackupCommand, bakeMacaroonCommand, + listMacaroonIDsCommand, + deleteMacaroonIDCommand, trackPaymentCommand, versionCommand, } diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index da27576fe..6ed36b0f5 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -101,7 +101,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { // yet, exist, so we need to create it with the help of the // main macaroon service. invoicesMac, err := cfg.MacService.NewMacaroon( - context.Background(), macaroons.DefaultRootKeyID, macaroonOps..., + context.Background(), macaroons.DefaultRootKeyID, + macaroonOps..., ) if err != nil { return nil, nil, err diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 3f53dc22d..c295802c4 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -166,7 +166,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { // exist, so we need to create it with the help of the main // macaroon service. routerMac, err := cfg.MacService.NewMacaroon( - context.Background(), macaroons.DefaultRootKeyID, macaroonOps..., + context.Background(), macaroons.DefaultRootKeyID, + macaroonOps..., ) if err != nil { return nil, nil, err diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 5f21c2dfe..a723c3918 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -113,7 +113,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { // exist, so we need to create it with the help of the main // macaroon service. signerMac, err := cfg.MacService.NewMacaroon( - context.Background(), macaroons.DefaultRootKeyID, macaroonOps..., + context.Background(), macaroons.DefaultRootKeyID, + macaroonOps..., ) if err != nil { return nil, nil, err diff --git a/rpcserver.go b/rpcserver.go index 0948b89c8..7a9a3bcda 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -194,6 +194,12 @@ var ( "onchain", "offchain", "address", "message", "peers", "info", "invoices", "signer", "macaroon", } + + // If the --no-macaroons flag is used to start lnd, the macaroon service + // is not initialized. errMacaroonDisabled is then returned when + // macaroon related services are used. + errMacaroonDisabled = fmt.Errorf("macaroon authentication disabled, " + + "remove --no-macaroons flag to enable") ) // stringInSlice returns true if a string is contained in the given slice. @@ -6383,8 +6389,7 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context, // If the --no-macaroons flag is used to start lnd, the macaroon service // is not initialized. Therefore we can't bake new macaroons. if r.macService == nil { - return nil, fmt.Errorf("macaroon authentication disabled, " + - "remove --no-macaroons flag to enable") + return nil, errMacaroonDisabled } helpMsg := fmt.Sprintf("supported actions are %v, supported entities "+ @@ -6416,10 +6421,11 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context, } } - // Convert root key id from uint64 to bytes. Because the DefaultRootKeyID is - // a digit 0 expressed in a byte slice of a string "0", we will keep the IDs - // in the same format - all must be numeric, and must be a byte slice of - // string value of the digit, e.g., uint64(123) to string(123). + // Convert root key id from uint64 to bytes. Because the + // DefaultRootKeyID is a digit 0 expressed in a byte slice of a string + // "0", we will keep the IDs in the same format - all must be numeric, + // and must be a byte slice of string value of the digit, e.g., + // uint64(123) to string(123). rootKeyID := []byte(strconv.FormatUint(req.RootKeyId, 10)) // Bake new macaroon with the given permissions and send it binary @@ -6442,15 +6448,15 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context, // ListMacaroonIDs returns a list of macaroon root key IDs in use. func (r *rpcServer) ListMacaroonIDs(ctx context.Context, - req *lnrpc.ListMacaroonIDsRequest) (*lnrpc.ListMacaroonIDsResponse, error) { + req *lnrpc.ListMacaroonIDsRequest) ( + *lnrpc.ListMacaroonIDsResponse, error) { rpcsLog.Debugf("[listmacaroonids]") // If the --no-macaroons flag is used to start lnd, the macaroon service // is not initialized. Therefore we can't show any IDs. if r.macService == nil { - return nil, fmt.Errorf("macaroon authentication disabled, " + - "remove --no-macaroons flag to enable") + return nil, errMacaroonDisabled } rootKeyIDByteSlice, err := r.macService.ListMacaroonIDs(ctx) @@ -6474,21 +6480,21 @@ func (r *rpcServer) ListMacaroonIDs(ctx context.Context, // DeleteMacaroonID removes a specific macaroon ID. func (r *rpcServer) DeleteMacaroonID(ctx context.Context, - req *lnrpc.DeleteMacaroonIDRequest) (*lnrpc.DeleteMacaroonIDResponse, error) { + req *lnrpc.DeleteMacaroonIDRequest) ( + *lnrpc.DeleteMacaroonIDResponse, error) { rpcsLog.Debugf("[deletemacaroonid]") // If the --no-macaroons flag is used to start lnd, the macaroon service - // is not initialized. Therefore we can't show any IDs. + // is not initialized. Therefore we can't delete any IDs. if r.macService == nil { - return nil, fmt.Errorf("macaroon authentication disabled, " + - "remove --no-macaroons flag to enable") + return nil, errMacaroonDisabled } - // Convert root key id from uint64 to bytes. Because the DefaultRootKeyID is - // a digit 0 expressed in a byte slice of a string "0", we will keep the IDs - // in the same format - all must be digit, and must be a byte slice of - // string value of the digit. + // Convert root key id from uint64 to bytes. Because the + // DefaultRootKeyID is a digit 0 expressed in a byte slice of a string + // "0", we will keep the IDs in the same format - all must be digit, and + // must be a byte slice of string value of the digit. rootKeyID := []byte(strconv.FormatUint(req.RootKeyId, 10)) deletedIDBytes, err := r.macService.DeleteMacaroonID(ctx, rootKeyID) if err != nil { @@ -6496,8 +6502,8 @@ func (r *rpcServer) DeleteMacaroonID(ctx context.Context, } return &lnrpc.DeleteMacaroonIDResponse{ - // If the root key ID doesn't exist, it won't be deleted. We will return - // a response with deleted = false, otherwise true. + // If the root key ID doesn't exist, it won't be deleted. We + // will return a response with deleted = false, otherwise true. Deleted: deletedIDBytes != nil, }, nil }