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.
This commit is contained in:
Oliver Gugger 2020-07-13 16:53:06 +02:00
parent d4c4cf01f0
commit c3227057f4
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

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