multi: refactor external subserver config

As a preparation for making more and more implementation details
configurable, we add a new ImplementationCfg struct that houses all the
interfaces that can be defined externally.
This commit is contained in:
Oliver Gugger 2021-09-23 16:54:39 +02:00
parent 140d5a8086
commit 0e279eb15a
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
6 changed files with 165 additions and 113 deletions

View File

@ -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)

View File

@ -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

115
config_builder.go Normal file
View File

@ -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
}

64
lnd.go
View File

@ -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

View File

@ -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 {

View File

@ -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
}