mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-21 01:00:15 +02:00
Merge pull request #4427 from yyforyongyu/customize-root-key-id
macaroons: customize root key ID
This commit is contained in:
commit
bd18f9acf5
@ -1,11 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
@ -18,7 +20,7 @@ var bakeMacaroonCommand = cli.Command{
|
|||||||
Name: "bakemacaroon",
|
Name: "bakemacaroon",
|
||||||
Category: "Macaroons",
|
Category: "Macaroons",
|
||||||
Usage: "Bakes a new macaroon with the provided list of permissions " +
|
Usage: "Bakes a new macaroon with the provided list of permissions " +
|
||||||
"and restrictions",
|
"and restrictions.",
|
||||||
ArgsUsage: "[--save_to=] [--timeout=] [--ip_address=] permissions...",
|
ArgsUsage: "[--save_to=] [--timeout=] [--ip_address=] permissions...",
|
||||||
Description: `
|
Description: `
|
||||||
Bake a new macaroon that grants the provided permissions and
|
Bake a new macaroon that grants the provided permissions and
|
||||||
@ -48,6 +50,10 @@ var bakeMacaroonCommand = cli.Command{
|
|||||||
Name: "ip_address",
|
Name: "ip_address",
|
||||||
Usage: "the IP address the macaroon will be bound to",
|
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),
|
Action: actionDecorator(bakeMacaroon),
|
||||||
}
|
}
|
||||||
@ -66,6 +72,7 @@ func bakeMacaroon(ctx *cli.Context) error {
|
|||||||
savePath string
|
savePath string
|
||||||
timeout int64
|
timeout int64
|
||||||
ipAddress net.IP
|
ipAddress net.IP
|
||||||
|
rootKeyID uint64
|
||||||
parsedPermissions []*lnrpc.MacaroonPermission
|
parsedPermissions []*lnrpc.MacaroonPermission
|
||||||
err error
|
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
|
// 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
|
// 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.
|
// validated server side. We just make sure we can parse it correctly.
|
||||||
@ -122,6 +133,7 @@ func bakeMacaroon(ctx *cli.Context) error {
|
|||||||
// RPC call.
|
// RPC call.
|
||||||
req := &lnrpc.BakeMacaroonRequest{
|
req := &lnrpc.BakeMacaroonRequest{
|
||||||
Permissions: parsedPermissions,
|
Permissions: parsedPermissions,
|
||||||
|
RootKeyId: rootKeyID,
|
||||||
}
|
}
|
||||||
resp, err := client.BakeMacaroon(context.Background(), req)
|
resp, err := client.BakeMacaroon(context.Background(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -180,3 +192,80 @@ func bakeMacaroon(ctx *cli.Context) error {
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
@ -301,6 +301,8 @@ func main() {
|
|||||||
verifyChanBackupCommand,
|
verifyChanBackupCommand,
|
||||||
restoreChanBackupCommand,
|
restoreChanBackupCommand,
|
||||||
bakeMacaroonCommand,
|
bakeMacaroonCommand,
|
||||||
|
listMacaroonIDsCommand,
|
||||||
|
deleteMacaroonIDCommand,
|
||||||
trackPaymentCommand,
|
trackPaymentCommand,
|
||||||
versionCommand,
|
versionCommand,
|
||||||
}
|
}
|
||||||
|
12
lnd.go
12
lnd.go
@ -863,8 +863,8 @@ func genMacaroons(ctx context.Context, svc *macaroons.Service,
|
|||||||
// access invoice related calls. This is useful for merchants and other
|
// access invoice related calls. This is useful for merchants and other
|
||||||
// services to allow an isolated instance that can only query and
|
// services to allow an isolated instance that can only query and
|
||||||
// modify invoices.
|
// modify invoices.
|
||||||
invoiceMac, err := svc.Oven.NewMacaroon(
|
invoiceMac, err := svc.NewMacaroon(
|
||||||
ctx, bakery.LatestVersion, nil, invoicePermissions...,
|
ctx, macaroons.DefaultRootKeyID, invoicePermissions...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -880,8 +880,8 @@ func genMacaroons(ctx context.Context, svc *macaroons.Service,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate the read-only macaroon and write it to a file.
|
// Generate the read-only macaroon and write it to a file.
|
||||||
roMacaroon, err := svc.Oven.NewMacaroon(
|
roMacaroon, err := svc.NewMacaroon(
|
||||||
ctx, bakery.LatestVersion, nil, readPermissions...,
|
ctx, macaroons.DefaultRootKeyID, readPermissions...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -897,8 +897,8 @@ func genMacaroons(ctx context.Context, svc *macaroons.Service,
|
|||||||
|
|
||||||
// Generate the admin macaroon and write it to a file.
|
// Generate the admin macaroon and write it to a file.
|
||||||
adminPermissions := append(readPermissions, writePermissions...)
|
adminPermissions := append(readPermissions, writePermissions...)
|
||||||
admMacaroon, err := svc.Oven.NewMacaroon(
|
admMacaroon, err := svc.NewMacaroon(
|
||||||
ctx, bakery.LatestVersion, nil, adminPermissions...,
|
ctx, macaroons.DefaultRootKeyID, adminPermissions...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -129,6 +129,11 @@ description):
|
|||||||
* BakeMacaroon
|
* BakeMacaroon
|
||||||
* Bakes a new macaroon with the provided list of permissions and
|
* Bakes a new macaroon with the provided list of permissions and
|
||||||
restrictions
|
restrictions
|
||||||
|
* ListMacaroonIDs
|
||||||
|
* List all the macaroon root key IDs that are in use.
|
||||||
|
* DeleteMacaroonID
|
||||||
|
* Remove a specific macaroon root key ID from the database and invalidates
|
||||||
|
all macaroons derived from the key with that ID.
|
||||||
|
|
||||||
## Service: WalletUnlocker
|
## Service: WalletUnlocker
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||||
)
|
)
|
||||||
@ -119,8 +120,9 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
|||||||
// At this point, we know that the chain notifier macaroon
|
// At this point, we know that the chain notifier macaroon
|
||||||
// doesn't yet, exist, so we need to create it with the help of
|
// doesn't yet, exist, so we need to create it with the help of
|
||||||
// the main macaroon service.
|
// the main macaroon service.
|
||||||
chainNotifierMac, err := cfg.MacService.Oven.NewMacaroon(
|
chainNotifierMac, err := cfg.MacService.NewMacaroon(
|
||||||
context.Background(), bakery.LatestVersion, nil,
|
context.Background(),
|
||||||
|
macaroons.DefaultRootKeyID,
|
||||||
macaroonOps...,
|
macaroonOps...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -99,8 +100,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
|||||||
// At this point, we know that the invoices macaroon doesn't
|
// At this point, we know that the invoices macaroon doesn't
|
||||||
// yet, exist, so we need to create it with the help of the
|
// yet, exist, so we need to create it with the help of the
|
||||||
// main macaroon service.
|
// main macaroon service.
|
||||||
invoicesMac, err := cfg.MacService.Oven.NewMacaroon(
|
invoicesMac, err := cfg.MacService.NewMacaroon(
|
||||||
context.Background(), bakery.LatestVersion, nil,
|
context.Background(), macaroons.DefaultRootKeyID,
|
||||||
macaroonOps...,
|
macaroonOps...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -133,6 +133,10 @@ http:
|
|||||||
- selector: lnrpc.Lightning.BakeMacaroon
|
- selector: lnrpc.Lightning.BakeMacaroon
|
||||||
post: "/v1/macaroon"
|
post: "/v1/macaroon"
|
||||||
body: "*"
|
body: "*"
|
||||||
|
- selector: lnrpc.Lightning.ListMacaroonIDs
|
||||||
|
get: "/v1/macaroon/ids"
|
||||||
|
- selector: lnrpc.Lightning.DeleteMacaroonID
|
||||||
|
delete: "/v1/macaroon/{root_key_id}"
|
||||||
|
|
||||||
# walletunlocker.proto
|
# walletunlocker.proto
|
||||||
- selector: lnrpc.WalletUnlocker.GenSeed
|
- selector: lnrpc.WalletUnlocker.GenSeed
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
|
|
||||||
@ -164,8 +165,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
|||||||
// At this point, we know that the router macaroon doesn't yet,
|
// At this point, we know that the router macaroon doesn't yet,
|
||||||
// exist, so we need to create it with the help of the main
|
// exist, so we need to create it with the help of the main
|
||||||
// macaroon service.
|
// macaroon service.
|
||||||
routerMac, err := cfg.MacService.Oven.NewMacaroon(
|
routerMac, err := cfg.MacService.NewMacaroon(
|
||||||
context.Background(), bakery.LatestVersion, nil,
|
context.Background(), macaroons.DefaultRootKeyID,
|
||||||
macaroonOps...,
|
macaroonOps...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
1723
lnrpc/rpc.pb.go
1723
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -1913,6 +1913,78 @@ func local_request_Lightning_BakeMacaroon_0(ctx context.Context, marshaler runti
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_Lightning_ListMacaroonIDs_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ListMacaroonIDsRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := client.ListMacaroonIDs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Lightning_ListMacaroonIDs_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ListMacaroonIDsRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := server.ListMacaroonIDs(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_Lightning_DeleteMacaroonID_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq DeleteMacaroonIDRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["root_key_id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "root_key_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.RootKeyId, err = runtime.Uint64(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "root_key_id", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.DeleteMacaroonID(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Lightning_DeleteMacaroonID_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq DeleteMacaroonIDRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["root_key_id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "root_key_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.RootKeyId, err = runtime.Uint64(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "root_key_id", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.DeleteMacaroonID(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterLightningHandlerServer registers the http handlers for service Lightning to "mux".
|
// RegisterLightningHandlerServer registers the http handlers for service Lightning to "mux".
|
||||||
// UnaryRPC :call LightningServer directly.
|
// UnaryRPC :call LightningServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
@ -2874,6 +2946,46 @@ func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Lightning_ListMacaroonIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Lightning_ListMacaroonIDs_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Lightning_ListMacaroonIDs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("DELETE", pattern_Lightning_DeleteMacaroonID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Lightning_DeleteMacaroonID_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Lightning_DeleteMacaroonID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3975,6 +4087,46 @@ func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Lightning_ListMacaroonIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Lightning_ListMacaroonIDs_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Lightning_ListMacaroonIDs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("DELETE", pattern_Lightning_DeleteMacaroonID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Lightning_DeleteMacaroonID_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Lightning_DeleteMacaroonID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4084,6 +4236,10 @@ var (
|
|||||||
pattern_Lightning_SubscribeChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "subscribe"}, "", runtime.AssumeColonVerbOpt(true)))
|
pattern_Lightning_SubscribeChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "subscribe"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
|
|
||||||
pattern_Lightning_BakeMacaroon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "macaroon"}, "", runtime.AssumeColonVerbOpt(true)))
|
pattern_Lightning_BakeMacaroon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "macaroon"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
|
|
||||||
|
pattern_Lightning_ListMacaroonIDs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "macaroon", "ids"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
|
|
||||||
|
pattern_Lightning_DeleteMacaroonID_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "macaroon", "root_key_id"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -4192,4 +4348,8 @@ var (
|
|||||||
forward_Lightning_SubscribeChannelBackups_0 = runtime.ForwardResponseStream
|
forward_Lightning_SubscribeChannelBackups_0 = runtime.ForwardResponseStream
|
||||||
|
|
||||||
forward_Lightning_BakeMacaroon_0 = runtime.ForwardResponseMessage
|
forward_Lightning_BakeMacaroon_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Lightning_ListMacaroonIDs_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Lightning_DeleteMacaroonID_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
|
@ -491,6 +491,19 @@ service Lightning {
|
|||||||
offline.
|
offline.
|
||||||
*/
|
*/
|
||||||
rpc BakeMacaroon (BakeMacaroonRequest) returns (BakeMacaroonResponse);
|
rpc BakeMacaroon (BakeMacaroonRequest) returns (BakeMacaroonResponse);
|
||||||
|
|
||||||
|
/* lncli: `listmacaroonids`
|
||||||
|
ListMacaroonIDs returns all root key IDs that are in use.
|
||||||
|
*/
|
||||||
|
rpc ListMacaroonIDs (ListMacaroonIDsRequest)
|
||||||
|
returns (ListMacaroonIDsResponse);
|
||||||
|
|
||||||
|
/* lncli: `deletemacaroonid`
|
||||||
|
DeleteMacaroonID deletes the specified macaroon ID and invalidates all
|
||||||
|
macaroons derived from that ID.
|
||||||
|
*/
|
||||||
|
rpc DeleteMacaroonID (DeleteMacaroonIDRequest)
|
||||||
|
returns (DeleteMacaroonIDResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message Utxo {
|
message Utxo {
|
||||||
@ -3327,12 +3340,31 @@ message MacaroonPermission {
|
|||||||
message BakeMacaroonRequest {
|
message BakeMacaroonRequest {
|
||||||
// The list of permissions the new macaroon should grant.
|
// The list of permissions the new macaroon should grant.
|
||||||
repeated MacaroonPermission permissions = 1;
|
repeated MacaroonPermission permissions = 1;
|
||||||
|
|
||||||
|
// The root key ID used to create the macaroon, must be a positive integer.
|
||||||
|
uint64 root_key_id = 2;
|
||||||
}
|
}
|
||||||
message BakeMacaroonResponse {
|
message BakeMacaroonResponse {
|
||||||
// The hex encoded macaroon, serialized in binary format.
|
// The hex encoded macaroon, serialized in binary format.
|
||||||
string macaroon = 1;
|
string macaroon = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ListMacaroonIDsRequest {
|
||||||
|
}
|
||||||
|
message ListMacaroonIDsResponse {
|
||||||
|
// The list of root key IDs that are in use.
|
||||||
|
repeated uint64 root_key_ids = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteMacaroonIDRequest {
|
||||||
|
// The root key ID to be removed.
|
||||||
|
uint64 root_key_id = 1;
|
||||||
|
}
|
||||||
|
message DeleteMacaroonIDResponse {
|
||||||
|
// A boolean indicates that the deletion is successful.
|
||||||
|
bool deleted = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Failure {
|
message Failure {
|
||||||
enum FailureCode {
|
enum FailureCode {
|
||||||
/*
|
/*
|
||||||
|
@ -1426,6 +1426,62 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/macaroon/ids": {
|
||||||
|
"get": {
|
||||||
|
"summary": "lncli: `listmacaroonids`\nListMacaroonIDs returns all root key IDs that are in use.",
|
||||||
|
"operationId": "ListMacaroonIDs",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/lnrpcListMacaroonIDsResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/runtimeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Lightning"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/macaroon/{root_key_id}": {
|
||||||
|
"delete": {
|
||||||
|
"summary": "lncli: `deletemacaroonid`\nDeleteMacaroonID deletes the specified macaroon ID and invalidates all\nmacaroons derived from that ID.",
|
||||||
|
"operationId": "DeleteMacaroonID",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/lnrpcDeleteMacaroonIDResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/runtimeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "root_key_id",
|
||||||
|
"description": "The root key ID to be removed.",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Lightning"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/newaddress": {
|
"/v1/newaddress": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "lncli: `newaddress`\nNewAddress creates a new address under control of the local wallet.",
|
"summary": "lncli: `newaddress`\nNewAddress creates a new address under control of the local wallet.",
|
||||||
@ -2399,6 +2455,11 @@
|
|||||||
"$ref": "#/definitions/lnrpcMacaroonPermission"
|
"$ref": "#/definitions/lnrpcMacaroonPermission"
|
||||||
},
|
},
|
||||||
"description": "The list of permissions the new macaroon should grant."
|
"description": "The list of permissions the new macaroon should grant."
|
||||||
|
},
|
||||||
|
"root_key_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "The root key ID used to create the macaroon, must be a positive integer."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3161,6 +3222,16 @@
|
|||||||
"lnrpcDeleteAllPaymentsResponse": {
|
"lnrpcDeleteAllPaymentsResponse": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"lnrpcDeleteMacaroonIDResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"deleted": {
|
||||||
|
"type": "boolean",
|
||||||
|
"format": "boolean",
|
||||||
|
"description": "A boolean indicates that the deletion is successful."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcDisconnectPeerResponse": {
|
"lnrpcDisconnectPeerResponse": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@ -4095,6 +4166,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcListMacaroonIDsResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"root_key_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"description": "The list of root key IDs that are in use."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcListPaymentsResponse": {
|
"lnrpcListPaymentsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||||
)
|
)
|
||||||
@ -111,8 +112,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
|||||||
// At this point, we know that the signer macaroon doesn't yet,
|
// At this point, we know that the signer macaroon doesn't yet,
|
||||||
// exist, so we need to create it with the help of the main
|
// exist, so we need to create it with the help of the main
|
||||||
// macaroon service.
|
// macaroon service.
|
||||||
signerMac, err := cfg.MacService.Oven.NewMacaroon(
|
signerMac, err := cfg.MacService.NewMacaroon(
|
||||||
context.Background(), bakery.LatestVersion, nil,
|
context.Background(), macaroons.DefaultRootKeyID,
|
||||||
macaroonOps...,
|
macaroonOps...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"github.com/lightningnetwork/lnd/sweep"
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||||
@ -156,8 +157,9 @@ func New(cfg *Config) (*WalletKit, lnrpc.MacaroonPerms, error) {
|
|||||||
// At this point, we know that the wallet kit macaroon doesn't
|
// At this point, we know that the wallet kit macaroon doesn't
|
||||||
// yet, exist, so we need to create it with the help of the
|
// yet, exist, so we need to create it with the help of the
|
||||||
// main macaroon service.
|
// main macaroon service.
|
||||||
walletKitMac, err := cfg.MacService.Oven.NewMacaroon(
|
walletKitMac, err := cfg.MacService.NewMacaroon(
|
||||||
context.Background(), bakery.LatestVersion, nil,
|
context.Background(),
|
||||||
|
macaroons.DefaultRootKeyID,
|
||||||
macaroonOps...,
|
macaroonOps...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -14287,6 +14287,14 @@ var testsCases = []*testCase{
|
|||||||
name: "macaroon authentication",
|
name: "macaroon authentication",
|
||||||
test: testMacaroonAuthentication,
|
test: testMacaroonAuthentication,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bake macaroon",
|
||||||
|
test: testBakeMacaroon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete macaroon id",
|
||||||
|
test: testDeleteMacaroonID,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "immediate payment after channel opened",
|
name: "immediate payment after channel opened",
|
||||||
test: testPaymentFollowingChannelOpen,
|
test: testPaymentFollowingChannelOpen,
|
||||||
|
@ -208,3 +208,7 @@
|
|||||||
<time> [ERR] RPCS: WS: error closing upgraded conn: write tcp4 <ip>-><ip>: write: connection reset by peer
|
<time> [ERR] RPCS: WS: error closing upgraded conn: write tcp4 <ip>-><ip>: write: connection reset by peer
|
||||||
<time> [ERR] NTFN: chain notifier shutting down
|
<time> [ERR] NTFN: chain notifier shutting down
|
||||||
<time> [ERR] NTFN: Failed getting UTXO: get utxo request cancelled
|
<time> [ERR] NTFN: Failed getting UTXO: get utxo request cancelled
|
||||||
|
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission action. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon]
|
||||||
|
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission entity. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon]
|
||||||
|
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: permission list cannot be empty. specify at least one action/entity pair. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon]
|
||||||
|
<time> [ERR] RPCS: [/lnrpc.Lightning/DeleteMacaroonID]: the specified ID cannot be deleted
|
||||||
|
@ -4,6 +4,9 @@ package itest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
@ -166,3 +169,308 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
t.Fatalf("returned address was not a regtest address")
|
t.Fatalf("returned address was not a regtest address")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testBakeMacaroon checks that when creating macaroons, the permissions param
|
||||||
|
// in the request must be set correctly, and the baked macaroon has the intended
|
||||||
|
// permissions.
|
||||||
|
func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
var (
|
||||||
|
ctxb = context.Background()
|
||||||
|
req = &lnrpc.BakeMacaroonRequest{}
|
||||||
|
testNode = net.Alice
|
||||||
|
)
|
||||||
|
|
||||||
|
// First test: when the permission list is empty in the request, an error
|
||||||
|
// should be returned.
|
||||||
|
adminMac, err := testNode.ReadMacaroon(
|
||||||
|
testNode.AdminMacPath(), defaultTimeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read admin.macaroon from node: %v", err)
|
||||||
|
}
|
||||||
|
conn, err := testNode.ConnectRPCWithMacaroon(adminMac)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect to alice: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
adminMacConnection := lnrpc.NewLightningClient(conn)
|
||||||
|
_, err = adminMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err == nil || !errContains(err, "permission list cannot be empty") {
|
||||||
|
t.Fatalf("expected an error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second test: when the action in the permission list is not valid,
|
||||||
|
// an error should be returned.
|
||||||
|
req = &lnrpc.BakeMacaroonRequest{
|
||||||
|
Permissions: []*lnrpc.MacaroonPermission{
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "invalid123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = adminMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err == nil || !errContains(err, "invalid permission action") {
|
||||||
|
t.Fatalf("expected an error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third test: when the entity in the permission list is not valid,
|
||||||
|
// an error should be returned.
|
||||||
|
req = &lnrpc.BakeMacaroonRequest{
|
||||||
|
Permissions: []*lnrpc.MacaroonPermission{
|
||||||
|
{
|
||||||
|
Entity: "invalid123",
|
||||||
|
Action: "read",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = adminMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err == nil || !errContains(err, "invalid permission entity") {
|
||||||
|
t.Fatalf("expected an error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth test: check that when no root key ID is specified, the default
|
||||||
|
// root key ID is used.
|
||||||
|
req = &lnrpc.BakeMacaroonRequest{
|
||||||
|
Permissions: []*lnrpc.MacaroonPermission{
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "read",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = adminMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listReq := &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
resp, err := adminMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if resp.RootKeyIds[0] != 0 {
|
||||||
|
t.Fatalf("expected ID to be 0, found: %v", resp.RootKeyIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fifth test: create a macaroon use a non-default root key ID.
|
||||||
|
rootKeyID := uint64(4200)
|
||||||
|
req = &lnrpc.BakeMacaroonRequest{
|
||||||
|
RootKeyId: rootKeyID,
|
||||||
|
Permissions: []*lnrpc.MacaroonPermission{
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "read",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bakeResp, err := adminMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
resp, err = adminMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the ListMacaroonIDs should give a list of two IDs, the default ID 0, and
|
||||||
|
// the newly created ID. The returned response is sorted to guarantee the
|
||||||
|
// order so that we can compare them one by one.
|
||||||
|
sort.Slice(resp.RootKeyIds, func(i, j int) bool {
|
||||||
|
return resp.RootKeyIds[i] < resp.RootKeyIds[j]
|
||||||
|
})
|
||||||
|
if resp.RootKeyIds[0] != 0 {
|
||||||
|
t.Fatalf("expected ID to be %v, found: %v", 0, resp.RootKeyIds[0])
|
||||||
|
}
|
||||||
|
if resp.RootKeyIds[1] != rootKeyID {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected ID to be %v, found: %v",
|
||||||
|
rootKeyID, resp.RootKeyIds[1],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sixth test: check the baked macaroon has the intended permissions. It
|
||||||
|
// should succeed in reading, and fail to write a macaroon.
|
||||||
|
newMac, err := readMacaroonFromHex(bakeResp.Macaroon)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load macaroon from bytes, error: %v", err)
|
||||||
|
}
|
||||||
|
conn, err = testNode.ConnectRPCWithMacaroon(newMac)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect to alice: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
newMacConnection := lnrpc.NewLightningClient(conn)
|
||||||
|
|
||||||
|
// BakeMacaroon requires a write permission, so this call should return an
|
||||||
|
// error.
|
||||||
|
_, err = newMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err == nil || !errContains(err, "permission denied") {
|
||||||
|
t.Fatalf("expected an error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMacaroon requires a read permission, so this call should succeed.
|
||||||
|
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
resp, err = newMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current macaroon can only work on entity macaroon, so a GetInfo request
|
||||||
|
// will fail.
|
||||||
|
infoReq := &lnrpc.GetInfoRequest{}
|
||||||
|
_, err = newMacConnection.GetInfo(ctxt, infoReq)
|
||||||
|
if err == nil || !errContains(err, "permission denied") {
|
||||||
|
t.Fatalf("expected error not returned, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testDeleteMacaroonID checks that when deleting a macaroon ID, it removes the
|
||||||
|
// specified ID and invalidates all macaroons derived from the key with that ID.
|
||||||
|
// Also, it checks deleting the reserved marcaroon ID, DefaultRootKeyID or is
|
||||||
|
// forbidden.
|
||||||
|
func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
var (
|
||||||
|
ctxb = context.Background()
|
||||||
|
testNode = net.Alice
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use admin macaroon to create a connection.
|
||||||
|
adminMac, err := testNode.ReadMacaroon(
|
||||||
|
testNode.AdminMacPath(), defaultTimeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read admin.macaroon from node: %v", err)
|
||||||
|
}
|
||||||
|
conn, err := testNode.ConnectRPCWithMacaroon(adminMac)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect to alice: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
adminMacConnection := lnrpc.NewLightningClient(conn)
|
||||||
|
|
||||||
|
// Record the number of macaroon IDs before creation.
|
||||||
|
listReq := &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
listResp, err := adminMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
numMacIDs := len(listResp.RootKeyIds)
|
||||||
|
|
||||||
|
// Create macaroons for testing.
|
||||||
|
rootKeyIDs := []uint64{1, 2, 3}
|
||||||
|
macList := []string{}
|
||||||
|
for _, id := range rootKeyIDs {
|
||||||
|
req := &lnrpc.BakeMacaroonRequest{
|
||||||
|
RootKeyId: id,
|
||||||
|
Permissions: []*lnrpc.MacaroonPermission{
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "read",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := adminMacConnection.BakeMacaroon(ctxt, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
macList = append(macList, resp.Macaroon)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the creation is successful.
|
||||||
|
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
listResp, err = adminMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number of macaroon IDs should be increased by len(rootKeyIDs)
|
||||||
|
if len(listResp.RootKeyIds) != numMacIDs+len(rootKeyIDs) {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected to have %v ids, found: %v",
|
||||||
|
numMacIDs+len(rootKeyIDs), len(listResp.RootKeyIds),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First test: check deleting the DefaultRootKeyID returns an error.
|
||||||
|
defaultID, _ := strconv.ParseUint(
|
||||||
|
string(macaroons.DefaultRootKeyID), 10, 64,
|
||||||
|
)
|
||||||
|
req := &lnrpc.DeleteMacaroonIDRequest{
|
||||||
|
RootKeyId: defaultID,
|
||||||
|
}
|
||||||
|
_, err = adminMacConnection.DeleteMacaroonID(ctxt, req)
|
||||||
|
if err == nil || !errContains(err, macaroons.ErrDeletionForbidden.Error()) {
|
||||||
|
t.Fatalf("expected an error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second test: check deleting the customized ID returns success.
|
||||||
|
req = &lnrpc.DeleteMacaroonIDRequest{
|
||||||
|
RootKeyId: rootKeyIDs[0],
|
||||||
|
}
|
||||||
|
resp, err := adminMacConnection.DeleteMacaroonID(ctxt, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if resp.Deleted != true {
|
||||||
|
t.Fatalf("expected the ID to be deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the deletion is successful.
|
||||||
|
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
listResp, err = adminMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
// The number of macaroon IDs should be decreased by 1.
|
||||||
|
if len(listResp.RootKeyIds) != numMacIDs+len(rootKeyIDs)-1 {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected to have %v ids, found: %v",
|
||||||
|
numMacIDs+len(rootKeyIDs)-1, len(listResp.RootKeyIds),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the deleted macaroon can no longer access macaroon:read.
|
||||||
|
deletedMac, err := readMacaroonFromHex(macList[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load macaroon from bytes, error: %v", err)
|
||||||
|
}
|
||||||
|
conn, err = testNode.ConnectRPCWithMacaroon(deletedMac)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect to alice: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
deletedMacConnection := lnrpc.NewLightningClient(conn)
|
||||||
|
|
||||||
|
// Because the macaroon is deleted, it will be treated as an invalid one.
|
||||||
|
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||||
|
_, err = deletedMacConnection.ListMacaroonIDs(ctxt, listReq)
|
||||||
|
if err == nil || !errContains(err, "cannot get macaroon") {
|
||||||
|
t.Fatalf("expected error not returned, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMacaroonFromHex loads a macaroon from a hex string.
|
||||||
|
func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) {
|
||||||
|
macBytes, err := hex.DecodeString(macHex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := &macaroon.Macaroon{}
|
||||||
|
if err := mac.UnmarshalBinary(macBytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mac, nil
|
||||||
|
}
|
||||||
|
@ -95,8 +95,8 @@ command line.
|
|||||||
Users can create their own macaroons with custom permissions if the provided
|
Users can create their own macaroons with custom permissions if the provided
|
||||||
default macaroons (`admin`, `invoice` and `readonly`) are not sufficient.
|
default macaroons (`admin`, `invoice` and `readonly`) are not sufficient.
|
||||||
|
|
||||||
For example, a macaroon that is only allowed to manage peers would be created
|
For example, a macaroon that is only allowed to manage peers with a default root
|
||||||
with the following command:
|
key `0` would be created with the following command:
|
||||||
|
|
||||||
`lncli bakemacaroon peers:read peers:write`
|
`lncli bakemacaroon peers:read peers:write`
|
||||||
|
|
||||||
@ -114,3 +114,19 @@ removing all three default macaroons (`admin.macaroon`, `invoice.macaroon` and
|
|||||||
`readonly.macaroon`, **NOT** the `macaroons.db`!) from their
|
`readonly.macaroon`, **NOT** the `macaroons.db`!) from their
|
||||||
`data/chain/<chain>/<network>/` directory inside the lnd data directory and
|
`data/chain/<chain>/<network>/` directory inside the lnd data directory and
|
||||||
restarting lnd.
|
restarting lnd.
|
||||||
|
|
||||||
|
|
||||||
|
## Root key rotation
|
||||||
|
|
||||||
|
To manage the root keys used by macaroons, there are `listmacaroonids` and
|
||||||
|
`deletemacaroonid` available through gPRC and command line.
|
||||||
|
Users can view a list of all macaroon root key IDs that are in use using:
|
||||||
|
|
||||||
|
`lncli listmacaroonids`
|
||||||
|
|
||||||
|
And remove a specific macaroon root key ID using command:
|
||||||
|
|
||||||
|
`lncli deletemacaroonid root_key_id`
|
||||||
|
|
||||||
|
Be careful with the `deletemacaroonid` command as when a root key is deleted,
|
||||||
|
**all the macaroons created from it are invalidated**.
|
44
macaroons/context.go
Normal file
44
macaroons/context.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package macaroons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RootKeyIDContextKey is the key to get rootKeyID from context.
|
||||||
|
RootKeyIDContextKey = contextKey{"rootkeyid"}
|
||||||
|
|
||||||
|
// ErrContextRootKeyID is used when the supplied context doesn't have
|
||||||
|
// a root key ID.
|
||||||
|
ErrContextRootKeyID = fmt.Errorf("failed to read root key ID " +
|
||||||
|
"from context")
|
||||||
|
)
|
||||||
|
|
||||||
|
// contextKey is the type we use to identify values in the context.
|
||||||
|
type contextKey struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithRootKeyID passes the root key ID value to context.
|
||||||
|
func ContextWithRootKeyID(ctx context.Context,
|
||||||
|
value interface{}) context.Context {
|
||||||
|
|
||||||
|
return context.WithValue(ctx, RootKeyIDContextKey, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootKeyIDFromContext retrieves the root key ID from context using the key
|
||||||
|
// RootKeyIDContextKey.
|
||||||
|
func RootKeyIDFromContext(ctx context.Context) ([]byte, error) {
|
||||||
|
id, ok := ctx.Value(RootKeyIDContextKey).([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrContextRootKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the id is not empty.
|
||||||
|
if len(id) == 0 {
|
||||||
|
return nil, ErrMissingRootKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
@ -20,6 +20,13 @@ var (
|
|||||||
// DBFilename is the filename within the data directory which contains
|
// DBFilename is the filename within the data directory which contains
|
||||||
// the macaroon stores.
|
// the macaroon stores.
|
||||||
DBFilename = "macaroons.db"
|
DBFilename = "macaroons.db"
|
||||||
|
|
||||||
|
// ErrMissingRootKeyID specifies the root key ID is missing.
|
||||||
|
ErrMissingRootKeyID = fmt.Errorf("missing root key ID")
|
||||||
|
|
||||||
|
// ErrDeletionForbidden is used when attempting to delete the
|
||||||
|
// DefaultRootKeyID or the encryptedKeyID.
|
||||||
|
ErrDeletionForbidden = fmt.Errorf("the specified ID cannot be deleted")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
|
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
|
||||||
@ -197,3 +204,39 @@ func (svc *Service) Close() error {
|
|||||||
func (svc *Service) CreateUnlock(password *[]byte) error {
|
func (svc *Service) CreateUnlock(password *[]byte) error {
|
||||||
return svc.rks.CreateUnlock(password)
|
return svc.rks.CreateUnlock(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMacaroon wraps around the function Oven.NewMacaroon with the defaults,
|
||||||
|
// - version is always bakery.LatestVersion;
|
||||||
|
// - caveats is always nil.
|
||||||
|
// In addition, it takes a rootKeyID parameter, and puts it into the context.
|
||||||
|
// The context is passed through Oven.NewMacaroon(), in which calls the function
|
||||||
|
// RootKey(), that reads the context for rootKeyID.
|
||||||
|
func (svc *Service) NewMacaroon(
|
||||||
|
ctx context.Context, rootKeyID []byte,
|
||||||
|
ops ...bakery.Op) (*bakery.Macaroon, error) {
|
||||||
|
|
||||||
|
// Check rootKeyID is not called with nil or empty bytes. We want the
|
||||||
|
// caller to be aware the value of root key ID used, so we won't replace
|
||||||
|
// it with the DefaultRootKeyID if not specified.
|
||||||
|
if len(rootKeyID) == 0 {
|
||||||
|
return nil, ErrMissingRootKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Pass the root key ID to context.
|
||||||
|
ctx = ContextWithRootKeyID(ctx, rootKeyID)
|
||||||
|
|
||||||
|
return svc.Oven.NewMacaroon(ctx, bakery.LatestVersion, nil, ops...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMacaroonIDs returns all the root key ID values except the value of
|
||||||
|
// encryptedKeyID.
|
||||||
|
func (svc *Service) ListMacaroonIDs(ctxt context.Context) ([][]byte, error) {
|
||||||
|
return svc.rks.ListMacaroonIDs(ctxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMacaroonID removes one specific root key ID. If the root key ID is
|
||||||
|
// found and deleted, it will be returned.
|
||||||
|
func (svc *Service) DeleteMacaroonID(ctxt context.Context,
|
||||||
|
rootKeyID []byte) ([]byte, error) {
|
||||||
|
return svc.rks.DeleteMacaroonID(ctxt, rootKeyID)
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||||
@ -72,8 +73,13 @@ func TestNewService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Third, check if the created service can bake macaroons.
|
// Third, check if the created service can bake macaroons.
|
||||||
macaroon, err := service.Oven.NewMacaroon(
|
_, err = service.NewMacaroon(context.TODO(), nil, testOperation)
|
||||||
context.TODO(), bakery.LatestVersion, nil, testOperation,
|
if err != macaroons.ErrMissingRootKeyID {
|
||||||
|
t.Fatalf("Received %v instead of ErrMissingRootKeyID", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
macaroon, err := service.NewMacaroon(
|
||||||
|
context.TODO(), macaroons.DefaultRootKeyID, testOperation,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error creating macaroon from service: %v", err)
|
t.Fatalf("Error creating macaroon from service: %v", err)
|
||||||
@ -117,8 +123,8 @@ func TestValidateMacaroon(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then, create a new macaroon that we can serialize.
|
// Then, create a new macaroon that we can serialize.
|
||||||
macaroon, err := service.Oven.NewMacaroon(
|
macaroon, err := service.NewMacaroon(
|
||||||
context.TODO(), bakery.LatestVersion, nil, testOperation,
|
context.TODO(), macaroons.DefaultRootKeyID, testOperation,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error creating macaroon from service: %v", err)
|
t.Fatalf("Error creating macaroon from service: %v", err)
|
||||||
@ -141,3 +147,84 @@ func TestValidateMacaroon(t *testing.T) {
|
|||||||
t.Fatalf("Error validating the macaroon: %v", err)
|
t.Fatalf("Error validating the macaroon: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestListMacaroonIDs checks that ListMacaroonIDs returns the expected result.
|
||||||
|
func TestListMacaroonIDs(t *testing.T) {
|
||||||
|
// First, initialize a dummy DB file with a store that the service
|
||||||
|
// can read from. Make sure the file is removed in the end.
|
||||||
|
tempDir := setupTestRootKeyStorage(t)
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
// Second, create the new service instance, unlock it and pass in a
|
||||||
|
// checker that we expect it to add to the bakery.
|
||||||
|
service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker)
|
||||||
|
require.NoError(t, err, "Error creating new service")
|
||||||
|
defer service.Close()
|
||||||
|
|
||||||
|
err = service.CreateUnlock(&defaultPw)
|
||||||
|
require.NoError(t, err, "Error unlocking root key storage")
|
||||||
|
|
||||||
|
// Third, make 3 new macaroons with different root key IDs.
|
||||||
|
expectedIDs := [][]byte{{1}, {2}, {3}}
|
||||||
|
for _, v := range expectedIDs {
|
||||||
|
_, err := service.NewMacaroon(context.TODO(), v, testOperation)
|
||||||
|
require.NoError(t, err, "Error creating macaroon from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, check that calling List return the expected values.
|
||||||
|
ids, _ := service.ListMacaroonIDs(context.TODO())
|
||||||
|
require.Equal(t, expectedIDs, ids, "root key IDs mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDeleteMacaroonID removes the specific root key ID.
|
||||||
|
func TestDeleteMacaroonID(t *testing.T) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// First, initialize a dummy DB file with a store that the service
|
||||||
|
// can read from. Make sure the file is removed in the end.
|
||||||
|
tempDir := setupTestRootKeyStorage(t)
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
// Second, create the new service instance, unlock it and pass in a
|
||||||
|
// checker that we expect it to add to the bakery.
|
||||||
|
service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker)
|
||||||
|
require.NoError(t, err, "Error creating new service")
|
||||||
|
defer service.Close()
|
||||||
|
|
||||||
|
err = service.CreateUnlock(&defaultPw)
|
||||||
|
require.NoError(t, err, "Error unlocking root key storage")
|
||||||
|
|
||||||
|
// Third, checks that removing encryptedKeyID returns an error.
|
||||||
|
encryptedKeyID := []byte("enckey")
|
||||||
|
_, err = service.DeleteMacaroonID(ctxb, encryptedKeyID)
|
||||||
|
require.Equal(t, macaroons.ErrDeletionForbidden, err)
|
||||||
|
|
||||||
|
// Fourth, checks that removing DefaultKeyID returns an error.
|
||||||
|
_, err = service.DeleteMacaroonID(ctxb, macaroons.DefaultRootKeyID)
|
||||||
|
require.Equal(t, macaroons.ErrDeletionForbidden, err)
|
||||||
|
|
||||||
|
// Fifth, checks that removing empty key id returns an error.
|
||||||
|
_, err = service.DeleteMacaroonID(ctxb, []byte{})
|
||||||
|
require.Equal(t, macaroons.ErrMissingRootKeyID, err)
|
||||||
|
|
||||||
|
// Sixth, checks that removing a non-existed key id returns nil.
|
||||||
|
nonExistedID := []byte("test-non-existed")
|
||||||
|
deletedID, err := service.DeleteMacaroonID(ctxb, nonExistedID)
|
||||||
|
require.NoError(t, err, "deleting macaroon ID got an error")
|
||||||
|
require.Nil(t, deletedID, "deleting non-existed ID should return nil")
|
||||||
|
|
||||||
|
// Seventh, make 3 new macaroons with different root key IDs, and delete
|
||||||
|
// one.
|
||||||
|
expectedIDs := [][]byte{{1}, {2}, {3}}
|
||||||
|
for _, v := range expectedIDs {
|
||||||
|
_, err := service.NewMacaroon(ctxb, v, testOperation)
|
||||||
|
require.NoError(t, err, "Error creating macaroon from service")
|
||||||
|
}
|
||||||
|
deletedID, err = service.DeleteMacaroonID(ctxb, expectedIDs[0])
|
||||||
|
require.NoError(t, err, "deleting macaroon ID got an error")
|
||||||
|
|
||||||
|
// Finally, check that the ID is deleted.
|
||||||
|
require.Equal(t, expectedIDs[0], deletedID, "expected ID to be removed")
|
||||||
|
ids, _ := service.ListMacaroonIDs(ctxb)
|
||||||
|
require.Equal(t, expectedIDs[1:], ids, "root key IDs mismatch")
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package macaroons
|
package macaroons
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -21,11 +22,9 @@ var (
|
|||||||
// rootKeyBucketName is the name of the root key store bucket.
|
// rootKeyBucketName is the name of the root key store bucket.
|
||||||
rootKeyBucketName = []byte("macrootkeys")
|
rootKeyBucketName = []byte("macrootkeys")
|
||||||
|
|
||||||
// defaultRootKeyID is the ID of the default root key. The first is
|
// DefaultRootKeyID is the ID of the default root key. The first is
|
||||||
// just 0, to emulate the memory storage that comes with bakery.
|
// just 0, to emulate the memory storage that comes with bakery.
|
||||||
//
|
DefaultRootKeyID = []byte("0")
|
||||||
// TODO(aakselrod): Add support for key rotation.
|
|
||||||
defaultRootKeyID = []byte("0")
|
|
||||||
|
|
||||||
// encryptedKeyID is the name of the database key that stores the
|
// encryptedKeyID is the name of the database key that stores the
|
||||||
// encryption key, encrypted with a salted + hashed password. The
|
// encryption key, encrypted with a salted + hashed password. The
|
||||||
@ -42,6 +41,10 @@ var (
|
|||||||
|
|
||||||
// ErrPasswordRequired specifies that a nil password has been passed.
|
// ErrPasswordRequired specifies that a nil password has been passed.
|
||||||
ErrPasswordRequired = fmt.Errorf("a non-nil password is required")
|
ErrPasswordRequired = fmt.Errorf("a non-nil password is required")
|
||||||
|
|
||||||
|
// ErrKeyValueForbidden is used when the root key ID uses encryptedKeyID as
|
||||||
|
// its value.
|
||||||
|
ErrKeyValueForbidden = fmt.Errorf("root key ID value is not allowed")
|
||||||
)
|
)
|
||||||
|
|
||||||
// RootKeyStorage implements the bakery.RootKeyStorage interface.
|
// RootKeyStorage implements the bakery.RootKeyStorage interface.
|
||||||
@ -157,8 +160,7 @@ func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error) {
|
|||||||
|
|
||||||
// RootKey implements the RootKey method for the bakery.RootKeyStorage
|
// RootKey implements the RootKey method for the bakery.RootKeyStorage
|
||||||
// interface.
|
// interface.
|
||||||
// TODO(aakselrod): Add support for key rotation.
|
func (r *RootKeyStorage) RootKey(ctx context.Context) ([]byte, []byte, error) {
|
||||||
func (r *RootKeyStorage) RootKey(_ context.Context) ([]byte, []byte, error) {
|
|
||||||
r.encKeyMtx.RLock()
|
r.encKeyMtx.RLock()
|
||||||
defer r.encKeyMtx.RUnlock()
|
defer r.encKeyMtx.RUnlock()
|
||||||
|
|
||||||
@ -166,8 +168,19 @@ func (r *RootKeyStorage) RootKey(_ context.Context) ([]byte, []byte, error) {
|
|||||||
return nil, nil, ErrStoreLocked
|
return nil, nil, ErrStoreLocked
|
||||||
}
|
}
|
||||||
var rootKey []byte
|
var rootKey []byte
|
||||||
id := defaultRootKeyID
|
|
||||||
err := kvdb.Update(r, func(tx kvdb.RwTx) error {
|
// Read the root key ID from the context. If no key is specified in the
|
||||||
|
// context, an error will be returned.
|
||||||
|
id, err := RootKeyIDFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Equal(id, encryptedKeyID) {
|
||||||
|
return nil, nil, ErrKeyValueForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
err = kvdb.Update(r, func(tx kvdb.RwTx) error {
|
||||||
ns := tx.ReadWriteBucket(rootKeyBucketName)
|
ns := tx.ReadWriteBucket(rootKeyBucketName)
|
||||||
dbKey := ns.Get(id)
|
dbKey := ns.Get(id)
|
||||||
|
|
||||||
@ -215,3 +228,88 @@ func (r *RootKeyStorage) Close() error {
|
|||||||
}
|
}
|
||||||
return r.Backend.Close()
|
return r.Backend.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListMacaroonIDs returns all the root key ID values except the value of
|
||||||
|
// encryptedKeyID.
|
||||||
|
func (r *RootKeyStorage) ListMacaroonIDs(_ context.Context) ([][]byte, error) {
|
||||||
|
r.encKeyMtx.RLock()
|
||||||
|
defer r.encKeyMtx.RUnlock()
|
||||||
|
|
||||||
|
// Check it's unlocked.
|
||||||
|
if r.encKey == nil {
|
||||||
|
return nil, ErrStoreLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootKeySlice [][]byte
|
||||||
|
|
||||||
|
// Read all the items in the bucket and append the keys, which are the
|
||||||
|
// root key IDs we want.
|
||||||
|
err := kvdb.View(r, func(tx kvdb.RTx) error {
|
||||||
|
|
||||||
|
// appendRootKey is a function closure that appends root key ID
|
||||||
|
// to rootKeySlice.
|
||||||
|
appendRootKey := func(k, _ []byte) error {
|
||||||
|
// Only append when the key value is not encryptedKeyID.
|
||||||
|
if !bytes.Equal(k, encryptedKeyID) {
|
||||||
|
rootKeySlice = append(rootKeySlice, k)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.ReadBucket(rootKeyBucketName).ForEach(appendRootKey)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootKeySlice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMacaroonID removes one specific root key ID. If the root key ID is
|
||||||
|
// found and deleted, it will be returned.
|
||||||
|
func (r *RootKeyStorage) DeleteMacaroonID(
|
||||||
|
_ context.Context, rootKeyID []byte) ([]byte, error) {
|
||||||
|
|
||||||
|
r.encKeyMtx.RLock()
|
||||||
|
defer r.encKeyMtx.RUnlock()
|
||||||
|
|
||||||
|
// Check it's unlocked.
|
||||||
|
if r.encKey == nil {
|
||||||
|
return nil, ErrStoreLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the rootKeyID is not empty.
|
||||||
|
if len(rootKeyID) == 0 {
|
||||||
|
return nil, ErrMissingRootKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting encryptedKeyID or DefaultRootKeyID is not allowed.
|
||||||
|
if bytes.Equal(rootKeyID, encryptedKeyID) ||
|
||||||
|
bytes.Equal(rootKeyID, DefaultRootKeyID) {
|
||||||
|
|
||||||
|
return nil, ErrDeletionForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootKeyIDDeleted []byte
|
||||||
|
err := kvdb.Update(r, func(tx kvdb.RwTx) error {
|
||||||
|
bucket := tx.ReadWriteBucket(rootKeyBucketName)
|
||||||
|
|
||||||
|
// Check the key can be found. If not, return nil.
|
||||||
|
if bucket.Get(rootKeyID) == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the key is found, we do the deletion.
|
||||||
|
if err := bucket.Delete(rootKeyID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rootKeyIDDeleted = rootKeyID
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootKeyIDDeleted, nil
|
||||||
|
}
|
||||||
|
@ -51,13 +51,45 @@ func TestStore(t *testing.T) {
|
|||||||
t.Fatalf("Error creating store encryption key: %v", err)
|
t.Fatalf("Error creating store encryption key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
key, id, err := store.RootKey(context.TODO())
|
// Check ErrContextRootKeyID is returned when no root key ID found in
|
||||||
|
// context.
|
||||||
|
_, _, err = store.RootKey(context.TODO())
|
||||||
|
if err != macaroons.ErrContextRootKeyID {
|
||||||
|
t.Fatalf("Received %v instead of ErrContextRootKeyID", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ErrMissingRootKeyID is returned when empty root key ID is used.
|
||||||
|
emptyKeyID := []byte{}
|
||||||
|
badCtx := macaroons.ContextWithRootKeyID(context.TODO(), emptyKeyID)
|
||||||
|
_, _, err = store.RootKey(badCtx)
|
||||||
|
if err != macaroons.ErrMissingRootKeyID {
|
||||||
|
t.Fatalf("Received %v instead of ErrMissingRootKeyID", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a context with illegal root key ID value.
|
||||||
|
encryptedKeyID := []byte("enckey")
|
||||||
|
badCtx = macaroons.ContextWithRootKeyID(context.TODO(), encryptedKeyID)
|
||||||
|
_, _, err = store.RootKey(badCtx)
|
||||||
|
if err != macaroons.ErrKeyValueForbidden {
|
||||||
|
t.Fatalf("Received %v instead of ErrKeyValueForbidden", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a context with root key ID value.
|
||||||
|
ctx := macaroons.ContextWithRootKeyID(
|
||||||
|
context.TODO(), macaroons.DefaultRootKeyID,
|
||||||
|
)
|
||||||
|
key, id, err := store.RootKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting root key from store: %v", err)
|
t.Fatalf("Error getting root key from store: %v", err)
|
||||||
}
|
}
|
||||||
rootID := id
|
|
||||||
|
|
||||||
key2, err := store.Get(context.TODO(), id)
|
rootID := id
|
||||||
|
if !bytes.Equal(rootID, macaroons.DefaultRootKeyID) {
|
||||||
|
t.Fatalf("Root key ID doesn't match: expected %v, got %v",
|
||||||
|
macaroons.DefaultRootKeyID, rootID)
|
||||||
|
}
|
||||||
|
|
||||||
|
key2, err := store.Get(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting key with ID %s: %v", string(id), err)
|
t.Fatalf("Error getting key with ID %s: %v", string(id), err)
|
||||||
}
|
}
|
||||||
@ -100,12 +132,12 @@ func TestStore(t *testing.T) {
|
|||||||
t.Fatalf("Received %v instead of ErrPasswordRequired", err)
|
t.Fatalf("Received %v instead of ErrPasswordRequired", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = store.RootKey(context.TODO())
|
_, _, err = store.RootKey(ctx)
|
||||||
if err != macaroons.ErrStoreLocked {
|
if err != macaroons.ErrStoreLocked {
|
||||||
t.Fatalf("Received %v instead of ErrStoreLocked", err)
|
t.Fatalf("Received %v instead of ErrStoreLocked", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = store.Get(context.TODO(), nil)
|
_, err = store.Get(ctx, nil)
|
||||||
if err != macaroons.ErrStoreLocked {
|
if err != macaroons.ErrStoreLocked {
|
||||||
t.Fatalf("Received %v instead of ErrStoreLocked", err)
|
t.Fatalf("Received %v instead of ErrStoreLocked", err)
|
||||||
}
|
}
|
||||||
@ -115,7 +147,7 @@ func TestStore(t *testing.T) {
|
|||||||
t.Fatalf("Error unlocking root key store: %v", err)
|
t.Fatalf("Error unlocking root key store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err = store.Get(context.TODO(), rootID)
|
key, err = store.Get(ctx, rootID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting key with ID %s: %v",
|
t.Fatalf("Error getting key with ID %s: %v",
|
||||||
string(rootID), err)
|
string(rootID), err)
|
||||||
@ -125,7 +157,7 @@ func TestStore(t *testing.T) {
|
|||||||
key2, key)
|
key2, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
key, id, err = store.RootKey(context.TODO())
|
key, id, err = store.RootKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting root key from store: %v", err)
|
t.Fatalf("Error getting root key from store: %v", err)
|
||||||
}
|
}
|
||||||
|
99
rpcserver.go
99
rpcserver.go
@ -12,6 +12,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -107,6 +108,10 @@ var (
|
|||||||
Entity: "signer",
|
Entity: "signer",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "read",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePermissions is a slice of all entities that allow write
|
// writePermissions is a slice of all entities that allow write
|
||||||
@ -148,6 +153,10 @@ var (
|
|||||||
Entity: "macaroon",
|
Entity: "macaroon",
|
||||||
Action: "generate",
|
Action: "generate",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "write",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoicePermissions is a slice of all the entities that allows a user
|
// invoicePermissions is a slice of all the entities that allows a user
|
||||||
@ -185,6 +194,12 @@ var (
|
|||||||
"onchain", "offchain", "address", "message",
|
"onchain", "offchain", "address", "message",
|
||||||
"peers", "info", "invoices", "signer", "macaroon",
|
"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.
|
// stringInSlice returns true if a string is contained in the given slice.
|
||||||
@ -429,6 +444,14 @@ func mainRPCServerPermissions() map[string][]bakery.Op {
|
|||||||
Entity: "macaroon",
|
Entity: "macaroon",
|
||||||
Action: "generate",
|
Action: "generate",
|
||||||
}},
|
}},
|
||||||
|
"/lnrpc.Lightning/ListMacaroonIDs": {{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "read",
|
||||||
|
}},
|
||||||
|
"/lnrpc.Lightning/DeleteMacaroonID": {{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "write",
|
||||||
|
}},
|
||||||
"/lnrpc.Lightning/SubscribePeerEvents": {{
|
"/lnrpc.Lightning/SubscribePeerEvents": {{
|
||||||
Entity: "peers",
|
Entity: "peers",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
@ -6387,8 +6410,7 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context,
|
|||||||
// If the --no-macaroons flag is used to start lnd, the macaroon service
|
// If the --no-macaroons flag is used to start lnd, the macaroon service
|
||||||
// is not initialized. Therefore we can't bake new macaroons.
|
// is not initialized. Therefore we can't bake new macaroons.
|
||||||
if r.macService == nil {
|
if r.macService == nil {
|
||||||
return nil, fmt.Errorf("macaroon authentication disabled, " +
|
return nil, errMacaroonDisabled
|
||||||
"remove --no-macaroons flag to enable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
helpMsg := fmt.Sprintf("supported actions are %v, supported entities "+
|
helpMsg := fmt.Sprintf("supported actions are %v, supported entities "+
|
||||||
@ -6420,10 +6442,17 @@ 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).
|
||||||
|
rootKeyID := []byte(strconv.FormatUint(req.RootKeyId, 10))
|
||||||
|
|
||||||
// Bake new macaroon with the given permissions and send it binary
|
// Bake new macaroon with the given permissions and send it binary
|
||||||
// serialized and hex encoded to the client.
|
// serialized and hex encoded to the client.
|
||||||
newMac, err := r.macService.Oven.NewMacaroon(
|
newMac, err := r.macService.NewMacaroon(
|
||||||
ctx, bakery.LatestVersion, nil, requestedPermissions...,
|
ctx, rootKeyID, requestedPermissions...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -6438,6 +6467,68 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context,
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListMacaroonIDs returns a list of macaroon root key IDs in use.
|
||||||
|
func (r *rpcServer) ListMacaroonIDs(ctx context.Context,
|
||||||
|
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, errMacaroonDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
rootKeyIDByteSlice, err := r.macService.ListMacaroonIDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootKeyIDs []uint64
|
||||||
|
for _, value := range rootKeyIDByteSlice {
|
||||||
|
// Convert bytes into uint64.
|
||||||
|
id, err := strconv.ParseUint(string(value), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rootKeyIDs = append(rootKeyIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lnrpc.ListMacaroonIDsResponse{RootKeyIds: rootKeyIDs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMacaroonID removes a specific macaroon ID.
|
||||||
|
func (r *rpcServer) DeleteMacaroonID(ctx context.Context,
|
||||||
|
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 delete any IDs.
|
||||||
|
if r.macService == nil {
|
||||||
|
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.
|
||||||
|
rootKeyID := []byte(strconv.FormatUint(req.RootKeyId, 10))
|
||||||
|
deletedIDBytes, err := r.macService.DeleteMacaroonID(ctx, rootKeyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
Deleted: deletedIDBytes != nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FundingStateStep is an advanced funding related call that allows the caller
|
// FundingStateStep is an advanced funding related call that allows the caller
|
||||||
// to either execute some preparatory steps for a funding workflow, or manually
|
// to either execute some preparatory steps for a funding workflow, or manually
|
||||||
// progress a funding workflow. The primary way a funding flow is identified is
|
// progress a funding workflow. The primary way a funding flow is identified is
|
||||||
|
Loading…
x
Reference in New Issue
Block a user