Skip to content

Commit

Permalink
subserver_perms: add Lnd's registered subserver perms
Browse files Browse the repository at this point in the history
  • Loading branch information
ellemouton committed Aug 30, 2022
1 parent 824e94a commit 4007c79
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion itest/litd_mode_integrated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ func bakeSuperMacaroon(cfg *LitNodeConfig, readOnly bool) (string, error) {
lndAdminCtx := macaroonContext(ctxt, lndAdminMacBytes)
lndConn := lnrpc.NewLightningClient(rawConn)

superMacPermissions := terminal.GetAllPermissions(readOnly)
superMacPermissions := terminal.GetAllPermissions(readOnly, nil)
nullID := [4]byte{}
superMacHex, err := terminal.BakeSuperMacaroon(
lndAdminCtx, lndConn, session.NewSuperMacaroonRootKeyID(nullID),
Expand Down
36 changes: 29 additions & 7 deletions rpc_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net"
"net/http"
"strings"
"sync"
"time"

"github.com/improbable-eng/grpc-web/go/grpcweb"
Expand Down Expand Up @@ -59,7 +60,7 @@ func (e *proxyErr) Unwrap() error {
// component.
func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
superMacValidator session.SuperMacaroonValidator,
permissionMap map[string][]bakery.Op,
getPermissionMap func() map[string][]bakery.Op,
bufListener *bufconn.Listener) *rpcProxy {

// The gRPC web calls are protected by HTTP basic auth which is defined
Expand All @@ -77,7 +78,7 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
p := &rpcProxy{
cfg: cfg,
basicAuth: basicAuth,
permissionMap: permissionMap,
getPermsMap: getPermissionMap,
macValidator: validator,
superMacValidator: superMacValidator,
bufListener: bufListener,
Expand Down Expand Up @@ -146,9 +147,16 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
// +---------------------+
//
type rpcProxy struct {
cfg *Config
basicAuth string
cfg *Config
basicAuth string

// permissionMap holds the required permissions for each supported URI.
// This map most _not_ be accessed directly. Instead, the
// rpcProxy.getPerms method should always be used.
permissionMap map[string][]bakery.Op
getPermsMap func() map[string][]bakery.Op
getPermsOnce sync.Once
permsMu sync.Mutex

macValidator macaroons.MacaroonValidator
superMacValidator session.SuperMacaroonValidator
Expand Down Expand Up @@ -373,7 +381,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.getPerms()[info.FullMethod]
if !ok {
return nil, fmt.Errorf("%s: unknown permissions "+
"required for method", info.FullMethod)
Expand Down Expand Up @@ -414,7 +422,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.getPerms()[info.FullMethod]
if !ok {
return fmt.Errorf("%s: unknown permissions required "+
"for method", info.FullMethod)
Expand Down Expand Up @@ -580,7 +588,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.getPerms()[fullMethod]
if !ok {
return nil, fmt.Errorf("%s: unknown permissions required for "+
"method", fullMethod)
Expand Down Expand Up @@ -624,6 +632,20 @@ func (p *rpcProxy) convertSuperMacaroon(ctx context.Context, macHex string,
return nil, nil
}

// getPerms returns a map from URI to permissions required for the URI. The
// first time it is called, the getPermsMap is used to set the contents of the
// map. After that, the same map is returned.
func (p *rpcProxy) getPerms() map[string][]bakery.Op {
p.permsMu.Lock()
defer p.permsMu.Unlock()

p.getPermsOnce.Do(func() {
p.permissionMap = p.getPermsMap()
})

return p.permissionMap
}

// dialBufConnBackend dials an in-memory connection to an RPC listener and
// ignores any TLS certificate mismatches.
func dialBufConnBackend(listener *bufconn.Listener) (*grpc.ClientConn, error) {
Expand Down
4 changes: 3 additions & 1 deletion session_rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightninglabs/lightning-terminal/session"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
"gopkg.in/macaroon.v2"
)
Expand Down Expand Up @@ -39,6 +40,7 @@ type sessionRpcServerConfig struct {
superMacBaker func(ctx context.Context, rootKeyID uint64,
recipe *session.MacaroonRecipe) (string, error)
firstConnectionDeadline time.Duration
getAllPermissions func(readOnly bool) []bakery.Op
}

// newSessionRPCServer creates a new sessionRpcServer using the passed config.
Expand Down Expand Up @@ -205,7 +207,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.getAllPermissions(readOnly),
Caveats: caveats,
},
)
Expand Down
170 changes: 156 additions & 14 deletions subserver_permissions.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
package terminal

import (
"net"
"strings"

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"
)

Expand Down Expand Up @@ -39,13 +59,30 @@ 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",
}

// lndSubServerPerms is a map from LND subserver name to permissions
// map.
lndSubServerPerms = make(map[string]map[string][]bakery.Op)
)

// getSubserverPermissions returns a merged map of all subserver macaroon
// permissions.
func getSubserverPermissions() map[string][]bakery.Op {
mapSize := len(faraday.RequiredPermissions) +
mapSize := len(litPermissions) + 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
Expand All @@ -64,30 +101,54 @@ func getSubserverPermissions() 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)
func getAllMethodPermissions(
lndBuildTags map[string]bool) map[string][]bakery.Op {

litsubServerPerms := getSubserverPermissions()
lndPerms := lnd.MainRPCServerPermissions()

mapSize := len(litsubServerPerms) + len(lndSubServerPerms) +
len(lndPerms) + len(whiteListedMethods)

result := make(map[string][]bakery.Op, mapSize)
for key, value := range lndPermissions {

for key, value := range lndPerms {
result[key] = value
}
for key, value := range subserverPermissions {

for key, value := range whiteListedMethods {
result[key] = value
}
for key, value := range whiteListedMethods {

for key, value := range litsubServerPerms {
result[key] = value
}

for subServerName, perms := range lndSubServerPerms {
name := subServerName
if tagName, ok := lndSubServerNameToTag[name]; ok {
name = tagName
}

if !lndBuildTags[strings.ToLower(name)] {
continue
}

for key, value := range perms {
result[key] = value
}
}

return result
}

// GetAllPermissions retrieves all the permissions needed to bake a super
// macaroon.
func GetAllPermissions(readOnly bool) []bakery.Op {
dedupMap := make(map[string]map[string]bool)
func GetAllPermissions(readOnly bool,
lndBuildTags map[string]bool) []bakery.Op {

for _, methodPerms := range getAllMethodPermissions() {
dedupMap := make(map[string]map[string]bool)
for _, methodPerms := range getAllMethodPermissions(lndBuildTags) {
for _, methodPerm := range methodPerms {
if methodPerm.Action == "" || methodPerm.Entity == "" {
continue
Expand Down Expand Up @@ -121,8 +182,9 @@ func GetAllPermissions(readOnly bool) []bakery.Op {

// isLndURI returns true if the given URI belongs to an RPC of lnd.
func isLndURI(uri string) bool {
_, ok := lnd.MainRPCServerPermissions()[uri]
return ok
_, lndSubServerCall := lndSubServerPerms[uri]
_, lndCall := lnd.MainRPCServerPermissions()[uri]
return lndCall || lndSubServerCall
}

// isLoopURI returns true if the given URI belongs to an RPC of loopd.
Expand All @@ -148,3 +210,83 @@ func isLitURI(uri string) bool {
_, ok := litPermissions[uri]
return ok
}

func init() {
ss := lnrpc.RegisteredSubServers()
for _, subServer := range ss {
_, perms, err := subServer.NewGrpcHandler().CreateSubServer(
&mockConfig{},
)
if err != nil {
panic(err)
}

name := subServer.SubServerName
lndSubServerPerms[name] = make(map[string][]bakery.Op)
for key, value := range perms {
lndSubServerPerms[name][key] = value
}
}
}

// 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
}
}
Loading

0 comments on commit 4007c79

Please sign in to comment.