multi: make request logger level configurable

This commit is contained in:
Elle Mouton 2023-01-26 17:13:04 +02:00
parent 2d57c25850
commit 8e61eb90da
No known key found for this signature in database
GPG key ID: D7D916376026F177
4 changed files with 131 additions and 32 deletions

View file

@ -18,6 +18,7 @@ import (
"github.com/lightninglabs/faraday/chain"
"github.com/lightninglabs/faraday/frdrpcserver"
"github.com/lightninglabs/lightning-terminal/autopilotserver"
"github.com/lightninglabs/lightning-terminal/firewall"
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopd"
@ -188,6 +189,8 @@ type Config struct {
Autopilot *autopilotserver.Config `group:"Autopilot server options" namespace:"autopilot"`
Firewall *firewall.Config `group:"Firewall options" namespace:"firewall"`
// faradayRpcConfig is a subset of faraday's full configuration that is
// passed into faraday's RPC server.
faradayRpcConfig *frdrpcserver.Config
@ -331,6 +334,7 @@ func defaultConfig() *Config {
Autopilot: &autopilotserver.Config{
PingCadence: time.Hour,
},
Firewall: firewall.DefaultConfig(),
}
}

20
firewall/config.go Normal file
View file

@ -0,0 +1,20 @@
package firewall
// Config holds all config options for the firewall.
type Config struct {
RequestLogger *RequestLoggerConfig `group:"request-logger" namespace:"request-logger" description:"request logger settings"`
}
// RequestLoggerConfig holds all the config options for the request logger.
type RequestLoggerConfig struct {
RequestLoggerLevel RequestLoggerLevel `long:"level" description:"Set the request logger level. Options include 'all', 'full' and 'interceptor''"`
}
// DefaultConfig constructs the default firewall Config struct.
func DefaultConfig() *Config {
return &Config{
RequestLogger: &RequestLoggerConfig{
RequestLoggerLevel: RequestLoggerLevelInterceptor,
},
}
}

View file

@ -3,6 +3,7 @@ package firewall
import (
"context"
"fmt"
"strings"
"sync"
"time"
@ -12,6 +13,7 @@ import (
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
)
const (
@ -35,10 +37,20 @@ var (
_ mid.RequestInterceptor = (*RequestLogger)(nil)
)
type RequestLoggerLevel string
const (
RequestLoggerLevelInterceptor = "interceptor"
RequestLoggerLevelAll = "all"
RequestLoggerLevelFull = "full"
)
// RequestLogger is a RequestInterceptor that just logs incoming RPC requests.
type RequestLogger struct {
actionsDB firewalldb.ActionsWriteDB
shouldLogAction func(ri *RequestInfo) (bool, bool)
// reqIDToAction is a map from request ID to an ActionLocator that can
// be used to find the corresponding action. This is used so that
// requests and responses can be easily linked. The mu mutex must be
@ -48,11 +60,55 @@ type RequestLogger struct {
}
// NewRequestLogger creates a new RequestLogger.
func NewRequestLogger(actionsDB firewalldb.ActionsWriteDB) *RequestLogger {
return &RequestLogger{
actionsDB: actionsDB,
reqIDToAction: make(map[uint64]*firewalldb.ActionLocator),
func NewRequestLogger(cfg *RequestLoggerConfig,
actionsDB firewalldb.ActionsWriteDB) (*RequestLogger, error) {
hasInterceptorCaveat := func(caveats []string) bool {
for _, c := range caveats {
if strings.HasPrefix(c, macaroons.CondLndCustom) {
return true
}
}
return false
}
var shouldLogAction func(ri *RequestInfo) (bool, bool)
switch cfg.RequestLoggerLevel {
// Only log requests that have an interceptor caveat attached.
case RequestLoggerLevelInterceptor:
shouldLogAction = func(ri *RequestInfo) (bool, bool) {
if hasInterceptorCaveat(ri.Caveats) {
return true, true
}
return false, false
}
// Log all requests but only log request params if the request
// has an interceptor caveat.
case RequestLoggerLevelAll:
shouldLogAction = func(ri *RequestInfo) (bool, bool) {
return true, hasInterceptorCaveat(ri.Caveats)
}
// Log all requests will all request parameters.
case RequestLoggerLevelFull:
shouldLogAction = func(ri *RequestInfo) (bool, bool) {
return true, true
}
default:
return nil, fmt.Errorf("unknown request logger level: %s. "+
"Expected either 'interceptor', 'all' or 'full'",
cfg.RequestLoggerLevel)
}
return &RequestLogger{
shouldLogAction: shouldLogAction,
actionsDB: actionsDB,
reqIDToAction: make(map[uint64]*firewalldb.ActionLocator),
}, nil
}
// Name returns the name of the interceptor.
@ -89,6 +145,11 @@ func (r *RequestLogger) Intercept(_ context.Context,
return mid.RPCOk(req)
}
shouldLogAction, withPayloadData := r.shouldLogAction(ri)
if !shouldLogAction {
return mid.RPCOk(req)
}
log.Tracef("RequestLogger: Intercepting %v", ri)
switch ri.MWRequestType {
@ -97,7 +158,7 @@ func (r *RequestLogger) Intercept(_ context.Context,
// Parse incoming requests and act on them.
case MWRequestTypeRequest:
return mid.RPCErr(req, r.addNewAction(ri))
return mid.RPCErr(req, r.addNewAction(ri, withPayloadData))
// Parse and possibly manipulate outgoing responses.
case MWRequestTypeResponse:
@ -120,7 +181,9 @@ func (r *RequestLogger) Intercept(_ context.Context,
}
// addNewAction persists the new action to the db.
func (r *RequestLogger) addNewAction(ri *RequestInfo) error {
func (r *RequestLogger) addNewAction(ri *RequestInfo,
withPayloadData bool) error {
// If no macaroon is provided, then an empty 4-byte array is used as the
// session ID. Otherwise, the macaroon is used to derive a session ID.
var sessionID [4]byte
@ -132,34 +195,40 @@ func (r *RequestLogger) addNewAction(ri *RequestInfo) error {
}
}
msg, err := mid.ParseProtobuf(ri.GRPCMessageType, ri.Serialized)
if err != nil {
return err
}
jsonMarshaler := &jsonpb.Marshaler{
EmitDefaults: true,
OrigName: true,
}
jsonStr, err := jsonMarshaler.MarshalToString(proto.MessageV1(msg))
if err != nil {
return fmt.Errorf("unable to decode response: %v", err)
}
action := &firewalldb.Action{
RPCMethod: ri.URI,
RPCParamsJson: []byte(jsonStr),
AttemptedAt: time.Now(),
State: firewalldb.ActionStateInit,
RPCMethod: ri.URI,
AttemptedAt: time.Now(),
State: firewalldb.ActionStateInit,
}
if ri.MetaInfo != nil {
action.ActorName = ri.MetaInfo.ActorName
action.FeatureName = ri.MetaInfo.Feature
action.Trigger = ri.MetaInfo.Trigger
action.Intent = ri.MetaInfo.Intent
action.StructuredJsonData = ri.MetaInfo.StructuredJsonData
if withPayloadData {
msg, err := mid.ParseProtobuf(ri.GRPCMessageType, ri.Serialized)
if err != nil {
return err
}
jsonMarshaler := &jsonpb.Marshaler{
EmitDefaults: true,
OrigName: true,
}
jsonStr, err := jsonMarshaler.MarshalToString(
proto.MessageV1(msg),
)
if err != nil {
return fmt.Errorf("unable to decode response: %v", err)
}
action.RPCParamsJson = []byte(jsonStr)
meta := ri.MetaInfo
if meta != nil {
action.ActorName = meta.ActorName
action.FeatureName = meta.Feature
action.Trigger = meta.Trigger
action.Intent = meta.Intent
action.StructuredJsonData = meta.StructuredJsonData
}
}
id, err := r.actionsDB.AddAction(sessionID, action)

View file

@ -727,7 +727,13 @@ func (g *LightningTerminal) startSubservers() error {
}
g.accountServiceStarted = true
requestLogger := firewall.NewRequestLogger(g.firewallDB)
requestLogger, err := firewall.NewRequestLogger(
g.cfg.Firewall.RequestLogger, g.firewallDB,
)
if err != nil {
return fmt.Errorf("error creating new request logger")
}
privacyMapper := firewall.NewPrivacyMapper(
g.firewallDB.PrivacyDB, firewall.CryptoRandIntn,
)