From 4ea494e8c5fc262289b66d016c0d33a70337622c Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 29 Jan 2021 15:49:48 +0100 Subject: [PATCH 01/10] lnrpc: wrap subservers in GrpcHandler In order to be able to register the subservers with the root grpc server before we have all dependencies available, we wrap them in an GrpcHandler struct. This struct will initially hold an empty reference to the subservers, which allows us to register with the GRPC server, and later populate and create the subserver instance. --- lnrpc/autopilotrpc/autopilot_server.go | 36 ++++++++++++++++++++---- lnrpc/autopilotrpc/driver.go | 7 ++--- lnrpc/chainrpc/chainnotifier_server.go | 36 ++++++++++++++++++++---- lnrpc/chainrpc/driver.go | 8 ++---- lnrpc/invoicesrpc/driver.go | 7 ++--- lnrpc/invoicesrpc/invoices_server.go | 36 ++++++++++++++++++++---- lnrpc/routerrpc/driver.go | 6 ++-- lnrpc/routerrpc/router_server.go | 36 ++++++++++++++++++++---- lnrpc/signrpc/driver.go | 8 ++---- lnrpc/signrpc/signer_server.go | 36 ++++++++++++++++++++---- lnrpc/sub_server.go | 31 +++++++++++++------- lnrpc/verrpc/driver.go | 6 ++-- lnrpc/verrpc/server.go | 33 ++++++++++++++++++---- lnrpc/walletrpc/driver.go | 8 ++++-- lnrpc/walletrpc/walletkit_server.go | 36 ++++++++++++++++++++---- lnrpc/watchtowerrpc/driver.go | 7 ++--- lnrpc/watchtowerrpc/handler.go | 38 +++++++++++++++++++++---- lnrpc/wtclientrpc/driver.go | 7 ++--- lnrpc/wtclientrpc/wtclient.go | 39 ++++++++++++++++++++------ rpcserver.go | 26 +++++++++-------- 20 files changed, 341 insertions(+), 106 deletions(-) diff --git a/lnrpc/autopilotrpc/autopilot_server.go b/lnrpc/autopilotrpc/autopilot_server.go index fd9649ad..b3ece6cb 100644 --- a/lnrpc/autopilotrpc/autopilot_server.go +++ b/lnrpc/autopilotrpc/autopilot_server.go @@ -51,6 +51,13 @@ var ( } ) +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + AutopilotServer +} + // Server is a sub-server of the main RPC server: the autopilot RPC. This sub // RPC server allows external callers to access the status of the autopilot // currently active within lnd, as well as configuring it at runtime. @@ -118,11 +125,11 @@ func (s *Server) Name() string { // is called, each sub-server won't be able to have // requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterAutopilotServer(grpcServer, s) + RegisterAutopilotServer(grpcServer, r) log.Debugf("Autopilot RPC server successfully register with root " + "gRPC server") @@ -134,8 +141,8 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -152,6 +159,25 @@ func (s *Server) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.AutopilotServer = subServer + return subServer, macPermissions, nil +} + // Status returns the current status of the autopilot agent. // // NOTE: Part of the AutopilotServer interface. diff --git a/lnrpc/autopilotrpc/driver.go b/lnrpc/autopilotrpc/driver.go index 7615e242..8f849c3e 100644 --- a/lnrpc/autopilotrpc/driver.go +++ b/lnrpc/autopilotrpc/driver.go @@ -13,7 +13,7 @@ import ( // that is meant for us in the config dispatcher, then we'll exit with an // error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *Server, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -48,9 +48,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, - lnrpc.MacaroonPerms, error) { - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/chainrpc/chainnotifier_server.go b/lnrpc/chainrpc/chainnotifier_server.go index 3e35ab60..80f3c0ea 100644 --- a/lnrpc/chainrpc/chainnotifier_server.go +++ b/lnrpc/chainrpc/chainnotifier_server.go @@ -72,6 +72,13 @@ var ( "still in the process of starting") ) +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + ChainNotifierServer +} + // Server is a sub-server of the main RPC server: the chain notifier RPC. This // RPC sub-server allows external callers to access the full chain notifier // capabilities of lnd. This allows callers to create custom protocols, external @@ -172,11 +179,11 @@ func (s *Server) Name() string { // sub-server to register itself with the main gRPC root server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterChainNotifierServer(grpcServer, s) + RegisterChainNotifierServer(grpcServer, r) log.Debug("ChainNotifier RPC server successfully register with root " + "gRPC server") @@ -188,8 +195,8 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -206,6 +213,25 @@ func (s *Server) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.ChainNotifierServer = subServer + return subServer, macPermissions, nil +} + // RegisterConfirmationsNtfn is a synchronous response-streaming RPC that // registers an intent for a client to be notified once a confirmation request // has reached its required number of confirmations on-chain. diff --git a/lnrpc/chainrpc/driver.go b/lnrpc/chainrpc/driver.go index 0a932fb6..be307f82 100644 --- a/lnrpc/chainrpc/driver.go +++ b/lnrpc/chainrpc/driver.go @@ -13,7 +13,7 @@ import ( // the config that is meant for us in the config dispatcher, then we'll exit // with an error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *Server, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -55,10 +55,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/invoicesrpc/driver.go b/lnrpc/invoicesrpc/driver.go index 54ad1fc1..5183c878 100644 --- a/lnrpc/invoicesrpc/driver.go +++ b/lnrpc/invoicesrpc/driver.go @@ -13,7 +13,7 @@ import ( // that is meant for us in the config dispatcher, then we'll exit with an // error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *Server, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -40,9 +40,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, - lnrpc.MacaroonPerms, error) { - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index 3f053956..9c092e57 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -66,6 +66,13 @@ var ( DefaultInvoicesMacFilename = "invoices.macaroon" ) +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + InvoicesServer +} + // Server is a sub-server of the main RPC server: the invoices RPC. This sub // RPC server allows external callers to access the status of the invoices // currently active within lnd, as well as configuring it at runtime. @@ -157,11 +164,11 @@ func (s *Server) Name() string { // RPC server to register itself with the main gRPC root server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterInvoicesServer(grpcServer, s) + RegisterInvoicesServer(grpcServer, r) log.Debugf("Invoices RPC server successfully registered with root " + "gRPC server") @@ -173,8 +180,8 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -191,6 +198,25 @@ func (s *Server) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.InvoicesServer = subServer + return subServer, macPermissions, nil +} + // SubscribeSingleInvoice returns a uni-directional stream (server -> client) // for notifying the client of state changes for a specified invoice. func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest, diff --git a/lnrpc/routerrpc/driver.go b/lnrpc/routerrpc/driver.go index e174e222..0899464c 100644 --- a/lnrpc/routerrpc/driver.go +++ b/lnrpc/routerrpc/driver.go @@ -11,7 +11,7 @@ import ( // config that is meant for us in the config dispatcher, then we'll exit with // an error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *Server, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -46,8 +46,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index bc9d7514..02192ddc 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -129,6 +129,13 @@ var ( DefaultRouterMacFilename = "router.macaroon" ) +// ServerShell a is shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + RouterServer +} + // Server is a stand alone sub RPC server which exposes functionality that // allows clients to route arbitrary payment through the Lightning Network. type Server struct { @@ -233,11 +240,11 @@ func (s *Server) Name() string { // sub RPC server to register itself with the main gRPC root server. Until this // is called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterRouterServer(grpcServer, s) + RegisterRouterServer(grpcServer, r) log.Debugf("Router RPC server successfully register with root gRPC " + "server") @@ -249,8 +256,8 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -267,6 +274,25 @@ func (s *Server) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.RouterServer = subServer + return subServer, macPermissions, nil +} + // SendPaymentV2 attempts to route a payment described by the passed // PaymentRequest to the final destination. If we are unable to route the // payment, or cannot find a route that satisfies the constraints in the diff --git a/lnrpc/signrpc/driver.go b/lnrpc/signrpc/driver.go index b598c33c..cd130d5e 100644 --- a/lnrpc/signrpc/driver.go +++ b/lnrpc/signrpc/driver.go @@ -13,7 +13,7 @@ import ( // config that is meant for us in the config dispatcher, then we'll exit with // an error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *Server, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -55,10 +55,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index fdeb3f58..c48c36e1 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -76,6 +76,13 @@ var ( DefaultSignerMacFilename = "signer.macaroon" ) +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + SignerServer +} + // Server is a sub-server of the main RPC server: the signer RPC. This sub RPC // server allows external callers to access the full signing capabilities of // lnd. This allows callers to create custom protocols, external to lnd, even @@ -167,11 +174,11 @@ func (s *Server) Name() string { // is called, each sub-server won't be able to have // requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterSignerServer(grpcServer, s) + RegisterSignerServer(grpcServer, r) log.Debugf("Signer RPC server successfully register with root gRPC " + "server") @@ -183,8 +190,8 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -201,6 +208,25 @@ func (s *Server) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.SignerServer = subServer + return subServer, macPermissions, nil +} + // SignOutputRaw generates a signature for the passed transaction according to // the data within the passed SignReq. If we're unable to find the keys that // correspond to the KeyLocators in the SignReq then we'll return an error. diff --git a/lnrpc/sub_server.go b/lnrpc/sub_server.go index 7970273a..23033abb 100644 --- a/lnrpc/sub_server.go +++ b/lnrpc/sub_server.go @@ -32,7 +32,14 @@ type SubServer interface { // Name returns a unique string representation of the sub-server. This // can be used to identify the sub-server and also de-duplicate them. Name() string +} +// GrpcHandler is the interface that should be registered with the root gRPC +// server, and is the interface that implements the subserver's defined RPC. +// Before the actual sub server has been created, this will be an empty shell +// allowing us to start the gRPC server before we have all the dependencies +// needed to create the subserver itself. +type GrpcHandler interface { // RegisterWithRootServer will be called by the root gRPC server to // direct a sub RPC server to register itself with the main gRPC root // server. Until this is called, each sub-server won't be able to have @@ -45,6 +52,14 @@ type SubServer interface { // routed towards it. RegisterWithRestServer(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error + + // CreateSubServer populates the subserver's dependencies using the + // passed SubServerConfigDispatcher. This method should fully + // initialize the sub-server instance, making it ready for action. It + // returns the macaroon permissions that the sub-server wishes to pass + // on to the root server for all methods routed towards it. + CreateSubServer(subCfgs SubServerConfigDispatcher) (SubServer, + MacaroonPerms, error) } // SubServerConfigDispatcher is an interface that all sub-servers will use to @@ -60,22 +75,18 @@ type SubServerConfigDispatcher interface { } // SubServerDriver is a template struct that allows the root server to create a -// sub-server with minimal knowledge. The root server only need a fully -// populated SubServerConfigDispatcher and with the aide of the -// RegisterSubServers method, it's able to create and initialize all -// sub-servers. +// sub-server gRPC handler with minimal knowledge. type SubServerDriver struct { // SubServerName is the full name of a sub-sever. // // NOTE: This MUST be unique. SubServerName string - // New creates, and fully initializes a new sub-server instance with - // the aide of the SubServerConfigDispatcher. This closure should - // return the SubServer, ready for action, along with the set of - // macaroon permissions that the sub-server wishes to pass on to the - // root server for all methods routed towards it. - New func(subCfgs SubServerConfigDispatcher) (SubServer, MacaroonPerms, error) + // NewGrpcHandler creates a a new sub-server gRPC interface that can be + // registered with the root gRPC server. It is not expected that the + // SubServer is ready for operation before its CreateSubServer and + // Start methods have been called. + NewGrpcHandler func() GrpcHandler } var ( diff --git a/lnrpc/verrpc/driver.go b/lnrpc/verrpc/driver.go index db250f7d..49977f69 100644 --- a/lnrpc/verrpc/driver.go +++ b/lnrpc/verrpc/driver.go @@ -9,10 +9,8 @@ import ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, - lnrpc.MacaroonPerms, error) { - - return &Server{}, macPermissions, nil + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/verrpc/server.go b/lnrpc/verrpc/server.go index ecbd29c1..31c34899 100644 --- a/lnrpc/verrpc/server.go +++ b/lnrpc/verrpc/server.go @@ -5,6 +5,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/lightningnetwork/lnd/build" + "github.com/lightningnetwork/lnd/lnrpc" "google.golang.org/grpc" "gopkg.in/macaroon-bakery.v2/bakery" ) @@ -18,6 +19,13 @@ var macPermissions = map[string][]bakery.Op{ }}, } +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + VersionerServer +} + // Server is an rpc server that supports querying for information about the // running binary. type Server struct{} @@ -48,9 +56,9 @@ func (s *Server) Name() string { // sub RPC server to register itself with the main gRPC root server. Until this // is called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { - RegisterVersionerServer(grpcServer, s) +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { + RegisterVersionerServer(grpcServer, r) log.Debugf("Versioner RPC server successfully registered with root " + "gRPC server") @@ -62,8 +70,8 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -80,6 +88,21 @@ func (s *Server) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(_ lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer := &Server{} + r.VersionerServer = subServer + return subServer, macPermissions, nil +} + // GetVersion returns information about the compiled binary. func (s *Server) GetVersion(_ context.Context, _ *VersionRequest) (*Version, error) { diff --git a/lnrpc/walletrpc/driver.go b/lnrpc/walletrpc/driver.go index deb02a6e..649445a2 100644 --- a/lnrpc/walletrpc/driver.go +++ b/lnrpc/walletrpc/driver.go @@ -12,7 +12,9 @@ import ( // sub server given the main config dispatcher method. If we're unable to find // the config that is meant for us in the config dispatcher, then we'll exit // with an error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, lnrpc.MacaroonPerms, error) { +func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + *WalletKit, lnrpc.MacaroonPerms, error) { + // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an // error, as we're unable to properly initialize ourselves without this @@ -67,8 +69,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (lnrpc.S func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 04fafc25..392c6f30 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -148,6 +148,13 @@ var ( // an empty label. var ErrZeroLabel = errors.New("cannot label transaction with empty label") +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + WalletKitServer +} + // WalletKit is a sub-RPC server that exposes a tool kit which allows clients // to execute common wallet operations. This includes requesting new addresses, // keys (for contracts!), and publishing transactions. @@ -233,11 +240,11 @@ func (w *WalletKit) Name() string { // sub RPC server to register itself with the main gRPC root server. Until this // is called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (w *WalletKit) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterWalletKitServer(grpcServer, w) + RegisterWalletKitServer(grpcServer, r) log.Debugf("WalletKit RPC server successfully registered with " + "root gRPC server") @@ -249,8 +256,8 @@ func (w *WalletKit) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (w *WalletKit) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -267,6 +274,25 @@ func (w *WalletKit) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.WalletKitServer = subServer + return subServer, macPermissions, nil +} + // ListUnspent returns useful information about each unspent output owned by the // wallet, as reported by the underlying `ListUnspentWitness`; the information // returned is: outpoint, amount in satoshis, address, address type, diff --git a/lnrpc/watchtowerrpc/driver.go b/lnrpc/watchtowerrpc/driver.go index dc9cecce..3fa5f1d2 100644 --- a/lnrpc/watchtowerrpc/driver.go +++ b/lnrpc/watchtowerrpc/driver.go @@ -13,7 +13,7 @@ import ( // that is meant for us in the config dispatcher, then we'll exit with an // error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *Handler, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -40,9 +40,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, - lnrpc.MacaroonPerms, error) { - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/watchtowerrpc/handler.go b/lnrpc/watchtowerrpc/handler.go index d4124708..88add9d0 100644 --- a/lnrpc/watchtowerrpc/handler.go +++ b/lnrpc/watchtowerrpc/handler.go @@ -5,7 +5,7 @@ package watchtowerrpc import ( "context" "errors" - fmt "fmt" + "fmt" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/lightningnetwork/lnd/lnrpc" @@ -35,6 +35,13 @@ var ( ErrTowerNotActive = errors.New("watchtower not active") ) +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + WatchtowerServer +} + // Handler is the RPC server we'll use to interact with the backing active // watchtower. type Handler struct { @@ -80,11 +87,11 @@ func (c *Handler) Name() string { // RPC server to register itself with the main gRPC root server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (c *Handler) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterWatchtowerServer(grpcServer, c) + RegisterWatchtowerServer(grpcServer, r) log.Debugf("Watchtower RPC server successfully register with root " + "gRPC server") @@ -96,8 +103,8 @@ func (c *Handler) RegisterWithRootServer(grpcServer *grpc.Server) error { // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (c *Handler) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -114,6 +121,25 @@ func (c *Handler) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.WatchtowerServer = subServer + return subServer, macPermissions, nil +} + // AddTower adds a new watchtower reachable at the given address and considers // it for new sessions. If the watchtower already exists, then any new addresses // included will be considered when dialing it for session negotiations and diff --git a/lnrpc/wtclientrpc/driver.go b/lnrpc/wtclientrpc/driver.go index 78e39976..cecbbf43 100644 --- a/lnrpc/wtclientrpc/driver.go +++ b/lnrpc/wtclientrpc/driver.go @@ -12,7 +12,7 @@ import ( // that is meant for us in the config dispatcher, then we'll exit with an // error. func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + *WatchtowerClient, lnrpc.MacaroonPerms, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -46,9 +46,8 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, - lnrpc.MacaroonPerms, error) { - return createNewSubServer(c) + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} }, } diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index 4d55e468..fa3c640d 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -64,6 +64,13 @@ var ( ErrWtclientNotActive = errors.New("watchtower client not active") ) +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + WatchtowerClientServer +} + // WatchtowerClient is the RPC server we'll use to interact with the backing // active watchtower client. // @@ -112,14 +119,11 @@ func (c *WatchtowerClient) Name() string { // RPC server to register itself with the main gRPC root server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (c *WatchtowerClient) RegisterWithRootServer(grpcServer *grpc.Server) error { +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. - RegisterWatchtowerClientServer(grpcServer, c) - - c.cfg.Log.Debugf("WatchtowerClient RPC server successfully registered " + - "with root gRPC server") + RegisterWatchtowerClientServer(grpcServer, r) return nil } @@ -128,8 +132,8 @@ func (c *WatchtowerClient) RegisterWithRootServer(grpcServer *grpc.Server) error // RPC server to register itself with the main REST mux server. Until this is // called, each sub-server won't be able to have requests routed towards it. // -// NOTE: This is part of the lnrpc.SubServer interface. -func (c *WatchtowerClient) RegisterWithRestServer(ctx context.Context, +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { // We make sure that we register it with the main REST server to ensure @@ -142,6 +146,25 @@ func (c *WatchtowerClient) RegisterWithRestServer(ctx context.Context, return nil } +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.WatchtowerClientServer = subServer + return subServer, macPermissions, nil +} + // isActive returns nil if the watchtower client is initialized so that we can // process RPC requests. func (c *WatchtowerClient) isActive() error { diff --git a/rpcserver.go b/rpcserver.go index 294b42f3..a7b200bd 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -492,7 +492,8 @@ type rpcServer struct { // own independent service. This allows us to expose a set of // micro-service like abstractions to the outside world for users to // consume. - subServers []lnrpc.SubServer + subServers []lnrpc.SubServer + subGrpcHandlers []lnrpc.GrpcHandler // grpcServer is the main gRPC server that this RPC server, and all the // sub-servers will use to register themselves and accept client @@ -616,8 +617,9 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, } var ( - subServers []lnrpc.SubServer - subServerPerms []lnrpc.MacaroonPerms + subServers []lnrpc.SubServer + subGrpcHandlers []lnrpc.GrpcHandler + subServerPerms []lnrpc.MacaroonPerms ) // Before we create any of the sub-servers, we need to ensure that all @@ -639,8 +641,9 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // Now that the sub-servers have all their dependencies in place, we // can create each sub-server! registeredSubServers := lnrpc.RegisteredSubServers() - for _, subServer := range registeredSubServers { - subServerInstance, macPerms, err := subServer.New(subServerCgs) + for _, driver := range registeredSubServers { + handler := driver.NewGrpcHandler() + subServer, macPerms, err := handler.CreateSubServer(subServerCgs) if err != nil { return nil, err } @@ -648,7 +651,8 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // We'll collect the sub-server, and also the set of // permissions it needs for macaroons so we can apply the // interceptors below. - subServers = append(subServers, subServerInstance) + subServers = append(subServers, subServer) + subGrpcHandlers = append(subGrpcHandlers, handler) subServerPerms = append(subServerPerms, macPerms) } @@ -767,6 +771,7 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, listenerCleanUp: []func(){cleanup}, restProxyDest: restProxyDest, subServers: subServers, + subGrpcHandlers: subGrpcHandlers, restListen: restListen, grpcServer: grpcServer, server: s, @@ -782,12 +787,11 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // Now the main RPC server has been registered, we'll iterate through // all the sub-RPC servers and register them to ensure that requests // are properly routed towards them. - for _, subServer := range subServers { + for _, subServer := range subGrpcHandlers { err := subServer.RegisterWithRootServer(grpcServer) if err != nil { return nil, fmt.Errorf("unable to register "+ - "sub-server %v with root: %v", - subServer.Name(), err) + "sub-server with root: %v", err) } } @@ -885,13 +889,13 @@ func (r *rpcServer) Start() error { if err != nil { return err } - for _, subServer := range r.subServers { + for _, subServer := range r.subGrpcHandlers { err := subServer.RegisterWithRestServer( restCtx, restMux, r.restProxyDest, r.restDialOpts, ) if err != nil { return fmt.Errorf("unable to register REST sub-server "+ - "%v with root: %v", subServer.Name(), err) + "with root: %v", err) } } From 3c81a5dd739ed70bdfae4b3c913ea5e8ff66996e Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 13 Oct 2020 11:24:40 +0200 Subject: [PATCH 02/10] rpcperms: add RPC interceptor chain This adds a new package rpcperms which houses the InterceptorChain struct. This is a central place where we'll craft interceptors to use for the GRPC server, which includes macaroon enforcement. This let us add the interceptor chain to the GRPC server before the macaroon service is ready, allowing us to avoid tearing down the GRPC server after the wallet has been unlocked. --- lnd.go | 16 +++ log.go | 36 ------ macaroons/service.go | 78 +----------- rpcperms/interceptor.go | 259 ++++++++++++++++++++++++++++++++++++++++ rpcserver.go | 122 ++++++------------- 5 files changed, 317 insertions(+), 194 deletions(-) create mode 100644 rpcperms/interceptor.go diff --git a/lnd.go b/lnd.go index 60e3d962..9cf61e5c 100644 --- a/lnd.go +++ b/lnd.go @@ -45,6 +45,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/walletunlocker" @@ -375,6 +376,15 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { return getListeners() } + // Create a new RPC interceptor chain that we'll add to the GRPC + // server. This will be used to log the API calls invoked on the GRPC + // server. + interceptorChain := rpcperms.NewInterceptorChain( + rpcsLog, cfg.NoMacaroons, + ) + rpcServerOpts := interceptorChain.CreateServerOpts() + serverOpts = append(serverOpts, rpcServerOpts...) + // We wait until the user provides a password over RPC. In case lnd is // started with the --noseedbackup flag, we use the default password // for wallet encryption. @@ -492,6 +502,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { ltndLog.Warnf(msg, "invoice", cfg.InvoiceMacPath) } } + + // We add the macaroon service to our RPC interceptor. This + // will start checking macaroons against permissions on every + // RPC invocation. + interceptorChain.AddMacaroonService(macaroonService) } // Now we're definitely done with the unlocker, shut it down so we can @@ -737,6 +752,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { cfg, server, macaroonService, cfg.SubRPCServers, serverOpts, restDialOpts, restProxyDest, atplManager, server.invoices, tower, restListen, rpcListeners, chainedAcceptor, + interceptorChain, ) if err != nil { err := fmt.Errorf("unable to create RPC server: %v", err) diff --git a/log.go b/log.go index 284b6ffc..c06d7418 100644 --- a/log.go +++ b/log.go @@ -1,8 +1,6 @@ package lnd import ( - "context" - "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btclog" "github.com/lightninglabs/neutrino" @@ -42,7 +40,6 @@ import ( "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/watchtower/wtclient" - "google.golang.org/grpc" ) // replaceableLogger is a thin wrapper around a logger that is used so the @@ -175,36 +172,3 @@ func (c logClosure) String() string { func newLogClosure(c func() string) logClosure { return logClosure(c) } - -// errorLogUnaryServerInterceptor is a simple UnaryServerInterceptor that will -// automatically log any errors that occur when serving a client's unary -// request. -func errorLogUnaryServerInterceptor(logger btclog.Logger) grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (interface{}, error) { - - resp, err := handler(ctx, req) - if err != nil { - // TODO(roasbeef): also log request details? - logger.Errorf("[%v]: %v", info.FullMethod, err) - } - - return resp, err - } -} - -// errorLogStreamServerInterceptor is a simple StreamServerInterceptor that -// will log any errors that occur while processing a client or server streaming -// RPC. -func errorLogStreamServerInterceptor(logger btclog.Logger) grpc.StreamServerInterceptor { - return func(srv interface{}, ss grpc.ServerStream, - info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - - err := handler(srv, ss) - if err != nil { - logger.Errorf("[%v]: %v", info.FullMethod, err) - } - - return err - } -} diff --git a/macaroons/service.go b/macaroons/service.go index e8d9c164..2bb2bc83 100644 --- a/macaroons/service.go +++ b/macaroons/service.go @@ -9,7 +9,6 @@ import ( "time" "github.com/lightningnetwork/lnd/channeldb/kvdb" - "google.golang.org/grpc" "google.golang.org/grpc/metadata" "gopkg.in/macaroon-bakery.v2/bakery" @@ -58,11 +57,11 @@ type Service struct { rks *RootKeyStorage - // externalValidators is a map between an absolute gRPC URIs and the + // ExternalValidators is a map between an absolute gRPC URIs and the // corresponding external macaroon validator to be used for that URI. // If no external validator for an URI is specified, the service will // use the internal validator. - externalValidators map[string]MacaroonValidator + ExternalValidators map[string]MacaroonValidator // StatelessInit denotes if the service was initialized in the stateless // mode where no macaroon files should be created on disk. @@ -125,7 +124,7 @@ func NewService(dir, location string, statelessInit bool, return &Service{ Bakery: *svc, rks: rootKeyStore, - externalValidators: make(map[string]MacaroonValidator), + ExternalValidators: make(map[string]MacaroonValidator), StatelessInit: statelessInit, }, nil } @@ -159,83 +158,16 @@ func (svc *Service) RegisterExternalValidator(fullMethod string, return fmt.Errorf("validator cannot be nil") } - _, ok := svc.externalValidators[fullMethod] + _, ok := svc.ExternalValidators[fullMethod] if ok { return fmt.Errorf("external validator for method %s already "+ "registered", fullMethod) } - svc.externalValidators[fullMethod] = validator + svc.ExternalValidators[fullMethod] = validator return nil } -// UnaryServerInterceptor is a GRPC interceptor that checks whether the -// request is authorized by the included macaroons. -func (svc *Service) UnaryServerInterceptor( - permissionMap map[string][]bakery.Op) grpc.UnaryServerInterceptor { - - return func(ctx context.Context, req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (interface{}, error) { - - uriPermissions, ok := permissionMap[info.FullMethod] - if !ok { - return nil, fmt.Errorf("%s: unknown permissions "+ - "required for method", info.FullMethod) - } - - // Find out if there is an external validator registered for - // this method. Fall back to the internal one if there isn't. - validator, ok := svc.externalValidators[info.FullMethod] - if !ok { - validator = svc - } - - // Now that we know what validator to use, let it do its work. - err := validator.ValidateMacaroon( - ctx, uriPermissions, info.FullMethod, - ) - if err != nil { - return nil, err - } - - return handler(ctx, req) - } -} - -// StreamServerInterceptor is a GRPC interceptor that checks whether the -// request is authorized by the included macaroons. -func (svc *Service) StreamServerInterceptor( - permissionMap map[string][]bakery.Op) grpc.StreamServerInterceptor { - - return func(srv interface{}, ss grpc.ServerStream, - info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - - uriPermissions, ok := permissionMap[info.FullMethod] - if !ok { - return fmt.Errorf("%s: unknown permissions required "+ - "for method", info.FullMethod) - } - - // Find out if there is an external validator registered for - // this method. Fall back to the internal one if there isn't. - validator, ok := svc.externalValidators[info.FullMethod] - if !ok { - validator = svc - } - - // Now that we know what validator to use, let it do its work. - err := validator.ValidateMacaroon( - ss.Context(), uriPermissions, info.FullMethod, - ) - if err != nil { - return err - } - - return handler(srv, ss) - } -} - // ValidateMacaroon validates the capabilities of a given request given a // bakery service, context, and uri. Within the passed context.Context, we // expect a macaroon to be encoded as request metadata using the key diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go new file mode 100644 index 00000000..ea918662 --- /dev/null +++ b/rpcperms/interceptor.go @@ -0,0 +1,259 @@ +package rpcperms + +import ( + "context" + "fmt" + "sync" + + "github.com/btcsuite/btclog" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/monitoring" + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// InterceptorChain is a struct that can be added to the running GRPC server, +// intercepting API calls. This is useful for logging, enforcing permissions +// etc. +type InterceptorChain struct { + // noMacaroons should be set true if we don't want to check macaroons. + noMacaroons bool + + // svc is the macaroon service used to enforce permissions in case + // macaroons are used. + svc *macaroons.Service + + // permissionMap is the permissions to enforce if macaroons are used. + permissionMap map[string][]bakery.Op + + // rpcsLog is the logger used to log calles to the RPCs intercepted. + rpcsLog btclog.Logger + + sync.RWMutex +} + +// NewInterceptorChain creates a new InterceptorChain. +func NewInterceptorChain(log btclog.Logger, noMacaroons bool) *InterceptorChain { + return &InterceptorChain{ + noMacaroons: noMacaroons, + permissionMap: make(map[string][]bakery.Op), + rpcsLog: log, + } +} + +// AddMacaroonService adds a macaroon service to the interceptor. After this is +// done every RPC call made will have to pass a valid macaroon to be accepted. +func (r *InterceptorChain) AddMacaroonService(svc *macaroons.Service) { + r.Lock() + defer r.Unlock() + + r.svc = svc +} + +// AddPermission adds a new macaroon rule for the given method. +func (r *InterceptorChain) AddPermission(method string, ops []bakery.Op) error { + r.Lock() + defer r.Unlock() + + if _, ok := r.permissionMap[method]; ok { + return fmt.Errorf("detected duplicate macaroon constraints "+ + "for path: %v", method) + } + + r.permissionMap[method] = ops + return nil +} + +// Permissions returns the current set of macaroon permissions. +func (r *InterceptorChain) Permissions() map[string][]bakery.Op { + r.RLock() + defer r.RUnlock() + + // Make a copy under the read lock to avoid races. + c := make(map[string][]bakery.Op) + for k, v := range r.permissionMap { + s := make([]bakery.Op, len(v)) + copy(s, v) + c[k] = s + } + + return c +} + +// CreateServerOpts creates the GRPC server options that can be added to a GRPC +// server in order to add this InterceptorChain. +func (r *InterceptorChain) CreateServerOpts() []grpc.ServerOption { + macUnaryInterceptors := []grpc.UnaryServerInterceptor{} + macStrmInterceptors := []grpc.StreamServerInterceptor{} + + // We'll add the macaroon interceptors. If macaroons aren't disabled, + // then these interceptors will enforce macaroon authentication. + unaryInterceptor := r.macaroonUnaryServerInterceptor() + macUnaryInterceptors = append(macUnaryInterceptors, unaryInterceptor) + + strmInterceptor := r.macaroonStreamServerInterceptor() + macStrmInterceptors = append(macStrmInterceptors, strmInterceptor) + + // Get interceptors for Prometheus to gather gRPC performance metrics. + // If monitoring is disabled, GetPromInterceptors() will return empty + // slices. + promUnaryInterceptors, promStrmInterceptors := + monitoring.GetPromInterceptors() + + // Concatenate the slices of unary and stream interceptors respectively. + unaryInterceptors := append(macUnaryInterceptors, promUnaryInterceptors...) + strmInterceptors := append(macStrmInterceptors, promStrmInterceptors...) + + // We'll also add our logging interceptors as well, so we can + // automatically log all errors that happen during RPC calls. + unaryInterceptors = append( + unaryInterceptors, errorLogUnaryServerInterceptor(r.rpcsLog), + ) + strmInterceptors = append( + strmInterceptors, errorLogStreamServerInterceptor(r.rpcsLog), + ) + + // Create server options from the interceptors we just set up. + chainedUnary := grpc_middleware.WithUnaryServerChain( + unaryInterceptors..., + ) + chainedStream := grpc_middleware.WithStreamServerChain( + strmInterceptors..., + ) + serverOpts := []grpc.ServerOption{chainedUnary, chainedStream} + + return serverOpts +} + +// errorLogUnaryServerInterceptor is a simple UnaryServerInterceptor that will +// automatically log any errors that occur when serving a client's unary +// request. +func errorLogUnaryServerInterceptor(logger btclog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + + resp, err := handler(ctx, req) + if err != nil { + // TODO(roasbeef): also log request details? + logger.Errorf("[%v]: %v", info.FullMethod, err) + } + + return resp, err + } +} + +// errorLogStreamServerInterceptor is a simple StreamServerInterceptor that +// will log any errors that occur while processing a client or server streaming +// RPC. +func errorLogStreamServerInterceptor(logger btclog.Logger) grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, + info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + + err := handler(srv, ss) + if err != nil { + logger.Errorf("[%v]: %v", info.FullMethod, err) + } + + return err + } +} + +// macaroonUnaryServerInterceptor is a GRPC interceptor that checks whether the +// request is authorized by the included macaroons. +func (r *InterceptorChain) macaroonUnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + + // If noMacaroons are set, we'll always allow the call. + if r.noMacaroons { + return handler(ctx, req) + } + + r.RLock() + svc := r.svc + r.RUnlock() + + // If the macaroon service is not yet active, allow the call. + // THis means that the wallet has not yet been unlocked, and we + // allow calls to the WalletUnlockerService. + if svc == nil { + return handler(ctx, req) + } + + r.RLock() + uriPermissions, ok := r.permissionMap[info.FullMethod] + r.RUnlock() + if !ok { + return nil, fmt.Errorf("%s: unknown permissions "+ + "required for method", info.FullMethod) + } + + // Find out if there is an external validator registered for + // this method. Fall back to the internal one if there isn't. + validator, ok := svc.ExternalValidators[info.FullMethod] + if !ok { + validator = svc + } + + // Now that we know what validator to use, let it do its work. + err := validator.ValidateMacaroon( + ctx, uriPermissions, info.FullMethod, + ) + if err != nil { + return nil, err + } + + return handler(ctx, req) + } +} + +// macaroonStreamServerInterceptor is a GRPC interceptor that checks whether +// the request is authorized by the included macaroons. +func (r *InterceptorChain) macaroonStreamServerInterceptor() grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, + info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + + // If noMacaroons are set, we'll always allow the call. + if r.noMacaroons { + return handler(srv, ss) + } + + r.RLock() + svc := r.svc + r.RUnlock() + + // If the macaroon service is not yet active, allow the call. + // THis means that the wallet has not yet been unlocked, and we + // allow calls to the WalletUnlockerService. + if svc == nil { + return handler(srv, ss) + } + + r.RLock() + uriPermissions, ok := r.permissionMap[info.FullMethod] + r.RUnlock() + if !ok { + return fmt.Errorf("%s: unknown permissions required "+ + "for method", info.FullMethod) + } + + // Find out if there is an external validator registered for + // this method. Fall back to the internal one if there isn't. + validator, ok := svc.ExternalValidators[info.FullMethod] + if !ok { + validator = svc + } + + // Now that we know what validator to use, let it do its work. + err := validator.ValidateMacaroon( + ss.Context(), uriPermissions, info.FullMethod, + ) + if err != nil { + return err + } + + return handler(srv, ss) + } +} diff --git a/rpcserver.go b/rpcserver.go index a7b200bd..954a7221 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -28,7 +28,6 @@ import ( "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/davecgh/go-spew/spew" - grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/build" @@ -67,6 +66,7 @@ import ( "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/watchtower" @@ -539,9 +539,8 @@ type rpcServer struct { // selfNode is our own pubkey. selfNode route.Vertex - // allPermissions is a map of all registered gRPC URIs (including - // internal and external subservers) to the permissions they require. - allPermissions map[string][]bakery.Op + // interceptorChain is the the interceptor added to our gRPC server. + interceptorChain *rpcperms.InterceptorChain } // A compile time check to ensure that rpcServer fully implements the @@ -559,8 +558,8 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, tower *watchtower.Standalone, restListen func(net.Addr) (net.Listener, error), - getListeners rpcListeners, - chanPredicate *chanacceptor.ChainedAcceptor) (*rpcServer, error) { + getListeners rpcListeners, chanPredicate *chanacceptor.ChainedAcceptor, + interceptorChain *rpcperms.InterceptorChain) (*rpcServer, error) { // Set up router rpc backend. channelGraph := s.localChanDB.ChannelGraph() @@ -659,18 +658,19 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // Next, we need to merge the set of sub server macaroon permissions // with the main RPC server permissions so we can unite them under a // single set of interceptors. - permissions := MainRPCServerPermissions() + for m, ops := range MainRPCServerPermissions() { + err := interceptorChain.AddPermission(m, ops) + if err != nil { + return nil, err + } + } + for _, subServerPerm := range subServerPerms { for method, ops := range subServerPerm { - // For each new method:ops combo, we also ensure that - // non of the sub-servers try to override each other. - if _, ok := permissions[method]; ok { - return nil, fmt.Errorf("detected duplicate "+ - "macaroon constraints for path: %v", - method) + err := interceptorChain.AddPermission(method, ops) + if err != nil { + return nil, err } - - permissions[method] = ops } } @@ -687,18 +687,11 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, if extSubserver != nil { macValidator := extSubserver.MacaroonValidator for method, ops := range extSubserver.Permissions { - // For each new method:ops combo, we also ensure - // that non of the sub-servers try to override - // each other. - if _, ok := permissions[method]; ok { - return nil, fmt.Errorf("detected "+ - "duplicate macaroon "+ - "constraints for path: %v", - method) + err := interceptorChain.AddPermission(method, ops) + if err != nil { + return nil, err } - permissions[method] = ops - // Give the external subservers the possibility // to also use their own validator to check any // macaroons attached to calls to this method. @@ -719,68 +712,26 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, } } - // If macaroons aren't disabled (a non-nil service), then we'll set up - // our set of interceptors which will allow us to handle the macaroon - // authentication in a single location. - macUnaryInterceptors := []grpc.UnaryServerInterceptor{} - macStrmInterceptors := []grpc.StreamServerInterceptor{} - if macService != nil { - unaryInterceptor := macService.UnaryServerInterceptor(permissions) - macUnaryInterceptors = append(macUnaryInterceptors, unaryInterceptor) - - strmInterceptor := macService.StreamServerInterceptor(permissions) - macStrmInterceptors = append(macStrmInterceptors, strmInterceptor) - } - - // Get interceptors for Prometheus to gather gRPC performance metrics. - // If monitoring is disabled, GetPromInterceptors() will return empty - // slices. - promUnaryInterceptors, promStrmInterceptors := monitoring.GetPromInterceptors() - - // Concatenate the slices of unary and stream interceptors respectively. - unaryInterceptors := append(macUnaryInterceptors, promUnaryInterceptors...) - strmInterceptors := append(macStrmInterceptors, promStrmInterceptors...) - - // We'll also add our logging interceptors as well, so we can - // automatically log all errors that happen during RPC calls. - unaryInterceptors = append( - unaryInterceptors, errorLogUnaryServerInterceptor(rpcsLog), - ) - strmInterceptors = append( - strmInterceptors, errorLogStreamServerInterceptor(rpcsLog), - ) - - // If any interceptors have been set up, add them to the server options. - if len(unaryInterceptors) != 0 && len(strmInterceptors) != 0 { - chainedUnary := grpc_middleware.WithUnaryServerChain( - unaryInterceptors..., - ) - chainedStream := grpc_middleware.WithStreamServerChain( - strmInterceptors..., - ) - serverOpts = append(serverOpts, chainedUnary, chainedStream) - } - // Finally, with all the pre-set up complete, we can create the main // gRPC server, and register the main lnrpc server along side. grpcServer := grpc.NewServer(serverOpts...) rootRPCServer := &rpcServer{ - cfg: cfg, - restDialOpts: restDialOpts, - listeners: listeners, - listenerCleanUp: []func(){cleanup}, - restProxyDest: restProxyDest, - subServers: subServers, - subGrpcHandlers: subGrpcHandlers, - restListen: restListen, - grpcServer: grpcServer, - server: s, - routerBackend: routerBackend, - chanPredicate: chanPredicate, - quit: make(chan struct{}, 1), - macService: macService, - selfNode: selfNode.PubKeyBytes, - allPermissions: permissions, + cfg: cfg, + restDialOpts: restDialOpts, + listeners: listeners, + listenerCleanUp: []func(){cleanup}, + restProxyDest: restProxyDest, + subServers: subServers, + subGrpcHandlers: subGrpcHandlers, + restListen: restListen, + grpcServer: grpcServer, + server: s, + routerBackend: routerBackend, + chanPredicate: chanPredicate, + quit: make(chan struct{}, 1), + macService: macService, + selfNode: selfNode.PubKeyBytes, + interceptorChain: interceptorChain, } lnrpc.RegisterLightningServer(grpcServer, rootRPCServer) @@ -6546,7 +6497,8 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context, // Either we have the special entity "uri" which specifies a // full gRPC URI or we have one of the pre-defined actions. if op.Entity == macaroons.PermissionEntityCustomURI { - _, ok := r.allPermissions[op.Action] + allPermissions := r.interceptorChain.Permissions() + _, ok := allPermissions[op.Action] if !ok { return nil, fmt.Errorf("invalid permission " + "action, must be an existing URI in " + @@ -6661,7 +6613,7 @@ func (r *rpcServer) ListPermissions(_ context.Context, rpcsLog.Debugf("[listpermissions]") permissionMap := make(map[string]*lnrpc.MacaroonPermissionList) - for uri, perms := range r.allPermissions { + for uri, perms := range r.interceptorChain.Permissions() { rpcPerms := make([]*lnrpc.MacaroonPermission, len(perms)) for idx, perm := range perms { rpcPerms[idx] = &lnrpc.MacaroonPermission{ From 73711941adb7875ec25036b0a41b06f95984b1f6 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 13 Oct 2020 11:24:40 +0200 Subject: [PATCH 03/10] rpcperms: add macaroon whitelist We extract common macaroon validating code into a method, and add a method whitelist, for methods that won't need macaroons. This give us explicit control over which methods don't require macaroons, to avoid inadvertently adding RPCs that are unauthenticated. For now this whitelist contains the WalletUnlocker methods, as the wallet password is required to open the macaroon db. --- rpcperms/interceptor.go | 133 +++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 71 deletions(-) diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index ea918662..5637f647 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -13,6 +13,18 @@ import ( "gopkg.in/macaroon-bakery.v2/bakery" ) +var ( + // macaroonWhitelist defines methods that we don't require macaroons to + // access. + macaroonWhitelist = map[string]struct{}{ + // We allow all calls to the WalletUnlocker without macaroons. + "/lnrpc.WalletUnlocker/GenSeed": {}, + "/lnrpc.WalletUnlocker/InitWallet": {}, + "/lnrpc.WalletUnlocker/UnlockWallet": {}, + "/lnrpc.WalletUnlocker/ChangePassword": {}, + } +) + // InterceptorChain is a struct that can be added to the running GRPC server, // intercepting API calls. This is useful for logging, enforcing permissions // etc. @@ -159,6 +171,52 @@ func errorLogStreamServerInterceptor(logger btclog.Logger) grpc.StreamServerInte } } +// checkMacaroon validates that the context contains the macaroon needed to +// invoke the given RPC method. +func (r *InterceptorChain) checkMacaroon(ctx context.Context, + fullMethod string) error { + + // If noMacaroons is set, we'll always allow the call. + if r.noMacaroons { + return nil + } + + // Check whether the method is whitelisted, if so we'll allow it + // regardless of macaroons. + _, ok := macaroonWhitelist[fullMethod] + if ok { + return nil + } + + r.RLock() + svc := r.svc + r.RUnlock() + + // If the macaroon service is not yet active, we cannot allow + // the call. + if svc == nil { + return fmt.Errorf("unable to determine macaroon permissions") + } + + r.RLock() + uriPermissions, ok := r.permissionMap[fullMethod] + r.RUnlock() + if !ok { + return fmt.Errorf("%s: unknown permissions required for method", + fullMethod) + } + + // Find out if there is an external validator registered for + // this method. Fall back to the internal one if there isn't. + validator, ok := svc.ExternalValidators[fullMethod] + if !ok { + validator = svc + } + + // Now that we know what validator to use, let it do its work. + return validator.ValidateMacaroon(ctx, uriPermissions, fullMethod) +} + // macaroonUnaryServerInterceptor is a GRPC interceptor that checks whether the // request is authorized by the included macaroons. func (r *InterceptorChain) macaroonUnaryServerInterceptor() grpc.UnaryServerInterceptor { @@ -166,42 +224,8 @@ func (r *InterceptorChain) macaroonUnaryServerInterceptor() grpc.UnaryServerInte info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - // If noMacaroons are set, we'll always allow the call. - if r.noMacaroons { - return handler(ctx, req) - } - - r.RLock() - svc := r.svc - r.RUnlock() - - // If the macaroon service is not yet active, allow the call. - // THis means that the wallet has not yet been unlocked, and we - // allow calls to the WalletUnlockerService. - if svc == nil { - return handler(ctx, req) - } - - r.RLock() - uriPermissions, ok := r.permissionMap[info.FullMethod] - r.RUnlock() - if !ok { - return nil, fmt.Errorf("%s: unknown permissions "+ - "required for method", info.FullMethod) - } - - // Find out if there is an external validator registered for - // this method. Fall back to the internal one if there isn't. - validator, ok := svc.ExternalValidators[info.FullMethod] - if !ok { - validator = svc - } - - // Now that we know what validator to use, let it do its work. - err := validator.ValidateMacaroon( - ctx, uriPermissions, info.FullMethod, - ) - if err != nil { + // Check macaroons. + if err := r.checkMacaroon(ctx, info.FullMethod); err != nil { return nil, err } @@ -215,41 +239,8 @@ func (r *InterceptorChain) macaroonStreamServerInterceptor() grpc.StreamServerIn return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - // If noMacaroons are set, we'll always allow the call. - if r.noMacaroons { - return handler(srv, ss) - } - - r.RLock() - svc := r.svc - r.RUnlock() - - // If the macaroon service is not yet active, allow the call. - // THis means that the wallet has not yet been unlocked, and we - // allow calls to the WalletUnlockerService. - if svc == nil { - return handler(srv, ss) - } - - r.RLock() - uriPermissions, ok := r.permissionMap[info.FullMethod] - r.RUnlock() - if !ok { - return fmt.Errorf("%s: unknown permissions required "+ - "for method", info.FullMethod) - } - - // Find out if there is an external validator registered for - // this method. Fall back to the internal one if there isn't. - validator, ok := svc.ExternalValidators[info.FullMethod] - if !ok { - validator = svc - } - - // Now that we know what validator to use, let it do its work. - err := validator.ValidateMacaroon( - ss.Context(), uriPermissions, info.FullMethod, - ) + // Check macaroons. + err := r.checkMacaroon(ss.Context(), info.FullMethod) if err != nil { return err } From 82fb22eda2103f9a2f7e7b679389cbe8e97bd411 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 14 Oct 2020 11:52:18 +0200 Subject: [PATCH 04/10] lnd+rpc: define external subserver config only once We don't have to define the external subserver config more than once, so it is not needed to be defined for every listener. Instead we move it to the ListenerConfig. --- lnd.go | 19 ++++----- rpcserver.go | 112 ++++++++++++++++++++++++++------------------------- 2 files changed, 67 insertions(+), 64 deletions(-) diff --git a/lnd.go b/lnd.go index 9cf61e5c..14016b9e 100644 --- a/lnd.go +++ b/lnd.go @@ -163,14 +163,6 @@ type ListenerWithSignal struct { // Ready will be closed by the server listening on Listener. Ready chan struct{} - - // ExternalRPCSubserverCfg is optional and specifies the registration - // callback and permissions to register external gRPC subservers. - ExternalRPCSubserverCfg *RPCSubserverConfig - - // ExternalRestRegistrar is optional and specifies the registration - // callback to register external REST subservers. - ExternalRestRegistrar RestRegistrar } // ListenerCfg is a wrapper around custom listeners that can be passed to lnd @@ -183,6 +175,14 @@ type ListenerCfg struct { // RPCListener can be set to the listener to use for the RPC server. If // nil a regular network listener will be created. RPCListener *ListenerWithSignal + + // ExternalRPCSubserverCfg is optional and specifies the registration + // callback and permissions to register external gRPC subservers. + ExternalRPCSubserverCfg *RPCSubserverConfig + + // ExternalRestRegistrar is optional and specifies the registration + // callback to register external REST subservers. + ExternalRestRegistrar RestRegistrar } // rpcListeners is a function type used for closures that fetches a set of RPC @@ -752,7 +752,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { cfg, server, macaroonService, cfg.SubRPCServers, serverOpts, restDialOpts, restProxyDest, atplManager, server.invoices, tower, restListen, rpcListeners, chainedAcceptor, - interceptorChain, + interceptorChain, lisCfg.ExternalRPCSubserverCfg, + lisCfg.ExternalRestRegistrar, ) if err != nil { err := fmt.Errorf("unable to create RPC server: %v", err) diff --git a/rpcserver.go b/rpcserver.go index 954a7221..0d355e00 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -541,6 +541,14 @@ type rpcServer struct { // interceptorChain is the the interceptor added to our gRPC server. interceptorChain *rpcperms.InterceptorChain + + // extSubserverCfg is optional and specifies the registration + // callback and permissions to register external gRPC subservers. + extSubserverCfg *RPCSubserverConfig + + // extRestRegistrar is optional and specifies the registration + // callback to register external REST subservers. + extRestRegistrar RestRegistrar } // A compile time check to ensure that rpcServer fully implements the @@ -559,7 +567,9 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, tower *watchtower.Standalone, restListen func(net.Addr) (net.Listener, error), getListeners rpcListeners, chanPredicate *chanacceptor.ChainedAcceptor, - interceptorChain *rpcperms.InterceptorChain) (*rpcServer, error) { + interceptorChain *rpcperms.InterceptorChain, + extSubserverCfg *RPCSubserverConfig, extRestRegistrar RestRegistrar) ( + *rpcServer, error) { // Set up router rpc backend. channelGraph := s.localChanDB.ChannelGraph() @@ -682,31 +692,28 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // External subserver possibly need to register their own permissions // and macaroon validator. - for _, lis := range listeners { - extSubserver := lis.ExternalRPCSubserverCfg - if extSubserver != nil { - macValidator := extSubserver.MacaroonValidator - for method, ops := range extSubserver.Permissions { - err := interceptorChain.AddPermission(method, ops) - if err != nil { - return nil, err - } + if extSubserverCfg != nil { + macValidator := extSubserverCfg.MacaroonValidator + for method, ops := range extSubserverCfg.Permissions { + err := interceptorChain.AddPermission(method, ops) + if err != nil { + return nil, err + } - // Give the external subservers the possibility - // to also use their own validator to check any - // macaroons attached to calls to this method. - // This allows them to have their own root key - // ID database and permission entities. - if macValidator != nil { - err := macService.RegisterExternalValidator( - method, macValidator, - ) - if err != nil { - return nil, fmt.Errorf("could "+ - "not register "+ - "external macaroon "+ - "validator: %v", err) - } + // Give the external subservers the possibility + // to also use their own validator to check any + // macaroons attached to calls to this method. + // This allows them to have their own root key + // ID database and permission entities. + if macValidator != nil { + err := macService.RegisterExternalValidator( + method, macValidator, + ) + if err != nil { + return nil, fmt.Errorf("could "+ + "not register "+ + "external macaroon "+ + "validator: %v", err) } } } @@ -732,6 +739,8 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, macService: macService, selfNode: selfNode.PubKeyBytes, interceptorChain: interceptorChain, + extSubserverCfg: extSubserverCfg, + extRestRegistrar: extRestRegistrar, } lnrpc.RegisterLightningServer(grpcServer, rootRPCServer) @@ -768,32 +777,27 @@ func (r *rpcServer) Start() error { } } + // Before actually listening on the gRPC listener, give external + // subservers the chance to register to our gRPC server. Those external + // subservers (think GrUB) are responsible for starting/stopping on + // their own, we just let them register their services to the same + // server instance so all of them can be exposed on the same + // port/listener. + if r.extSubserverCfg != nil && r.extSubserverCfg.Registrar != nil { + registerer := r.extSubserverCfg.Registrar + err := registerer.RegisterGrpcSubserver(r.grpcServer) + if err != nil { + rpcsLog.Errorf("error registering external gRPC "+ + "subserver: %v", err) + } + } + // With all the sub-servers started, we'll spin up the listeners for // the main RPC server itself. for _, lis := range r.listeners { go func(lis *ListenerWithSignal) { rpcsLog.Infof("RPC server listening on %s", lis.Addr()) - // Before actually listening on the gRPC listener, give - // external subservers the chance to register to our - // gRPC server. Those external subservers (think GrUB) - // are responsible for starting/stopping on their own, - // we just let them register their services to the same - // server instance so all of them can be exposed on the - // same port/listener. - extSubCfg := lis.ExternalRPCSubserverCfg - if extSubCfg != nil && extSubCfg.Registrar != nil { - registerer := extSubCfg.Registrar - err := registerer.RegisterGrpcSubserver( - r.grpcServer, - ) - if err != nil { - rpcsLog.Errorf("error registering "+ - "external gRPC subserver: %v", - err) - } - } - // Close the ready chan to indicate we are listening. close(lis.Ready) _ = r.grpcServer.Serve(lis) @@ -853,16 +857,14 @@ func (r *rpcServer) Start() error { // Before listening on any of the interfaces, we also want to give the // external subservers a chance to register their own REST proxy stub // with our mux instance. - for _, lis := range r.listeners { - if lis.ExternalRestRegistrar != nil { - err := lis.ExternalRestRegistrar.RegisterRestSubserver( - restCtx, restMux, r.restProxyDest, - r.restDialOpts, - ) - if err != nil { - rpcsLog.Errorf("error registering "+ - "external REST subserver: %v", err) - } + if r.extRestRegistrar != nil { + err := r.extRestRegistrar.RegisterRestSubserver( + restCtx, restMux, r.restProxyDest, + r.restDialOpts, + ) + if err != nil { + rpcsLog.Errorf("error registering "+ + "external REST subserver: %v", err) } } From 4bbf5c4b6dafdd141627213a743ac76c25c39382 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 2 Feb 2021 13:45:29 +0100 Subject: [PATCH 05/10] rpcserver: use same grpc server for both services This commit achieves what we have been building up to: running the WalletUnlockerService and the LightningService on the same gRPC server simultaneously! To achieve this, we first create the RPC server in a "interface only" way, only creating the struct and setting the dependencies we have available before the wallet has been unlocked. After the wallet has been unlocked and we have created all the subsystems we need, we add those to the RPC server, and start the sub-servers. This means that the WalletUnlockerService and the LightningService both will be registered and available at all times on the gRPC server. However, before the wallet has been unlocked, the LightningService should not be used since the RPC server is not yet ready to handle the calls. Similarly, after the wallet has been unlocked, the WalletUnlockerService should not be used. This we will ensure in following commits. --- lnd.go | 319 +++++++++++++++++++++++++++++---------------------- rpcserver.go | 307 +++++++++++++++++-------------------------------- 2 files changed, 286 insertions(+), 340 deletions(-) diff --git a/lnd.go b/lnd.go index 14016b9e..8aa31739 100644 --- a/lnd.go +++ b/lnd.go @@ -45,6 +45,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/tor" @@ -185,13 +186,6 @@ type ListenerCfg struct { ExternalRestRegistrar RestRegistrar } -// rpcListeners is a function type used for closures that fetches a set of RPC -// listeners for the current configuration. If no custom listeners are present, -// this should return normal listeners from the RPC endpoints defined in the -// config. The second return value us a closure that will close the fetched -// listeners. -type rpcListeners func() ([]*ListenerWithSignal, func(), error) - // Main is the true entry point for lnd. It accepts a fully populated and // validated main configuration struct and an optional listener config struct. // This function starts all main system components then blocks until a signal @@ -280,22 +274,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { defer cleanUp() - // We use the first RPC listener as the destination for our REST proxy. - // If the listener is set to listen on all interfaces, we replace it - // with localhost, as we cannot dial it directly. - restProxyDest := cfg.RPCListeners[0].String() - switch { - case strings.Contains(restProxyDest, "0.0.0.0"): - restProxyDest = strings.Replace( - restProxyDest, "0.0.0.0", "127.0.0.1", 1, - ) - - case strings.Contains(restProxyDest, "[::]"): - restProxyDest = strings.Replace( - restProxyDest, "[::]", "[::1]", 1, - ) - } - // Before starting the wallet, we'll create and start our Neutrino // light client instance, if enabled, in order to allow it to sync // while the rest of the daemon continues startup. @@ -320,7 +298,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { var ( walletInitParams WalletUnlockParams - shutdownUnlocker = func() {} privateWalletPw = lnwallet.DefaultPrivatePassphrase publicWalletPw = lnwallet.DefaultPublicPassphrase ) @@ -330,11 +307,14 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { // this information. walletInitParams.Birthday = time.Now() - // getListeners is a closure that creates listeners from the - // RPCListeners defined in the config. It also returns a cleanup - // closure and the server options to use for the GRPC server. - getListeners := func() ([]*ListenerWithSignal, func(), error) { - var grpcListeners []*ListenerWithSignal + // If we have chosen to start with a dedicated listener for the + // rpc server, we set it directly. + var grpcListeners []*ListenerWithSignal + if lisCfg.RPCListener != nil { + grpcListeners = []*ListenerWithSignal{lisCfg.RPCListener} + } else { + // Otherwise we create listeners from the RPCListeners defined + // in the config. for _, grpcEndpoint := range cfg.RPCListeners { // Start a gRPC server listening for HTTP/2 // connections. @@ -342,38 +322,16 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { if err != nil { ltndLog.Errorf("unable to listen on %s", grpcEndpoint) - return nil, nil, err + return err } + defer lis.Close() + grpcListeners = append( grpcListeners, &ListenerWithSignal{ Listener: lis, Ready: make(chan struct{}), }) } - - cleanup := func() { - for _, lis := range grpcListeners { - lis.Close() - } - } - return grpcListeners, cleanup, nil - } - - // walletUnlockerListeners is a closure we'll hand to the wallet - // unlocker, that will be called when it needs listeners for its GPRC - // server. - walletUnlockerListeners := func() ([]*ListenerWithSignal, func(), - error) { - - // If we have chosen to start with a dedicated listener for the - // wallet unlocker, we return it directly. - if lisCfg.WalletUnlocker != nil { - return []*ListenerWithSignal{lisCfg.WalletUnlocker}, - func() {}, nil - } - - // Otherwise we'll return the regular listeners. - return getListeners() } // Create a new RPC interceptor chain that we'll add to the GRPC @@ -385,14 +343,50 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { rpcServerOpts := interceptorChain.CreateServerOpts() serverOpts = append(serverOpts, rpcServerOpts...) + grpcServer := grpc.NewServer(serverOpts...) + defer grpcServer.Stop() + + // We'll create the WalletUnlockerService and register this with the + // GRPC server. + pwService := createWalletUnlockerService(cfg) + lnrpc.RegisterWalletUnlockerServer(grpcServer, pwService) + + // Initialize, and register our implementation of the gRPC interface + // exported by the rpcServer. + rpcServer := newRPCServer( + cfg, interceptorChain, lisCfg.ExternalRPCSubserverCfg, + lisCfg.ExternalRestRegistrar, + ) + + err = rpcServer.RegisterWithGrpcServer(grpcServer) + if err != nil { + return err + } + + // Now that both the WalletUnlocker and LightningService have been + // registered with the GRPC server, we can start listening. + err = startGrpcListen(cfg, grpcServer, grpcListeners) + if err != nil { + return err + } + + // Now start the REST proxy for our gRPC server above. We'll ensure + // we direct LND to connect to its loopback address rather than a + // wildcard to prevent certificate issues when accessing the proxy + // externally. + stopProxy, err := startRestProxy( + cfg, rpcServer, restDialOpts, restListen, + ) + if err != nil { + return err + } + defer stopProxy() + // We wait until the user provides a password over RPC. In case lnd is // started with the --noseedbackup flag, we use the default password // for wallet encryption. if !cfg.NoSeedBackup { - params, shutdown, err := waitForWalletPassword( - cfg, cfg.RESTListeners, serverOpts, restDialOpts, - restProxyDest, restListen, walletUnlockerListeners, - ) + params, err := waitForWalletPassword(cfg, pwService) if err != nil { err := fmt.Errorf("unable to set up wallet password "+ "listeners: %v", err) @@ -401,7 +395,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { } walletInitParams = *params - shutdownUnlocker = shutdown privateWalletPw = walletInitParams.Password publicWalletPw = walletInitParams.Password defer func() { @@ -509,10 +502,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { interceptorChain.AddMacaroonService(macaroonService) } - // Now we're definitely done with the unlocker, shut it down so we can - // start the main RPC service later. - shutdownUnlocker() - // With the information parsed from the configuration, create valid // instances of the pertinent interfaces required to operate the // Lightning Network Daemon. @@ -732,31 +721,14 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { } defer atplManager.Stop() - // rpcListeners is a closure we'll hand to the rpc server, that will be - // called when it needs listeners for its GPRC server. - rpcListeners := func() ([]*ListenerWithSignal, func(), error) { - // If we have chosen to start with a dedicated listener for the - // rpc server, we return it directly. - if lisCfg.RPCListener != nil { - return []*ListenerWithSignal{lisCfg.RPCListener}, - func() {}, nil - } - - // Otherwise we'll return the regular listeners. - return getListeners() - } - - // Initialize, and register our implementation of the gRPC interface - // exported by the rpcServer. - rpcServer, err := newRPCServer( - cfg, server, macaroonService, cfg.SubRPCServers, serverOpts, - restDialOpts, restProxyDest, atplManager, server.invoices, - tower, restListen, rpcListeners, chainedAcceptor, - interceptorChain, lisCfg.ExternalRPCSubserverCfg, - lisCfg.ExternalRestRegistrar, + // Now we have created all dependencies necessary to populate and + // start the RPC server. + err = rpcServer.addDeps( + server, macaroonService, cfg.SubRPCServers, atplManager, + server.invoices, tower, chainedAcceptor, ) if err != nil { - err := fmt.Errorf("unable to create RPC server: %v", err) + err := fmt.Errorf("unable to add deps to RPC server: %v", err) ltndLog.Error(err) return err } @@ -1141,14 +1113,9 @@ type WalletUnlockParams struct { MacResponseChan chan []byte } -// waitForWalletPassword will spin up gRPC and REST endpoints for the -// WalletUnlocker server, and block until a password is provided by -// the user to this RPC server. -func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, - serverOpts []grpc.ServerOption, restDialOpts []grpc.DialOption, - restProxyDest string, restListen func(net.Addr) (net.Listener, error), - getListeners rpcListeners) (*WalletUnlockParams, func(), error) { - +// createWalletUnlockerService creates a WalletUnlockerService from the passed +// config. +func createWalletUnlockerService(cfg *Config) *walletunlocker.UnlockerService { chainConfig := cfg.Bitcoin if cfg.registeredChains.PrimaryChain() == chainreg.LitecoinChain { chainConfig = cfg.Litecoin @@ -1160,36 +1127,16 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, macaroonFiles := []string{ cfg.AdminMacPath, cfg.ReadMacPath, cfg.InvoiceMacPath, } - pwService := walletunlocker.New( + return walletunlocker.New( chainConfig.ChainDir, cfg.ActiveNetParams.Params, !cfg.SyncFreelist, macaroonFiles, cfg.DB.Bolt.DBTimeout, cfg.ResetWalletTransactions, ) +} - // Set up a new PasswordService, which will listen for passwords - // provided over RPC. - grpcServer := grpc.NewServer(serverOpts...) - lnrpc.RegisterWalletUnlockerServer(grpcServer, pwService) - - var shutdownFuncs []func() - shutdown := func() { - // Make sure nothing blocks on reading on the macaroon channel, - // otherwise the GracefulStop below will never return. - close(pwService.MacResponseChan) - - for _, shutdownFn := range shutdownFuncs { - shutdownFn() - } - } - shutdownFuncs = append(shutdownFuncs, grpcServer.GracefulStop) - - // Start a gRPC server listening for HTTP/2 connections, solely used - // for getting the encryption password from the client. - listeners, cleanup, err := getListeners() - if err != nil { - return nil, shutdown, err - } - shutdownFuncs = append(shutdownFuncs, cleanup) +// startGrpcListen starts the GRPC server on the passed listeners. +func startGrpcListen(cfg *Config, grpcServer *grpc.Server, + listeners []*ListenerWithSignal) error { // Use a WaitGroup so we can be sure the instructions on how to input the // password is the last thing to be printed to the console. @@ -1198,8 +1145,7 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, for _, lis := range listeners { wg.Add(1) go func(lis *ListenerWithSignal) { - rpcsLog.Infof("Password RPC server listening on %s", - lis.Addr()) + rpcsLog.Infof("RPC server listening on %s", lis.Addr()) // Close the ready chan to indicate we are listening. close(lis.Ready) @@ -1209,29 +1155,102 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, }(lis) } - // Start a REST proxy for our gRPC server above. + // If Prometheus monitoring is enabled, start the Prometheus exporter. + if cfg.Prometheus.Enabled() { + err := monitoring.ExportPrometheusMetrics( + grpcServer, cfg.Prometheus, + ) + if err != nil { + return err + } + } + + // Wait for gRPC servers to be up running. + wg.Wait() + + return nil +} + +// startRestProxy starts the given REST proxy on the listeners found in the +// config. +func startRestProxy(cfg *Config, rpcServer *rpcServer, restDialOpts []grpc.DialOption, + restListen func(net.Addr) (net.Listener, error)) (func(), error) { + + // We use the first RPC listener as the destination for our REST proxy. + // If the listener is set to listen on all interfaces, we replace it + // with localhost, as we cannot dial it directly. + restProxyDest := cfg.RPCListeners[0].String() + switch { + case strings.Contains(restProxyDest, "0.0.0.0"): + restProxyDest = strings.Replace( + restProxyDest, "0.0.0.0", "127.0.0.1", 1, + ) + + case strings.Contains(restProxyDest, "[::]"): + restProxyDest = strings.Replace( + restProxyDest, "[::]", "[::1]", 1, + ) + } + + var shutdownFuncs []func() + shutdown := func() { + for _, shutdownFn := range shutdownFuncs { + shutdownFn() + } + } + + // Start a REST proxy for our gRPC server. ctx := context.Background() ctx, cancel := context.WithCancel(ctx) shutdownFuncs = append(shutdownFuncs, cancel) - mux := proxy.NewServeMux() + // We'll set up a proxy that will forward REST calls to the GRPC + // server. + // + // The default JSON marshaler of the REST proxy only sets OrigName to + // true, which instructs it to use the same field names as specified in + // the proto file and not switch to camel case. What we also want is + // that the marshaler prints all values, even if they are falsey. + customMarshalerOption := proxy.WithMarshalerOption( + proxy.MIMEWildcard, &proxy.JSONPb{ + OrigName: true, + EmitDefaults: true, + }, + ) + mux := proxy.NewServeMux(customMarshalerOption) - err = lnrpc.RegisterWalletUnlockerHandlerFromEndpoint( + // Register both services with the REST proxy. + err := lnrpc.RegisterWalletUnlockerHandlerFromEndpoint( ctx, mux, restProxyDest, restDialOpts, ) if err != nil { - return nil, shutdown, err + return nil, err } - srv := &http.Server{Handler: allowCORS(mux, cfg.RestCORS)} + err = rpcServer.RegisterWithRestProxy( + ctx, mux, restDialOpts, restProxyDest, + ) + if err != nil { + return nil, err + } - for _, restEndpoint := range restEndpoints { + // Wrap the default grpc-gateway handler with the WebSocket handler. + restHandler := lnrpc.NewWebSocketProxy(mux, rpcsLog) + + // Use a WaitGroup so we can be sure the instructions on how to input the + // password is the last thing to be printed to the console. + var wg sync.WaitGroup + + // Now spin up a network listener for each requested port and start a + // goroutine that serves REST with the created mux there. + for _, restEndpoint := range cfg.RESTListeners { lis, err := restListen(restEndpoint) if err != nil { - ltndLog.Errorf("Password gRPC proxy unable to listen "+ - "on %s", restEndpoint) - return nil, shutdown, err + ltndLog.Errorf("gRPC proxy unable to listen on %s", + restEndpoint) + return nil, err } + shutdownFuncs = append(shutdownFuncs, func() { err := lis.Close() if err != nil { @@ -1242,16 +1261,38 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, wg.Add(1) go func() { - rpcsLog.Infof("Password gRPC proxy started at %s", - lis.Addr()) + rpcsLog.Infof("gRPC proxy started at %s", lis.Addr()) + + // Create our proxy chain now. A request will pass + // through the following chain: + // req ---> CORS handler --> WS proxy ---> + // REST proxy --> gRPC endpoint + corsHandler := allowCORS(restHandler, cfg.RestCORS) + wg.Done() - _ = srv.Serve(lis) + err := http.Serve(lis, corsHandler) + if err != nil && !lnrpc.IsClosedConnError(err) { + rpcsLog.Error(err) + } }() } - // Wait for gRPC and REST servers to be up running. + // Wait for REST servers to be up running. wg.Wait() + return shutdown, nil +} + +// waitForWalletPassword blocks until a password is provided by the user to +// this RPC server. +func waitForWalletPassword(cfg *Config, + pwService *walletunlocker.UnlockerService) (*WalletUnlockParams, error) { + + chainConfig := cfg.Bitcoin + if cfg.registeredChains.PrimaryChain() == chainreg.LitecoinChain { + chainConfig = cfg.Litecoin + } + // Wait for user to provide the password. ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " + "create` to create a wallet, `lncli unlock` to unlock an " + @@ -1276,7 +1317,7 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, // version, then we'll return an error as we don't understand // this. if cipherSeed.InternalVersion != keychain.KeyDerivationVersion { - return nil, shutdown, fmt.Errorf("invalid internal "+ + return nil, fmt.Errorf("invalid internal "+ "seed version %v, current version is %v", cipherSeed.InternalVersion, keychain.KeyDerivationVersion) @@ -1303,7 +1344,7 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, ltndLog.Errorf("Could not unload new "+ "wallet: %v", err) } - return nil, shutdown, err + return nil, err } // For new wallets, the ResetWalletTransactions flag is a no-op. @@ -1321,7 +1362,7 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, UnloadWallet: loader.UnloadWallet, StatelessInit: initMsg.StatelessInit, MacResponseChan: pwService.MacResponseChan, - }, shutdown, nil + }, nil // The wallet has already been created in the past, and is simply being // unlocked. So we'll just return these passphrases. @@ -1345,10 +1386,10 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, UnloadWallet: unlockMsg.UnloadWallet, StatelessInit: unlockMsg.StatelessInit, MacResponseChan: pwService.MacResponseChan, - }, shutdown, nil + }, nil case <-signal.ShutdownChannel(): - return nil, shutdown, fmt.Errorf("shutting down") + return nil, fmt.Errorf("shutting down") } } diff --git a/rpcserver.go b/rpcserver.go index 0d355e00..123da961 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "math" - "net" "net/http" "runtime" "sort" @@ -60,7 +59,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" - "github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/peer" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/record" @@ -495,33 +493,6 @@ type rpcServer struct { subServers []lnrpc.SubServer subGrpcHandlers []lnrpc.GrpcHandler - // grpcServer is the main gRPC server that this RPC server, and all the - // sub-servers will use to register themselves and accept client - // requests from. - grpcServer *grpc.Server - - // listeners is a list of listeners to use when starting the grpc - // server. We make it configurable such that the grpc server can listen - // on custom interfaces. - listeners []*ListenerWithSignal - - // listenerCleanUp are a set of closures functions that will allow this - // main RPC server to clean up all the listening socket created for the - // server. - listenerCleanUp []func() - - // restDialOpts are a set of gRPC dial options that the REST server - // proxy will use to connect to the main gRPC server. - restDialOpts []grpc.DialOption - - // restProxyDest is the address to forward REST requests to. - restProxyDest string - - // restListen is a function closure that allows the REST server proxy to - // connect to the main gRPC server to proxy all incoming requests, - // applying the current TLS configuration, if any. - restListen func(net.Addr) (net.Listener, error) - // routerBackend contains the backend implementation of the router // rpc sub server. routerBackend *routerrpc.RouterBackend @@ -555,27 +526,48 @@ type rpcServer struct { // LightningServer gRPC service. var _ lnrpc.LightningServer = (*rpcServer)(nil) -// newRPCServer creates and returns a new instance of the rpcServer. The -// rpcServer will handle creating all listening sockets needed by it, and any -// of the sub-servers that it maintains. The set of serverOpts should be the -// base level options passed to the grPC server. This typically includes things -// like requiring TLS, etc. -func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, - subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption, - restDialOpts []grpc.DialOption, restProxyDest string, - atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, - tower *watchtower.Standalone, - restListen func(net.Addr) (net.Listener, error), - getListeners rpcListeners, chanPredicate *chanacceptor.ChainedAcceptor, - interceptorChain *rpcperms.InterceptorChain, - extSubserverCfg *RPCSubserverConfig, extRestRegistrar RestRegistrar) ( - *rpcServer, error) { +// newRPCServer creates and returns a new instance of the rpcServer. Before +// dependencies are added, this will be an non-functioning RPC server only to +// be used to register the LightningService with the gRPC server. +func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain, + extSubserverCfg *RPCSubserverConfig, + extRestRegistrar RestRegistrar) *rpcServer { + + // We go trhough the list of registered sub-servers, and create a gRPC + // handler for each. These are used to register with the gRPC server + // before all dependencies are available. + registeredSubServers := lnrpc.RegisteredSubServers() + + var subServerHandlers []lnrpc.GrpcHandler + for _, subServer := range registeredSubServers { + subServerHandlers = append( + subServerHandlers, subServer.NewGrpcHandler(), + ) + } + + return &rpcServer{ + cfg: cfg, + subGrpcHandlers: subServerHandlers, + interceptorChain: interceptorChain, + extSubserverCfg: extSubserverCfg, + extRestRegistrar: extRestRegistrar, + quit: make(chan struct{}, 1), + } +} + +// addDeps populates all dependencies needed by the RPC server, and any +// of the sub-servers that it maintains. When this is done, the RPC server can +// be started, and start accepting RPC calls. +func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, + subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager, + invoiceRegistry *invoices.InvoiceRegistry, tower *watchtower.Standalone, + chanPredicate *chanacceptor.ChainedAcceptor) error { // Set up router rpc backend. channelGraph := s.localChanDB.ChannelGraph() selfNode, err := channelGraph.SourceNode() if err != nil { - return nil, err + return err } graph := s.localChanDB.ChannelGraph() routerBackend := &routerrpc.RouterBackend{ @@ -606,10 +598,10 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, }, FindRoute: s.chanRouter.FindRoute, MissionControl: s.missionControl, - ActiveNetParams: cfg.ActiveNetParams.Params, + ActiveNetParams: r.cfg.ActiveNetParams.Params, Tower: s.controlTower, - MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry, - DefaultFinalCltvDelta: uint16(cfg.Bitcoin.TimeLockDelta), + MaxTotalTimelock: r.cfg.MaxOutgoingCltvExpiry, + DefaultFinalCltvDelta: uint16(r.cfg.Bitcoin.TimeLockDelta), SubscribeHtlcEvents: s.htlcNotifier.SubscribeHtlcEvents, InterceptableForwarder: s.interceptableSwitch, SetChannelEnabled: func(outpoint wire.OutPoint) error { @@ -626,9 +618,8 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, } var ( - subServers []lnrpc.SubServer - subGrpcHandlers []lnrpc.GrpcHandler - subServerPerms []lnrpc.MacaroonPerms + subServers []lnrpc.SubServer + subServerPerms []lnrpc.MacaroonPerms ) // Before we create any of the sub-servers, we need to ensure that all @@ -637,31 +628,30 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // // TODO(roasbeef): extend sub-sever config to have both (local vs remote) DB err = subServerCgs.PopulateDependencies( - cfg, s.cc, cfg.networkDir, macService, atpl, invoiceRegistry, - s.htlcSwitch, cfg.ActiveNetParams.Params, s.chanRouter, + r.cfg, s.cc, r.cfg.networkDir, macService, atpl, invoiceRegistry, + s.htlcSwitch, r.cfg.ActiveNetParams.Params, s.chanRouter, routerBackend, s.nodeSigner, s.localChanDB, s.remoteChanDB, s.sweeper, tower, s.towerClient, s.anchorTowerClient, - cfg.net.ResolveTCPAddr, genInvoiceFeatures, rpcsLog, + r.cfg.net.ResolveTCPAddr, genInvoiceFeatures, rpcsLog, ) if err != nil { - return nil, err + return err } // Now that the sub-servers have all their dependencies in place, we // can create each sub-server! - registeredSubServers := lnrpc.RegisteredSubServers() - for _, driver := range registeredSubServers { - handler := driver.NewGrpcHandler() - subServer, macPerms, err := handler.CreateSubServer(subServerCgs) + for _, subServerInstance := range r.subGrpcHandlers { + subServer, macPerms, err := subServerInstance.CreateSubServer( + subServerCgs, + ) if err != nil { - return nil, err + return err } // We'll collect the sub-server, and also the set of // permissions it needs for macaroons so we can apply the // interceptors below. subServers = append(subServers, subServer) - subGrpcHandlers = append(subGrpcHandlers, handler) subServerPerms = append(subServerPerms, macPerms) } @@ -669,35 +659,29 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, // with the main RPC server permissions so we can unite them under a // single set of interceptors. for m, ops := range MainRPCServerPermissions() { - err := interceptorChain.AddPermission(m, ops) + err := r.interceptorChain.AddPermission(m, ops) if err != nil { - return nil, err + return err } } for _, subServerPerm := range subServerPerms { for method, ops := range subServerPerm { - err := interceptorChain.AddPermission(method, ops) + err := r.interceptorChain.AddPermission(method, ops) if err != nil { - return nil, err + return err } } } - // Get the listeners and server options to use for this rpc server. - listeners, cleanup, err := getListeners() - if err != nil { - return nil, err - } - // External subserver possibly need to register their own permissions // and macaroon validator. - if extSubserverCfg != nil { - macValidator := extSubserverCfg.MacaroonValidator - for method, ops := range extSubserverCfg.Permissions { - err := interceptorChain.AddPermission(method, ops) + if r.extSubserverCfg != nil { + macValidator := r.extSubserverCfg.MacaroonValidator + for method, ops := range r.extSubserverCfg.Permissions { + err := r.interceptorChain.AddPermission(method, ops) if err != nil { - return nil, err + return err } // Give the external subservers the possibility @@ -710,7 +694,7 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, method, macValidator, ) if err != nil { - return nil, fmt.Errorf("could "+ + return fmt.Errorf("could "+ "not register "+ "external macaroon "+ "validator: %v", err) @@ -719,43 +703,50 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, } } - // Finally, with all the pre-set up complete, we can create the main - // gRPC server, and register the main lnrpc server along side. - grpcServer := grpc.NewServer(serverOpts...) - rootRPCServer := &rpcServer{ - cfg: cfg, - restDialOpts: restDialOpts, - listeners: listeners, - listenerCleanUp: []func(){cleanup}, - restProxyDest: restProxyDest, - subServers: subServers, - subGrpcHandlers: subGrpcHandlers, - restListen: restListen, - grpcServer: grpcServer, - server: s, - routerBackend: routerBackend, - chanPredicate: chanPredicate, - quit: make(chan struct{}, 1), - macService: macService, - selfNode: selfNode.PubKeyBytes, - interceptorChain: interceptorChain, - extSubserverCfg: extSubserverCfg, - extRestRegistrar: extRestRegistrar, - } - lnrpc.RegisterLightningServer(grpcServer, rootRPCServer) + // Finally, with all the set up complete, add the last dependencies to + // the rpc server. + r.server = s + r.subServers = subServers + r.routerBackend = routerBackend + r.chanPredicate = chanPredicate + r.macService = macService + r.selfNode = selfNode.PubKeyBytes + return nil +} + +// RegisterWithGrpcServer registers the rpcServer and any subservers with the +// root gRPC server. +func (r *rpcServer) RegisterWithGrpcServer(grpcServer *grpc.Server) error { + // Register the main RPC server. + lnrpc.RegisterLightningServer(grpcServer, r) // Now the main RPC server has been registered, we'll iterate through // all the sub-RPC servers and register them to ensure that requests // are properly routed towards them. - for _, subServer := range subGrpcHandlers { + for _, subServer := range r.subGrpcHandlers { err := subServer.RegisterWithRootServer(grpcServer) if err != nil { - return nil, fmt.Errorf("unable to register "+ + return fmt.Errorf("unable to register "+ "sub-server with root: %v", err) } } - return rootRPCServer, nil + // Before actually listening on the gRPC listener, give external + // subservers the chance to register to our gRPC server. Those external + // subservers (think GrUB) are responsible for starting/stopping on + // their own, we just let them register their services to the same + // server instance so all of them can be exposed on the same + // port/listener. + if r.extSubserverCfg != nil && r.extSubserverCfg.Registrar != nil { + registerer := r.extSubserverCfg.Registrar + err := registerer.RegisterGrpcSubserver(grpcServer) + if err != nil { + rpcsLog.Errorf("error registering external gRPC "+ + "subserver: %v", err) + } + } + + return nil } // Start launches any helper goroutines required for the rpcServer to function. @@ -777,76 +768,27 @@ func (r *rpcServer) Start() error { } } - // Before actually listening on the gRPC listener, give external - // subservers the chance to register to our gRPC server. Those external - // subservers (think GrUB) are responsible for starting/stopping on - // their own, we just let them register their services to the same - // server instance so all of them can be exposed on the same - // port/listener. - if r.extSubserverCfg != nil && r.extSubserverCfg.Registrar != nil { - registerer := r.extSubserverCfg.Registrar - err := registerer.RegisterGrpcSubserver(r.grpcServer) - if err != nil { - rpcsLog.Errorf("error registering external gRPC "+ - "subserver: %v", err) - } - } + return nil +} - // With all the sub-servers started, we'll spin up the listeners for - // the main RPC server itself. - for _, lis := range r.listeners { - go func(lis *ListenerWithSignal) { - rpcsLog.Infof("RPC server listening on %s", lis.Addr()) - - // Close the ready chan to indicate we are listening. - close(lis.Ready) - _ = r.grpcServer.Serve(lis) - }(lis) - } - - // If Prometheus monitoring is enabled, start the Prometheus exporter. - if r.cfg.Prometheus.Enabled() { - err := monitoring.ExportPrometheusMetrics( - r.grpcServer, r.cfg.Prometheus, - ) - if err != nil { - return err - } - } - - // The default JSON marshaler of the REST proxy only sets OrigName to - // true, which instructs it to use the same field names as specified in - // the proto file and not switch to camel case. What we also want is - // that the marshaler prints all values, even if they are falsey. - customMarshalerOption := proxy.WithMarshalerOption( - proxy.MIMEWildcard, &proxy.JSONPb{ - OrigName: true, - EmitDefaults: true, - }, - ) - - // Now start the REST proxy for our gRPC server above. We'll ensure - // we direct LND to connect to its loopback address rather than a - // wildcard to prevent certificate issues when accessing the proxy - // externally. - restMux := proxy.NewServeMux(customMarshalerOption) - restCtx, restCancel := context.WithCancel(context.Background()) - r.listenerCleanUp = append(r.listenerCleanUp, restCancel) - - // Wrap the default grpc-gateway handler with the WebSocket handler. - restHandler := lnrpc.NewWebSocketProxy(restMux, rpcsLog) +// RegisterWithRestProxy registers the RPC server and any subservers with the +// given REST proxy. +func (r *rpcServer) RegisterWithRestProxy(restCtx context.Context, + restMux *proxy.ServeMux, restDialOpts []grpc.DialOption, + restProxyDest string) error { // With our custom REST proxy mux created, register our main RPC and // give all subservers a chance to register as well. err := lnrpc.RegisterLightningHandlerFromEndpoint( - restCtx, restMux, r.restProxyDest, r.restDialOpts, + restCtx, restMux, restProxyDest, restDialOpts, ) if err != nil { return err } + for _, subServer := range r.subGrpcHandlers { err := subServer.RegisterWithRestServer( - restCtx, restMux, r.restProxyDest, r.restDialOpts, + restCtx, restMux, restProxyDest, restDialOpts, ) if err != nil { return fmt.Errorf("unable to register REST sub-server "+ @@ -859,44 +801,13 @@ func (r *rpcServer) Start() error { // with our mux instance. if r.extRestRegistrar != nil { err := r.extRestRegistrar.RegisterRestSubserver( - restCtx, restMux, r.restProxyDest, - r.restDialOpts, + restCtx, restMux, restProxyDest, restDialOpts, ) if err != nil { rpcsLog.Errorf("error registering "+ "external REST subserver: %v", err) } } - - // Now spin up a network listener for each requested port and start a - // goroutine that serves REST with the created mux there. - for _, restEndpoint := range r.cfg.RESTListeners { - lis, err := r.restListen(restEndpoint) - if err != nil { - ltndLog.Errorf("gRPC proxy unable to listen on %s", - restEndpoint) - return err - } - - r.listenerCleanUp = append(r.listenerCleanUp, func() { - _ = lis.Close() - }) - - go func() { - rpcsLog.Infof("gRPC proxy started at %s", lis.Addr()) - - // Create our proxy chain now. A request will pass - // through the following chain: - // req ---> CORS handler --> WS proxy ---> - // REST proxy --> gRPC endpoint - corsHandler := allowCORS(restHandler, r.cfg.RestCORS) - err := http.Serve(lis, corsHandler) - if err != nil && !lnrpc.IsClosedConnError(err) { - rpcsLog.Error(err) - } - }() - } - return nil } @@ -924,12 +835,6 @@ func (r *rpcServer) Stop() error { } } - // Finally, we can clean up all the listening sockets to ensure that we - // give the file descriptors back to the OS. - for _, cleanUp := range r.listenerCleanUp { - cleanUp() - } - return nil } From 5e9e03858d994615e71ab94a178454fff2739cc0 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 5 Feb 2021 13:30:59 +0100 Subject: [PATCH 06/10] lntest/node: call FetchNode info in wait.NoError after init In case we call it before the RPC server is fully active. --- lntest/node.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lntest/node.go b/lntest/node.go index 1181b785..89afca73 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -801,8 +801,9 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { hn.SignerClient = signrpc.NewSignerClient(conn) // Set the harness node's pubkey to what the node claims in GetInfo. - err := hn.FetchNodeInfo() - if err != nil { + // Since the RPC might not be immediately active, we wrap the call in a + // wait.NoError. + if err := wait.NoError(hn.FetchNodeInfo, DefaultTimeout); err != nil { return err } @@ -812,7 +813,7 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { // until then, we'll create a dummy subscription to ensure we can do so // successfully before proceeding. We use a dummy subscription in order // to not consume an update from the real one. - err = wait.NoError(func() error { + err := wait.NoError(func() error { req := &lnrpc.GraphTopologySubscription{} ctx, cancelFunc := context.WithCancel(context.Background()) topologyClient, err := hn.SubscribeChannelGraph(ctx, req) From 0d7763fb96a53541fa39e4879bb39f9a2350a33d Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 9 Feb 2021 14:57:33 +0100 Subject: [PATCH 07/10] walletunlocker: expose WalletExists --- walletunlocker/service.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/walletunlocker/service.go b/walletunlocker/service.go index 9855f22a..0c3c0b3d 100644 --- a/walletunlocker/service.go +++ b/walletunlocker/service.go @@ -160,6 +160,17 @@ func New(chainDir string, params *chaincfg.Params, noFreelistSync bool, } } +// WalletExists returns whether a wallet exists on the file path the +// UnlockerService is using. +func (u *UnlockerService) WalletExists() (bool, error) { + netDir := btcwallet.NetworkDir(u.chainDir, u.netParams) + loader := wallet.NewLoader( + u.netParams, netDir, u.noFreelistSync, u.dbTimeout, 0, + ) + + return loader.WalletExists() +} + // GenSeed is the first method that should be used to instantiate a new lnd // instance. This method allows a caller to generate a new aezeed cipher seed // given an optional passphrase. If provided, the passphrase will be necessary From 2877511fce2e88374162f304c5975d2249f78696 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 5 Feb 2021 13:33:01 +0100 Subject: [PATCH 08/10] rpcperms+lnd: gate RPC calls on RPC state This commit makes us gate the calls to the RPC servers according to the current RPC state. This ensures we won't try to call the RPC server before it has been fully initialized, and that we won't call the walletUnlocker after the wallet already has been unlocked. --- lnd.go | 26 ++++-- lntest/harness.go | 8 +- rpcperms/interceptor.go | 198 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 204 insertions(+), 28 deletions(-) diff --git a/lnd.go b/lnd.go index 8aa31739..f1aedc5b 100644 --- a/lnd.go +++ b/lnd.go @@ -334,11 +334,18 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { } } - // Create a new RPC interceptor chain that we'll add to the GRPC - // server. This will be used to log the API calls invoked on the GRPC - // server. + // We'll create the WalletUnlockerService and check whether the wallet + // already exists. + pwService := createWalletUnlockerService(cfg) + walletExists, err := pwService.WalletExists() + if err != nil { + return err + } + + // Create a new RPC interceptor that we'll add to the GRPC server. This + // will be used to log the API calls invoked on the GRPC server. interceptorChain := rpcperms.NewInterceptorChain( - rpcsLog, cfg.NoMacaroons, + rpcsLog, cfg.NoMacaroons, walletExists, ) rpcServerOpts := interceptorChain.CreateServerOpts() serverOpts = append(serverOpts, rpcServerOpts...) @@ -346,9 +353,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { grpcServer := grpc.NewServer(serverOpts...) defer grpcServer.Stop() - // We'll create the WalletUnlockerService and register this with the - // GRPC server. - pwService := createWalletUnlockerService(cfg) + // Register the WalletUnlockerService with the GRPC server. lnrpc.RegisterWalletUnlockerServer(grpcServer, pwService) // Initialize, and register our implementation of the gRPC interface @@ -410,6 +415,10 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { } } + // Now that the wallet password has been provided, transition the RPC + // state into Unlocked. + interceptorChain.SetWalletUnlocked() + var macaroonService *macaroons.Service if !cfg.NoMacaroons { // Create the macaroon authentication/authorization service. @@ -739,6 +748,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { } defer rpcServer.Stop() + // We transition the RPC state to Active, as the RPC server is up. + interceptorChain.SetRPCActive() + // If we're not in regtest or simnet mode, We'll wait until we're fully // synced to continue the start up of the remainder of the daemon. This // ensures that we don't accept any possibly invalid state transitions, or diff --git a/lntest/harness.go b/lntest/harness.go index bf8dd450..b54234da 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -300,8 +300,12 @@ func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string, ctxt, cancel := context.WithTimeout(ctxb, DefaultTimeout) defer cancel() - genSeedResp, err := node.GenSeed(ctxt, genSeedReq) - if err != nil { + + var genSeedResp *lnrpc.GenSeedResponse + if err := wait.NoError(func() error { + genSeedResp, err = node.GenSeed(ctxt, genSeedReq) + return err + }, DefaultTimeout); err != nil { return nil, nil, nil, err } diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index 5637f647..eb3370b1 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -7,13 +7,57 @@ import ( "github.com/btcsuite/btclog" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/monitoring" "google.golang.org/grpc" "gopkg.in/macaroon-bakery.v2/bakery" ) +// rpcState is an enum that we use to keep track of the current RPC service +// state. This will transition as we go from startup to unlocking the wallet, +// and finally fully active. +type rpcState uint8 + +const ( + // walletNotCreated is the starting state if the RPC server is active, + // but the wallet is not yet created. In this state we'll only allow + // calls to the WalletUnlockerService. + walletNotCreated rpcState = iota + + // walletLocked indicates the RPC server is active, but the wallet is + // locked. In this state we'll only allow calls to the + // WalletUnlockerService. + walletLocked + + // walletUnlocked means that the wallet has been unlocked, but the full + // RPC server is not yeat ready. + walletUnlocked + + // rpcActive means that the RPC server is ready to accept calls. + rpcActive +) + var ( + // ErrNoWallet is returned if the wallet does not exist. + ErrNoWallet = fmt.Errorf("wallet not created, create one to enable " + + "full RPC access") + + // ErrWalletLocked is returned if the wallet is locked and any service + // other than the WalletUnlocker is called. + ErrWalletLocked = fmt.Errorf("wallet locked, unlock it to enable " + + "full RPC access") + + // ErrWalletUnlocked is returned if the WalletUnlocker service is + // called when the wallet already has been unlocked. + ErrWalletUnlocked = fmt.Errorf("wallet already unlocked, " + + "WalletUnlocker service is no longer available") + + // ErrRPCStarting is returned if the wallet has been unlocked but the + // RPC server is not yet ready to accept calls. + ErrRPCStarting = fmt.Errorf("the RPC server is in the process of " + + "starting up, but not yet ready to accept calls") + // macaroonWhitelist defines methods that we don't require macaroons to // access. macaroonWhitelist = map[string]struct{}{ @@ -29,6 +73,9 @@ var ( // intercepting API calls. This is useful for logging, enforcing permissions // etc. type InterceptorChain struct { + // state is the current RPC state of our RPC server. + state rpcState + // noMacaroons should be set true if we don't want to check macaroons. noMacaroons bool @@ -46,14 +93,39 @@ type InterceptorChain struct { } // NewInterceptorChain creates a new InterceptorChain. -func NewInterceptorChain(log btclog.Logger, noMacaroons bool) *InterceptorChain { +func NewInterceptorChain(log btclog.Logger, noMacaroons, + walletExists bool) *InterceptorChain { + + startState := walletNotCreated + if walletExists { + startState = walletLocked + } + return &InterceptorChain{ + state: startState, noMacaroons: noMacaroons, permissionMap: make(map[string][]bakery.Op), rpcsLog: log, } } +// SetWalletUnlocked moves the RPC state from either walletNotCreated or +// walletLocked to walletUnlocked. +func (r *InterceptorChain) SetWalletUnlocked() { + r.Lock() + defer r.Unlock() + + r.state = walletUnlocked +} + +// SetRPCActive moves the RPC state from walletUnlocked to rpcActive. +func (r *InterceptorChain) SetRPCActive() { + r.Lock() + defer r.Unlock() + + r.state = rpcActive +} + // AddMacaroonService adds a macaroon service to the interceptor. After this is // done every RPC call made will have to pass a valid macaroon to be accepted. func (r *InterceptorChain) AddMacaroonService(svc *macaroons.Service) { @@ -96,16 +168,36 @@ func (r *InterceptorChain) Permissions() map[string][]bakery.Op { // CreateServerOpts creates the GRPC server options that can be added to a GRPC // server in order to add this InterceptorChain. func (r *InterceptorChain) CreateServerOpts() []grpc.ServerOption { - macUnaryInterceptors := []grpc.UnaryServerInterceptor{} - macStrmInterceptors := []grpc.StreamServerInterceptor{} + var unaryInterceptors []grpc.UnaryServerInterceptor + var strmInterceptors []grpc.StreamServerInterceptor + + // The first interceptors we'll add to the chain is our logging + // interceptors, so we can automatically log all errors that happen + // during RPC calls. + unaryInterceptors = append( + unaryInterceptors, errorLogUnaryServerInterceptor(r.rpcsLog), + ) + strmInterceptors = append( + strmInterceptors, errorLogStreamServerInterceptor(r.rpcsLog), + ) + + // Next we'll add our RPC state check interceptors, that will check + // whether the attempted call is allowed in the current state. + unaryInterceptors = append( + unaryInterceptors, r.rpcStateUnaryServerInterceptor(), + ) + strmInterceptors = append( + strmInterceptors, r.rpcStateStreamServerInterceptor(), + ) // We'll add the macaroon interceptors. If macaroons aren't disabled, // then these interceptors will enforce macaroon authentication. - unaryInterceptor := r.macaroonUnaryServerInterceptor() - macUnaryInterceptors = append(macUnaryInterceptors, unaryInterceptor) - - strmInterceptor := r.macaroonStreamServerInterceptor() - macStrmInterceptors = append(macStrmInterceptors, strmInterceptor) + unaryInterceptors = append( + unaryInterceptors, r.macaroonUnaryServerInterceptor(), + ) + strmInterceptors = append( + strmInterceptors, r.macaroonStreamServerInterceptor(), + ) // Get interceptors for Prometheus to gather gRPC performance metrics. // If monitoring is disabled, GetPromInterceptors() will return empty @@ -114,17 +206,8 @@ func (r *InterceptorChain) CreateServerOpts() []grpc.ServerOption { monitoring.GetPromInterceptors() // Concatenate the slices of unary and stream interceptors respectively. - unaryInterceptors := append(macUnaryInterceptors, promUnaryInterceptors...) - strmInterceptors := append(macStrmInterceptors, promStrmInterceptors...) - - // We'll also add our logging interceptors as well, so we can - // automatically log all errors that happen during RPC calls. - unaryInterceptors = append( - unaryInterceptors, errorLogUnaryServerInterceptor(r.rpcsLog), - ) - strmInterceptors = append( - strmInterceptors, errorLogStreamServerInterceptor(r.rpcsLog), - ) + unaryInterceptors = append(unaryInterceptors, promUnaryInterceptors...) + strmInterceptors = append(strmInterceptors, promStrmInterceptors...) // Create server options from the interceptors we just set up. chainedUnary := grpc_middleware.WithUnaryServerChain( @@ -248,3 +331,80 @@ func (r *InterceptorChain) macaroonStreamServerInterceptor() grpc.StreamServerIn return handler(srv, ss) } } + +// checkRPCState checks whether a call to the given server is allowed in the +// current RPC state. +func (r *InterceptorChain) checkRPCState(srv interface{}) error { + r.RLock() + state := r.state + r.RUnlock() + + switch state { + + // If the wallet does not exists, only calls to the WalletUnlocker are + // accepted. + case walletNotCreated: + _, ok := srv.(lnrpc.WalletUnlockerServer) + if !ok { + return ErrNoWallet + } + + // If the wallet is locked, only calls to the WalletUnlocker are + // accepted. + case walletLocked: + _, ok := srv.(lnrpc.WalletUnlockerServer) + if !ok { + return ErrWalletLocked + } + + // If the wallet is unlocked, but the RPC not yet active, we reject. + case walletUnlocked: + _, ok := srv.(lnrpc.WalletUnlockerServer) + if ok { + return ErrWalletUnlocked + } + + return ErrRPCStarting + + // If the RPC is active, we allow calls to any service except the + // WalletUnlocker. + case rpcActive: + _, ok := srv.(lnrpc.WalletUnlockerServer) + if ok { + return ErrWalletUnlocked + } + + default: + return fmt.Errorf("unknown RPC state: %v", state) + } + + return nil +} + +// rpcStateUnaryServerInterceptor is a GRPC interceptor that checks whether +// calls to the given gGRPC server is allowed in the current rpc state. +func (r *InterceptorChain) rpcStateUnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + + if err := r.checkRPCState(info.Server); err != nil { + return nil, err + } + + return handler(ctx, req) + } +} + +// rpcStateStreamServerInterceptor is a GRPC interceptor that checks whether +// calls to the given gGRPC server is allowed in the current rpc state. +func (r *InterceptorChain) rpcStateStreamServerInterceptor() grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, + info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + + if err := r.checkRPCState(srv); err != nil { + return err + } + + return handler(srv, ss) + } +} From b2e0a7d684b304a58b2b06211fbd5c7731e1b24a Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 15 Feb 2021 10:55:08 +0100 Subject: [PATCH 09/10] rpcperms: export macaroon interceptor methods Since we need access to these methods from external subservers in some cases, we export them. --- rpcperms/interceptor.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index eb3370b1..207344e3 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -193,10 +193,10 @@ func (r *InterceptorChain) CreateServerOpts() []grpc.ServerOption { // We'll add the macaroon interceptors. If macaroons aren't disabled, // then these interceptors will enforce macaroon authentication. unaryInterceptors = append( - unaryInterceptors, r.macaroonUnaryServerInterceptor(), + unaryInterceptors, r.MacaroonUnaryServerInterceptor(), ) strmInterceptors = append( - strmInterceptors, r.macaroonStreamServerInterceptor(), + strmInterceptors, r.MacaroonStreamServerInterceptor(), ) // Get interceptors for Prometheus to gather gRPC performance metrics. @@ -300,9 +300,9 @@ func (r *InterceptorChain) checkMacaroon(ctx context.Context, return validator.ValidateMacaroon(ctx, uriPermissions, fullMethod) } -// macaroonUnaryServerInterceptor is a GRPC interceptor that checks whether the +// MacaroonUnaryServerInterceptor is a GRPC interceptor that checks whether the // request is authorized by the included macaroons. -func (r *InterceptorChain) macaroonUnaryServerInterceptor() grpc.UnaryServerInterceptor { +func (r *InterceptorChain) MacaroonUnaryServerInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { @@ -316,9 +316,9 @@ func (r *InterceptorChain) macaroonUnaryServerInterceptor() grpc.UnaryServerInte } } -// macaroonStreamServerInterceptor is a GRPC interceptor that checks whether +// MacaroonStreamServerInterceptor is a GRPC interceptor that checks whether // the request is authorized by the included macaroons. -func (r *InterceptorChain) macaroonStreamServerInterceptor() grpc.StreamServerInterceptor { +func (r *InterceptorChain) MacaroonStreamServerInterceptor() grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { From 148f1be75f75e97be509bfc6d5f43d7c75485acf Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 8 Mar 2021 13:31:55 +0100 Subject: [PATCH 10/10] itest: add RPC errors to whitelist Since we now log RPC errors as the first thing in our interceptor chain, more benign errors bubble up. We add them to the whitelist. --- lntest/itest/log_error_whitelist.txt | 9 +++++++++ lntest/itest/log_substitutions.txt | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lntest/itest/log_error_whitelist.txt b/lntest/itest/log_error_whitelist.txt index bf2d06ea..49ee52f3 100644 --- a/lntest/itest/log_error_whitelist.txt +++ b/lntest/itest/log_error_whitelist.txt @@ -232,6 +232,15 @@