diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index 315e48e2..28a90eaf 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -110,7 +110,7 @@ func (p *PrivacyMapper) Intercept(ctx context.Context, return nil, fmt.Errorf("could not extract ID from macaroon") } - log.Tracef("PrivacyMapper: Intercepting %v", ri) + log.Infof("PrivacyMapper: Intercepting %v", ri) switch r := req.InterceptType.(type) { case *lnrpc.RPCMiddlewareRequest_StreamAuth: diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go index 1ffe38ca..18e5b094 100644 --- a/firewall/rule_enforcer.go +++ b/firewall/rule_enforcer.go @@ -106,7 +106,7 @@ func (r *RuleEnforcer) Intercept(ctx context.Context, return mid.RPCOk(req) } - log.Tracef("RuleEnforcer: Intercepting %v", ri) + log.Infof("RuleEnforcer: Intercepting %v", ri) if ri.MetaInfo == nil { return mid.RPCErrString(req, "missing MetaInfo") @@ -224,6 +224,7 @@ func (r *RuleEnforcer) handleRequest(ctx context.Context, return nil, fmt.Errorf("could not extract ID from macaroon") } + log.Infof("ELLE: here before collecting enforcers") rules, err := r.collectEnforcers(ri, sessionID) if err != nil { return nil, fmt.Errorf("error parsing rules: %v", err) @@ -332,6 +333,7 @@ func (r *RuleEnforcer) collectEnforcers(ri *RequestInfo, ) for rule, value := range ri.Rules.FeatureRules[ri.MetaInfo.Feature] { + log.Infof("ELLE: got rule: %s", rule) r, err := r.initRule( ri.RequestID, rule, []byte(value), ri.MetaInfo.Feature, sessionID, false, ri.WithPrivacy, diff --git a/firewalldb/kvstores.go b/firewalldb/kvstores.go index 76e4b9d8..86213bd4 100644 --- a/firewalldb/kvstores.go +++ b/firewalldb/kvstores.go @@ -268,7 +268,7 @@ func (tx *kvStoreTx) GlobalTemp() KVStore { // // NOTE: this is part of the KVStoreTx interface. func (tx *kvStoreTx) LocalTemp() KVStore { - fn := getSessionRuleBucket(true, tx.ruleName, tx.groupID) + fn := getSessionRuleBucket(false, tx.ruleName, tx.groupID) if tx.featureName != "" { fn = getSessionFeatureRuleBucket( false, tx.ruleName, tx.groupID, tx.featureName, diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go index fab5ad2f..14f57f89 100644 --- a/itest/litd_firewall_test.go +++ b/itest/litd_firewall_test.go @@ -80,14 +80,13 @@ var ( }, } - sendToSelf = &litrpc.RuleValue_SendToSelf{ - SendToSelf: &litrpc.SendToSelf{}, - } - - offChainBudget = &litrpc.RuleValue_OffChainBudget{ - OffChainBudget: &litrpc.OffChainBudget{ - MaxAmtMsat: 1200000000, - MaxFeesMsat: 200000, + pubChannelsOnly = &litrpc.RuleValue_ChannelConstraint{ + ChannelConstraint: &litrpc.ChannelConstraint{ + MinCapacitySat: 100, + MaxCapacitySat: 50000, + MaxPushSat: 0, + PrivateAllowed: false, + PublicAllowed: true, }, } @@ -133,6 +132,45 @@ var ( }, MaxVal: &rules.HistoryLimit{}, } + + onChainBudgetRule = &mock.RuleRanges{ + Default: &rules.OnChainBudget{ + AbsoluteAmtSats: 1000000000, + MaxSatPerVByte: 10000, + }, + MinVal: &rules.OnChainBudget{ + AbsoluteAmtSats: 10000000, + MaxSatPerVByte: 1000, + }, + MaxVal: &rules.OnChainBudget{ + AbsoluteAmtSats: 2000000000, + MaxSatPerVByte: 20000, + }, + } + + chanConstraintsRule = &mock.RuleRanges{ + Default: &rules.ChannelConstraint{ + MinCapacitySat: 10, + MaxCapacitySat: 10000, + MaxPushSat: 0, + PrivateAllowed: true, + PublicAllowed: true, + }, + MinVal: &rules.ChannelConstraint{ + MinCapacitySat: 1, + MaxCapacitySat: 100, + MaxPushSat: 0, + PrivateAllowed: false, + PublicAllowed: false, + }, + MaxVal: &rules.ChannelConstraint{ + MinCapacitySat: 1000000, + MaxCapacitySat: 10000000000, + MaxPushSat: 10, + PrivateAllowed: true, + PublicAllowed: true, + }, + } ) // assertStatusErr asserts that the given error contains the given status code. @@ -185,6 +223,10 @@ func testFirewallRules(ctx context.Context, net *NetworkHarness, testSessionLinking(net, t) }) + t.t.Run("temp", func(_ *testing.T) { + testPrivChansOnly(net, t) + }) + t.t.Run("privacy flags", func(_ *testing.T) { testPrivacyFlags(net, t) }) @@ -891,6 +933,210 @@ func testSessionLinking(net *NetworkHarness, t *harnessTest) { assertStatusErr(t.t, err, codes.ResourceExhausted) } +func testPrivChansOnly(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // We create a connection to the Alice node's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.LitTLSCertPath) + require.NoError(t.t, err) + defer rawConn.Close() + + // New node to open channel to. + charlie, err := net.NewNode(t.t, "Charlie", nil, false, true) + require.NoError(t.t, err) + defer shutdownAndAssert(net, t, charlie) + + macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + flags := session.PrivacyFlags{session.ClearPubkeys, session.ClearNetworkAddresses} + + // Now we will override the autopilots features set so that we can + // just focus on the fee bounds rule for now. + net.autopilotServer.SetFeatures(map[string]*mock.Feature{ + "OpenChannels": { + Description: "open channels while you sleep!", + Rules: map[string]*mock.RuleRanges{ + rules.OnChainBudgetName: onChainBudgetRule, + rules.ChanConstraintName: chanConstraintsRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/OpenChannel": {{ + Entity: "onchain", + Action: "write", + }}, + "/lnrpc.Lightning/OpenChannelSync": {{ + Entity: "onchain", + Action: "write", + }}, + "/lnrpc.Lightning/BatchOpenChannel": {{ + Entity: "onchain", + Action: "write", + }}, + "/lnrpc.Lightning/ConnectPeer": {{ + Entity: "peers", + Action: "write", + }}, + }, + PrivacyFlags: flags.Serialize(), + }, + }) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Add a new Autopilot session that subscribes to the "OpenChannels" + // feature and set the rule that disallows private channels and allows + // public ones. + + sessResp, err := litClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: map[string]*litrpc.FeatureConfig{ + "OpenChannels": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.ChanConstraintName: { + Value: pubChannelsOnly, + }, + }, + }, + }, + }, + PrivacyFlags: flags.Serialize(), + PrivacyFlagsSet: true, + }, + ) + require.NoError(t.t, err) + + // We now connect to the mailbox from the PoV of the autopilot server. + lndConn, metaDataInjector, cleanup := newAutopilotLndConn( + ctx, t.t, net, sessResp.Session, + ) + t.t.Cleanup(func() { require.NoError(t.t, cleanup()) }) + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the OpenChannels feature. + metaInfo := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "OpenChannels", + } + caveat, err := metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds := metaDataInjector.addCaveat(caveat) + + // Should be able to tell alice to connect to charlie using charlie's + // real pub key and address since the relevant flags were set. + _, err = lndConn.ConnectPeer( + ctx, &lnrpc.ConnectPeerRequest{ + Addr: &lnrpc.LightningAddress{ + Pubkey: charlie.PubKeyStr, + Host: charlie.Cfg.P2PAddr(), + }, + }, caveatCreds, + ) + require.NoError(t.t, err) + + // Check from Alice's PoV + + // Ok now try open a private channel to charlie. Should fail since + // we dont allow private channnels. + /* NOTE: this currently passes which is incorrect for 2 reasons: + / 1) it doesnt actually let the call through.... so we should not get + a nil error here. + 2) it shouldnt pass cause our rules say no priv chans. but this + doesnt get checked cause it is a stream call. + + _, err = lndConn.OpenChannel(ctx, &lnrpc.OpenChannelRequest{ + NodePubkey: charlie.PubKey[:], + LocalFundingAmount: 50000, + Private: true, + }, caveatCreds) + require.NoError(t.t, err) + */ + + /* + Also fails due to this not being supported by privacy mapper. + _, err = lndConn.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ + NodePubkey: charlie.PubKey[:], + LocalFundingAmount: 50000, + Private: true, + }, caveatCreds) + require.NoError(t.t, err) + */ + + // priv channels not allowed. + _, err = lndConn.BatchOpenChannel(ctx, &lnrpc.BatchOpenChannelRequest{ + Channels: []*lnrpc.BatchOpenChannel{ + { + NodePubkey: charlie.PubKey[:], + LocalFundingAmount: 50000, + Private: true, + }, + }, + }, caveatCreds) + require.ErrorContains(t.t, err, "private channels not allowed") + + // public channel but too big. + _, err = lndConn.BatchOpenChannel(ctx, &lnrpc.BatchOpenChannelRequest{ + Channels: []*lnrpc.BatchOpenChannel{ + { + NodePubkey: charlie.PubKey[:], + LocalFundingAmount: 60000, + }, + }, + }, caveatCreds) + require.ErrorContains(t.t, err, "invalid total capacity") + + // All good. + batchResp, err := lndConn.BatchOpenChannel(ctx, &lnrpc.BatchOpenChannelRequest{ + Channels: []*lnrpc.BatchOpenChannel{ + { + NodePubkey: charlie.PubKey[:], + LocalFundingAmount: 40000, + }, + }, + }, caveatCreds) + require.NoError(t.t, err) + require.Len(t.t, batchResp.PendingChannels, 1) + + net.Miner.MineBlocks(10) + + // Query Alice's privacy mapper to see what the real txid is. + cp := fmt.Sprintf("%s:%d", fmt.Sprintf("%x", batchResp.PendingChannels[0].Txid), batchResp.PendingChannels[0].OutputIndex) + aliceFW := litrpc.NewFirewallClient(rawConn) + privMapResp, err := aliceFW.PrivacyMapConversion( + ctxm, &litrpc.PrivacyMapConversionRequest{ + SessionId: sessResp.Session.Id, + Input: cp, + }, + ) + require.NoError(t.t, err) + + txid, outputIndex, err := firewalldb.DecodeChannelPoint(privMapResp.Output) + require.NoError(t.t, err) + + defer closeChannelAndAssert(t, net, net.Alice, &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: txid, + }, + OutputIndex: outputIndex, + }, false) +} + // testRateLimitAndPrivacyMapper tests that an Autopilot session is forced to // adhere to the rate limits applied to the features of a session. Along the // way, the privacy mapper is also tested. diff --git a/rules/channel_constraints.go b/rules/channel_constraints.go index e2300b57..db5c4a94 100644 --- a/rules/channel_constraints.go +++ b/rules/channel_constraints.go @@ -47,6 +47,8 @@ func (m *ChanConstraintMgr) NewEnforcer(_ Config, values Values) (Enforcer, "ChannelConstraint, got %T", values) } + log.Infof("ELLE: %+v", bounds) + return bounds, nil } @@ -226,6 +228,9 @@ type ChanOpenReq interface { // checkOpenRequest verifies that the given request is valid given the // channel constraints. func checkOpenRequest(e *ChannelConstraint, req ChanOpenReq) error { + + log.Infof("ELLE: req: %v. allowed? %v", req.GetPrivate(), e.PrivateAllowed) + if req.GetPrivate() && !e.PrivateAllowed { return fmt.Errorf("private channels not allowed") } diff --git a/rules/onchain_budget.go b/rules/onchain_budget.go index 69316634..7436f0d0 100644 --- a/rules/onchain_budget.go +++ b/rules/onchain_budget.go @@ -220,6 +220,7 @@ func (o *OnChainBudgetEnforcer) checkers() map[string]mid.RoundTripChecker { } amt := uint64(r.LocalFundingAmount + r.PushSat) + return o.handlePendingPayment(ctx, &onChainState{Amount: amt}) }