lnrpc: add command for baking new macaroons

This commit is contained in:
Oliver Gugger 2019-10-23 13:28:17 +02:00
parent c66c4052ec
commit 6a463de7a2
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
6 changed files with 944 additions and 534 deletions

View File

@ -124,6 +124,9 @@ description):
* 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

File diff suppressed because it is too large Load Diff

View File

@ -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
// 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) {
@ -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
}
@ -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_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 (
@ -2358,4 +2402,6 @@ var (
forward_Lightning_VerifyChanBackup_0 = runtime.ForwardResponseMessage
forward_Lightning_RestoreChannelBackups_0 = runtime.ForwardResponseMessage
forward_Lightning_BakeMacaroon_0 = runtime.ForwardResponseMessage
)

View File

@ -777,6 +777,18 @@ service Lightning {
*/
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 {
@ -2585,3 +2597,19 @@ message ChannelBackupSubscription {}
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"];
}

View File

@ -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": {
"get": {
"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)",
"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": {
"type": "object",
"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": {
"type": "object",
"properties": {

View File

@ -151,6 +151,10 @@ var (
Entity: "signer",
Action: "generate",
},
{
Entity: "macaroon",
Action: "generate",
},
}
// invoicePermissions is a slice of all the entities that allows a user
@ -174,8 +178,28 @@ var (
Action: "write",
},
}
// 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
// the permissions they require.
func mainRPCServerPermissions() map[string][]bakery.Op {
@ -396,6 +420,10 @@ func mainRPCServerPermissions() map[string][]bakery.Op {
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/BakeMacaroon": {{
Entity: "macaroon",
Action: "generate",
}},
}
}
@ -449,6 +477,10 @@ type rpcServer struct {
chanPredicate *chanacceptor.ChainedAcceptor
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
@ -623,6 +655,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
routerBackend: routerBackend,
chanPredicate: chanPredicate,
quit: make(chan struct{}, 1),
macService: macService,
}
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
@ -5243,3 +5276,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
}