mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
multi: add Lnd's registered subserver perms
In this commit, we let the PermissionsManager manage LND's subserver permissions. Once Litd is connected to LND, it can get LND's build tags and pass them to the PermissionsManager which will then adjust its list of permissions accordingly.
This commit is contained in:
parent
c2eb98db38
commit
b3ad8114fe
5 changed files with 247 additions and 15 deletions
|
|
@ -26,6 +26,8 @@ import (
|
|||
"github.com/lightninglabs/pool/poolrpc"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -81,17 +83,33 @@ var (
|
|||
// gRPC request. One byte version and then 4 bytes content length.
|
||||
emptyGrpcWebRequest = []byte{0, 0, 0, 0, 0}
|
||||
|
||||
lndRequestFn = func(ctx context.Context,
|
||||
lnrpcRequestFn = func(ctx context.Context,
|
||||
c grpc.ClientConnInterface) (proto.Message, error) {
|
||||
|
||||
lndConn := lnrpc.NewLightningClient(c)
|
||||
return lndConn.GetInfo(
|
||||
lnrpcConn := lnrpc.NewLightningClient(c)
|
||||
return lnrpcConn.GetInfo(
|
||||
ctx, &lnrpc.GetInfoRequest{},
|
||||
)
|
||||
}
|
||||
lndMacaroonFn = func(cfg *LitNodeConfig) string {
|
||||
return cfg.AdminMacPath
|
||||
}
|
||||
routerrpcRequestFn = func(ctx context.Context,
|
||||
c grpc.ClientConnInterface) (proto.Message, error) {
|
||||
|
||||
routerrpcConn := routerrpc.NewRouterClient(c)
|
||||
return routerrpcConn.GetMissionControlConfig(
|
||||
ctx, &routerrpc.GetMissionControlConfigRequest{},
|
||||
)
|
||||
}
|
||||
walletrpcRequestFn = func(ctx context.Context,
|
||||
c grpc.ClientConnInterface) (proto.Message, error) {
|
||||
|
||||
walletrpcConn := walletrpc.NewWalletKitClient(c)
|
||||
return walletrpcConn.ListUnspent(
|
||||
ctx, &walletrpc.ListUnspentRequest{},
|
||||
)
|
||||
}
|
||||
faradayRequestFn = func(ctx context.Context,
|
||||
c grpc.ClientConnInterface) (proto.Message, error) {
|
||||
|
||||
|
|
@ -145,14 +163,32 @@ var (
|
|||
allowedThroughLNC bool
|
||||
grpcWebURI string
|
||||
restWebURI string
|
||||
restPOST bool
|
||||
}{{
|
||||
name: "lnrpc",
|
||||
macaroonFn: lndMacaroonFn,
|
||||
requestFn: lndRequestFn,
|
||||
requestFn: lnrpcRequestFn,
|
||||
successPattern: "\"identity_pubkey\":\"0",
|
||||
allowedThroughLNC: true,
|
||||
grpcWebURI: "/lnrpc.Lightning/GetInfo",
|
||||
restWebURI: "/v1/getinfo",
|
||||
}, {
|
||||
name: "routerrpc",
|
||||
macaroonFn: lndMacaroonFn,
|
||||
requestFn: routerrpcRequestFn,
|
||||
successPattern: "\"config\":{",
|
||||
allowedThroughLNC: true,
|
||||
grpcWebURI: "/routerrpc.Router/GetMissionControlConfig",
|
||||
restWebURI: "/v2/router/mccfg",
|
||||
}, {
|
||||
name: "walletrpc",
|
||||
macaroonFn: lndMacaroonFn,
|
||||
requestFn: walletrpcRequestFn,
|
||||
successPattern: "\"utxos\":[",
|
||||
allowedThroughLNC: true,
|
||||
grpcWebURI: "/walletrpc.WalletKit/ListUnspent",
|
||||
restWebURI: "/v2/wallet/utxos",
|
||||
restPOST: true,
|
||||
}, {
|
||||
name: "frdrpc",
|
||||
macaroonFn: faradayMacaroonFn,
|
||||
|
|
@ -322,6 +358,7 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
|
|||
endpoint.macaroonFn(cfg),
|
||||
endpoint.restWebURI,
|
||||
endpoint.successPattern,
|
||||
endpoint.restPOST,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
@ -529,7 +566,7 @@ func runGRPCWebAuthTest(t *testing.T, hostPort, uiPassword, grpcWebURI string) {
|
|||
|
||||
// runRESTAuthTest tests authentication of the given REST interface.
|
||||
func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,
|
||||
successPattern string) {
|
||||
successPattern string, usePOST bool) {
|
||||
|
||||
basicAuth := base64.StdEncoding.EncodeToString(
|
||||
[]byte(fmt.Sprintf("%s:%s", uiPassword, uiPassword)),
|
||||
|
|
@ -539,13 +576,19 @@ func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,
|
|||
}
|
||||
url := fmt.Sprintf("https://%s%s", hostPort, restURI)
|
||||
|
||||
method := "GET"
|
||||
if usePOST {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
// First test a REST call without authorization, which should fail.
|
||||
body, responseHeader, err := callURL(url, "GET", nil, nil, false)
|
||||
body, responseHeader, err := callURL(url, method, nil, nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(
|
||||
require.Equalf(
|
||||
t, "application/grpc",
|
||||
responseHeader.Get("grpc-metadata-content-type"),
|
||||
"response headers: %v, body: %v", responseHeader, body,
|
||||
)
|
||||
require.Equal(
|
||||
t, "application/json",
|
||||
|
|
@ -558,7 +601,7 @@ func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,
|
|||
|
||||
// Now add the UI password which should make the request succeed.
|
||||
body, responseHeader, err = callURL(
|
||||
url, "GET", nil, basicAuthHeader, false,
|
||||
url, method, nil, basicAuthHeader, false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, body, successPattern)
|
||||
|
|
@ -573,7 +616,7 @@ func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,
|
|||
},
|
||||
}
|
||||
body, responseHeader, err = callURL(
|
||||
url, "GET", nil, macaroonHeader, false,
|
||||
url, method, nil, macaroonHeader, false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, body, successPattern)
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ func testModeRemote(net *NetworkHarness, t *harnessTest) {
|
|||
endpoint.macaroonFn(cfg),
|
||||
endpoint.restWebURI,
|
||||
endpoint.successPattern,
|
||||
endpoint.restPOST,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ windows-386 \
|
|||
windows-amd64 \
|
||||
windows-arm
|
||||
|
||||
LND_RELEASE_TAGS = litd autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc
|
||||
LND_RELEASE_TAGS = litd autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc neutrinorpc peersrpc
|
||||
|
||||
# By default we will build all systems. But with the 'sys' tag, a specific
|
||||
# system can be specified. This is useful to release for a subset of
|
||||
|
|
|
|||
|
|
@ -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,26 @@ 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",
|
||||
}
|
||||
|
||||
// lndAutoCompiledSubServers is a map of the LND subservers that are
|
||||
// automatically compiled with it and therefore don't need a build tag.
|
||||
lndAutoCompiledSubServers = map[string]bool{
|
||||
"VersionRPC": true,
|
||||
"RouterRPC": true,
|
||||
"WatchtowerClientRPC": true,
|
||||
}
|
||||
)
|
||||
|
||||
// subServerName is a name used to identify a particular Lit sub-server.
|
||||
|
|
@ -54,6 +95,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,8 +108,13 @@ 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
|
||||
|
|
@ -78,6 +130,32 @@ func NewPermissionsManager() (*PermissionsManager, error) {
|
|||
permissions[lndPerms][k] = v
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// If this sub-server is one that we know is
|
||||
// automatically compiled in LND then we add it to our
|
||||
// map of active permissions.
|
||||
if lndAutoCompiledSubServers[name] {
|
||||
permissions[lndPerms][key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allPerms := make(map[string][]bakery.Op)
|
||||
for _, perms := range permissions {
|
||||
for k, v := range perms {
|
||||
|
|
@ -86,15 +164,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
pm.permsMu.RLock()
|
||||
defer pm.permsMu.RUnlock()
|
||||
|
||||
ops, ok := pm.perms[uri]
|
||||
return ops, ok
|
||||
}
|
||||
|
|
@ -103,6 +214,9 @@ func (pm *PermissionsManager) URIPermissions(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) ActivePermissions(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 +277,16 @@ 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 {
|
||||
var lndSubServerCall bool
|
||||
for _, subserverPermissions := range pm.lndSubServerPerms {
|
||||
_, found := subserverPermissions[uri]
|
||||
if found {
|
||||
lndSubServerCall = true
|
||||
break
|
||||
}
|
||||
}
|
||||
_, 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 +312,65 @@ func (pm *PermissionsManager) IsLitURI(uri string) bool {
|
|||
_, ok := pm.fixedPerms[litPerms][uri]
|
||||
return ok
|
||||
}
|
||||
|
||||
// mockConfig implements lnrpc.SubServerConfigDispatcher. It provides the
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -494,6 +494,10 @@ func (g *LightningTerminal) startSubservers() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Pass LND's build tags to the permission manager so that it can
|
||||
// filter the available permissions accordingly.
|
||||
g.permsMgr.OnLNDBuildTags(g.lndClient.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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue