2020-10-13 12:24:40 +03:00
|
|
|
package rpcperms
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btclog"
|
|
|
|
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
|
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
|
|
|
"github.com/lightningnetwork/lnd/monitoring"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
|
|
|
)
|
|
|
|
|
2020-10-13 12:24:40 +03:00
|
|
|
var (
|
|
|
|
// macaroonWhitelist defines methods that we don't require macaroons to
|
|
|
|
// access.
|
|
|
|
macaroonWhitelist = map[string]struct{}{
|
|
|
|
// We allow all calls to the WalletUnlocker without macaroons.
|
|
|
|
"/lnrpc.WalletUnlocker/GenSeed": {},
|
|
|
|
"/lnrpc.WalletUnlocker/InitWallet": {},
|
|
|
|
"/lnrpc.WalletUnlocker/UnlockWallet": {},
|
|
|
|
"/lnrpc.WalletUnlocker/ChangePassword": {},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-10-13 12:24:40 +03:00
|
|
|
// InterceptorChain is a struct that can be added to the running GRPC server,
|
|
|
|
// intercepting API calls. This is useful for logging, enforcing permissions
|
|
|
|
// etc.
|
|
|
|
type InterceptorChain struct {
|
|
|
|
// noMacaroons should be set true if we don't want to check macaroons.
|
|
|
|
noMacaroons bool
|
|
|
|
|
|
|
|
// svc is the macaroon service used to enforce permissions in case
|
|
|
|
// macaroons are used.
|
|
|
|
svc *macaroons.Service
|
|
|
|
|
|
|
|
// permissionMap is the permissions to enforce if macaroons are used.
|
|
|
|
permissionMap map[string][]bakery.Op
|
|
|
|
|
|
|
|
// rpcsLog is the logger used to log calles to the RPCs intercepted.
|
|
|
|
rpcsLog btclog.Logger
|
|
|
|
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInterceptorChain creates a new InterceptorChain.
|
|
|
|
func NewInterceptorChain(log btclog.Logger, noMacaroons bool) *InterceptorChain {
|
|
|
|
return &InterceptorChain{
|
|
|
|
noMacaroons: noMacaroons,
|
|
|
|
permissionMap: make(map[string][]bakery.Op),
|
|
|
|
rpcsLog: log,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddMacaroonService adds a macaroon service to the interceptor. After this is
|
|
|
|
// done every RPC call made will have to pass a valid macaroon to be accepted.
|
|
|
|
func (r *InterceptorChain) AddMacaroonService(svc *macaroons.Service) {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
|
|
|
r.svc = svc
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddPermission adds a new macaroon rule for the given method.
|
|
|
|
func (r *InterceptorChain) AddPermission(method string, ops []bakery.Op) error {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
|
|
|
if _, ok := r.permissionMap[method]; ok {
|
|
|
|
return fmt.Errorf("detected duplicate macaroon constraints "+
|
|
|
|
"for path: %v", method)
|
|
|
|
}
|
|
|
|
|
|
|
|
r.permissionMap[method] = ops
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Permissions returns the current set of macaroon permissions.
|
|
|
|
func (r *InterceptorChain) Permissions() map[string][]bakery.Op {
|
|
|
|
r.RLock()
|
|
|
|
defer r.RUnlock()
|
|
|
|
|
|
|
|
// Make a copy under the read lock to avoid races.
|
|
|
|
c := make(map[string][]bakery.Op)
|
|
|
|
for k, v := range r.permissionMap {
|
|
|
|
s := make([]bakery.Op, len(v))
|
|
|
|
copy(s, v)
|
|
|
|
c[k] = s
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateServerOpts creates the GRPC server options that can be added to a GRPC
|
|
|
|
// server in order to add this InterceptorChain.
|
|
|
|
func (r *InterceptorChain) CreateServerOpts() []grpc.ServerOption {
|
|
|
|
macUnaryInterceptors := []grpc.UnaryServerInterceptor{}
|
|
|
|
macStrmInterceptors := []grpc.StreamServerInterceptor{}
|
|
|
|
|
|
|
|
// We'll add the macaroon interceptors. If macaroons aren't disabled,
|
|
|
|
// then these interceptors will enforce macaroon authentication.
|
|
|
|
unaryInterceptor := r.macaroonUnaryServerInterceptor()
|
|
|
|
macUnaryInterceptors = append(macUnaryInterceptors, unaryInterceptor)
|
|
|
|
|
|
|
|
strmInterceptor := r.macaroonStreamServerInterceptor()
|
|
|
|
macStrmInterceptors = append(macStrmInterceptors, strmInterceptor)
|
|
|
|
|
|
|
|
// Get interceptors for Prometheus to gather gRPC performance metrics.
|
|
|
|
// If monitoring is disabled, GetPromInterceptors() will return empty
|
|
|
|
// slices.
|
|
|
|
promUnaryInterceptors, promStrmInterceptors :=
|
|
|
|
monitoring.GetPromInterceptors()
|
|
|
|
|
|
|
|
// Concatenate the slices of unary and stream interceptors respectively.
|
|
|
|
unaryInterceptors := append(macUnaryInterceptors, promUnaryInterceptors...)
|
|
|
|
strmInterceptors := append(macStrmInterceptors, promStrmInterceptors...)
|
|
|
|
|
|
|
|
// We'll also add our logging interceptors as well, so we can
|
|
|
|
// automatically log all errors that happen during RPC calls.
|
|
|
|
unaryInterceptors = append(
|
|
|
|
unaryInterceptors, errorLogUnaryServerInterceptor(r.rpcsLog),
|
|
|
|
)
|
|
|
|
strmInterceptors = append(
|
|
|
|
strmInterceptors, errorLogStreamServerInterceptor(r.rpcsLog),
|
|
|
|
)
|
|
|
|
|
|
|
|
// Create server options from the interceptors we just set up.
|
|
|
|
chainedUnary := grpc_middleware.WithUnaryServerChain(
|
|
|
|
unaryInterceptors...,
|
|
|
|
)
|
|
|
|
chainedStream := grpc_middleware.WithStreamServerChain(
|
|
|
|
strmInterceptors...,
|
|
|
|
)
|
|
|
|
serverOpts := []grpc.ServerOption{chainedUnary, chainedStream}
|
|
|
|
|
|
|
|
return serverOpts
|
|
|
|
}
|
|
|
|
|
|
|
|
// errorLogUnaryServerInterceptor is a simple UnaryServerInterceptor that will
|
|
|
|
// automatically log any errors that occur when serving a client's unary
|
|
|
|
// request.
|
|
|
|
func errorLogUnaryServerInterceptor(logger btclog.Logger) grpc.UnaryServerInterceptor {
|
|
|
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
|
|
|
|
handler grpc.UnaryHandler) (interface{}, error) {
|
|
|
|
|
|
|
|
resp, err := handler(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
// TODO(roasbeef): also log request details?
|
|
|
|
logger.Errorf("[%v]: %v", info.FullMethod, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// errorLogStreamServerInterceptor is a simple StreamServerInterceptor that
|
|
|
|
// will log any errors that occur while processing a client or server streaming
|
|
|
|
// RPC.
|
|
|
|
func errorLogStreamServerInterceptor(logger btclog.Logger) grpc.StreamServerInterceptor {
|
|
|
|
return func(srv interface{}, ss grpc.ServerStream,
|
|
|
|
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
|
|
|
|
|
|
|
err := handler(srv, ss)
|
|
|
|
if err != nil {
|
|
|
|
logger.Errorf("[%v]: %v", info.FullMethod, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-13 12:24:40 +03:00
|
|
|
// checkMacaroon validates that the context contains the macaroon needed to
|
|
|
|
// invoke the given RPC method.
|
|
|
|
func (r *InterceptorChain) checkMacaroon(ctx context.Context,
|
|
|
|
fullMethod string) error {
|
|
|
|
|
|
|
|
// If noMacaroons is set, we'll always allow the call.
|
|
|
|
if r.noMacaroons {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether the method is whitelisted, if so we'll allow it
|
|
|
|
// regardless of macaroons.
|
|
|
|
_, ok := macaroonWhitelist[fullMethod]
|
|
|
|
if ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r.RLock()
|
|
|
|
svc := r.svc
|
|
|
|
r.RUnlock()
|
|
|
|
|
|
|
|
// If the macaroon service is not yet active, we cannot allow
|
|
|
|
// the call.
|
|
|
|
if svc == nil {
|
|
|
|
return fmt.Errorf("unable to determine macaroon permissions")
|
|
|
|
}
|
|
|
|
|
|
|
|
r.RLock()
|
|
|
|
uriPermissions, ok := r.permissionMap[fullMethod]
|
|
|
|
r.RUnlock()
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%s: unknown permissions required for method",
|
|
|
|
fullMethod)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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[fullMethod]
|
|
|
|
if !ok {
|
|
|
|
validator = svc
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know what validator to use, let it do its work.
|
|
|
|
return validator.ValidateMacaroon(ctx, uriPermissions, fullMethod)
|
|
|
|
}
|
|
|
|
|
2020-10-13 12:24:40 +03:00
|
|
|
// macaroonUnaryServerInterceptor is a GRPC interceptor that checks whether the
|
|
|
|
// request is authorized by the included macaroons.
|
|
|
|
func (r *InterceptorChain) macaroonUnaryServerInterceptor() grpc.UnaryServerInterceptor {
|
|
|
|
return func(ctx context.Context, req interface{},
|
|
|
|
info *grpc.UnaryServerInfo,
|
|
|
|
handler grpc.UnaryHandler) (interface{}, error) {
|
|
|
|
|
2020-10-13 12:24:40 +03:00
|
|
|
// Check macaroons.
|
|
|
|
if err := r.checkMacaroon(ctx, info.FullMethod); err != nil {
|
2020-10-13 12:24:40 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return handler(ctx, req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// macaroonStreamServerInterceptor is a GRPC interceptor that checks whether
|
|
|
|
// the request is authorized by the included macaroons.
|
|
|
|
func (r *InterceptorChain) macaroonStreamServerInterceptor() grpc.StreamServerInterceptor {
|
|
|
|
return func(srv interface{}, ss grpc.ServerStream,
|
|
|
|
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
|
|
|
|
2020-10-13 12:24:40 +03:00
|
|
|
// Check macaroons.
|
|
|
|
err := r.checkMacaroon(ss.Context(), info.FullMethod)
|
2020-10-13 12:24:40 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return handler(srv, ss)
|
|
|
|
}
|
|
|
|
}
|