From a89b3502e4903fe9d475cc7977a654cb803bde50 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 13 May 2025 08:53:03 +0200 Subject: [PATCH] firewall: extract SessionID from gRPC metadata In this commit, we update our various firewall interceptors so that they rely on the session ID passed via gRPC metadata to extract a session ID. For the PrivacyMapper and RuleEnforcer, these _MUST_ always contain a session ID and so we error out if one was not found. For the request logger, the session ID is optional and so we pass it to the new SessionID field in the AddActionReq - our bbolt actions DB will not make use of this field on persistence (but our incoming SQL version will). --- firewall/privacy_mapper.go | 6 ++++-- firewall/privacy_mapper_test.go | 9 +++++++++ firewall/request_info.go | 18 ++++++++++++++++++ firewall/request_logger.go | 1 + firewall/rule_enforcer.go | 6 ++++-- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index fed4ba53..49aaf20f 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -106,9 +106,11 @@ func (p *PrivacyMapper) Intercept(ctx context.Context, "interception request: %v", err) } - sessionID, err := session.IDFromMacaroon(ri.Macaroon) + sessionID, err := ri.SessionID.UnwrapOrErr( + fmt.Errorf("no session ID found in request info"), + ) if err != nil { - return nil, fmt.Errorf("could not extract ID from macaroon") + return nil, err } log.Tracef("PrivacyMapper: Intercepting %v", ri) diff --git a/firewall/privacy_mapper_test.go b/firewall/privacy_mapper_test.go index 9dcc814b..61b24cd4 100644 --- a/firewall/privacy_mapper_test.go +++ b/firewall/privacy_mapper_test.go @@ -13,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/rpcperms" "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" "gopkg.in/macaroon-bakery.v2/bakery" "gopkg.in/macaroon.v2" @@ -907,6 +908,9 @@ func TestPrivacyMapper(t *testing.T) { rawMsg, err := proto.Marshal(test.msg) require.NoError(t, err) + md := make(metadata.MD) + session.AddToGRPCMetadata(md, sessionID) + interceptReq := &rpcperms.InterceptionRequest{ Type: test.msgType, Macaroon: mac, @@ -916,6 +920,7 @@ func TestPrivacyMapper(t *testing.T) { ProtoTypeName: string( proto.MessageName(test.msg), ), + CtxMetadataPairs: md, } mwReq, err := interceptReq.ToRPC(1, 2) @@ -1006,6 +1011,9 @@ func TestPrivacyMapper(t *testing.T) { amounts := make([]uint64, numSamples) timestamps := make([]uint64, numSamples) + md := make(metadata.MD) + session.AddToGRPCMetadata(md, sessionID) + for i := 0; i < numSamples; i++ { interceptReq := &rpcperms.InterceptionRequest{ Type: rpcperms.TypeResponse, @@ -1016,6 +1024,7 @@ func TestPrivacyMapper(t *testing.T) { ProtoTypeName: string( proto.MessageName(msg), ), + CtxMetadataPairs: md, } mwReq, err := interceptReq.ToRPC(1, 2) diff --git a/firewall/request_info.go b/firewall/request_info.go index 10a52493..fd312b71 100644 --- a/firewall/request_info.go +++ b/firewall/request_info.go @@ -4,7 +4,10 @@ import ( "fmt" "strings" + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc/metadata" "gopkg.in/macaroon.v2" ) @@ -25,6 +28,7 @@ const ( // RequestInfo stores the parsed representation of an incoming RPC middleware // request. type RequestInfo struct { + SessionID fn.Option[session.ID] MsgID uint64 RequestID uint64 MWRequestType string @@ -76,8 +80,22 @@ func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { return nil, fmt.Errorf("invalid request type: %T", t) } + md := make(metadata.MD) + for k, vs := range req.MetadataPairs { + for _, v := range vs.Values { + md.Append(k, v) + } + } + + sessionID, err := session.FromGRPCMetadata(md) + if err != nil { + return nil, fmt.Errorf("error extracting session ID "+ + "from request: %v", err) + } + ri.MsgID = req.MsgId ri.RequestID = req.RequestId + ri.SessionID = sessionID // If there is no macaroon in the request, then there is nothing left // to parse. diff --git a/firewall/request_logger.go b/firewall/request_logger.go index 3463dff2..0a98e645 100644 --- a/firewall/request_logger.go +++ b/firewall/request_logger.go @@ -194,6 +194,7 @@ func (r *RequestLogger) addNewAction(ctx context.Context, ri *RequestInfo, } actionReq := &firewalldb.AddActionReq{ + SessionID: ri.SessionID, MacaroonIdentifier: macaroonID, RPCMethod: ri.URI, } diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go index 35f92c53..54d2b3a6 100644 --- a/firewall/rule_enforcer.go +++ b/firewall/rule_enforcer.go @@ -237,9 +237,11 @@ func (r *RuleEnforcer) Intercept(ctx context.Context, func (r *RuleEnforcer) handleRequest(ctx context.Context, ri *RequestInfo) (proto.Message, error) { - sessionID, err := session.IDFromMacaroon(ri.Macaroon) + sessionID, err := ri.SessionID.UnwrapOrErr( + fmt.Errorf("no session ID found in request info"), + ) if err != nil { - return nil, fmt.Errorf("could not extract ID from macaroon") + return nil, err } rules, err := r.collectEnforcers(ctx, ri, sessionID)