Merge pull request #4464 from guggero/macaroon-interceptor
Advanced macaroons 2/2: Custom macaroon validator for external subservers
This commit is contained in:
commit
f80549d6e5
8
lnd.go
8
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
|
||||
@ -393,7 +399,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 "+
|
||||
|
@ -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
|
||||
@ -54,7 +71,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 +94,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.
|
||||
@ -97,7 +114,11 @@ func NewService(dir 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 {
|
||||
|
@ -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()
|
||||
|
||||
|
27
rpcserver.go
27
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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user