From d4c4cf01f0536c2e14589169557fed610f0e449d Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 13 Jul 2020 16:33:39 +0200 Subject: [PATCH 1/4] lnd+macaroons: specify location when creating macaroon service To allow the macaroon service to be used in other projects, we want the location to be passed in as a parameter instead of being hard coded. --- lnd.go | 2 +- macaroons/service.go | 4 ++-- macaroons/service_test.go | 16 ++++++++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lnd.go b/lnd.go index 2ed6f30de..b4fde6044 100644 --- a/lnd.go +++ b/lnd.go @@ -393,7 +393,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { if !cfg.NoMacaroons { // Create the macaroon authentication/authorization service. macaroonService, err = macaroons.NewService( - cfg.networkDir, macaroons.IPLockChecker, + cfg.networkDir, "lnd", macaroons.IPLockChecker, ) if err != nil { err := fmt.Errorf("unable to set up macaroon "+ diff --git a/macaroons/service.go b/macaroons/service.go index 0e84ddeb8..a1f750ac6 100644 --- a/macaroons/service.go +++ b/macaroons/service.go @@ -54,7 +54,7 @@ type Service struct { // listing the same checker more than once is not harmful. Default checkers, // such as those for `allow`, `time-before`, `declared`, and `error` caveats // are registered automatically and don't need to be added. -func NewService(dir string, checks ...Checker) (*Service, error) { +func NewService(dir, location string, checks ...Checker) (*Service, error) { // Ensure that the path to the directory exists. if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, 0700); err != nil { @@ -77,7 +77,7 @@ func NewService(dir string, checks ...Checker) (*Service, error) { } macaroonParams := bakery.BakeryParams{ - Location: "lnd", + Location: location, RootKeyStore: rootKeyStore, // No third-party caveat support for now. // TODO(aakselrod): Add third-party caveat support. diff --git a/macaroons/service_test.go b/macaroons/service_test.go index 90c9b5736..5f584240d 100644 --- a/macaroons/service_test.go +++ b/macaroons/service_test.go @@ -66,7 +66,9 @@ func TestNewService(t *testing.T) { // Second, create the new service instance, unlock it and pass in a // checker that we expect it to add to the bakery. - service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker) + service, err := macaroons.NewService( + tempDir, "lnd", macaroons.IPLockChecker, + ) if err != nil { t.Fatalf("Error creating new service: %v", err) } @@ -115,7 +117,9 @@ func TestValidateMacaroon(t *testing.T) { // First, initialize the service and unlock it. tempDir := setupTestRootKeyStorage(t) defer os.RemoveAll(tempDir) - service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker) + service, err := macaroons.NewService( + tempDir, "lnd", macaroons.IPLockChecker, + ) if err != nil { t.Fatalf("Error creating new service: %v", err) } @@ -173,7 +177,9 @@ func TestListMacaroonIDs(t *testing.T) { // Second, create the new service instance, unlock it and pass in a // checker that we expect it to add to the bakery. - service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker) + service, err := macaroons.NewService( + tempDir, "lnd", macaroons.IPLockChecker, + ) require.NoError(t, err, "Error creating new service") defer service.Close() @@ -203,7 +209,9 @@ func TestDeleteMacaroonID(t *testing.T) { // Second, create the new service instance, unlock it and pass in a // checker that we expect it to add to the bakery. - service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker) + service, err := macaroons.NewService( + tempDir, "lnd", macaroons.IPLockChecker, + ) require.NoError(t, err, "Error creating new service") defer service.Close() From c3227057f480aeb5d188de22366958810cccb9eb Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 13 Jul 2020 16:53:06 +0200 Subject: [PATCH 2/4] macaroons: allow external validators to be registered When external subservers register themselves to be served through the same gRPC interface as the main lnd RPC, their requests are also intercepted by the main lnd macaroon interceptor. If the external subservers want to use their own macaroons that are independent of lnd's, they need a way to overwrite the default validator of the macaroon interceptor. We add this mechanism with the concept of external validators. --- macaroons/service.go | 64 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/macaroons/service.go b/macaroons/service.go index a1f750ac6..8f3402d09 100644 --- a/macaroons/service.go +++ b/macaroons/service.go @@ -38,6 +38,17 @@ var ( PermissionEntityCustomURI = "uri" ) +// MacaroonValidator is an interface type that can check if macaroons are valid. +type MacaroonValidator interface { + // 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. + ValidateMacaroon(ctx context.Context, + requiredPermissions []bakery.Op, fullMethod string) error +} + // Service encapsulates bakery.Bakery and adds a Close() method that zeroes the // root key service encryption keys, as well as utility methods to validate a // macaroon against the bakery and gRPC middleware for macaroon-based auth. @@ -45,6 +56,12 @@ type Service struct { bakery.Bakery rks *RootKeyStorage + + // 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 } // NewService returns a service backed by the macaroon Bolt DB stored in the @@ -97,7 +114,11 @@ func NewService(dir, location string, checks ...Checker) (*Service, error) { } } - return &Service{*svc, rootKeyStore}, nil + return &Service{ + Bakery: *svc, + rks: rootKeyStore, + externalValidators: make(map[string]MacaroonValidator), + }, nil } // isRegistered checks to see if the required checker has already been @@ -118,6 +139,27 @@ func isRegistered(c *checkers.Checker, name string) bool { return false } +// RegisterExternalValidator registers a custom, external macaroon validator for +// the specified absolute gRPC URI. That validator is then fully responsible to +// make sure any macaroon passed for a request to that URI is valid and +// satisfies all conditions. +func (svc *Service) RegisterExternalValidator(fullMethod string, + validator MacaroonValidator) error { + + if validator == nil { + return fmt.Errorf("validator cannot be nil") + } + + _, ok := svc.externalValidators[fullMethod] + if ok { + return fmt.Errorf("external validator for method %s already "+ + "registered", fullMethod) + } + + 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( @@ -133,7 +175,15 @@ func (svc *Service) UnaryServerInterceptor( "required for method", info.FullMethod) } - err := svc.ValidateMacaroon( + // 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 { @@ -158,7 +208,15 @@ func (svc *Service) StreamServerInterceptor( "for method", info.FullMethod) } - err := svc.ValidateMacaroon( + // 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 { From 8988a01d7e5e172c02e8ce9ded66566b11fe2255 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 13 Jul 2020 17:06:51 +0200 Subject: [PATCH 3/4] lnd+rpcserver: allow external subservers to specify macaroon validator Give the external subservers the possibility to also use their own validator to check any macaroons attached to calls to their registered gRPC URIs. This allows them to have their own root key ID database and permission entities. --- lnd.go | 6 ++++++ rpcserver.go | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lnd.go b/lnd.go index b4fde6044..d5bc77687 100644 --- a/lnd.go +++ b/lnd.go @@ -145,6 +145,12 @@ type RPCSubserverConfig struct { // 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 diff --git a/rpcserver.go b/rpcserver.go index be6e7a8e3..784179f4e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -661,10 +661,12 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, return nil, err } - // External subserver possibly need to register their own permissions. + // 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 { // For each new method:ops combo, we also ensure // that non of the sub-servers try to override @@ -677,6 +679,23 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, } 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. + // 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) + } + } } } } From 061040c57b6d61f3d6b9b0af15ec068007a619e8 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 10 Sep 2020 10:25:38 +0200 Subject: [PATCH 4/4] rpcserver: export MainRPCServerPermissions To be able to properly delegate requests to the correct component in LiT we need to know all URIs of lnd and the other subservers. --- rpcserver.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 784179f4e..fde2d3b89 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -213,9 +213,9 @@ func stringInSlice(a string, slice []string) bool { return false } -// mainRPCServerPermissions returns a mapping of the main RPC server calls to +// MainRPCServerPermissions returns a mapping of the main RPC server calls to // the permissions they require. -func mainRPCServerPermissions() map[string][]bakery.Op { +func MainRPCServerPermissions() map[string][]bakery.Op { return map[string][]bakery.Op{ "/lnrpc.Lightning/SendCoins": {{ Entity: "onchain", @@ -640,7 +640,7 @@ 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() + permissions := MainRPCServerPermissions() for _, subServerPerm := range subServerPerms { for method, ops := range subServerPerm { // For each new method:ops combo, we also ensure that