Skip to content

Commit 3f8789f

Browse files
guggeroellemouton
authored andcommitted
firewall: implement firewall
1 parent 6d76e22 commit 3f8789f

File tree

3 files changed

+245
-0
lines changed

3 files changed

+245
-0
lines changed

firewall/request_info.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package firewall
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/lightningnetwork/lnd/lnrpc"
8+
"gopkg.in/macaroon.v2"
9+
)
10+
11+
const (
12+
// MWRequestTypeStreamAuth represents the type name for a stream
13+
// authentication interception message.
14+
MWRequestTypeStreamAuth = "stream_auth"
15+
16+
// MWRequestTypeRequest represents the type name for a request
17+
// interception message.
18+
MWRequestTypeRequest = "request"
19+
20+
// MWRequestTypeResponse represents the type name for a response
21+
// interception message.
22+
MWRequestTypeResponse = "response"
23+
)
24+
25+
// RequestInfo stores the parsed representation of an incoming RPC middleware
26+
// request.
27+
type RequestInfo struct {
28+
MsgID uint64
29+
RequestID uint64
30+
MWRequestType string
31+
URI string
32+
GRPCMessageType string
33+
Streaming bool
34+
Macaroon *macaroon.Macaroon
35+
Caveats []string
36+
MetaInfo *InterceptMetaInfo
37+
Rules []*InterceptRule
38+
}
39+
40+
// NewInfoFromRequest parses the given RPC middleware interception request and
41+
// returns a RequestInfo struct.
42+
func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) {
43+
var ri *RequestInfo
44+
switch t := req.InterceptType.(type) {
45+
case *lnrpc.RPCMiddlewareRequest_StreamAuth:
46+
ri = &RequestInfo{
47+
MWRequestType: MWRequestTypeStreamAuth,
48+
URI: t.StreamAuth.MethodFullUri,
49+
Streaming: true,
50+
}
51+
52+
case *lnrpc.RPCMiddlewareRequest_Request:
53+
ri = &RequestInfo{
54+
MWRequestType: MWRequestTypeRequest,
55+
URI: t.Request.MethodFullUri,
56+
GRPCMessageType: t.Request.TypeName,
57+
Streaming: t.Request.StreamRpc,
58+
}
59+
60+
case *lnrpc.RPCMiddlewareRequest_Response:
61+
ri = &RequestInfo{
62+
MWRequestType: MWRequestTypeResponse,
63+
URI: t.Response.MethodFullUri,
64+
GRPCMessageType: t.Response.TypeName,
65+
Streaming: t.Response.StreamRpc,
66+
}
67+
68+
default:
69+
return nil, fmt.Errorf("invalid request type: %T", t)
70+
}
71+
72+
ri.MsgID = req.MsgId
73+
ri.RequestID = req.RequestId
74+
75+
ri.Macaroon = &macaroon.Macaroon{}
76+
if err := ri.Macaroon.UnmarshalBinary(req.RawMacaroon); err != nil {
77+
return nil, fmt.Errorf("error parsing macaroon: %v", err)
78+
}
79+
80+
ri.Caveats = make([]string, len(ri.Macaroon.Caveats()))
81+
for idx, cav := range ri.Macaroon.Caveats() {
82+
ri.Caveats[idx] = string(cav.Id)
83+
84+
// Apply any meta information sent as a custom caveat. Only the
85+
// last one will be considered if there are multiple caveats.
86+
metaInfo, err := ParseMetaInfoCaveat(ri.Caveats[idx])
87+
if err == nil {
88+
ri.MetaInfo = metaInfo
89+
90+
// The same caveat can't be a meta info and rule list.
91+
continue
92+
}
93+
94+
// Also apply the rule list sent as a custom caveat. Only the
95+
// last set of rules will be considered if there are multiple
96+
// caveats.
97+
rules, err := ParseRuleCaveat(ri.Caveats[idx])
98+
if err == nil {
99+
ri.Rules = rules
100+
}
101+
}
102+
103+
return ri, nil
104+
}
105+
106+
// String returns the string representation of the request info struct.
107+
func (ri *RequestInfo) String() string {
108+
return fmt.Sprintf("Request={msg_id=%d, request_id=%d, type=%v, "+
109+
"uri=%v, grpc_message_type=%v, streaming=%v, caveats=[%v], "+
110+
"meta_info=%v, rules=[%v]}",
111+
ri.MsgID, ri.RequestID, ri.MWRequestType, ri.URI,
112+
ri.GRPCMessageType, ri.Streaming, strings.Join(ri.Caveats, ","),
113+
ri.MetaInfo, ri.Rules)
114+
}

firewall/request_logger.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package firewall
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
8+
"github.com/lightningnetwork/lnd/lnrpc"
9+
)
10+
11+
const (
12+
// RequestLoggerName is the name of the RequestLogger interceptor.
13+
RequestLoggerName = "lit-macaroon-firewall-logger"
14+
)
15+
16+
// A compile-time assertion that RuleEnforcer is a
17+
// rpcmiddleware.RequestInterceptor.
18+
var _ mid.RequestInterceptor = (*RequestLogger)(nil)
19+
20+
// RequestLogger is a RequestInterceptor that just logs incoming RPC requests.
21+
type RequestLogger struct {
22+
// ObservedRequests is a list of all requests that were observed because
23+
// they contained additional meta information about their intent.
24+
//
25+
// TODO(guggero): Replace by persistent storage to keep a history for
26+
// the user.
27+
ObservedRequests []*RequestInfo
28+
}
29+
30+
// Name returns the name of the interceptor.
31+
func (r *RequestLogger) Name() string {
32+
return RequestLoggerName
33+
}
34+
35+
// ReadOnly returns true if this interceptor should be registered in read-only
36+
// mode. In read-only mode no custom caveat name can be specified.
37+
func (r *RequestLogger) ReadOnly() bool {
38+
return true
39+
}
40+
41+
// CustomCaveatName returns the name of the custom caveat that is expected to be
42+
// handled by this interceptor. Cannot be specified in read-only mode.
43+
func (r *RequestLogger) CustomCaveatName() string {
44+
return ""
45+
}
46+
47+
// Intercept processes an RPC middleware interception request and returns the
48+
// interception result which either accepts or rejects the intercepted message.
49+
func (r *RequestLogger) Intercept(_ context.Context,
50+
req *lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse, error) {
51+
52+
ri, err := NewInfoFromRequest(req)
53+
if err != nil {
54+
return nil, fmt.Errorf("error parsing incoming RPC middleware "+
55+
"interception request: %v", err)
56+
}
57+
58+
log.Infof("RequestLogger: Intercepting %v", ri)
59+
60+
// Persist the observed request if it is tagged with specific meta
61+
// information.
62+
if ri.MetaInfo != nil {
63+
r.ObservedRequests = append(r.ObservedRequests, ri)
64+
}
65+
66+
// Send empty response, accepting the request.
67+
return mid.RPCOk(req)
68+
}

firewall/rule_enforcer.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package firewall
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
8+
"github.com/lightningnetwork/lnd/lnrpc"
9+
)
10+
11+
const (
12+
// RuleEnforcerName is the name of the RuleEnforcer interceptor.
13+
RuleEnforcerName = "lit-macaroon-firewall"
14+
)
15+
16+
// A compile-time assertion that RuleEnforcer is a
17+
// rpcmiddleware.RequestInterceptor.
18+
var _ mid.RequestInterceptor = (*RuleEnforcer)(nil)
19+
20+
// RuleEnforcer is a RequestInterceptor that makes sure all firewall related
21+
// custom caveats in a macaroon are properly enforced.
22+
type RuleEnforcer struct {
23+
}
24+
25+
// Name returns the name of the interceptor.
26+
func (r *RuleEnforcer) Name() string {
27+
return RuleEnforcerName
28+
}
29+
30+
// ReadOnly returns true if this interceptor should be registered in read-only
31+
// mode. In read-only mode no custom caveat name can be specified.
32+
func (r *RuleEnforcer) ReadOnly() bool {
33+
return false
34+
}
35+
36+
// CustomCaveatName returns the name of the custom caveat that is expected to be
37+
// handled by this interceptor. Cannot be specified in read-only mode.
38+
func (r *RuleEnforcer) CustomCaveatName() string {
39+
return RuleEnforcerCaveat
40+
}
41+
42+
// Intercept processes an RPC middleware interception request and returns the
43+
// interception result which either accepts or rejects the intercepted message.
44+
func (r *RuleEnforcer) Intercept(_ context.Context,
45+
req *lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse, error) {
46+
47+
ri, err := NewInfoFromRequest(req)
48+
if err != nil {
49+
return nil, fmt.Errorf("error parsing incoming RPC middleware "+
50+
"interception request: %v", err)
51+
}
52+
53+
log.Infof("Enforcing rule %v", ri)
54+
55+
// Enforce actual rules.
56+
if len(ri.Rules) > 0 {
57+
// TODO(guggero): Implement rules and their enforcement.
58+
log.Debugf("There are %d rules to enforce", len(ri.Rules))
59+
}
60+
61+
// Send empty response, accepting the request.
62+
return mid.RPCOk(req)
63+
}

0 commit comments

Comments
 (0)