• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.81
/macaroons/constraints.go
1
package macaroons
2

3
import (
4
        "bytes"
5
        "context"
6
        "fmt"
7
        "net"
8
        "strings"
9
        "time"
10

11
        "google.golang.org/grpc/peer"
12
        "gopkg.in/macaroon-bakery.v2/bakery/checkers"
13
        macaroon "gopkg.in/macaroon.v2"
14
)
15

16
const (
17
        // CondLndCustom is the first party caveat condition name that is used
18
        // for all custom caveats in lnd. Every custom caveat entry will be
19
        // encoded as the string
20
        // "lnd-custom <custom-caveat-name> <custom-caveat-condition>"
21
        // in the serialized macaroon. We choose a single space as the delimiter
22
        // between the because that is also used by the macaroon bakery library.
23
        CondLndCustom = "lnd-custom"
24
)
25

26
// CustomCaveatAcceptor is an interface that contains a single method for
27
// checking whether a macaroon with the given custom caveat name should be
28
// accepted or not.
29
type CustomCaveatAcceptor interface {
30
        // CustomCaveatSupported returns nil if a macaroon with the given custom
31
        // caveat name can be validated by any component in lnd (for example an
32
        // RPC middleware). If no component is registered to handle the given
33
        // custom caveat then an error must be returned. This method only checks
34
        // the availability of a validating component, not the validity of the
35
        // macaroon itself.
36
        CustomCaveatSupported(customCaveatName string) error
37
}
38

39
// Constraint type adds a layer of indirection over macaroon caveats.
40
type Constraint func(*macaroon.Macaroon) error
41

42
// Checker type adds a layer of indirection over macaroon checkers. A Checker
43
// returns the name of the checker and the checker function; these are used to
44
// register the function with the bakery service's compound checker.
45
type Checker func() (string, checkers.Func)
46

47
// AddConstraints returns new derived macaroon by applying every passed
48
// constraint and tightening its restrictions.
49
func AddConstraints(mac *macaroon.Macaroon,
50
        cs ...Constraint) (*macaroon.Macaroon, error) {
1✔
51

1✔
52
        // The macaroon library's Clone() method has a subtle bug that doesn't
1✔
53
        // correctly clone all caveats. We need to use our own, safe clone
1✔
54
        // function instead.
1✔
55
        newMac, err := SafeCopyMacaroon(mac)
1✔
56
        if err != nil {
1✔
57
                return nil, err
×
58
        }
×
59

60
        for _, constraint := range cs {
2✔
61
                if err := constraint(newMac); err != nil {
1✔
62
                        return nil, err
×
63
                }
×
64
        }
65
        return newMac, nil
1✔
66
}
67

68
// Each *Constraint function is a functional option, which takes a pointer
69
// to the macaroon and adds another restriction to it. For each *Constraint,
70
// the corresponding *Checker is provided if not provided by default.
71

72
// TimeoutConstraint restricts the lifetime of the macaroon
73
// to the amount of seconds given.
74
func TimeoutConstraint(seconds int64) func(*macaroon.Macaroon) error {
3✔
75
        return func(mac *macaroon.Macaroon) error {
6✔
76
                macaroonTimeout := time.Duration(seconds)
3✔
77
                requestTimeout := time.Now().Add(time.Second * macaroonTimeout)
3✔
78
                caveat := checkers.TimeBeforeCaveat(requestTimeout)
3✔
79
                return mac.AddFirstPartyCaveat([]byte(caveat.Condition))
3✔
80
        }
3✔
81
}
82

83
// IPLockConstraint locks macaroon to a specific IP address.
84
// If address is an empty string, this constraint does nothing to
85
// accommodate default value's desired behavior.
86
func IPLockConstraint(ipAddr string) func(*macaroon.Macaroon) error {
2✔
87
        return func(mac *macaroon.Macaroon) error {
4✔
88
                if ipAddr != "" {
4✔
89
                        macaroonIPAddr := net.ParseIP(ipAddr)
2✔
90
                        if macaroonIPAddr == nil {
3✔
91
                                return fmt.Errorf("incorrect macaroon IP-" +
1✔
92
                                        "lock address")
1✔
93
                        }
1✔
94
                        caveat := checkers.Condition("ipaddr",
1✔
95
                                macaroonIPAddr.String())
1✔
96
                        return mac.AddFirstPartyCaveat([]byte(caveat))
1✔
97
                }
98
                return nil
×
99
        }
100
}
101

102
// IPLockChecker accepts client IP from the validation context and compares it
103
// with IP locked in the macaroon. It is of the `Checker` type.
104
func IPLockChecker() (string, checkers.Func) {
5✔
105
        return "ipaddr", func(ctx context.Context, cond, arg string) error {
5✔
UNCOV
106
                // Get peer info and extract IP address from it for macaroon
×
UNCOV
107
                // check.
×
UNCOV
108
                pr, ok := peer.FromContext(ctx)
×
UNCOV
109
                if !ok {
×
110
                        return fmt.Errorf("unable to get peer info from context")
×
111
                }
×
UNCOV
112
                peerAddr, _, err := net.SplitHostPort(pr.Addr.String())
×
UNCOV
113
                if err != nil {
×
114
                        return fmt.Errorf("unable to parse peer address")
×
115
                }
×
116

UNCOV
117
                if !net.ParseIP(arg).Equal(net.ParseIP(peerAddr)) {
×
UNCOV
118
                        msg := "macaroon locked to different IP address"
×
UNCOV
119
                        return fmt.Errorf(msg)
×
UNCOV
120
                }
×
UNCOV
121
                return nil
×
122
        }
123
}
124

125
// CustomConstraint returns a function that adds a custom caveat condition to
126
// a macaroon.
127
func CustomConstraint(name, condition string) func(*macaroon.Macaroon) error {
2✔
128
        return func(mac *macaroon.Macaroon) error {
4✔
129
                // We rely on a name being set for the interception, so don't
2✔
130
                // allow creating a caveat without a name in the first place.
2✔
131
                if name == "" {
2✔
132
                        return fmt.Errorf("name cannot be empty")
×
133
                }
×
134

135
                // The inner (custom) condition is optional.
136
                outerCondition := fmt.Sprintf("%s %s", name, condition)
2✔
137
                if condition == "" {
3✔
138
                        outerCondition = name
1✔
139
                }
1✔
140

141
                caveat := checkers.Condition(CondLndCustom, outerCondition)
2✔
142
                return mac.AddFirstPartyCaveat([]byte(caveat))
2✔
143
        }
144
}
145

146
// CustomChecker returns a Checker function that is used by the macaroon bakery
147
// library to check whether a custom caveat is supported by lnd in general or
148
// not. Support in this context means: An additional gRPC interceptor was set up
149
// that validates the content (=condition) of the custom caveat. If such an
150
// interceptor is in place then the acceptor should return a nil error. If no
151
// interceptor exists for the custom caveat in the macaroon of a request context
152
// then a non-nil error should be returned and the macaroon is rejected as a
153
// whole.
UNCOV
154
func CustomChecker(acceptor CustomCaveatAcceptor) Checker {
×
UNCOV
155
        // We return the general name of all lnd custom macaroons and a function
×
UNCOV
156
        // that splits the outer condition to extract the name of the custom
×
UNCOV
157
        // condition and the condition itself. In the bakery library that's used
×
UNCOV
158
        // here, a caveat always has the following form:
×
UNCOV
159
        //
×
UNCOV
160
        // <condition-name> <condition-value>
×
UNCOV
161
        //
×
UNCOV
162
        // Because a checker function needs to be bound to the condition name we
×
UNCOV
163
        // have to choose a static name for the first part ("lnd-custom", see
×
UNCOV
164
        // CondLndCustom. Otherwise we'd need to register a new Checker function
×
UNCOV
165
        // for each custom caveat that's registered. To allow for a generic
×
UNCOV
166
        // custom caveat handling, we just add another layer and expand the
×
UNCOV
167
        // initial <condition-value> into
×
UNCOV
168
        //
×
UNCOV
169
        // "<custom-condition-name> <custom-condition-value>"
×
UNCOV
170
        //
×
UNCOV
171
        // The full caveat string entry of a macaroon that uses this generic
×
UNCOV
172
        // mechanism would therefore look like this:
×
UNCOV
173
        //
×
UNCOV
174
        // "lnd-custom <custom-condition-name> <custom-condition-value>"
×
UNCOV
175
        checker := func(_ context.Context, _, outerCondition string) error {
×
UNCOV
176
                if outerCondition != strings.TrimSpace(outerCondition) {
×
177
                        return fmt.Errorf("unexpected white space found in " +
×
178
                                "caveat condition")
×
179
                }
×
UNCOV
180
                if outerCondition == "" {
×
181
                        return fmt.Errorf("expected custom caveat, got empty " +
×
182
                                "string")
×
183
                }
×
184

185
                // The condition part of the original caveat is now name and
186
                // condition of the custom caveat (we add a layer of conditions
187
                // to allow one custom checker to work for all custom lnd
188
                // conditions that implement arbitrary business logic).
UNCOV
189
                parts := strings.Split(outerCondition, " ")
×
UNCOV
190
                customCaveatName := parts[0]
×
UNCOV
191

×
UNCOV
192
                return acceptor.CustomCaveatSupported(customCaveatName)
×
193
        }
194

UNCOV
195
        return func() (string, checkers.Func) {
×
UNCOV
196
                return CondLndCustom, checker
×
UNCOV
197
        }
×
198
}
199

200
// HasCustomCaveat tests if the given macaroon has a custom caveat with the
201
// given custom caveat name.
202
func HasCustomCaveat(mac *macaroon.Macaroon, customCaveatName string) bool {
7✔
203
        if mac == nil {
8✔
204
                return false
1✔
205
        }
1✔
206

207
        caveatPrefix := []byte(fmt.Sprintf(
6✔
208
                "%s %s", CondLndCustom, customCaveatName,
6✔
209
        ))
6✔
210
        for _, caveat := range mac.Caveats() {
12✔
211
                if bytes.HasPrefix(caveat.Id, caveatPrefix) {
8✔
212
                        return true
2✔
213
                }
2✔
214
        }
215

216
        return false
4✔
217
}
218

219
// GetCustomCaveatCondition returns the custom caveat condition for the given
220
// custom caveat name from the given macaroon.
221
func GetCustomCaveatCondition(mac *macaroon.Macaroon,
222
        customCaveatName string) string {
2✔
223

2✔
224
        if mac == nil {
2✔
225
                return ""
×
226
        }
×
227

228
        caveatPrefix := []byte(fmt.Sprintf(
2✔
229
                "%s %s ", CondLndCustom, customCaveatName,
2✔
230
        ))
2✔
231
        for _, caveat := range mac.Caveats() {
4✔
232
                // The caveat id has a format of
2✔
233
                // "lnd-custom [custom-caveat-name] [custom-caveat-condition]"
2✔
234
                // and we only want the condition part. If we match the prefix
2✔
235
                // part we return the condition that comes after the prefix.
2✔
236
                if bytes.HasPrefix(caveat.Id, caveatPrefix) {
3✔
237
                        caveatSplit := strings.SplitN(
1✔
238
                                string(caveat.Id),
1✔
239
                                string(caveatPrefix),
1✔
240
                                2,
1✔
241
                        )
1✔
242
                        if len(caveatSplit) == 2 {
2✔
243
                                return caveatSplit[1]
1✔
244
                        }
1✔
245
                }
246
        }
247

248
        // We didn't find a condition for the given custom caveat name.
249
        return ""
1✔
250
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc