mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
multi: implement lit-autopilot rpc server
This commit is contained in:
parent
f9ffc0cdef
commit
3b67325360
5 changed files with 613 additions and 23 deletions
|
|
@ -153,6 +153,14 @@ var (
|
|||
litConn := litrpc.NewAccountsClient(c)
|
||||
return litConn.ListAccounts(ctx, &litrpc.ListAccountsRequest{})
|
||||
}
|
||||
litAutopilotRequestFn = func(ctx context.Context,
|
||||
c grpc.ClientConnInterface) (proto.Message, error) {
|
||||
|
||||
litConn := litrpc.NewAutopilotClient(c)
|
||||
return litConn.ListAutopilotFeatures(
|
||||
ctx, &litrpc.ListAutopilotFeaturesRequest{},
|
||||
)
|
||||
}
|
||||
litMacaroonFn = func(cfg *LitNodeConfig) string {
|
||||
return cfg.LitMacPath
|
||||
}
|
||||
|
|
@ -232,6 +240,13 @@ var (
|
|||
successPattern: "\"accounts\":[]",
|
||||
allowedThroughLNC: false,
|
||||
grpcWebURI: "/litrpc.Accounts/ListAccounts",
|
||||
}, {
|
||||
name: "litrpc-autopilot",
|
||||
macaroonFn: litMacaroonFn,
|
||||
requestFn: litAutopilotRequestFn,
|
||||
successPattern: "\"features\":{",
|
||||
allowedThroughLNC: true,
|
||||
grpcWebURI: "/litrpc.Autopilot/ListAutopilotFeatures",
|
||||
}}
|
||||
|
||||
// customURIs is a map of endpoint URIs that we want to allow via a
|
||||
|
|
|
|||
|
|
@ -62,10 +62,26 @@ var (
|
|||
Entity: "account",
|
||||
Action: "write",
|
||||
}},
|
||||
"/litrpc.Lit/ListActions": {{
|
||||
"/litrpc.Firewall/ListActions": {{
|
||||
Entity: "actions",
|
||||
Action: "read",
|
||||
}},
|
||||
"/litrpc.Autopilot/ListAutopilotFeatures": {{
|
||||
Entity: "autopilot",
|
||||
Action: "read",
|
||||
}},
|
||||
"/litrpc.Autopilot/AddAutopilotSession": {{
|
||||
Entity: "autopilot",
|
||||
Action: "write",
|
||||
}},
|
||||
"/litrpc.Autopilot/ListAutopilotSessions": {{
|
||||
Entity: "autopilot",
|
||||
Action: "read",
|
||||
}},
|
||||
"/litrpc.Autopilot/RevokeAutopilotSession": {{
|
||||
Entity: "autopilot",
|
||||
Action: "write",
|
||||
}},
|
||||
}
|
||||
|
||||
// whiteListedLNDMethods is a map of all lnd RPC methods that don't
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ import (
|
|||
type Type uint8
|
||||
|
||||
const (
|
||||
TypeMacaroonReadonly Type = 0
|
||||
TypeMacaroonAdmin Type = 1
|
||||
TypeMacaroonCustom Type = 2
|
||||
TypeUIPassword Type = 3
|
||||
typeReservedForFutureUse Type = 4
|
||||
TypeMacaroonAccount Type = 5
|
||||
TypeMacaroonReadonly Type = 0
|
||||
TypeMacaroonAdmin Type = 1
|
||||
TypeMacaroonCustom Type = 2
|
||||
TypeUIPassword Type = 3
|
||||
TypeAutopilot Type = 4
|
||||
TypeMacaroonAccount Type = 5
|
||||
)
|
||||
|
||||
// State represents the state of a session.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package terminal
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -10,9 +12,12 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightninglabs/lightning-node-connect/mailbox"
|
||||
"github.com/lightninglabs/lightning-terminal/accounts"
|
||||
"github.com/lightninglabs/lightning-terminal/autopilotserver"
|
||||
"github.com/lightninglabs/lightning-terminal/firewall"
|
||||
"github.com/lightninglabs/lightning-terminal/firewalldb"
|
||||
"github.com/lightninglabs/lightning-terminal/litrpc"
|
||||
"github.com/lightninglabs/lightning-terminal/perms"
|
||||
"github.com/lightninglabs/lightning-terminal/rules"
|
||||
"github.com/lightninglabs/lightning-terminal/session"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -33,6 +38,7 @@ const readOnlyAction = "***readonly***"
|
|||
type sessionRpcServer struct {
|
||||
litrpc.UnimplementedSessionsServer
|
||||
litrpc.UnimplementedFirewallServer
|
||||
litrpc.UnimplementedAutopilotServer
|
||||
|
||||
cfg *sessionRpcServerConfig
|
||||
db *session.DB
|
||||
|
|
@ -54,6 +60,9 @@ type sessionRpcServerConfig struct {
|
|||
firstConnectionDeadline time.Duration
|
||||
permMgr *perms.Manager
|
||||
actionsDB *firewalldb.DB
|
||||
autopilot autopilotserver.Autopilot
|
||||
ruleMgrs rules.ManagerSet
|
||||
privMap firewalldb.NewPrivacyMapDB
|
||||
}
|
||||
|
||||
// newSessionRPCServer creates a new sessionRpcServer using the passed config.
|
||||
|
|
@ -96,10 +105,68 @@ func (s *sessionRpcServer) start() error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("error listing sessions: %v", err)
|
||||
}
|
||||
|
||||
for _, sess := range sessions {
|
||||
key := sess.LocalPublicKey.SerializeCompressed()
|
||||
|
||||
if sess.Type == session.TypeAutopilot {
|
||||
// We only start the autopilot sessions if the autopilot
|
||||
// client has been enabled.
|
||||
if s.cfg.autopilot == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do a sanity check to ensure that we have the static
|
||||
// remote pub key stored for this session. This should
|
||||
// never not be the case.
|
||||
if sess.RemotePublicKey == nil {
|
||||
log.Errorf("no static remote key found for "+
|
||||
"autopilot session %x", key)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if sess.State != session.StateInUse &&
|
||||
sess.State != session.StateCreated {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if sess.Expiry.Before(time.Now()) {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctxc, cancel := context.WithTimeout(
|
||||
ctx, defaultConnectTimeout,
|
||||
)
|
||||
|
||||
// Register the session with the autopilot client.
|
||||
perm, err := s.cfg.autopilot.ActivateSession(
|
||||
ctxc, sess.LocalPublicKey,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Errorf("error activating autopilot "+
|
||||
"session (%x) with the client", key,
|
||||
err)
|
||||
|
||||
if perm {
|
||||
err := s.db.RevokeSession(
|
||||
sess.LocalPublicKey,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("error revoking "+
|
||||
"session: %v", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.resumeSession(sess); err != nil {
|
||||
log.Errorf("error resuming session (%x): %v",
|
||||
sess.LocalPublicKey.SerializeCompressed(), err)
|
||||
log.Errorf("error resuming session (%x): %v", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,8 +299,9 @@ func (s *sessionRpcServer) AddSession(_ context.Context,
|
|||
// No other types are currently supported.
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid session type, only admin, " +
|
||||
"readonly, account and custom macaroon types " +
|
||||
"supported in LiT")
|
||||
"readonly, custom and account macaroon types supported in " +
|
||||
"LiT. Autopilot sessions must be added using " +
|
||||
"AddAutoPilotSession method")
|
||||
}
|
||||
|
||||
// Collect the de-duped permissions.
|
||||
|
|
@ -263,7 +331,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context,
|
|||
return nil, fmt.Errorf("error starting session: %v", err)
|
||||
}
|
||||
|
||||
rpcSession, err := marshalRPCSession(sess)
|
||||
rpcSession, err := s.marshalRPCSession(sess)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling session: %v", err)
|
||||
}
|
||||
|
|
@ -325,11 +393,14 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error {
|
|||
|
||||
// For custom session types, we use the caveats and permissions that
|
||||
// were persisted on session creation.
|
||||
case session.TypeMacaroonCustom:
|
||||
if sess.MacaroonRecipe != nil {
|
||||
permissions = sess.MacaroonRecipe.Permissions
|
||||
case session.TypeMacaroonCustom, session.TypeAutopilot:
|
||||
if sess.MacaroonRecipe == nil {
|
||||
break
|
||||
}
|
||||
|
||||
permissions = sess.MacaroonRecipe.Permissions
|
||||
caveats = append(caveats, sess.MacaroonRecipe.Caveats...)
|
||||
|
||||
// No other types are currently supported.
|
||||
default:
|
||||
log.Debugf("Not resuming session %x with type %d", pubKeyBytes,
|
||||
|
|
@ -436,16 +507,24 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error {
|
|||
pubKeyBytes)
|
||||
}
|
||||
|
||||
if s.cfg.autopilot != nil {
|
||||
ctx := context.Background()
|
||||
ctxc, cancel := context.WithTimeout(
|
||||
ctx, defaultConnectTimeout,
|
||||
)
|
||||
|
||||
s.cfg.autopilot.SessionRevoked(ctxc, pubKey)
|
||||
cancel()
|
||||
}
|
||||
|
||||
err = s.sessionServer.StopSession(pubKey)
|
||||
if err != nil {
|
||||
log.Debugf("Error stopping session: "+
|
||||
"%v", err)
|
||||
log.Debugf("Error stopping session: %v", err)
|
||||
}
|
||||
|
||||
err = s.db.RevokeSession(pubKey)
|
||||
if err != nil {
|
||||
log.Debugf("error revoking session: "+
|
||||
"%v", err)
|
||||
log.Debugf("error revoking session: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -465,7 +544,7 @@ func (s *sessionRpcServer) ListSessions(_ context.Context,
|
|||
Sessions: make([]*litrpc.Session, len(sessions)),
|
||||
}
|
||||
for idx, sess := range sessions {
|
||||
response.Sessions[idx], err = marshalRPCSession(sess)
|
||||
response.Sessions[idx], err = s.marshalRPCSession(sess)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling session: %v",
|
||||
err)
|
||||
|
|
@ -477,7 +556,7 @@ func (s *sessionRpcServer) ListSessions(_ context.Context,
|
|||
|
||||
// RevokeSession revokes a single session and also stops it if it is currently
|
||||
// active.
|
||||
func (s *sessionRpcServer) RevokeSession(_ context.Context,
|
||||
func (s *sessionRpcServer) RevokeSession(ctx context.Context,
|
||||
req *litrpc.RevokeSessionRequest) (*litrpc.RevokeSessionResponse, error) {
|
||||
|
||||
pubKey, err := btcec.ParsePubKey(req.LocalPublicKey)
|
||||
|
|
@ -489,6 +568,10 @@ func (s *sessionRpcServer) RevokeSession(_ context.Context,
|
|||
return nil, fmt.Errorf("error revoking session: %v", err)
|
||||
}
|
||||
|
||||
if s.cfg.autopilot != nil {
|
||||
s.cfg.autopilot.SessionRevoked(ctx, pubKey)
|
||||
}
|
||||
|
||||
// If the session expired already it might not be running anymore. So we
|
||||
// only log possible errors here.
|
||||
if err := s.sessionServer.StopSession(pubKey); err != nil {
|
||||
|
|
@ -604,7 +687,6 @@ func (s *sessionRpcServer) ListActions(_ context.Context,
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resp := make([]*litrpc.Action, len(actions))
|
||||
for i, a := range actions {
|
||||
state, err := marshalActionState(a.State)
|
||||
|
|
@ -634,8 +716,438 @@ func (s *sessionRpcServer) ListActions(_ context.Context,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// ListAutopilotFeatures fetches all the features supported by the autopilot
|
||||
// server along with the rules that we need to support in order to subscribe
|
||||
// to those features.
|
||||
func (s *sessionRpcServer) ListAutopilotFeatures(ctx context.Context,
|
||||
_ *litrpc.ListAutopilotFeaturesRequest) (
|
||||
*litrpc.ListAutopilotFeaturesResponse, error) {
|
||||
|
||||
fs, err := s.cfg.autopilot.ListFeatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
features := make(map[string]*litrpc.Feature, len(fs))
|
||||
for i, f := range fs {
|
||||
rules, upgrade, err := convertRules(s.cfg.ruleMgrs, f.Rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
features[i] = &litrpc.Feature{
|
||||
Name: f.Name,
|
||||
Description: f.Description,
|
||||
Rules: rules,
|
||||
PermissionsList: marshalPerms(f.Permissions),
|
||||
RequiresUpgrade: upgrade,
|
||||
}
|
||||
}
|
||||
|
||||
return &litrpc.ListAutopilotFeaturesResponse{
|
||||
Features: features,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddAutopilotSession creates a new LNC session and attempts to register it
|
||||
// with the Autopilot server.
|
||||
func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context,
|
||||
req *litrpc.AddAutopilotSessionRequest) (
|
||||
*litrpc.AddAutopilotSessionResponse, error) {
|
||||
|
||||
if len(req.Features) == 0 {
|
||||
return nil, fmt.Errorf("must include at least one feature")
|
||||
}
|
||||
|
||||
expiry := time.Unix(int64(req.ExpiryTimestampSeconds), 0)
|
||||
if time.Now().After(expiry) {
|
||||
return nil, fmt.Errorf("expiry must be in the future")
|
||||
}
|
||||
|
||||
privacy := !req.NoPrivacyMapper
|
||||
privacyMapPairs := make(map[string]string)
|
||||
|
||||
// First need to fetch all the perms that need to be baked into this
|
||||
// mac based on the features.
|
||||
allFeatures, err := s.cfg.autopilot.ListFeatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create lookup map of all the features that autopilot server offers.
|
||||
autopilotFeatureMap := make(map[string]*autopilotserver.Feature)
|
||||
for _, f := range allFeatures {
|
||||
autopilotFeatureMap[f.Name] = f
|
||||
}
|
||||
|
||||
// allRules represents all the rules that our firewall knows about.
|
||||
allRules := s.cfg.ruleMgrs.GetAllRules()
|
||||
|
||||
// Check that each requested feature is a valid autopilot feature and
|
||||
// that the necessary rules for the feature have been specified.
|
||||
featureRules := make(map[string]map[string]string, len(req.Features))
|
||||
for f, rs := range req.Features {
|
||||
// Check that the features is known by the autopilot server.
|
||||
autopilotFeature, ok := autopilotFeatureMap[f]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not a features "+
|
||||
"provided by the Autopilot server", f)
|
||||
}
|
||||
|
||||
// reqRules is the rules specified in the request.
|
||||
var reqRules []rules.Values
|
||||
if rs.Rules != nil {
|
||||
reqRules = make([]rules.Values, 0, len(rs.Rules.Rules))
|
||||
for ruleName, rule := range rs.Rules.Rules {
|
||||
v, err := s.cfg.ruleMgrs.UnmarshalRuleValues(
|
||||
ruleName, rule,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if privacy {
|
||||
var privMapPairs map[string]string
|
||||
v, privMapPairs, err = v.RealToPseudo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range privMapPairs {
|
||||
privacyMapPairs[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
reqRules = append(reqRules, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a lookup map for the rules specified in this feature.
|
||||
// Also check that each of the rules in the request is one known
|
||||
// to us.
|
||||
frs := make(map[string]rules.Values)
|
||||
for _, r := range reqRules {
|
||||
frs[r.RuleName()] = r
|
||||
_, ok := allRules[r.RuleName()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not a known rule",
|
||||
r.RuleName())
|
||||
}
|
||||
}
|
||||
|
||||
// For each of the specified rules, we check that the values
|
||||
// set for those rules are sane given the bounds provided by
|
||||
// the autopilot server for the given feature.
|
||||
for _, r := range reqRules {
|
||||
ruleName := r.RuleName()
|
||||
|
||||
autopilotSpecs, ok := autopilotFeature.Rules[ruleName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("autopilot did not "+
|
||||
"specify %s as a rule for feature %s",
|
||||
ruleName, autopilotFeature.Name)
|
||||
}
|
||||
|
||||
min, err := s.cfg.ruleMgrs.InitRuleValues(
|
||||
ruleName, autopilotSpecs.MinVal,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
max, err := s.cfg.ruleMgrs.InitRuleValues(
|
||||
ruleName, autopilotSpecs.MaxVal,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = r.VerifySane(min, max); err != nil {
|
||||
return nil, fmt.Errorf("rule value for %s "+
|
||||
"not valid for feature %s. Expected "+
|
||||
"rule value between %s and %s. Got "+
|
||||
"%s. %v", ruleName,
|
||||
autopilotFeature.Name, min, max, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
// If the request did not contain specific values for a rule,
|
||||
// the default Autopilot rule value is used.
|
||||
finalRules := make(
|
||||
[]rules.Values, 0, len(autopilotFeature.Rules),
|
||||
)
|
||||
for name, values := range autopilotFeature.Rules {
|
||||
if r, ok := frs[name]; ok {
|
||||
finalRules = append(finalRules, r)
|
||||
continue
|
||||
}
|
||||
|
||||
defaults, err := s.cfg.ruleMgrs.InitRuleValues(
|
||||
name, values.Default,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalRules = append(finalRules, defaults)
|
||||
}
|
||||
|
||||
featureRules[f], err = marshalRulesToStringMap(finalRules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
interceptRules := &firewall.InterceptRules{
|
||||
FeatureRules: featureRules,
|
||||
}
|
||||
|
||||
// Gather all the permissions we need to add to the macaroon given the
|
||||
// feature list.
|
||||
var dedupedPerms = make(map[string]map[string]bool)
|
||||
for name, feature := range autopilotFeatureMap {
|
||||
if _, ok := req.Features[name]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ops := range feature.Permissions {
|
||||
for _, op := range ops {
|
||||
if dedupedPerms[op.Entity] == nil {
|
||||
dedupedPerms[op.Entity] = make(
|
||||
map[string]bool,
|
||||
)
|
||||
}
|
||||
|
||||
dedupedPerms[op.Entity][op.Action] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var perms []bakery.Op
|
||||
for entity, actions := range dedupedPerms {
|
||||
for action := range actions {
|
||||
perms = append(perms, bakery.Op{
|
||||
Entity: entity,
|
||||
Action: action,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rulesCaveatStr, err := firewall.RulesToCaveat(interceptRules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
featureConfig := make(map[string][]byte, len(req.Features))
|
||||
for name, f := range req.Features {
|
||||
featureConfig[name] = f.Config
|
||||
}
|
||||
|
||||
caveats := []macaroon.Caveat{{Id: []byte(rulesCaveatStr)}}
|
||||
if privacy {
|
||||
caveats = append(caveats, firewall.MetaPrivacyCaveat)
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(
|
||||
req.Label, session.TypeAutopilot, expiry, req.MailboxServerAddr,
|
||||
req.DevServer, perms, caveats, featureConfig, privacy,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating new session: %v", err)
|
||||
}
|
||||
|
||||
// Register all the privacy map pairs for this session ID.
|
||||
privDB := s.cfg.privMap(sess.ID)
|
||||
err = privDB.Update(func(tx firewalldb.PrivacyMapTx) error {
|
||||
for r, p := range privacyMapPairs {
|
||||
err := tx.NewPair(r, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt to register the session with the Autopilot server.
|
||||
remoteKey, err := s.cfg.autopilot.RegisterSession(
|
||||
ctx, sess.LocalPublicKey, sess.ServerAddr, sess.DevServer,
|
||||
featureConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error registering session with "+
|
||||
"autopilot server: %v", err)
|
||||
}
|
||||
|
||||
// We only persist this session if we successfully retrieved the
|
||||
// autopilot's static key.
|
||||
sess.RemotePublicKey = remoteKey
|
||||
if err := s.db.StoreSession(sess); err != nil {
|
||||
return nil, fmt.Errorf("error storing session: %v", err)
|
||||
}
|
||||
|
||||
if err := s.resumeSession(sess); err != nil {
|
||||
return nil, fmt.Errorf("error starting session: %v", err)
|
||||
}
|
||||
|
||||
rpcSession, err := s.marshalRPCSession(sess)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling session: %v", err)
|
||||
}
|
||||
|
||||
return &litrpc.AddAutopilotSessionResponse{
|
||||
Session: rpcSession,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListAutopilotSessions fetches and returns all the sessions from the DB that
|
||||
// are of type TypeAutopilot.
|
||||
func (s *sessionRpcServer) ListAutopilotSessions(_ context.Context,
|
||||
_ *litrpc.ListAutopilotSessionsRequest) (
|
||||
*litrpc.ListAutopilotSessionsResponse, error) {
|
||||
|
||||
sessions, err := s.db.ListSessions(func(s *session.Session) bool {
|
||||
return s.Type == session.TypeAutopilot
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching sessions: %v", err)
|
||||
}
|
||||
|
||||
response := &litrpc.ListAutopilotSessionsResponse{
|
||||
Sessions: make([]*litrpc.Session, len(sessions)),
|
||||
}
|
||||
for idx, sess := range sessions {
|
||||
response.Sessions[idx], err = s.marshalRPCSession(sess)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling session: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// RevokeAutopilotSession revokes an autopilot session.
|
||||
func (s *sessionRpcServer) RevokeAutopilotSession(ctx context.Context,
|
||||
req *litrpc.RevokeAutopilotSessionRequest) (
|
||||
*litrpc.RevokeAutopilotSessionResponse, error) {
|
||||
|
||||
pubKey, err := btcec.ParsePubKey(req.LocalPublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing public key: %v", err)
|
||||
}
|
||||
|
||||
sess, err := s.db.GetSession(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sess.Type != session.TypeAutopilot {
|
||||
return nil, session.ErrSessionNotFound
|
||||
}
|
||||
|
||||
_, err = s.RevokeSession(
|
||||
ctx, &litrpc.RevokeSessionRequest{
|
||||
LocalPublicKey: req.LocalPublicKey,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &litrpc.RevokeAutopilotSessionResponse{}, nil
|
||||
}
|
||||
|
||||
func marshalRulesToStringMap(rs []rules.Values) (map[string]string, error) {
|
||||
res := make(map[string]string, len(rs))
|
||||
for _, r := range rs {
|
||||
b, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res[r.RuleName()] = string(b)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// marshalPerms attempts to convert a set of permissions into their RPC
|
||||
// counterpart. If the list includes a rule that LiT does not know about, a nil
|
||||
// entry is included in the returned map. A bool is returned that indicates if
|
||||
// an upgrade is needed in order to enforce an unknown rule.
|
||||
func convertRules(ruleMgr rules.ManagerSet,
|
||||
ruleList map[string]*autopilotserver.RuleValues) (
|
||||
map[string]*litrpc.RuleValues, bool, error) {
|
||||
|
||||
var (
|
||||
upgrade bool
|
||||
res = make(
|
||||
map[string]*litrpc.RuleValues, len(ruleList),
|
||||
)
|
||||
knownRules = ruleMgr.GetAllRules()
|
||||
)
|
||||
for name, rule := range ruleList {
|
||||
known := true
|
||||
if !knownRules[name] {
|
||||
upgrade = true
|
||||
known = false
|
||||
}
|
||||
|
||||
defaultVals, err := ruleMgr.InitRuleValues(name, rule.Default)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
minVals, err := ruleMgr.InitRuleValues(name, rule.MinVal)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
maxVals, err := ruleMgr.InitRuleValues(name, rule.MaxVal)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
res[name] = &litrpc.RuleValues{
|
||||
Known: known,
|
||||
Defaults: defaultVals.ToProto(),
|
||||
MinValue: minVals.ToProto(),
|
||||
MaxValue: maxVals.ToProto(),
|
||||
}
|
||||
}
|
||||
|
||||
return res, upgrade, nil
|
||||
}
|
||||
|
||||
// marshalPerms converts a set of permissions into their RPC counterpart.
|
||||
func marshalPerms(perms map[string][]bakery.Op) []*litrpc.Permissions {
|
||||
var res []*litrpc.Permissions
|
||||
|
||||
for method, ops := range perms {
|
||||
rpcOps := make([]*litrpc.MacaroonPermission, len(ops))
|
||||
for i, op := range ops {
|
||||
rpcOps[i] = &litrpc.MacaroonPermission{
|
||||
Entity: op.Entity,
|
||||
Action: op.Action,
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, &litrpc.Permissions{
|
||||
Method: method,
|
||||
Operations: rpcOps,
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// marshalRPCSession converts a session into its RPC counterpart.
|
||||
func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) {
|
||||
func (s *sessionRpcServer) marshalRPCSession(sess *session.Session) (
|
||||
*litrpc.Session, error) {
|
||||
|
||||
rpcState, err := marshalRPCState(sess.State)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -663,6 +1175,40 @@ func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) {
|
|||
revokedAt = uint64(sess.RevokedAt.Unix())
|
||||
}
|
||||
|
||||
featureInfo := make(map[string]*litrpc.RulesMap)
|
||||
if sess.MacaroonRecipe != nil {
|
||||
for _, cav := range sess.MacaroonRecipe.Caveats {
|
||||
info, err := firewall.ParseRuleCaveat(string(cav.Id))
|
||||
if errors.Is(err, firewall.ErrNoRulesCaveat) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for feature, rules := range info.FeatureRules {
|
||||
ruleMap := make(map[string]*litrpc.RuleValue)
|
||||
for name, rule := range rules {
|
||||
val, err := s.cfg.ruleMgrs.InitRuleValues(name, []byte(rule))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sess.WithPrivacyMapper {
|
||||
db := s.cfg.privMap(sess.ID)
|
||||
val, err = val.PseudoToReal(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ruleMap[name] = val.ToProto()
|
||||
}
|
||||
|
||||
featureInfo[feature] = &litrpc.RulesMap{Rules: ruleMap}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &litrpc.Session{
|
||||
Id: sess.ID[:],
|
||||
Label: sess.Label,
|
||||
|
|
@ -678,6 +1224,7 @@ func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) {
|
|||
CreatedAt: uint64(sess.CreatedAt.Unix()),
|
||||
RevokedAt: revokedAt,
|
||||
MacaroonRecipe: macRecipe,
|
||||
AutopilotFeatureInfo: featureInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -747,6 +1294,9 @@ func marshalRPCType(typ session.Type) (litrpc.SessionType, error) {
|
|||
case session.TypeMacaroonAccount:
|
||||
return litrpc.SessionType_TYPE_MACAROON_ACCOUNT, nil
|
||||
|
||||
case session.TypeAutopilot:
|
||||
return litrpc.SessionType_TYPE_AUTOPILOT, nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown type <%d>", typ)
|
||||
}
|
||||
|
|
@ -770,6 +1320,9 @@ func unmarshalRPCType(typ litrpc.SessionType) (session.Type, error) {
|
|||
case litrpc.SessionType_TYPE_MACAROON_ACCOUNT:
|
||||
return session.TypeMacaroonAccount, nil
|
||||
|
||||
case litrpc.SessionType_TYPE_AUTOPILOT:
|
||||
return session.TypeAutopilot, nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown type <%d>", typ)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,6 +321,8 @@ func (g *LightningTerminal) Run() error {
|
|||
firstConnectionDeadline: g.cfg.FirstLNCConnDeadline,
|
||||
permMgr: g.permsMgr,
|
||||
actionsDB: g.firewallDB,
|
||||
autopilot: g.autopilotClient,
|
||||
ruleMgrs: g.ruleMgrs,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create new session rpc "+
|
||||
|
|
@ -818,6 +820,10 @@ func (g *LightningTerminal) registerSubDaemonGrpcServers(server *grpc.Server,
|
|||
}
|
||||
|
||||
litrpc.RegisterFirewallServer(server, g.sessionRpcServer)
|
||||
|
||||
if !g.cfg.Autopilot.Disable {
|
||||
litrpc.RegisterAutopilotServer(server, g.sessionRpcServer)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRestSubserver is a callback on the lnd.SubserverConfig struct that is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue