Skip to content

Commit f2fe3cc

Browse files
committed
subservers: move permission logic
1 parent 3c95ab6 commit f2fe3cc

File tree

5 files changed

+373
-365
lines changed

5 files changed

+373
-365
lines changed

perms/manager.go

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package perms
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
"sync"
7+
8+
faraday "github.com/lightninglabs/faraday/frdrpcserver/perms"
9+
loop "github.com/lightninglabs/loop/loopd/perms"
10+
pool "github.com/lightninglabs/pool/perms"
11+
"github.com/lightningnetwork/lnd"
12+
"github.com/lightningnetwork/lnd/lnrpc"
13+
"gopkg.in/macaroon-bakery.v2/bakery"
14+
)
15+
16+
const (
17+
poolPerms string = "pool"
18+
loopPerms string = "loop"
19+
faradayPerms string = "faraday"
20+
litPerms string = "lit"
21+
lndPerms string = "lnd"
22+
)
23+
24+
// Manager manages the permission lists that Lit requires.
25+
type Manager struct {
26+
// lndSubServerPerms is a map from LND subserver name to permissions
27+
// map. This is used once the manager receives a list of build tags
28+
// that LND has been compiled with so that the correct permissions can
29+
// be extracted based on subservers that LND has been compiled with.
30+
lndSubServerPerms map[string]map[string][]bakery.Op
31+
32+
// fixedPerms is constructed once on creation of the Manager.
33+
// It contains all the permissions that will not change throughout the
34+
// lifetime of the manager. It maps sub-server name to uri to permission
35+
// operations.
36+
fixedPerms map[string]map[string][]bakery.Op
37+
38+
// perms is a map containing all permissions that the manager knows
39+
// are available for use. This map will start out not including any of
40+
// lnd's sub-server permissions. Only when the LND build tags are
41+
// obtained and OnLNDBuildTags is called will this map include the
42+
// available LND sub-server permissions. This map must only be accessed
43+
// once the permsMu mutex is held.
44+
perms map[string][]bakery.Op
45+
permsMu sync.RWMutex
46+
}
47+
48+
// NewManager constructs a new Manager instance and collects any of the
49+
// fixed permissions. If withAllSubServers is true, then all the LND sub-server
50+
// permissions will be added to the available permissions set regardless of
51+
// whether LND was compiled with those sub-servers. If it is not set, however,
52+
// then OnLNDBuildTags can be used to specify the exact sub-servers that LND
53+
// was compiled with and then only the corresponding permissions will be added.
54+
func NewManager(withAllSubServers bool) (*Manager, error) {
55+
permissions := make(map[string]map[string][]bakery.Op)
56+
permissions[faradayPerms] = faraday.RequiredPermissions
57+
permissions[loopPerms] = loop.RequiredPermissions
58+
permissions[poolPerms] = pool.RequiredPermissions
59+
permissions[litPerms] = RequiredPermissions
60+
permissions[lndPerms] = lnd.MainRPCServerPermissions()
61+
for k, v := range whiteListedLNDMethods {
62+
permissions[lndPerms][k] = v
63+
}
64+
65+
// Collect all LND sub-server permissions along with the name of the
66+
// sub-server that each permission is associated with.
67+
lndSubServerPerms := make(map[string]map[string][]bakery.Op)
68+
ss := lnrpc.RegisteredSubServers()
69+
for _, subServer := range ss {
70+
_, perms, err := subServer.NewGrpcHandler().CreateSubServer(
71+
&mockConfig{},
72+
)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
name := subServer.SubServerName
78+
lndSubServerPerms[name] = make(map[string][]bakery.Op)
79+
for key, value := range perms {
80+
lndSubServerPerms[name][key] = value
81+
82+
// If this sub-server is one that we know is
83+
// automatically compiled in LND then we add it to our
84+
// map of active permissions. We also add the permission
85+
// if withAllSubServers is true.
86+
if withAllSubServers ||
87+
lndAutoCompiledSubServers[name] {
88+
89+
permissions[lndPerms][key] = value
90+
}
91+
}
92+
}
93+
94+
allPerms := make(map[string][]bakery.Op)
95+
for _, perms := range permissions {
96+
for k, v := range perms {
97+
allPerms[k] = v
98+
}
99+
}
100+
101+
return &Manager{
102+
lndSubServerPerms: lndSubServerPerms,
103+
fixedPerms: permissions,
104+
perms: allPerms,
105+
}, nil
106+
}
107+
108+
// OnLNDBuildTags should be called once a list of LND build tags has been
109+
// obtained. It then uses those build tags to decide which of the LND sub-server
110+
// permissions to add to the main permissions list. This method should only
111+
// be called once.
112+
func (pm *Manager) OnLNDBuildTags(lndBuildTags []string) {
113+
pm.permsMu.Lock()
114+
defer pm.permsMu.Unlock()
115+
116+
tagLookup := make(map[string]bool)
117+
for _, t := range lndBuildTags {
118+
tagLookup[strings.ToLower(t)] = true
119+
}
120+
121+
for subServerName, perms := range pm.lndSubServerPerms {
122+
name := subServerName
123+
if tagName, ok := lndSubServerNameToTag[name]; ok {
124+
name = tagName
125+
}
126+
127+
if !tagLookup[strings.ToLower(name)] {
128+
continue
129+
}
130+
131+
for key, value := range perms {
132+
pm.perms[key] = value
133+
}
134+
}
135+
}
136+
137+
// URIPermissions returns a list of permission operations for the given URI if
138+
// the uri is known to the manager. The second return parameter will be false
139+
// if the URI is unknown to the manager.
140+
func (pm *Manager) URIPermissions(uri string) ([]bakery.Op, bool) {
141+
pm.permsMu.RLock()
142+
defer pm.permsMu.RUnlock()
143+
144+
ops, ok := pm.perms[uri]
145+
return ops, ok
146+
}
147+
148+
// MatchRegexURI first checks that the given URI is in fact a regex. If it is,
149+
// then it is used to match on the perms that the manager has. The return values
150+
// are a list of URIs that match the regex and the boolean represents whether
151+
// the given uri is in fact a regex.
152+
func (pm *Manager) MatchRegexURI(uriRegex string) ([]string, bool) {
153+
pm.permsMu.RLock()
154+
defer pm.permsMu.RUnlock()
155+
156+
// If the given uri string is one of our permissions, then it is not
157+
// a regex.
158+
if _, ok := pm.perms[uriRegex]; ok {
159+
return nil, false
160+
}
161+
162+
// Construct the regex type from the given string.
163+
r, err := regexp.Compile(uriRegex)
164+
if err != nil {
165+
return nil, false
166+
}
167+
168+
// Iterate over the list of permissions and collect all permissions that
169+
// match the given regex.
170+
var matches []string
171+
for uri := range pm.perms {
172+
if !r.MatchString(uri) {
173+
continue
174+
}
175+
176+
matches = append(matches, uri)
177+
}
178+
179+
return matches, true
180+
}
181+
182+
// ActivePermissions returns all the available active permissions that the
183+
// manager is aware of. Optionally, readOnly can be set to true if only the
184+
// read-only permissions should be returned.
185+
func (pm *Manager) ActivePermissions(readOnly bool) []bakery.Op {
186+
pm.permsMu.RLock()
187+
defer pm.permsMu.RUnlock()
188+
189+
// De-dup the permissions and optionally apply the read-only filter.
190+
dedupMap := make(map[string]map[string]bool)
191+
for _, methodPerms := range pm.perms {
192+
for _, methodPerm := range methodPerms {
193+
if methodPerm.Action == "" || methodPerm.Entity == "" {
194+
continue
195+
}
196+
197+
if readOnly && methodPerm.Action != "read" {
198+
continue
199+
}
200+
201+
if dedupMap[methodPerm.Entity] == nil {
202+
dedupMap[methodPerm.Entity] = make(
203+
map[string]bool,
204+
)
205+
}
206+
dedupMap[methodPerm.Entity][methodPerm.Action] = true
207+
}
208+
}
209+
210+
result := make([]bakery.Op, 0, len(dedupMap))
211+
for entity, actions := range dedupMap {
212+
for action := range actions {
213+
result = append(result, bakery.Op{
214+
Entity: entity,
215+
Action: action,
216+
})
217+
}
218+
}
219+
220+
return result
221+
}
222+
223+
// GetLitPerms returns a map of all permissions that the manager is aware of
224+
// _except_ for any LND permissions. In other words, this returns permissions
225+
// for which the external validator of Lit is responsible.
226+
func (pm *Manager) GetLitPerms() map[string][]bakery.Op {
227+
mapSize := len(pm.fixedPerms[litPerms]) +
228+
len(pm.fixedPerms[faradayPerms]) +
229+
len(pm.fixedPerms[loopPerms]) + len(pm.fixedPerms[poolPerms])
230+
231+
result := make(map[string][]bakery.Op, mapSize)
232+
for key, value := range pm.fixedPerms[faradayPerms] {
233+
result[key] = value
234+
}
235+
for key, value := range pm.fixedPerms[loopPerms] {
236+
result[key] = value
237+
}
238+
for key, value := range pm.fixedPerms[poolPerms] {
239+
result[key] = value
240+
}
241+
for key, value := range pm.fixedPerms[litPerms] {
242+
result[key] = value
243+
}
244+
return result
245+
}
246+
247+
// IsLndURI returns true if the given URI belongs to an RPC of lnd.
248+
func (pm *Manager) IsLndURI(uri string) bool {
249+
var lndSubServerCall bool
250+
for _, subserverPermissions := range pm.lndSubServerPerms {
251+
_, found := subserverPermissions[uri]
252+
if found {
253+
lndSubServerCall = true
254+
break
255+
}
256+
}
257+
_, lndCall := pm.fixedPerms[lndPerms][uri]
258+
return lndCall || lndSubServerCall
259+
}
260+
261+
// IsLoopURI returns true if the given URI belongs to an RPC of loopd.
262+
func (pm *Manager) IsLoopURI(uri string) bool {
263+
_, ok := pm.fixedPerms[loopPerms][uri]
264+
return ok
265+
}
266+
267+
// IsFaradayURI returns true if the given URI belongs to an RPC of faraday.
268+
func (pm *Manager) IsFaradayURI(uri string) bool {
269+
_, ok := pm.fixedPerms[faradayPerms][uri]
270+
return ok
271+
}
272+
273+
// IsPoolURI returns true if the given URI belongs to an RPC of poold.
274+
func (pm *Manager) IsPoolURI(uri string) bool {
275+
_, ok := pm.fixedPerms[poolPerms][uri]
276+
return ok
277+
}
278+
279+
// IsLitURI returns true if the given URI belongs to an RPC of LiT.
280+
func (pm *Manager) IsLitURI(uri string) bool {
281+
_, ok := pm.fixedPerms[litPerms][uri]
282+
return ok
283+
}
File renamed without changes.

perms/mock.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package perms
2+
3+
import (
4+
"net"
5+
6+
"github.com/lightningnetwork/lnd/autopilot"
7+
"github.com/lightningnetwork/lnd/chainreg"
8+
"github.com/lightningnetwork/lnd/lnrpc"
9+
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
10+
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
11+
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
12+
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
13+
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
14+
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
15+
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
16+
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
17+
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
18+
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
19+
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
20+
"github.com/lightningnetwork/lnd/lntest/mock"
21+
"github.com/lightningnetwork/lnd/routing"
22+
"github.com/lightningnetwork/lnd/sweep"
23+
)
24+
25+
// mockConfig implements lnrpc.SubServerConfigDispatcher. It provides the
26+
// functionality required so that the lnrpc.GrpcHandler.CreateSubServer
27+
// function can be called without panicking.
28+
type mockConfig struct{}
29+
30+
var _ lnrpc.SubServerConfigDispatcher = (*mockConfig)(nil)
31+
32+
// FetchConfig is a mock implementation of lnrpc.SubServerConfigDispatcher. It
33+
// is used as a parameter to lnrpc.GrpcHandler.CreateSubServer and allows the
34+
// function to be called without panicking. This is useful because
35+
// CreateSubServer can be used to extract the permissions required by each
36+
// registered subserver.
37+
//
38+
// TODO(elle): remove this once the sub-server permission lists in LND have been
39+
// exported
40+
func (t *mockConfig) FetchConfig(subServerName string) (interface{}, bool) {
41+
switch subServerName {
42+
case "InvoicesRPC":
43+
return &invoicesrpc.Config{}, true
44+
case "WatchtowerClientRPC":
45+
return &wtclientrpc.Config{
46+
Resolver: func(_, _ string) (*net.TCPAddr, error) {
47+
return nil, nil
48+
},
49+
}, true
50+
case "AutopilotRPC":
51+
return &autopilotrpc.Config{
52+
Manager: &autopilot.Manager{},
53+
}, true
54+
case "ChainRPC":
55+
return &chainrpc.Config{
56+
ChainNotifier: &chainreg.NoChainBackend{},
57+
Chain: &mock.ChainIO{},
58+
}, true
59+
case "DevRPC":
60+
return &devrpc.Config{}, true
61+
case "NeutrinoKitRPC":
62+
return &neutrinorpc.Config{}, true
63+
case "PeersRPC":
64+
return &peersrpc.Config{}, true
65+
case "RouterRPC":
66+
return &routerrpc.Config{
67+
Router: &routing.ChannelRouter{},
68+
}, true
69+
case "SignRPC":
70+
return &signrpc.Config{
71+
Signer: &mock.DummySigner{},
72+
}, true
73+
case "WalletKitRPC":
74+
return &walletrpc.Config{
75+
FeeEstimator: &chainreg.NoChainBackend{},
76+
Wallet: &mock.WalletController{},
77+
KeyRing: &mock.SecretKeyRing{},
78+
Sweeper: &sweep.UtxoSweeper{},
79+
Chain: &mock.ChainIO{},
80+
}, true
81+
case "WatchtowerRPC":
82+
return &watchtowerrpc.Config{}, true
83+
default:
84+
return nil, false
85+
}
86+
}

0 commit comments

Comments
 (0)