@@ -4,8 +4,16 @@ import (
4
4
"context"
5
5
"fmt"
6
6
7
+ "github.com/lightninglabs/lightning-terminal/firewalldb"
8
+ "github.com/lightninglabs/lightning-terminal/perms"
7
9
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
10
+ "github.com/lightninglabs/lightning-terminal/rules"
11
+ "github.com/lightninglabs/lightning-terminal/session"
12
+ "github.com/lightninglabs/lndclient"
8
13
"github.com/lightningnetwork/lnd/lnrpc"
14
+ "google.golang.org/grpc/codes"
15
+ "google.golang.org/grpc/status"
16
+ "google.golang.org/protobuf/proto"
9
17
)
10
18
11
19
const (
@@ -20,6 +28,47 @@ var _ mid.RequestInterceptor = (*RuleEnforcer)(nil)
20
28
// RuleEnforcer is a RequestInterceptor that makes sure all firewall related
21
29
// custom caveats in a macaroon are properly enforced.
22
30
type RuleEnforcer struct {
31
+ ruleDB firewalldb.RulesDB
32
+ actionsDB firewalldb.ActionReadDBGetter
33
+ markActionErrored func (reqID uint64 , reason string ) error
34
+ newPrivMap firewalldb.NewPrivacyMapDB
35
+
36
+ permsMgr * perms.Manager
37
+ getFeaturePerms featurePerms
38
+
39
+ nodeID [33 ]byte
40
+
41
+ routerClient lndclient.RouterClient
42
+ lndClient lndclient.LightningClient
43
+
44
+ ruleMgrs rules.ManagerSet
45
+ }
46
+
47
+ // featurePerms defines the signature of a function that can be used to fetch
48
+ // feature permissions.
49
+ type featurePerms func (ctx context.Context ) (map [string ]map [string ]bool , error )
50
+
51
+ // NewRuleEnforcer constructs a new RuleEnforcer instance.
52
+ func NewRuleEnforcer (ruleDB firewalldb.RulesDB ,
53
+ actionsDB firewalldb.ActionReadDBGetter , getFeaturePerms featurePerms ,
54
+ permsMgr * perms.Manager , nodeID [33 ]byte ,
55
+ routerClient lndclient.RouterClient ,
56
+ lndClient lndclient.LightningClient , ruleMgrs rules.ManagerSet ,
57
+ markActionErrored func (reqID uint64 , reason string ) error ,
58
+ privMap firewalldb.NewPrivacyMapDB ) * RuleEnforcer {
59
+
60
+ return & RuleEnforcer {
61
+ ruleDB : ruleDB ,
62
+ actionsDB : actionsDB ,
63
+ permsMgr : permsMgr ,
64
+ getFeaturePerms : getFeaturePerms ,
65
+ nodeID : nodeID ,
66
+ routerClient : routerClient ,
67
+ lndClient : lndClient ,
68
+ ruleMgrs : ruleMgrs ,
69
+ markActionErrored : markActionErrored ,
70
+ newPrivMap : privMap ,
71
+ }
23
72
}
24
73
25
74
// Name returns the name of the interceptor.
@@ -41,7 +90,7 @@ func (r *RuleEnforcer) CustomCaveatName() string {
41
90
42
91
// Intercept processes an RPC middleware interception request and returns the
43
92
// interception result which either accepts or rejects the intercepted message.
44
- func (r * RuleEnforcer ) Intercept (_ context.Context ,
93
+ func (r * RuleEnforcer ) Intercept (ctx context.Context ,
45
94
req * lnrpc.RPCMiddlewareRequest ) (* lnrpc.RPCMiddlewareResponse , error ) {
46
95
47
96
ri , err := NewInfoFromRequest (req )
@@ -50,15 +99,287 @@ func (r *RuleEnforcer) Intercept(_ context.Context,
50
99
"interception request: %v" , err )
51
100
}
52
101
53
- log .Infof ("Enforcing rule %v" , ri )
102
+ if ri .Rules == nil {
103
+ return mid .RPCOk (req )
104
+ }
105
+
106
+ log .Tracef ("RuleEnforcer: Intercepting %v" , ri )
107
+
108
+ if ri .MetaInfo == nil {
109
+ return mid .RPCErrString (req , "missing MetaInfo" )
110
+ }
111
+
112
+ // Ensure that the specified feature name is one listed in the macaroon.
113
+ featureName := ri .MetaInfo .Feature
114
+ _ , ok := ri .Rules .FeatureRules [featureName ]
115
+ if len (ri .Rules .FeatureRules ) != 0 && ! ok {
116
+ return mid .RPCErrString (req , "feature %s does not correspond " +
117
+ "to a feature specified in the macaroon caveat" ,
118
+ featureName )
119
+ }
120
+
121
+ // Ensure that the feature specified in the MetaInfo is one that we
122
+ // know about from our last interaction with the Autopilot server.
123
+ featurePerms , err := r .getFeaturePerms (ctx )
124
+ if err != nil {
125
+ return mid .RPCErrString (req , "unable to get feature " +
126
+ "permissions" )
127
+ }
128
+
129
+ perms , ok := featurePerms [featureName ]
130
+ if ! ok {
131
+ return mid .RPCErrString (req , "feature %s is not a known " +
132
+ "feature" , featureName )
133
+ }
134
+
135
+ // Then check that this URI is allowed given the list of perms the
136
+ // Autopilot told us this feature could use.
137
+ if ! perms [ri .URI ] {
138
+ return mid .RPCErrString (req , "Method %s is not allowed for " +
139
+ "feature %s" , ri .URI , featureName )
140
+ }
141
+
142
+ switch ri .MWRequestType {
143
+ case MWRequestTypeStreamAuth :
144
+ return mid .RPCOk (req )
145
+
146
+ // Parse incoming requests and act on them.
147
+ case MWRequestTypeRequest :
148
+ replacement , err := r .handleRequest (ctx , ri )
149
+ if err != nil {
150
+ dbErr := r .markActionErrored (ri .RequestID , err .Error ())
151
+ if dbErr != nil {
152
+ log .Error ("could not mark action for " +
153
+ "request ID %d as Errored: %v" ,
154
+ ri .RequestID , dbErr )
155
+ }
156
+
157
+ return mid .RPCErr (req , err )
158
+ }
159
+
160
+ // No error occurred but the request should be replaced with
161
+ // the given custom request. Wrap it in the correct RPC
162
+ // request of the interceptor now.
163
+ if replacement != nil {
164
+ return mid .RPCReplacement (req , replacement )
165
+ }
166
+
167
+ // No error and no replacement, just return an empty request of
168
+ // the correct type.
169
+ return mid .RPCOk (req )
170
+
171
+ // Parse and possibly manipulate outgoing responses.
172
+ case MWRequestTypeResponse :
173
+ if ri .IsError {
174
+ replacementErr , err := r .handleErrorResponse (ctx , ri )
175
+ if err != nil {
176
+ return mid .RPCErr (req , err )
177
+ }
178
+
179
+ // No error occurred but the response error should be
180
+ // replaced with the given custom error. Wrap it in the
181
+ // correct RPC response of the interceptor now.
182
+ if replacementErr != nil {
183
+ return mid .RPCErrReplacement (
184
+ req , replacementErr ,
185
+ )
186
+ }
187
+
188
+ // No error and no replacement, just return an empty
189
+ // response of the correct type.
190
+ return mid .RPCOk (req )
191
+ }
192
+
193
+ replacement , err := r .handleResponse (ctx , ri )
194
+ if err != nil {
195
+ return mid .RPCErr (req , err )
196
+ }
197
+
198
+ // No error occurred but the response should be replaced with
199
+ // the given custom response. Wrap it in the correct RPC
200
+ // response of the interceptor now.
201
+ if replacement != nil {
202
+ return mid .RPCReplacement (req , replacement )
203
+ }
204
+
205
+ // No error and no replacement, just return an empty response of
206
+ // the correct type.
207
+ return mid .RPCOk (req )
208
+
209
+ default :
210
+ return mid .RPCErrString (req , "invalid intercept type: %v" , r )
211
+ }
212
+ }
213
+
214
+ // handleRequest gathers the rules that will need to enforced for the given
215
+ // feature and runs the request against each of those.
216
+ func (r * RuleEnforcer ) handleRequest (ctx context.Context ,
217
+ ri * RequestInfo ) (proto.Message , error ) {
218
+
219
+ sessionID , err := session .IDFromMacaroon (ri .Macaroon )
220
+ if err != nil {
221
+ return nil , fmt .Errorf ("could not extract ID from macaroon" )
222
+ }
223
+
224
+ rules , err := r .collectEnforcers (ri , sessionID )
225
+ if err != nil {
226
+ return nil , fmt .Errorf ("error parsing rules: %v" , err )
227
+ }
228
+
229
+ msg , err := mid .ParseProtobuf (
230
+ ri .GRPCMessageType , ri .Serialized ,
231
+ )
232
+ if err != nil {
233
+ return nil , fmt .Errorf ("error parsing proto: %v" , err )
234
+ }
235
+
236
+ for _ , rule := range rules {
237
+ newRequest , err := rule .HandleRequest (ctx , ri .URI , msg )
238
+ if err != nil {
239
+ st := status .Errorf (
240
+ codes .ResourceExhausted , "rule violation: %v" ,
241
+ err ,
242
+ )
243
+ return nil , st
244
+ }
245
+
246
+ if newRequest != nil {
247
+ msg = newRequest
248
+ }
249
+ }
250
+
251
+ return nil , nil
252
+ }
253
+
254
+ // handleResponse gathers the rules that will need to be enforced for the given
255
+ // feature and runs the response against each of those.
256
+ func (r * RuleEnforcer ) handleResponse (ctx context.Context ,
257
+ ri * RequestInfo ) (proto.Message , error ) {
258
+
259
+ sessionID , err := session .IDFromMacaroon (ri .Macaroon )
260
+ if err != nil {
261
+ return nil , fmt .Errorf ("could not extract ID from macaroon" )
262
+ }
263
+
264
+ enforcers , err := r .collectEnforcers (ri , sessionID )
265
+ if err != nil {
266
+ return nil , fmt .Errorf ("error parsing rules: %v" , err )
267
+ }
268
+
269
+ msg , err := mid .ParseProtobuf (ri .GRPCMessageType , ri .Serialized )
270
+ if err != nil {
271
+ return nil , fmt .Errorf ("error parsing proto: %v" , err )
272
+ }
273
+
274
+ for _ , enforcer := range enforcers {
275
+ newResponse , err := enforcer .HandleResponse (ctx , ri .URI , msg )
276
+ if err != nil {
277
+ return nil , err
278
+ }
279
+
280
+ if newResponse != nil {
281
+ msg = newResponse
282
+ }
283
+ }
284
+
285
+ return msg , nil
286
+ }
287
+
288
+ // handleErrorResponse gathers the rules that will need to be enforced for the
289
+ // given feature and runs the response error against each of those.
290
+ func (r * RuleEnforcer ) handleErrorResponse (ctx context.Context ,
291
+ ri * RequestInfo ) (error , error ) {
292
+
293
+ sessionID , err := session .IDFromMacaroon (ri .Macaroon )
294
+ if err != nil {
295
+ return nil , fmt .Errorf ("could not extract ID from macaroon" )
296
+ }
297
+
298
+ enforcers , err := r .collectEnforcers (ri , sessionID )
299
+ if err != nil {
300
+ return nil , fmt .Errorf ("error parsing rules: %v" , err )
301
+ }
302
+
303
+ parsedErr := mid .ParseResponseErr (ri .Serialized )
304
+
305
+ for _ , enforcer := range enforcers {
306
+ newErr , err := enforcer .HandleErrorResponse (
307
+ ctx , ri .URI , parsedErr ,
308
+ )
309
+ if err != nil {
310
+ return nil , err
311
+ }
312
+
313
+ if newErr != nil {
314
+ parsedErr = newErr
315
+ }
316
+ }
317
+
318
+ return parsedErr , nil
319
+ }
320
+
321
+ // collectRule initialises and returns all the Rules that need to be enforced
322
+ // for the given request.
323
+ func (r * RuleEnforcer ) collectEnforcers (ri * RequestInfo , sessionID session.ID ) (
324
+ []rules.Enforcer , error ) {
325
+
326
+ ruleEnforcers := make (
327
+ []rules.Enforcer , 0 ,
328
+ len (ri .Rules .FeatureRules )+ len (ri .Rules .SessionRules ),
329
+ )
330
+
331
+ for rule , value := range ri .Rules .FeatureRules [ri .MetaInfo .Feature ] {
332
+ r , err := r .initRule (
333
+ ri .RequestID , rule , []byte (value ), ri .MetaInfo .Feature ,
334
+ sessionID , false , ri .WithPrivacy ,
335
+ )
336
+ if err != nil {
337
+ return nil , err
338
+ }
339
+
340
+ ruleEnforcers = append (ruleEnforcers , r )
341
+ }
342
+
343
+ return ruleEnforcers , nil
344
+ }
345
+
346
+ // initRule initialises a rule.Rule with any required config values.
347
+ func (r * RuleEnforcer ) initRule (reqID uint64 , name string , value []byte ,
348
+ featureName string , sessionID session.ID , sessionRule ,
349
+ privacy bool ) (rules.Enforcer , error ) {
350
+
351
+ ruleValues , err := r .ruleMgrs .InitRuleValues (name , value )
352
+ if err != nil {
353
+ return nil , err
354
+ }
355
+
356
+ if privacy {
357
+ privMap := r .newPrivMap (sessionID )
358
+ ruleValues , err = ruleValues .PseudoToReal (privMap )
359
+ if err != nil {
360
+ return nil , fmt .Errorf ("could not prepare rule " +
361
+ "value: %v" , err )
362
+ }
363
+ }
364
+
365
+ allActionsDB := r .actionsDB .GetActionsReadDB (sessionID , featureName )
366
+ actionsDB := allActionsDB .FeatureActionsDB ()
367
+ rulesDB := r .ruleDB .GetKVStores (name , sessionID , featureName )
368
+
369
+ if sessionRule {
370
+ actionsDB = allActionsDB .SessionActionsDB ()
371
+ rulesDB = r .ruleDB .GetKVStores (name , sessionID , "" )
372
+ }
54
373
55
- // Enforce actual rules.
56
- numRules := len (ri .Rules .SessionRules ) + len (ri .Rules .FeatureRules )
57
- if numRules > 0 {
58
- // TODO(guggero): Implement rules and their enforcement.
59
- log .Debugf ("There are %d rules to enforce" , numRules )
374
+ cfg := & rules.ConfigImpl {
375
+ Stores : rulesDB ,
376
+ ActionsDB : actionsDB ,
377
+ MethodPerms : r .permsMgr .URIPermissions ,
378
+ NodeID : r .nodeID ,
379
+ RouterClient : r .routerClient ,
380
+ LndClient : r .lndClient ,
381
+ ReqID : int64 (reqID ),
60
382
}
61
383
62
- // Send empty response, accepting the request.
63
- return mid .RPCOk (req )
384
+ return r .ruleMgrs .InitEnforcer (cfg , name , ruleValues )
64
385
}
0 commit comments