mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-25 16:23:49 +02:00
Merge pull request #1160 from guggero/new-macaroon
lncli: add command to create new macaroon
This commit is contained in:
commit
b110a3a197
182
cmd/lncli/cmd_bake_macaroon.go
Normal file
182
cmd/lncli/cmd_bake_macaroon.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
"gopkg.in/macaroon.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bakeMacaroonCommand = cli.Command{
|
||||||
|
Name: "bakemacaroon",
|
||||||
|
Category: "Macaroons",
|
||||||
|
Usage: "Bakes a new macaroon with the provided list of permissions " +
|
||||||
|
"and restrictions",
|
||||||
|
ArgsUsage: "[--save_to=] [--timeout=] [--ip_address=] permissions...",
|
||||||
|
Description: `
|
||||||
|
Bake a new macaroon that grants the provided permissions and
|
||||||
|
optionally adds restrictions (timeout, IP address) to it.
|
||||||
|
|
||||||
|
The new macaroon can either be shown on command line in hex serialized
|
||||||
|
format or it can be saved directly to a file using the --save_to
|
||||||
|
argument.
|
||||||
|
|
||||||
|
A permission is a tuple of an entity and an action, separated by a
|
||||||
|
colon. Multiple operations can be added as arguments, for example:
|
||||||
|
|
||||||
|
lncli bakemacaroon info:read invoices:write foo:bar
|
||||||
|
`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "save_to",
|
||||||
|
Usage: "save the created macaroon to this file " +
|
||||||
|
"using the default binary format",
|
||||||
|
},
|
||||||
|
cli.Uint64Flag{
|
||||||
|
Name: "timeout",
|
||||||
|
Usage: "the number of seconds the macaroon will be " +
|
||||||
|
"valid before it times out",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "ip_address",
|
||||||
|
Usage: "the IP address the macaroon will be bound to",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(bakeMacaroon),
|
||||||
|
}
|
||||||
|
|
||||||
|
func bakeMacaroon(ctx *cli.Context) error {
|
||||||
|
client, cleanUp := getClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
// Show command help if no arguments.
|
||||||
|
if ctx.NArg() == 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "bakemacaroon")
|
||||||
|
}
|
||||||
|
args := ctx.Args()
|
||||||
|
|
||||||
|
var (
|
||||||
|
savePath string
|
||||||
|
timeout int64
|
||||||
|
ipAddress net.IP
|
||||||
|
parsedPermissions []*lnrpc.MacaroonPermission
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if ctx.String("save_to") != "" {
|
||||||
|
savePath = cleanAndExpandPath(ctx.String("save_to"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.IsSet("timeout") {
|
||||||
|
timeout = ctx.Int64("timeout")
|
||||||
|
if timeout <= 0 {
|
||||||
|
return fmt.Errorf("timeout must be greater than 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.IsSet("ip_address") {
|
||||||
|
ipAddress = net.ParseIP(ctx.String("ip_address"))
|
||||||
|
if ipAddress == nil {
|
||||||
|
return fmt.Errorf("unable to parse ip_address: %s",
|
||||||
|
ctx.String("ip_address"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
for _, permission := range args {
|
||||||
|
tuple := strings.Split(permission, ":")
|
||||||
|
if len(tuple) != 2 {
|
||||||
|
return fmt.Errorf("unable to parse "+
|
||||||
|
"permission tuple: %s", permission)
|
||||||
|
}
|
||||||
|
entity, action := tuple[0], tuple[1]
|
||||||
|
if entity == "" {
|
||||||
|
return fmt.Errorf("invalid permission [%s]. entity "+
|
||||||
|
"cannot be empty", permission)
|
||||||
|
}
|
||||||
|
if action == "" {
|
||||||
|
return fmt.Errorf("invalid permission [%s]. action "+
|
||||||
|
"cannot be empty", permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No we can assume that we have a formally valid entity:action
|
||||||
|
// tuple. The rest of the validation happens server side.
|
||||||
|
parsedPermissions = append(
|
||||||
|
parsedPermissions, &lnrpc.MacaroonPermission{
|
||||||
|
Entity: entity,
|
||||||
|
Action: action,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have gathered all the input we need and can do the actual
|
||||||
|
// RPC call.
|
||||||
|
req := &lnrpc.BakeMacaroonRequest{
|
||||||
|
Permissions: parsedPermissions,
|
||||||
|
}
|
||||||
|
resp, err := client.BakeMacaroon(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we should have gotten a valid macaroon. Unmarshal it so we can
|
||||||
|
// add first-party caveats (if necessary) to it.
|
||||||
|
macBytes, err := hex.DecodeString(resp.Macaroon)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unmarshalMac := &macaroon.Macaroon{}
|
||||||
|
if err = unmarshalMac.UnmarshalBinary(macBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now apply the desired constraints to the macaroon. This will always
|
||||||
|
// create a new macaroon object, even if no constraints are added.
|
||||||
|
macConstraints := make([]macaroons.Constraint, 0)
|
||||||
|
if timeout > 0 {
|
||||||
|
macConstraints = append(
|
||||||
|
macConstraints, macaroons.TimeoutConstraint(timeout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if ipAddress != nil {
|
||||||
|
macConstraints = append(
|
||||||
|
macConstraints,
|
||||||
|
macaroons.IPLockConstraint(ipAddress.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
constrainedMac, err := macaroons.AddConstraints(
|
||||||
|
unmarshalMac, macConstraints...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
macBytes, err = constrainedMac.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can output the result. We either write it binary serialized to
|
||||||
|
// a file or write to the standard output using hex encoding.
|
||||||
|
switch {
|
||||||
|
case savePath != "":
|
||||||
|
err = ioutil.WriteFile(savePath, macBytes, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Macaroon saved to %s\n", savePath)
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Printf("%s\n", hex.EncodeToString(macBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -298,6 +298,7 @@ func main() {
|
|||||||
exportChanBackupCommand,
|
exportChanBackupCommand,
|
||||||
verifyChanBackupCommand,
|
verifyChanBackupCommand,
|
||||||
restoreChanBackupCommand,
|
restoreChanBackupCommand,
|
||||||
|
bakeMacaroonCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any extra commands determined by build flags.
|
// Add any extra commands determined by build flags.
|
||||||
|
@ -120,7 +120,13 @@ description):
|
|||||||
enforced by the node globally for each channel.
|
enforced by the node globally for each channel.
|
||||||
* UpdateChannelPolicy
|
* UpdateChannelPolicy
|
||||||
* Allows the caller to update the fee schedule and channel policies for all channels
|
* Allows the caller to update the fee schedule and channel policies for all channels
|
||||||
globally, or a particular channel
|
globally, or a particular channel.
|
||||||
|
* ForwardingHistory
|
||||||
|
* ForwardingHistory allows the caller to query the htlcswitch for a
|
||||||
|
record of all HTLCs forwarded.
|
||||||
|
* BakeMacaroon
|
||||||
|
* Bakes a new macaroon with the provided list of permissions and
|
||||||
|
restrictions
|
||||||
|
|
||||||
## Service: WalletUnlocker
|
## Service: WalletUnlocker
|
||||||
|
|
||||||
|
1246
lnrpc/rpc.pb.go
1246
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -834,6 +834,19 @@ func request_Lightning_RestoreChannelBackups_0(ctx context.Context, marshaler ru
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_Lightning_BakeMacaroon_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq BakeMacaroonRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.BakeMacaroon(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterWalletUnlockerHandlerFromEndpoint is same as RegisterWalletUnlockerHandler but
|
// RegisterWalletUnlockerHandlerFromEndpoint is same as RegisterWalletUnlockerHandler but
|
||||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||||
func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||||
@ -2193,6 +2206,35 @@ func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_Lightning_BakeMacaroon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
if cn, ok := w.(http.CloseNotifier); ok {
|
||||||
|
go func(done <-chan struct{}, closed <-chan bool) {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-closed:
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}(ctx.Done(), cn.CloseNotify())
|
||||||
|
}
|
||||||
|
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_BakeMacaroon_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_BakeMacaroon_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2276,6 +2318,8 @@ var (
|
|||||||
pattern_Lightning_VerifyChanBackup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "verify"}, ""))
|
pattern_Lightning_VerifyChanBackup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "verify"}, ""))
|
||||||
|
|
||||||
pattern_Lightning_RestoreChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "restore"}, ""))
|
pattern_Lightning_RestoreChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "restore"}, ""))
|
||||||
|
|
||||||
|
pattern_Lightning_BakeMacaroon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "macaroon"}, ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -2358,4 +2402,6 @@ var (
|
|||||||
forward_Lightning_VerifyChanBackup_0 = runtime.ForwardResponseMessage
|
forward_Lightning_VerifyChanBackup_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_Lightning_RestoreChannelBackups_0 = runtime.ForwardResponseMessage
|
forward_Lightning_RestoreChannelBackups_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Lightning_BakeMacaroon_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
|
@ -777,6 +777,18 @@ service Lightning {
|
|||||||
*/
|
*/
|
||||||
rpc SubscribeChannelBackups(ChannelBackupSubscription) returns (stream ChanBackupSnapshot) {
|
rpc SubscribeChannelBackups(ChannelBackupSubscription) returns (stream ChanBackupSnapshot) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** lncli: `bakemacaroon`
|
||||||
|
BakeMacaroon allows the creation of a new macaroon with custom read and
|
||||||
|
write permissions. No first-party caveats are added since this can be done
|
||||||
|
offline.
|
||||||
|
*/
|
||||||
|
rpc BakeMacaroon(BakeMacaroonRequest) returns (BakeMacaroonResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v1/macaroon"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
message Utxo {
|
message Utxo {
|
||||||
@ -2585,3 +2597,19 @@ message ChannelBackupSubscription {}
|
|||||||
|
|
||||||
message VerifyChanBackupResponse {
|
message VerifyChanBackupResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message MacaroonPermission {
|
||||||
|
/// The entity a permission grants access to.
|
||||||
|
string entity = 1 [json_name = "entity"];
|
||||||
|
|
||||||
|
/// The action that is granted.
|
||||||
|
string action = 2 [json_name = "action"];
|
||||||
|
}
|
||||||
|
message BakeMacaroonRequest {
|
||||||
|
/// The list of permissions the new macaroon should grant.
|
||||||
|
repeated MacaroonPermission permissions = 1 [json_name = "permissions"];
|
||||||
|
}
|
||||||
|
message BakeMacaroonResponse {
|
||||||
|
/// The hex encoded macaroon, serialized in binary format.
|
||||||
|
string macaroon = 1 [json_name = "macaroon"];
|
||||||
|
}
|
||||||
|
@ -917,6 +917,33 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/macaroon": {
|
||||||
|
"post": {
|
||||||
|
"summary": "* lncli: `bakemacaroon`\nBakeMacaroon allows the creation of a new macaroon with custom read and\nwrite permissions. No first-party caveats are added since this can be done\noffline.",
|
||||||
|
"operationId": "BakeMacaroon",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/lnrpcBakeMacaroonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/lnrpcBakeMacaroonRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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.",
|
||||||
@ -1512,6 +1539,27 @@
|
|||||||
"description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)",
|
"description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)",
|
||||||
"title": "* \n`AddressType` has to be one of:"
|
"title": "* \n`AddressType` has to be one of:"
|
||||||
},
|
},
|
||||||
|
"lnrpcBakeMacaroonRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/lnrpcMacaroonPermission"
|
||||||
|
},
|
||||||
|
"description": "/ The list of permissions the new macaroon should grant."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lnrpcBakeMacaroonResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"macaroon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "/ The hex encoded macaroon, serialized in binary format."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcChain": {
|
"lnrpcChain": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -2746,6 +2794,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcMacaroonPermission": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "/ The entity a permission grants access to."
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "/ The action that is granted."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcMultiChanBackup": {
|
"lnrpcMultiChanBackup": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
94
rpcserver.go
94
rpcserver.go
@ -151,6 +151,10 @@ var (
|
|||||||
Entity: "signer",
|
Entity: "signer",
|
||||||
Action: "generate",
|
Action: "generate",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "generate",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoicePermissions is a slice of all the entities that allows a user
|
// invoicePermissions is a slice of all the entities that allows a user
|
||||||
@ -178,8 +182,28 @@ var (
|
|||||||
Action: "read",
|
Action: "read",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(guggero): Refactor into constants that are used for all
|
||||||
|
// permissions in this file. Also expose the list of possible
|
||||||
|
// permissions in an RPC when per RPC permissions are
|
||||||
|
// implemented.
|
||||||
|
validActions = []string{"read", "write", "generate"}
|
||||||
|
validEntities = []string{
|
||||||
|
"onchain", "offchain", "address", "message",
|
||||||
|
"peers", "info", "invoices", "signer", "macaroon",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// stringInSlice returns true if a string is contained in the given slice.
|
||||||
|
func stringInSlice(a string, slice []string) bool {
|
||||||
|
for _, b := range slice {
|
||||||
|
if b == a {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// mainRPCServerPermissions returns a mapping of the main RPC server calls to
|
// mainRPCServerPermissions returns a mapping of the main RPC server calls to
|
||||||
// the permissions they require.
|
// the permissions they require.
|
||||||
func mainRPCServerPermissions() map[string][]bakery.Op {
|
func mainRPCServerPermissions() map[string][]bakery.Op {
|
||||||
@ -400,6 +424,10 @@ func mainRPCServerPermissions() map[string][]bakery.Op {
|
|||||||
Entity: "offchain",
|
Entity: "offchain",
|
||||||
Action: "write",
|
Action: "write",
|
||||||
}},
|
}},
|
||||||
|
"/lnrpc.Lightning/BakeMacaroon": {{
|
||||||
|
Entity: "macaroon",
|
||||||
|
Action: "generate",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,6 +481,10 @@ type rpcServer struct {
|
|||||||
chanPredicate *chanacceptor.ChainedAcceptor
|
chanPredicate *chanacceptor.ChainedAcceptor
|
||||||
|
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
|
|
||||||
|
// macService is the macaroon service that we need to mint new
|
||||||
|
// macaroons.
|
||||||
|
macService *macaroons.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure that rpcServer fully implements the
|
// A compile time check to ensure that rpcServer fully implements the
|
||||||
@ -627,6 +659,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
|||||||
routerBackend: routerBackend,
|
routerBackend: routerBackend,
|
||||||
chanPredicate: chanPredicate,
|
chanPredicate: chanPredicate,
|
||||||
quit: make(chan struct{}, 1),
|
quit: make(chan struct{}, 1),
|
||||||
|
macService: macService,
|
||||||
}
|
}
|
||||||
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
|
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
|
||||||
|
|
||||||
@ -5247,3 +5280,64 @@ func (r *rpcServer) ChannelAcceptor(stream lnrpc.Lightning_ChannelAcceptorServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BakeMacaroon allows the creation of a new macaroon with custom read and write
|
||||||
|
// permissions. No first-party caveats are added since this can be done offline.
|
||||||
|
func (r *rpcServer) BakeMacaroon(ctx context.Context,
|
||||||
|
req *lnrpc.BakeMacaroonRequest) (*lnrpc.BakeMacaroonResponse, error) {
|
||||||
|
|
||||||
|
rpcsLog.Debugf("[bakemacaroon]")
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
helpMsg := fmt.Sprintf("supported actions are %v, supported entities "+
|
||||||
|
"are %v", validActions, validEntities)
|
||||||
|
|
||||||
|
// Don't allow empty permission list as it doesn't make sense to have
|
||||||
|
// a macaroon that is not allowed to access any RPC.
|
||||||
|
if len(req.Permissions) == 0 {
|
||||||
|
return nil, fmt.Errorf("permission list cannot be empty. "+
|
||||||
|
"specify at least one action/entity pair. %s", helpMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and map permission struct used by gRPC to the one used by
|
||||||
|
// the bakery.
|
||||||
|
requestedPermissions := make([]bakery.Op, len(req.Permissions))
|
||||||
|
for idx, op := range req.Permissions {
|
||||||
|
if !stringInSlice(op.Action, validActions) {
|
||||||
|
return nil, fmt.Errorf("invalid permission action. %s",
|
||||||
|
helpMsg)
|
||||||
|
}
|
||||||
|
if !stringInSlice(op.Entity, validEntities) {
|
||||||
|
return nil, fmt.Errorf("invalid permission entity. %s",
|
||||||
|
helpMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedPermissions[idx] = bakery.Op{
|
||||||
|
Entity: op.Entity,
|
||||||
|
Action: op.Action,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bake new macaroon with the given permissions and send it binary
|
||||||
|
// serialized and hex encoded to the client.
|
||||||
|
newMac, err := r.macService.Oven.NewMacaroon(
|
||||||
|
ctx, bakery.LatestVersion, nil, requestedPermissions...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newMacBytes, err := newMac.M().MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := &lnrpc.BakeMacaroonResponse{}
|
||||||
|
resp.Macaroon = hex.EncodeToString(newMacBytes)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user