diff --git a/cmd/lnd/main.go b/cmd/lnd/main.go index 2d13fed4c..052f3de8c 100644 --- a/cmd/lnd/main.go +++ b/cmd/lnd/main.go @@ -31,11 +31,12 @@ func main() { // Help was requested, exit normally. os.Exit(0) } + implCfg := loadedConfig.ImplementationConfig() // Call the "real" main in a nested manner so the defers will properly // be executed in the case of a graceful shutdown. if err = lnd.Main( - loadedConfig, lnd.ListenerCfg{}, shutdownInterceptor, + loadedConfig, lnd.ListenerCfg{}, implCfg, shutdownInterceptor, ); err != nil { _, _ = fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/config.go b/config.go index 93401a038..dd2fa0b1b 100644 --- a/config.go +++ b/config.go @@ -1536,6 +1536,17 @@ func (c *Config) graphDatabaseDir() string { ) } +// ImplementationConfig returns the configuration of what actual implementations +// should be used when creating the main lnd instance. +func (c *Config) ImplementationConfig() *ImplementationCfg { + defaultImpl := &DefaultWalletImpl{} + return &ImplementationCfg{ + GrpcRegistrar: defaultImpl, + RestRegistrar: defaultImpl, + ExternalValidator: defaultImpl, + } +} + // CleanAndExpandPath expands environment variables and leading ~ in the // passed path, cleans the result, and returns it. // This function is taken from https://github.com/btcsuite/btcd diff --git a/config_builder.go b/config_builder.go new file mode 100644 index 000000000..f24e66535 --- /dev/null +++ b/config_builder.go @@ -0,0 +1,115 @@ +package lnd + +import ( + "context" + "fmt" + + proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lightningnetwork/lnd/macaroons" + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// GrpcRegistrar is an interface that must be satisfied by an external subserver +// that wants to be able to register its own gRPC server onto lnd's main +// grpc.Server instance. +type GrpcRegistrar interface { + // RegisterGrpcSubserver is called for each net.Listener on which lnd + // creates a grpc.Server instance. External subservers implementing this + // method can then register their own gRPC server structs to the main + // server instance. + RegisterGrpcSubserver(*grpc.Server) error +} + +// RestRegistrar is an interface that must be satisfied by an external subserver +// that wants to be able to register its own REST mux onto lnd's main +// proxy.ServeMux instance. +type RestRegistrar interface { + // RegisterRestSubserver is called after lnd creates the main + // proxy.ServeMux instance. External subservers implementing this method + // can then register their own REST proxy stubs to the main server + // instance. + RegisterRestSubserver(context.Context, *proxy.ServeMux, string, + []grpc.DialOption) error +} + +// ExternalValidator is an interface that must be satisfied by an external +// macaroon validator. +type ExternalValidator interface { + macaroons.MacaroonValidator + + // Permissions returns the permissions that the external validator is + // validating. It is a map between the full HTTP URI of each RPC and its + // required macaroon permissions. If multiple action/entity tuples are + // specified per URI, they are all required. See rpcserver.go for a list + // of valid action and entity values. + Permissions() map[string][]bakery.Op +} + +// ImplementationCfg is a struct that holds all configuration items for +// components that can be implemented outside lnd itself. +type ImplementationCfg struct { + // GrpcRegistrar is a type that can register additional gRPC subservers + // before the main gRPC server is started. + GrpcRegistrar + + // RestRegistrar is a type that can register additional REST subservers + // before the main REST proxy is started. + RestRegistrar + + // ExternalValidator is a type that can provide external macaroon + // validation. + ExternalValidator +} + +// DefaultWalletImpl is the default implementation of our normal, btcwallet +// backed configuration. +type DefaultWalletImpl struct { +} + +// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux +// instance. External subservers implementing this method can then register +// their own REST proxy stubs to the main server instance. +// +// NOTE: This is part of the GrpcRegistrar interface. +func (d *DefaultWalletImpl) RegisterRestSubserver(context.Context, + *proxy.ServeMux, string, []grpc.DialOption) error { + + return nil +} + +// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a +// grpc.Server instance. External subservers implementing this method can then +// register their own gRPC server structs to the main server instance. +// +// NOTE: This is part of the GrpcRegistrar interface. +func (d *DefaultWalletImpl) RegisterGrpcSubserver(*grpc.Server) error { + return nil +} + +// ValidateMacaroon extracts the macaroon from the context's gRPC metadata, +// checks its signature, makes sure all specified permissions for the called +// method are contained within and finally ensures all caveat conditions are +// met. A non-nil error is returned if any of the checks fail. +// +// NOTE: This is part of the ExternalValidator interface. +func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context, + requiredPermissions []bakery.Op, fullMethod string) error { + + // Because the default implementation does not return any permissions, + // we shouldn't be registered as an external validator at all and this + // should never be invoked. + return fmt.Errorf("default implementation does not support external " + + "macaroon validation") +} + +// Permissions returns the permissions that the external validator is +// validating. It is a map between the full HTTP URI of each RPC and its +// required macaroon permissions. If multiple action/entity tuples are specified +// per URI, they are all required. See rpcserver.go for a list of valid action +// and entity values. +// +// NOTE: This is part of the ExternalValidator interface. +func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op { + return nil +} diff --git a/lnd.go b/lnd.go index 31127058b..d9eb2bd1c 100644 --- a/lnd.go +++ b/lnd.go @@ -122,51 +122,6 @@ func AdminAuthOptions(cfg *Config, skipMacaroons bool) ([]grpc.DialOption, error return opts, nil } -// GrpcRegistrar is an interface that must be satisfied by an external subserver -// that wants to be able to register its own gRPC server onto lnd's main -// grpc.Server instance. -type GrpcRegistrar interface { - // RegisterGrpcSubserver is called for each net.Listener on which lnd - // creates a grpc.Server instance. External subservers implementing this - // method can then register their own gRPC server structs to the main - // server instance. - RegisterGrpcSubserver(*grpc.Server) error -} - -// RestRegistrar is an interface that must be satisfied by an external subserver -// that wants to be able to register its own REST mux onto lnd's main -// proxy.ServeMux instance. -type RestRegistrar interface { - // RegisterRestSubserver is called after lnd creates the main - // proxy.ServeMux instance. External subservers implementing this method - // can then register their own REST proxy stubs to the main server - // instance. - RegisterRestSubserver(context.Context, *proxy.ServeMux, string, - []grpc.DialOption) error -} - -// RPCSubserverConfig is a struct that can be used to register an external -// subserver with the custom permissions that map to the gRPC server that is -// going to be registered with the GrpcRegistrar. -type RPCSubserverConfig struct { - // Registrar is a callback that is invoked for each net.Listener on - // which lnd creates a grpc.Server instance. - Registrar GrpcRegistrar - - // Permissions is the permissions required for the external subserver. - // It is a map between the full HTTP URI of each RPC and its required - // macaroon permissions. If multiple action/entity tuples are specified - // per URI, they are all required. See rpcserver.go for a list of valid - // action and entity values. - Permissions map[string][]bakery.Op - - // MacaroonValidator is a custom macaroon validator that should be used - // instead of the default lnd validator. If specified, the custom - // validator is used for all URIs specified in the above Permissions - // map. - MacaroonValidator macaroons.MacaroonValidator -} - // ListenerWithSignal is a net.Listener that has an additional Ready channel that // will be closed when a server starts listening. type ListenerWithSignal struct { @@ -187,14 +142,6 @@ type ListenerCfg struct { // RPCListeners can be set to the listeners to use for the RPC server. // If empty a regular network listener will be created. RPCListeners []*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 } var errStreamIsolationWithProxySkip = errors.New( @@ -205,7 +152,9 @@ var errStreamIsolationWithProxySkip = errors.New( // validated main configuration struct and an optional listener config struct. // This function starts all main system components then blocks until a signal // is received on the shutdownChan at which point everything is shut down again. -func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error { +func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, + interceptor signal.Interceptor) error { + defer func() { ltndLog.Info("Shutdown complete\n") err := cfg.LogWriter.Close() @@ -387,12 +336,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error // Initialize, and register our implementation of the gRPC interface // exported by the rpcServer. - rpcServer := newRPCServer( - cfg, interceptorChain, lisCfg.ExternalRPCSubserverCfg, - lisCfg.ExternalRestRegistrar, - interceptor, - ) - + rpcServer := newRPCServer(cfg, interceptorChain, implCfg, interceptor) err = rpcServer.RegisterWithGrpcServer(grpcServer) if err != nil { return err diff --git a/mobile/bindings.go b/mobile/bindings.go index da771c4af..ad214f77c 100644 --- a/mobile/bindings.go +++ b/mobile/bindings.go @@ -99,6 +99,7 @@ func Start(extraArgs string, rpcReady Callback) { Ready: rpcListening, }}, } + implCfg := loadedConfig.ImplementationConfig() // Call the "real" main in a nested manner so the defers will properly // be executed in the case of a graceful shutdown. @@ -107,7 +108,7 @@ func Start(extraArgs string, rpcReady Callback) { defer close(quit) if err := lnd.Main( - loadedConfig, cfg, shutdownInterceptor, + loadedConfig, cfg, implCfg, shutdownInterceptor, ); err != nil { if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { diff --git a/rpcserver.go b/rpcserver.go index 92ffd0251..d56067a9e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -611,16 +611,12 @@ type rpcServer struct { // selfNode is our own pubkey. selfNode route.Vertex - // interceptorChain is the the interceptor added to our gRPC server. + // interceptorChain is 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 + // implCfg is the configuration for some of the interfaces that can be + // provided externally. + implCfg *ImplementationCfg // interceptor is used to be able to request a shutdown interceptor signal.Interceptor @@ -634,9 +630,7 @@ var _ lnrpc.LightningServer = (*rpcServer)(nil) // 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, - interceptor signal.Interceptor) *rpcServer { + implCfg *ImplementationCfg, interceptor signal.Interceptor) *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 @@ -654,8 +648,7 @@ func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain, cfg: cfg, subGrpcHandlers: subServerHandlers, interceptorChain: interceptorChain, - extSubserverCfg: extSubserverCfg, - extRestRegistrar: extRestRegistrar, + implCfg: implCfg, quit: make(chan struct{}, 1), interceptor: interceptor, } @@ -785,30 +778,22 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, // External subserver possibly need to register their own permissions // and macaroon validator. - 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 err - } + for method, ops := range r.implCfg.ExternalValidator.Permissions() { + err := r.interceptorChain.AddPermission(method, ops) + if err != nil { + return 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 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. + err = macService.RegisterExternalValidator( + method, r.implCfg.ExternalValidator, + ) + if err != nil { + return fmt.Errorf("could not register external "+ + "macaroon validator: %v", err) } } @@ -846,13 +831,10 @@ func (r *rpcServer) RegisterWithGrpcServer(grpcServer *grpc.Server) error { // 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) - } + err := r.implCfg.RegisterGrpcSubserver(grpcServer) + if err != nil { + rpcsLog.Errorf("error registering external gRPC "+ + "subserver: %v", err) } return nil @@ -908,14 +890,12 @@ func (r *rpcServer) RegisterWithRestProxy(restCtx context.Context, // 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. - if r.extRestRegistrar != nil { - err := r.extRestRegistrar.RegisterRestSubserver( - restCtx, restMux, restProxyDest, restDialOpts, - ) - if err != nil { - rpcsLog.Errorf("error registering "+ - "external REST subserver: %v", err) - } + err = r.implCfg.RegisterRestSubserver( + restCtx, restMux, restProxyDest, restDialOpts, + ) + if err != nil { + rpcsLog.Errorf("error registering external REST subserver: %v", + err) } return nil }