diff --git a/README.md b/README.md index afac59f4d..5baf695a7 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ To use LiT with a remote `lnd` instance please [follow these instructions](./doc Note that LiT requires `lnd` to be built with **all of its subservers** and requires running at least v0.11.0. Download the latest [official release binary](https://github.com/lightningnetwork/lnd/releases/latest) or build `lnd` from source by following the [installation instructions](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md). If you choose to build `lnd` from source, use the following command to enable all the relevant subservers: ```shell -⛰ make install tags="signrpc walletrpc chainrpc invoicesrpc" +⛰ make install tags="signrpc walletrpc chainrpc invoicesrpc verrpc" ``` ## Interaction diff --git a/subserver_permissions.go b/subserver_permissions.go index 28871f1ac..d6d537b1c 100644 --- a/subserver_permissions.go +++ b/subserver_permissions.go @@ -1,10 +1,31 @@ package terminal import ( + "net" + "strings" + "sync" + faraday "github.com/lightninglabs/faraday/frdrpcserver/perms" loop "github.com/lightninglabs/loop/loopd/perms" pool "github.com/lightninglabs/pool/perms" "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/autopilot" + "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/autopilotrpc" + "github.com/lightningnetwork/lnd/lnrpc/chainrpc" + "github.com/lightningnetwork/lnd/lnrpc/devrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" + "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" + "github.com/lightningnetwork/lnd/lntest/mock" + "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/sweep" "gopkg.in/macaroon-bakery.v2/bakery" ) @@ -39,6 +60,18 @@ var ( "/lnrpc.State/SubscribeState": {}, "/lnrpc.State/GetState": {}, } + + // lndSubServerNameToTag is a map from the name of an LND subserver to + // the name of the LND tag that corresponds to the subserver. This map + // only contains the subserver-to-tag pairs for the pairs where the + // names differ. + lndSubServerNameToTag = map[string]string{ + "WalletKitRPC": "walletrpc", + "DevRPC": "dev", + "NeutrinoKitRPC": "neutrinorpc", + "VersionRPC": "verrpc", + "WatchtowerClientRPC": "wtclientrpc", + } ) // subServerName is a name used to identify a particular Lit sub-server. @@ -54,6 +87,12 @@ const ( // PermissionsManager manages the permission lists that Lit requires. type PermissionsManager struct { + // lndSubServerPerms is a map from LND subserver name to permissions + // map. This is used once the manager receives a list of build tags + // that LND has been compiled with so that the correct permissions can + // be extracted based on subservers that LND has been compiled with. + lndSubServerPerms map[string]map[string][]bakery.Op + // 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 @@ -61,13 +100,37 @@ type PermissionsManager struct { 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 + // are available for use. This map will start out not including any of + // lnd's sub-server permissions. Only when the LND build tags are + // obtained and OnLNDBuildTags is called will this map include the + // available LND sub-server permissions. This map must only be accessed + // once the permsMu mutex is held. + perms map[string][]bakery.Op + permsMu sync.RWMutex } // NewPermissionsManager constructs a new PermissionsManager instance and // collects any of the fixed permissions. func NewPermissionsManager() (*PermissionsManager, error) { + // Collect all LND sub-server permissions along with the name of the + // sub-server that each permission is associated with. + lndSubServerPerms := make(map[string]map[string][]bakery.Op) + ss := lnrpc.RegisteredSubServers() + for _, subServer := range ss { + _, perms, err := subServer.NewGrpcHandler().CreateSubServer( + &mockConfig{}, + ) + if err != nil { + return nil, err + } + + name := subServer.SubServerName + lndSubServerPerms[name] = make(map[string][]bakery.Op) + for key, value := range perms { + lndSubServerPerms[name][key] = value + } + } + permissions := make(map[subServerName]map[string][]bakery.Op) permissions[faradayPerms] = faraday.RequiredPermissions permissions[loopPerms] = loop.RequiredPermissions @@ -86,15 +149,48 @@ func NewPermissionsManager() (*PermissionsManager, error) { } return &PermissionsManager{ - fixedPerms: permissions, - perms: allPerms, + lndSubServerPerms: lndSubServerPerms, + fixedPerms: permissions, + perms: allPerms, }, nil } +// OnLNDBuildTags should be called once a list of LND build tags has been +// obtained. It then uses those build tags to decide which of the LND sub-server +// permissions to add to the main permissions list. This method should only +// be called once. +func (pm *PermissionsManager) OnLNDBuildTags(lndBuildTags []string) { + pm.permsMu.Lock() + defer pm.permsMu.Unlock() + + tagLookup := make(map[string]bool) + for _, t := range lndBuildTags { + tagLookup[strings.ToLower(t)] = true + } + + for subServerName, perms := range pm.lndSubServerPerms { + name := subServerName + if tagName, ok := lndSubServerNameToTag[name]; ok { + name = tagName + } + + if !tagLookup[strings.ToLower(name)] { + continue + } + + for key, value := range perms { + pm.perms[key] = value + } + } +} + // GetPermOps 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) GetPermOps(uri string) ([]bakery.Op, bool) { + pm.permsMu.RLock() + defer pm.permsMu.RUnlock() + ops, ok := pm.perms[uri] return ops, ok } @@ -103,6 +199,9 @@ func (pm *PermissionsManager) GetPermOps(uri string) ([]bakery.Op, bool) { // manager is aware of. Optionally, readOnly can be set to true if only the // read-only permissions should be returned. func (pm *PermissionsManager) GetPermissionsList(readOnly bool) []bakery.Op { + pm.permsMu.RLock() + defer pm.permsMu.RUnlock() + // De-dup the permissions and optionally apply the read-only filter. dedupMap := make(map[string]map[string]bool) for _, methodPerms := range pm.perms { @@ -163,8 +262,9 @@ func (pm *PermissionsManager) GetLitPerms() map[string][]bakery.Op { // IsLndURI returns true if the given URI belongs to an RPC of lnd. func (pm *PermissionsManager) IsLndURI(uri string) bool { + _, lndSubServerCall := pm.lndSubServerPerms[uri] _, lndCall := pm.fixedPerms[lndPerms][uri] - return lndCall + return lndCall || lndSubServerCall } // IsLoopURI returns true if the given URI belongs to an RPC of loopd. @@ -190,3 +290,65 @@ func (pm *PermissionsManager) IsLitURI(uri string) bool { _, ok := pm.fixedPerms[litPerms][uri] return ok } + +// mockConfig implements lnrpc.SubServerConfigDispatcher. It provides th +// functionality required so that the lnrpc.GrpcHandler.CreateSubServer +// function can be called without panicking. +type mockConfig struct{} + +var _ lnrpc.SubServerConfigDispatcher = (*mockConfig)(nil) + +// FetchConfig is a mock implementation of lnrpc.SubServerConfigDispatcher. It +// is used as a parameter to lnrpc.GrpcHandler.CreateSubServer and allows the +// function to be called without panicking. This is useful because +// CreateSubServer can be used to extract the permissions required by each +// registered subserver. +// +// TODO(elle): remove this once the sub-server permission lists in LND have been +// exported +func (t *mockConfig) FetchConfig(subServerName string) (interface{}, bool) { + switch subServerName { + case "InvoicesRPC": + return &invoicesrpc.Config{}, true + case "WatchtowerClientRPC": + return &wtclientrpc.Config{ + Resolver: func(_, _ string) (*net.TCPAddr, error) { + return nil, nil + }, + }, true + case "AutopilotRPC": + return &autopilotrpc.Config{ + Manager: &autopilot.Manager{}, + }, true + case "ChainRPC": + return &chainrpc.Config{ + ChainNotifier: &chainreg.NoChainBackend{}, + }, true + case "DevRPC": + return &devrpc.Config{}, true + case "NeutrinoKitRPC": + return &neutrinorpc.Config{}, true + case "PeersRPC": + return &peersrpc.Config{}, true + case "RouterRPC": + return &routerrpc.Config{ + Router: &routing.ChannelRouter{}, + }, true + case "SignRPC": + return &signrpc.Config{ + Signer: &mock.DummySigner{}, + }, true + case "WalletKitRPC": + return &walletrpc.Config{ + FeeEstimator: &chainreg.NoChainBackend{}, + Wallet: &mock.WalletController{}, + KeyRing: &mock.SecretKeyRing{}, + Sweeper: &sweep.UtxoSweeper{}, + Chain: &mock.ChainIO{}, + }, true + case "WatchtowerRPC": + return &watchtowerrpc.Config{}, true + default: + return nil, false + } +} diff --git a/terminal.go b/terminal.go index 152bd70af..4c9cfc255 100644 --- a/terminal.go +++ b/terminal.go @@ -494,6 +494,16 @@ func (g *LightningTerminal) startSubservers() error { return err } + // Collect the tags that LND was built with. + version, err := g.lndClient.Versioner.GetVersion(ctxc) + if err != nil { + return err + } + + // Pass LND's build tags to the permission manager so that it can + // filter the available permissions accordingly. + g.permsMgr.OnLNDBuildTags(version.BuildTags) + // In the integrated mode, we received an admin macaroon once lnd was // ready. We can now bake a "super macaroon" that contains all // permissions of all daemons that we can use for any internal calls.