Merge pull request #9739 from ellemouton/rpcInterceptorMD
lnrpc+rpcperms: add ctx metadata pairs to RPCMiddlewareRequest
This commit is contained in:
6 files changed
+958
-781
No files matched your search
@@ -307,6 +307,10 @@ close transaction.
|
|||||||
|
|
||||||
* [Allow custom lock ID and
|
* [Allow custom lock ID and
|
||||||
duration in FundPsbt](https://github.com/lightningnetwork/lnd/pull/9724) RPC.
|
duration in FundPsbt](https://github.com/lightningnetwork/lnd/pull/9724) RPC.
|
||||||
|
|
||||||
|
* Expand the [lnrpc.RPCMiddlewareRequest](https://github.com/lightningnetwork/lnd/pull/9739)
|
||||||
|
to include any [gRPC metadata](https://grpc.io/docs/guides/metadata) pairs
|
||||||
|
that are passed to the initial request via the `context.Context`.
|
||||||
|
|
||||||
## lncli Updates
|
## lncli Updates
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"github.com/lightningnetwork/lnd/zpay32"
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"gopkg.in/macaroon.v2"
|
"gopkg.in/macaroon.v2"
|
||||||
)
|
)
|
||||||
@@ -223,6 +224,18 @@ func middlewareInterceptionTest(t *testing.T,
|
|||||||
ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// Add some gRPC metadata pairs to the context that we use for one of
|
||||||
|
// the calls. This is so that we can test that the interceptor does
|
||||||
|
// properly receive the pairs via the interceptor request.
|
||||||
|
requestMetadata := metadata.MD{
|
||||||
|
"itest-metadata-key": []string{"itest-metadata-value"},
|
||||||
|
"itest-metadata-key2": []string{
|
||||||
|
"itest-metadata-value1",
|
||||||
|
"itest-metadata-value2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctxm := metadata.NewOutgoingContext(ctxc, requestMetadata)
|
||||||
|
|
||||||
// Create a client connection that we'll use to simulate user requests
|
// Create a client connection that we'll use to simulate user requests
|
||||||
// to lnd with.
|
// to lnd with.
|
||||||
cleanup, client := macaroonClient(t, node, userMac)
|
cleanup, client := macaroonClient(t, node, userMac)
|
||||||
@@ -234,10 +247,11 @@ func middlewareInterceptionTest(t *testing.T,
|
|||||||
req := &lnrpc.ListChannelsRequest{ActiveOnly: true}
|
req := &lnrpc.ListChannelsRequest{ActiveOnly: true}
|
||||||
go registration.interceptUnary(
|
go registration.interceptUnary(
|
||||||
"/lnrpc.Lightning/ListChannels", req, nil, readOnly, false,
|
"/lnrpc.Lightning/ListChannels", req, nil, readOnly, false,
|
||||||
|
requestMetadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do the actual call now and wait for the interceptor to do its thing.
|
// Do the actual call now and wait for the interceptor to do its thing.
|
||||||
resp, err := client.ListChannels(ctxc, req)
|
resp, err := client.ListChannels(ctxm, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Did we receive the correct intercept message?
|
// Did we receive the correct intercept message?
|
||||||
@@ -252,7 +266,7 @@ func middlewareInterceptionTest(t *testing.T,
|
|||||||
}
|
}
|
||||||
go registration.interceptUnary(
|
go registration.interceptUnary(
|
||||||
"/lnrpc.Lightning/ListChannels", invalidReq, nil, readOnly,
|
"/lnrpc.Lightning/ListChannels", invalidReq, nil, readOnly,
|
||||||
false,
|
false, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do the actual call now and wait for the interceptor to do its thing.
|
// Do the actual call now and wait for the interceptor to do its thing.
|
||||||
@@ -384,7 +398,7 @@ func middlewareResponseManipulationTest(t *testing.T,
|
|||||||
req := &lnrpc.ListChannelsRequest{ActiveOnly: true}
|
req := &lnrpc.ListChannelsRequest{ActiveOnly: true}
|
||||||
go registration.interceptUnary(
|
go registration.interceptUnary(
|
||||||
"/lnrpc.Lightning/ListChannels", req, replacementResponse,
|
"/lnrpc.Lightning/ListChannels", req, replacementResponse,
|
||||||
readOnly, false,
|
readOnly, false, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do the actual call now and wait for the interceptor to do its thing.
|
// Do the actual call now and wait for the interceptor to do its thing.
|
||||||
@@ -408,7 +422,7 @@ func middlewareResponseManipulationTest(t *testing.T,
|
|||||||
}
|
}
|
||||||
go registration.interceptUnary(
|
go registration.interceptUnary(
|
||||||
"/lnrpc.Lightning/ListChannels", invalidReq, betterError,
|
"/lnrpc.Lightning/ListChannels", invalidReq, betterError,
|
||||||
readOnly, false,
|
readOnly, false, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do the actual call now and wait for the interceptor to do its thing.
|
// Do the actual call now and wait for the interceptor to do its thing.
|
||||||
@@ -500,7 +514,7 @@ func middlewareRequestManipulationTest(t *testing.T, node *node.HarnessNode,
|
|||||||
}
|
}
|
||||||
go registration.interceptUnary(
|
go registration.interceptUnary(
|
||||||
"/lnrpc.Lightning/AddInvoice", req, replacementRequest,
|
"/lnrpc.Lightning/AddInvoice", req, replacementRequest,
|
||||||
readOnly, true,
|
readOnly, true, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do the actual call now and wait for the interceptor to do its thing.
|
// Do the actual call now and wait for the interceptor to do its thing.
|
||||||
@@ -717,12 +731,28 @@ func registerMiddleware(t *testing.T, node *node.HarnessNode,
|
|||||||
// read from the response channel.
|
// read from the response channel.
|
||||||
func (h *middlewareHarness) interceptUnary(methodURI string,
|
func (h *middlewareHarness) interceptUnary(methodURI string,
|
||||||
expectedRequest proto.Message, responseReplacement interface{},
|
expectedRequest proto.Message, responseReplacement interface{},
|
||||||
readOnly bool, replaceRequest bool) {
|
readOnly bool, replaceRequest bool, expectedMetadata metadata.MD) {
|
||||||
|
|
||||||
// Read intercept message and make sure it's for an RPC request.
|
// Read intercept message and make sure it's for an RPC request.
|
||||||
reqIntercept, err := h.stream.Recv()
|
reqIntercept, err := h.stream.Recv()
|
||||||
require.NoError(h.t, err)
|
require.NoError(h.t, err)
|
||||||
|
|
||||||
|
// Check that we have the expected metadata in the request.
|
||||||
|
if len(expectedMetadata) > 0 {
|
||||||
|
require.GreaterOrEqual(
|
||||||
|
h.t, len(reqIntercept.MetadataPairs),
|
||||||
|
len(expectedMetadata),
|
||||||
|
)
|
||||||
|
|
||||||
|
for key := range expectedMetadata {
|
||||||
|
require.Contains(h.t, reqIntercept.MetadataPairs, key)
|
||||||
|
require.Equal(
|
||||||
|
h.t, expectedMetadata[key],
|
||||||
|
reqIntercept.MetadataPairs[key].Values,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the custom condition is populated correctly (if we're using
|
// Make sure the custom condition is populated correctly (if we're using
|
||||||
// a macaroon with a custom condition).
|
// a macaroon with a custom condition).
|
||||||
if !readOnly {
|
if !readOnly {
|
||||||
|
|||||||
+860
-770
File diff suppressed because it is too large.
Load diff
@@ -5128,6 +5128,22 @@ message RPCMiddlewareRequest {
|
|||||||
intercept message.
|
intercept message.
|
||||||
*/
|
*/
|
||||||
uint64 msg_id = 7;
|
uint64 msg_id = 7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The metadata pairs that were sent along with the original gRPC request via
|
||||||
|
the golang context.Context using explicit [gRPC
|
||||||
|
metadata](https://grpc.io/docs/guides/metadata/). Context values are not
|
||||||
|
propagated via gRPC and so we send any pairs along explicitly here so that
|
||||||
|
the interceptor can access them.
|
||||||
|
*/
|
||||||
|
map<string, MetadataValues> metadata_pairs = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MetadataValues {
|
||||||
|
/*
|
||||||
|
The set of metadata values that correspond to the metadata key.
|
||||||
|
*/
|
||||||
|
repeated string values = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StreamAuth {
|
message StreamAuth {
|
||||||
|
|||||||
@@ -6093,6 +6093,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcMetadataValues": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"values": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "The set of metadata values that correspond to the metadata key."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcMiddlewareRegistration": {
|
"lnrpcMiddlewareRegistration": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -7051,6 +7063,13 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64",
|
"format": "uint64",
|
||||||
"description": "The unique message ID of this middleware intercept message. There can be\nmultiple middleware intercept messages per single gRPC request (one for the\nincoming request and one for the outgoing response) or gRPC stream (one for\neach incoming message and one for each outgoing response). This message ID\nmust be referenced when responding (accepting/rejecting/modifying) to an\nintercept message."
|
"description": "The unique message ID of this middleware intercept message. There can be\nmultiple middleware intercept messages per single gRPC request (one for the\nincoming request and one for the outgoing response) or gRPC stream (one for\neach incoming message and one for each outgoing response). This message ID\nmust be referenced when responding (accepting/rejecting/modifying) to an\nintercept message."
|
||||||
|
},
|
||||||
|
"metadata_pairs": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/definitions/lnrpcMetadataValues"
|
||||||
|
},
|
||||||
|
"description": "The metadata pairs that were sent along with the original gRPC request via\nthe golang context.Context using explicit [gRPC\nmetadata](https://grpc.io/docs/guides/metadata/). Context values are not\npropagated via gRPC and so we send any pairs along explicitly here so that\nthe interceptor can access them."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/reflect/protoreflect"
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
"google.golang.org/protobuf/reflect/protoregistry"
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
@@ -391,6 +392,10 @@ type InterceptionRequest struct {
|
|||||||
// IsError indicates that the message contained within this request is
|
// IsError indicates that the message contained within this request is
|
||||||
// an error. Will only ever be true for response messages.
|
// an error. Will only ever be true for response messages.
|
||||||
IsError bool
|
IsError bool
|
||||||
|
|
||||||
|
// CtxMetadataPairs contains the metadata pairs that were sent along
|
||||||
|
// with the RPC request via the context.
|
||||||
|
CtxMetadataPairs metadata.MD
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMessageInterceptionRequest creates a new interception request for either
|
// NewMessageInterceptionRequest creates a new interception request for either
|
||||||
@@ -404,12 +409,15 @@ func NewMessageInterceptionRequest(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
md, _ := metadata.FromIncomingContext(ctx)
|
||||||
|
|
||||||
req := &InterceptionRequest{
|
req := &InterceptionRequest{
|
||||||
Type: authType,
|
Type: authType,
|
||||||
StreamRPC: isStream,
|
StreamRPC: isStream,
|
||||||
Macaroon: mac,
|
Macaroon: mac,
|
||||||
RawMacaroon: rawMacaroon,
|
RawMacaroon: rawMacaroon,
|
||||||
FullURI: fullMethod,
|
FullURI: fullMethod,
|
||||||
|
CtxMetadataPairs: md,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The message is either a proto message or an error, we don't support
|
// The message is either a proto message or an error, we don't support
|
||||||
@@ -486,11 +494,21 @@ func macaroonFromContext(ctx context.Context) (*macaroon.Macaroon, []byte,
|
|||||||
func (r *InterceptionRequest) ToRPC(requestID,
|
func (r *InterceptionRequest) ToRPC(requestID,
|
||||||
msgID uint64) (*lnrpc.RPCMiddlewareRequest, error) {
|
msgID uint64) (*lnrpc.RPCMiddlewareRequest, error) {
|
||||||
|
|
||||||
|
mdPairs := make(
|
||||||
|
map[string]*lnrpc.MetadataValues, len(r.CtxMetadataPairs),
|
||||||
|
)
|
||||||
|
for key, values := range r.CtxMetadataPairs {
|
||||||
|
mdPairs[key] = &lnrpc.MetadataValues{
|
||||||
|
Values: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rpcRequest := &lnrpc.RPCMiddlewareRequest{
|
rpcRequest := &lnrpc.RPCMiddlewareRequest{
|
||||||
RequestId: requestID,
|
RequestId: requestID,
|
||||||
MsgId: msgID,
|
MsgId: msgID,
|
||||||
RawMacaroon: r.RawMacaroon,
|
RawMacaroon: r.RawMacaroon,
|
||||||
CustomCaveatCondition: r.CustomCaveatCondition,
|
CustomCaveatCondition: r.CustomCaveatCondition,
|
||||||
|
MetadataPairs: mdPairs,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
|
|||||||
Reference in new issue
Block a user