diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 0bb0594e..bc52570a 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -834,7 +834,12 @@ func bakeSuperMacaroon(cfg *LitNodeConfig, readOnly bool) (string, error) { lndAdminCtx := macaroonContext(ctxt, lndAdminMacBytes) lndConn := lnrpc.NewLightningClient(rawConn) - superMacPermissions := terminal.GetAllPermissions(readOnly) + permsMgr, err := terminal.NewPermissionsManager() + if err != nil { + return "", err + } + + superMacPermissions := permsMgr.ActivePermissions(readOnly) nullID := [4]byte{} superMacHex, err := terminal.BakeSuperMacaroon( lndAdminCtx, lndConn, session.NewSuperMacaroonRootKeyID(nullID), diff --git a/rpc_proxy.go b/rpc_proxy.go index 1662da77..3a05f5e0 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -24,7 +24,6 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" - "gopkg.in/macaroon-bakery.v2/bakery" "gopkg.in/macaroon.v2" ) @@ -59,8 +58,7 @@ func (e *proxyErr) Unwrap() error { // component. func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, superMacValidator session.SuperMacaroonValidator, - permissionMap map[string][]bakery.Op, - bufListener *bufconn.Listener) *rpcProxy { + permsMgr *PermissionsManager, bufListener *bufconn.Listener) *rpcProxy { // The gRPC web calls are protected by HTTP basic auth which is defined // by base64(username:password). Because we only have a password, we @@ -77,7 +75,7 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, p := &rpcProxy{ cfg: cfg, basicAuth: basicAuth, - permissionMap: permissionMap, + permsMgr: permsMgr, macValidator: validator, superMacValidator: superMacValidator, bufListener: bufListener, @@ -146,9 +144,9 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, // +---------------------+ // type rpcProxy struct { - cfg *Config - basicAuth string - permissionMap map[string][]bakery.Op + cfg *Config + basicAuth string + permsMgr *PermissionsManager macValidator macaroons.MacaroonValidator superMacValidator session.SuperMacaroonValidator @@ -345,17 +343,17 @@ func (p *rpcProxy) makeDirector(allowLitRPC bool) func(ctx context.Context, // handled by the integrated daemons that are hooking into lnd's // gRPC server. switch { - case isFaradayURI(requestURI) && p.cfg.faradayRemote: + case p.permsMgr.IsFaradayURI(requestURI) && p.cfg.faradayRemote: return outCtx, p.faradayConn, nil - case isLoopURI(requestURI) && p.cfg.loopRemote: + case p.permsMgr.IsLoopURI(requestURI) && p.cfg.loopRemote: return outCtx, p.loopConn, nil - case isPoolURI(requestURI) && p.cfg.poolRemote: + case p.permsMgr.IsPoolURI(requestURI) && p.cfg.poolRemote: return outCtx, p.poolConn, nil // Calls to LiT session RPC aren't allowed in some cases. - case isLitURI(requestURI) && !allowLitRPC: + case p.permsMgr.IsLitURI(requestURI) && !allowLitRPC: return outCtx, nil, status.Errorf( codes.Unimplemented, "unknown service %s", requestURI, @@ -373,7 +371,7 @@ func (p *rpcProxy) UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - uriPermissions, ok := p.permissionMap[info.FullMethod] + uriPermissions, ok := p.permsMgr.URIPermissions(info.FullMethod) if !ok { return nil, fmt.Errorf("%s: unknown permissions "+ "required for method", info.FullMethod) @@ -414,7 +412,7 @@ func (p *rpcProxy) StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - uriPermissions, ok := p.permissionMap[info.FullMethod] + uriPermissions, ok := p.permsMgr.URIPermissions(info.FullMethod) if !ok { return fmt.Errorf("%s: unknown permissions required "+ "for method", info.FullMethod) @@ -503,31 +501,31 @@ func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string, macData []byte ) switch { - case isLndURI(requestURI): + case p.permsMgr.IsLndURI(requestURI): _, _, _, macPath, macData = p.cfg.lndConnectParams() - case isFaradayURI(requestURI): + case p.permsMgr.IsFaradayURI(requestURI): if p.cfg.faradayRemote { macPath = p.cfg.Remote.Faraday.MacaroonPath } else { macPath = p.cfg.Faraday.MacaroonPath } - case isLoopURI(requestURI): + case p.permsMgr.IsLoopURI(requestURI): if p.cfg.loopRemote { macPath = p.cfg.Remote.Loop.MacaroonPath } else { macPath = p.cfg.Loop.MacaroonPath } - case isPoolURI(requestURI): + case p.permsMgr.IsPoolURI(requestURI): if p.cfg.poolRemote { macPath = p.cfg.Remote.Pool.MacaroonPath } else { macPath = p.cfg.Pool.MacaroonPath } - case isLitURI(requestURI): + case p.permsMgr.IsLitURI(requestURI): macPath = p.cfg.MacaroonPath default: @@ -580,7 +578,7 @@ func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string, func (p *rpcProxy) convertSuperMacaroon(ctx context.Context, macHex string, fullMethod string) ([]byte, error) { - requiredPermissions, ok := p.permissionMap[fullMethod] + requiredPermissions, ok := p.permsMgr.URIPermissions(fullMethod) if !ok { return nil, fmt.Errorf("%s: unknown permissions required for "+ "method", fullMethod) @@ -605,17 +603,17 @@ func (p *rpcProxy) convertSuperMacaroon(ctx context.Context, macHex string, // Is this actually a request that goes to a daemon that is running // remotely? switch { - case isFaradayURI(fullMethod) && p.cfg.faradayRemote: + case p.permsMgr.IsFaradayURI(fullMethod) && p.cfg.faradayRemote: return readMacaroon(lncfg.CleanAndExpandPath( p.cfg.Remote.Faraday.MacaroonPath, )) - case isLoopURI(fullMethod) && p.cfg.loopRemote: + case p.permsMgr.IsLoopURI(fullMethod) && p.cfg.loopRemote: return readMacaroon(lncfg.CleanAndExpandPath( p.cfg.Remote.Loop.MacaroonPath, )) - case isPoolURI(fullMethod) && p.cfg.poolRemote: + case p.permsMgr.IsPoolURI(fullMethod) && p.cfg.poolRemote: return readMacaroon(lncfg.CleanAndExpandPath( p.cfg.Remote.Pool.MacaroonPath, )) diff --git a/session_rpcserver.go b/session_rpcserver.go index ed403215..1272ff03 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -39,6 +39,7 @@ type sessionRpcServerConfig struct { superMacBaker func(ctx context.Context, rootKeyID uint64, recipe *session.MacaroonRecipe) (string, error) firstConnectionDeadline time.Duration + permMgr *PermissionsManager } // newSessionRPCServer creates a new sessionRpcServer using the passed config. @@ -205,7 +206,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { mac, err := s.cfg.superMacBaker( context.Background(), sess.MacaroonRootKey, &session.MacaroonRecipe{ - Permissions: GetAllPermissions(readOnly), + Permissions: s.cfg.permMgr.ActivePermissions(readOnly), Caveats: caveats, }, ) diff --git a/subserver_permissions.go b/subserver_permissions.go index f350f5e6..d809c3eb 100644 --- a/subserver_permissions.go +++ b/subserver_permissions.go @@ -26,9 +26,9 @@ var ( }}, } - // whiteListedMethods is a map of all lnd RPC methods that don't require - // any macaroon authentication. - whiteListedMethods = map[string][]bakery.Op{ + // whiteListedLNDMethods is a map of all lnd RPC methods that don't + // require any macaroon authentication. + whiteListedLNDMethods = map[string][]bakery.Op{ "/lnrpc.WalletUnlocker/GenSeed": {}, "/lnrpc.WalletUnlocker/InitWallet": {}, "/lnrpc.WalletUnlocker/UnlockWallet": {}, @@ -41,53 +41,71 @@ var ( } ) -// getSubserverPermissions returns a merged map of all subserver macaroon -// permissions. -func getSubserverPermissions() map[string][]bakery.Op { - mapSize := len(faraday.RequiredPermissions) + - len(loop.RequiredPermissions) + len(pool.RequiredPermissions) - result := make(map[string][]bakery.Op, mapSize) - for key, value := range faraday.RequiredPermissions { - result[key] = value - } - for key, value := range loop.RequiredPermissions { - result[key] = value - } - for key, value := range pool.RequiredPermissions { - result[key] = value - } - for key, value := range litPermissions { - result[key] = value - } - return result +// subServerName is a name used to identify a particular Lit sub-server. +type subServerName string + +const ( + poolPerms subServerName = "pool" + loopPerms subServerName = "loop" + faradayPerms subServerName = "faraday" + litPerms subServerName = "lit" + lndPerms subServerName = "lnd" +) + +// PermissionsManager manages the permission lists that Lit requires. +type PermissionsManager struct { + // fixedPerms is constructed once on creation of the PermissionsManager. + // It contains all the permissions that will not change throughout the + // lifetime of the manager. It maps sub-server name to uri to permission + // operations. + fixedPerms map[subServerName]map[string][]bakery.Op + + // perms is a map containing all permissions that the manager knows + // are available for use. + perms map[string][]bakery.Op } -// getAllMethodPermissions returns a merged map of lnd's and all subservers' -// method macaroon permissions. -func getAllMethodPermissions() map[string][]bakery.Op { - subserverPermissions := getSubserverPermissions() - lndPermissions := lnd.MainRPCServerPermissions() - mapSize := len(subserverPermissions) + len(lndPermissions) + - len(whiteListedMethods) - result := make(map[string][]bakery.Op, mapSize) - for key, value := range lndPermissions { - result[key] = value +// NewPermissionsManager constructs a new PermissionsManager instance and +// collects any of the fixed permissions. +func NewPermissionsManager() (*PermissionsManager, error) { + permissions := make(map[subServerName]map[string][]bakery.Op) + permissions[faradayPerms] = faraday.RequiredPermissions + permissions[loopPerms] = loop.RequiredPermissions + permissions[poolPerms] = pool.RequiredPermissions + permissions[litPerms] = litPermissions + permissions[lndPerms] = lnd.MainRPCServerPermissions() + for k, v := range whiteListedLNDMethods { + permissions[lndPerms][k] = v } - for key, value := range subserverPermissions { - result[key] = value + + allPerms := make(map[string][]bakery.Op) + for _, perms := range permissions { + for k, v := range perms { + allPerms[k] = v + } } - for key, value := range whiteListedMethods { - result[key] = value - } - return result + + return &PermissionsManager{ + fixedPerms: permissions, + perms: allPerms, + }, nil } -// GetAllPermissions retrieves all the permissions needed to bake a super -// macaroon. -func GetAllPermissions(readOnly bool) []bakery.Op { +// URIPermissions returns a list of permission operations for the given URI if +// the uri is known to the manager. The second return parameter will be false +// if the URI is unknown to the manager. +func (pm *PermissionsManager) URIPermissions(uri string) ([]bakery.Op, bool) { + ops, ok := pm.perms[uri] + return ops, ok +} + +// ActivePermissions returns all the available active permissions that the +// manager is aware of. Optionally, readOnly can be set to true if only the +// read-only permissions should be returned. +func (pm *PermissionsManager) ActivePermissions(readOnly bool) []bakery.Op { + // De-dup the permissions and optionally apply the read-only filter. dedupMap := make(map[string]map[string]bool) - - for _, methodPerms := range getAllMethodPermissions() { + for _, methodPerms := range pm.perms { for _, methodPerm := range methodPerms { if methodPerm.Action == "" || methodPerm.Entity == "" { continue @@ -119,32 +137,56 @@ func GetAllPermissions(readOnly bool) []bakery.Op { return result } -// isLndURI returns true if the given URI belongs to an RPC of lnd. -func isLndURI(uri string) bool { - _, ok := lnd.MainRPCServerPermissions()[uri] +// GetLitPerms returns a map of all permissions that the manager is aware of +// _except_ for any LND permissions. In other words, this returns permissions +// for which the external validator of Lit is responsible. +func (pm *PermissionsManager) GetLitPerms() map[string][]bakery.Op { + mapSize := len(pm.fixedPerms[litPerms]) + + len(pm.fixedPerms[faradayPerms]) + + len(pm.fixedPerms[loopPerms]) + len(pm.fixedPerms[poolPerms]) + + result := make(map[string][]bakery.Op, mapSize) + for key, value := range pm.fixedPerms[faradayPerms] { + result[key] = value + } + for key, value := range pm.fixedPerms[loopPerms] { + result[key] = value + } + for key, value := range pm.fixedPerms[poolPerms] { + result[key] = value + } + for key, value := range pm.fixedPerms[litPerms] { + result[key] = value + } + return result +} + +// IsLndURI returns true if the given URI belongs to an RPC of lnd. +func (pm *PermissionsManager) IsLndURI(uri string) bool { + _, lndCall := pm.fixedPerms[lndPerms][uri] + return lndCall +} + +// IsLoopURI returns true if the given URI belongs to an RPC of loopd. +func (pm *PermissionsManager) IsLoopURI(uri string) bool { + _, ok := pm.fixedPerms[loopPerms][uri] return ok } -// isLoopURI returns true if the given URI belongs to an RPC of loopd. -func isLoopURI(uri string) bool { - _, ok := loop.RequiredPermissions[uri] +// IsFaradayURI returns true if the given URI belongs to an RPC of faraday. +func (pm *PermissionsManager) IsFaradayURI(uri string) bool { + _, ok := pm.fixedPerms[faradayPerms][uri] return ok } -// isFaradayURI returns true if the given URI belongs to an RPC of faraday. -func isFaradayURI(uri string) bool { - _, ok := faraday.RequiredPermissions[uri] +// IsPoolURI returns true if the given URI belongs to an RPC of poold. +func (pm *PermissionsManager) IsPoolURI(uri string) bool { + _, ok := pm.fixedPerms[poolPerms][uri] return ok } -// isPoolURI returns true if the given URI belongs to an RPC of poold. -func isPoolURI(uri string) bool { - _, ok := pool.RequiredPermissions[uri] - return ok -} - -// isLitURI returns true if the given URI belongs to an RPC of LiT. -func isLitURI(uri string) bool { - _, ok := litPermissions[uri] +// IsLitURI returns true if the given URI belongs to an RPC of LiT. +func (pm *PermissionsManager) IsLitURI(uri string) bool { + _, ok := pm.fixedPerms[litPerms][uri] return ok } diff --git a/terminal.go b/terminal.go index 710266db..2a1e0ebc 100644 --- a/terminal.go +++ b/terminal.go @@ -135,6 +135,8 @@ type LightningTerminal struct { defaultImplCfg *lnd.ImplementationCfg + permsMgr *PermissionsManager + // lndInterceptorChain is a reference to lnd's interceptor chain that // guards all incoming calls. This is only set in integrated mode! lndInterceptorChain *rpcperms.InterceptorChain @@ -193,6 +195,12 @@ func (g *LightningTerminal) Run() error { // Show version at startup. log.Infof("LiT version: %s", Version()) + // Construct a new PermissionsManager. + g.permsMgr, err = NewPermissionsManager() + if err != nil { + return fmt.Errorf("could not create permissions manager") + } + // Create the instances of our subservers now so we can hook them up to // lnd once it's fully started. bufRpcListener := bufconn.Listen(100) @@ -200,8 +208,7 @@ func (g *LightningTerminal) Run() error { g.loopServer = loopd.New(g.cfg.Loop, nil) g.poolServer = pool.NewServer(g.cfg.Pool) g.rpcProxy = newRpcProxy( - g.cfg, g, g.validateSuperMacaroon, getAllMethodPermissions(), - bufRpcListener, + g.cfg, g, g.validateSuperMacaroon, g.permsMgr, bufRpcListener, ) g.sessionRpcServer, err = newSessionRPCServer(&sessionRpcServerConfig{ basicAuth: g.rpcProxy.basicAuth, @@ -233,6 +240,7 @@ func (g *LightningTerminal) Run() error { ) }, firstConnectionDeadline: g.cfg.FirstLNCConnDeadline, + permMgr: g.permsMgr, }) if err != nil { return fmt.Errorf("could not create new session rpc "+ @@ -497,7 +505,7 @@ func (g *LightningTerminal) startSubservers() error { ctx, g.basicClient, session.NewSuperMacaroonRootKeyID( [4]byte{}, ), - GetAllPermissions(false), nil, + g.permsMgr.ActivePermissions(false), nil, ) if err != nil { return err @@ -704,7 +712,7 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, // process. Calls that we proxy to a remote host don't need to be // checked as they'll have their own interceptor. switch { - case isFaradayURI(fullMethod): + case g.permsMgr.IsFaradayURI(fullMethod): // In remote mode we just pass through the request, the remote // daemon will check the macaroon. if g.cfg.faradayRemote { @@ -728,7 +736,7 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, } } - case isLoopURI(fullMethod): + case g.permsMgr.IsLoopURI(fullMethod): // In remote mode we just pass through the request, the remote // daemon will check the macaroon. if g.cfg.loopRemote { @@ -752,7 +760,7 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, } } - case isPoolURI(fullMethod): + case g.permsMgr.IsPoolURI(fullMethod): // In remote mode we just pass through the request, the remote // daemon will check the macaroon. if g.cfg.poolRemote { @@ -776,7 +784,7 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, } } - case isLitURI(fullMethod): + case g.permsMgr.IsLitURI(fullMethod): if !g.macaroonServiceStarted { return fmt.Errorf("the macaroon service has not " + "started yet") @@ -806,7 +814,7 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, // // NOTE: This is part of the lnd.ExternalValidator interface. func (g *LightningTerminal) Permissions() map[string][]bakery.Op { - return getSubserverPermissions() + return g.permsMgr.GetLitPerms() } // BuildWalletConfig is responsible for creating or unlocking and then