• 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

73.33
/macaroons/service.go
1
package macaroons
2

3
import (
4
        "context"
5
        "encoding/hex"
6
        "fmt"
7

8
        "google.golang.org/grpc/metadata"
9
        "gopkg.in/macaroon-bakery.v2/bakery"
10
        "gopkg.in/macaroon-bakery.v2/bakery/checkers"
11
        macaroon "gopkg.in/macaroon.v2"
12
)
13

14
var (
15
        // ErrMissingRootKeyID specifies the root key ID is missing.
16
        ErrMissingRootKeyID = fmt.Errorf("missing root key ID")
17

18
        // ErrDeletionForbidden is used when attempting to delete the
19
        // DefaultRootKeyID or the encryptedKeyID.
20
        ErrDeletionForbidden = fmt.Errorf("the specified ID cannot be deleted")
21

22
        // PermissionEntityCustomURI is a special entity name for a permission
23
        // that does not describe an entity:action pair but instead specifies a
24
        // specific URI that needs to be granted access to. This can be used for
25
        // more fine-grained permissions where a macaroon only grants access to
26
        // certain methods instead of a whole list of methods that define the
27
        // same entity:action pairs. For example: uri:/lnrpc.Lightning/GetInfo
28
        // only gives access to the GetInfo call.
29
        PermissionEntityCustomURI = "uri"
30

31
        // ErrUnknownVersion is returned when a macaroon is of an unknown
32
        // is presented.
33
        ErrUnknownVersion = fmt.Errorf("unknown macaroon version")
34

35
        // ErrInvalidID is returned when a macaroon ID is invalid.
36
        ErrInvalidID = fmt.Errorf("invalid ID")
37
)
38

39
// MacaroonValidator is an interface type that can check if macaroons are valid.
40
type MacaroonValidator interface {
41
        // ValidateMacaroon extracts the macaroon from the context's gRPC
42
        // metadata, checks its signature, makes sure all specified permissions
43
        // for the called method are contained within and finally ensures all
44
        // caveat conditions are met. A non-nil error is returned if any of the
45
        // checks fail.
46
        ValidateMacaroon(ctx context.Context,
47
                requiredPermissions []bakery.Op, fullMethod string) error
48
}
49

50
// ExtendedRootKeyStore is an interface augments the existing
51
// macaroons.RootKeyStorage interface by adding a number of additional utility
52
// methods such as encrypting and decrypting the root key given a password.
53
type ExtendedRootKeyStore interface {
54
        bakery.RootKeyStore
55

56
        // Close closes the RKS and zeros out any in-memory encryption keys.
57
        Close() error
58

59
        // CreateUnlock calls the underlying root key store's CreateUnlock and
60
        // returns the result.
61
        CreateUnlock(password *[]byte) error
62

63
        // ListMacaroonIDs returns all the root key ID values except the value
64
        // of encryptedKeyID.
65
        ListMacaroonIDs(ctxt context.Context) ([][]byte, error)
66

67
        // DeleteMacaroonID removes one specific root key ID. If the root key
68
        // ID is found and deleted, it will be returned.
69
        DeleteMacaroonID(ctxt context.Context, rootKeyID []byte) ([]byte, error)
70

71
        // ChangePassword calls the underlying root key store's ChangePassword
72
        // and returns the result.
73
        ChangePassword(oldPw, newPw []byte) error
74

75
        // GenerateNewRootKey calls the underlying root key store's
76
        // GenerateNewRootKey and returns the result.
77
        GenerateNewRootKey() error
78

79
        // SetRootKey calls the underlying root key store's SetRootKey and
80
        // returns the result.
81
        SetRootKey(rootKey []byte) error
82
}
83

84
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
85
// root key service encryption keys, as well as utility methods to validate a
86
// macaroon against the bakery and gRPC middleware for macaroon-based auth.
87
type Service struct {
88
        bakery.Bakery
89

90
        rks bakery.RootKeyStore
91

92
        // ExternalValidators is a map between an absolute gRPC URIs and the
93
        // corresponding external macaroon validator to be used for that URI.
94
        // If no external validator for an URI is specified, the service will
95
        // use the internal validator.
96
        ExternalValidators map[string]MacaroonValidator
97

98
        // StatelessInit denotes if the service was initialized in the stateless
99
        // mode where no macaroon files should be created on disk.
100
        StatelessInit bool
101
}
102

103
// NewService returns a service backed by the macaroon DB backend. The `checks`
104
// argument can be any of the `Checker` type functions defined in this package,
105
// or a custom checker if desired. This constructor prevents double-registration
106
// of checkers to prevent panics, so listing the same checker more than once is
107
// not harmful. Default checkers, such as those for `allow`, `time-before`,
108
// `declared`, and `error` caveats are registered automatically and don't need
109
// to be added.
110
func NewService(keyStore bakery.RootKeyStore, location string,
111
        statelessInit bool, checks ...Checker) (*Service, error) {
9✔
112

9✔
113
        macaroonParams := bakery.BakeryParams{
9✔
114
                Location:     location,
9✔
115
                RootKeyStore: keyStore,
9✔
116
                // No third-party caveat support for now.
9✔
117
                // TODO(aakselrod): Add third-party caveat support.
9✔
118
                Locator: nil,
9✔
119
                Key:     nil,
9✔
120
        }
9✔
121

9✔
122
        svc := bakery.New(macaroonParams)
9✔
123

9✔
124
        // Register all custom caveat checkers with the bakery's checker.
9✔
125
        // TODO(aakselrod): Add more checks as required.
9✔
126
        checker := svc.Checker.FirstPartyCaveatChecker.(*checkers.Checker)
9✔
127
        for _, check := range checks {
14✔
128
                cond, fun := check()
5✔
129
                if !isRegistered(checker, cond) {
10✔
130
                        checker.Register(cond, "std", fun)
5✔
131
                }
5✔
132
        }
133

134
        return &Service{
9✔
135
                Bakery:             *svc,
9✔
136
                rks:                keyStore,
9✔
137
                ExternalValidators: make(map[string]MacaroonValidator),
9✔
138
                StatelessInit:      statelessInit,
9✔
139
        }, nil
9✔
140
}
141

142
// isRegistered checks to see if the required checker has already been
143
// registered in order to avoid a panic caused by double registration.
144
func isRegistered(c *checkers.Checker, name string) bool {
5✔
145
        if c == nil {
5✔
146
                return false
×
147
        }
×
148

149
        for _, info := range c.Info() {
20✔
150
                if info.Name == name &&
15✔
151
                        info.Prefix == "" &&
15✔
152
                        info.Namespace == "std" {
15✔
153
                        return true
×
154
                }
×
155
        }
156

157
        return false
5✔
158
}
159

160
// RegisterExternalValidator registers a custom, external macaroon validator for
161
// the specified absolute gRPC URI. That validator is then fully responsible to
162
// make sure any macaroon passed for a request to that URI is valid and
163
// satisfies all conditions.
164
func (svc *Service) RegisterExternalValidator(fullMethod string,
165
        validator MacaroonValidator) error {
×
166

×
167
        if validator == nil {
×
168
                return fmt.Errorf("validator cannot be nil")
×
169
        }
×
170

171
        _, ok := svc.ExternalValidators[fullMethod]
×
172
        if ok {
×
173
                return fmt.Errorf("external validator for method %s already "+
×
174
                        "registered", fullMethod)
×
175
        }
×
176

177
        svc.ExternalValidators[fullMethod] = validator
×
178
        return nil
×
179
}
180

181
// ValidateMacaroon validates the capabilities of a given request given a
182
// bakery service, context, and uri. Within the passed context.Context, we
183
// expect a macaroon to be encoded as request metadata using the key
184
// "macaroon".
185
func (svc *Service) ValidateMacaroon(ctx context.Context,
186
        requiredPermissions []bakery.Op, fullMethod string) error {
3✔
187

3✔
188
        // Get macaroon bytes from context and unmarshal into macaroon.
3✔
189
        macHex, err := RawMacaroonFromContext(ctx)
3✔
190
        if err != nil {
3✔
UNCOV
191
                return err
×
UNCOV
192
        }
×
193

194
        // With the macaroon obtained, we'll now decode the hex-string encoding.
195
        macBytes, err := hex.DecodeString(macHex)
3✔
196
        if err != nil {
3✔
197
                return err
×
198
        }
×
199

200
        return svc.CheckMacAuth(
3✔
201
                ctx, macBytes, requiredPermissions, fullMethod,
3✔
202
        )
3✔
203
}
204

205
// CheckMacAuth checks that the macaroon is not disobeying any caveats and is
206
// authorized to perform the operation the user wants to perform.
207
func (svc *Service) CheckMacAuth(ctx context.Context, macBytes []byte,
208
        requiredPermissions []bakery.Op, fullMethod string) error {
5✔
209

5✔
210
        // With the macaroon obtained, we'll now unmarshal it from binary into
5✔
211
        // its concrete struct representation.
5✔
212
        mac := &macaroon.Macaroon{}
5✔
213
        err := mac.UnmarshalBinary(macBytes)
5✔
214
        if err != nil {
5✔
215
                return err
×
216
        }
×
217

218
        // Ensure that the macaroon is using the exact same version as we
219
        // expect. In the future, we can relax this check to phase in new
220
        // versions.
221
        if mac.Version() != macaroon.V2 {
6✔
222
                return fmt.Errorf("%w: %v", ErrUnknownVersion,
1✔
223
                        mac.Version())
1✔
224
        }
1✔
225

226
        // Run a similar version check on the ID used for the macaroon as well.
227
        const minIDLength = 1
4✔
228
        if len(mac.Id()) < minIDLength {
5✔
229
                return ErrInvalidID
1✔
230
        }
1✔
231
        if mac.Id()[0] != byte(bakery.Version3) {
3✔
UNCOV
232
                return ErrInvalidID
×
UNCOV
233
        }
×
234

235
        // Check the method being called against the permitted operation, the
236
        // expiration time and IP address and return the result.
237
        authChecker := svc.Checker.Auth(macaroon.Slice{mac})
3✔
238
        _, err = authChecker.Allow(ctx, requiredPermissions...)
3✔
239

3✔
240
        // If the macaroon contains broad permissions and checks out, we're
3✔
241
        // done.
3✔
242
        if err == nil {
5✔
243
                return nil
2✔
244
        }
2✔
245

246
        // To also allow the special permission of "uri:<FullMethod>" to be a
247
        // valid permission, we need to check it manually in case there is no
248
        // broader scope permission defined.
249
        _, err = authChecker.Allow(ctx, bakery.Op{
1✔
250
                Entity: PermissionEntityCustomURI,
1✔
251
                Action: fullMethod,
1✔
252
        })
1✔
253
        return err
1✔
254
}
255

256
// Close closes the database that underlies the RootKeyStore and zeroes the
257
// encryption keys.
258
func (svc *Service) Close() error {
8✔
259
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
16✔
260
                return boltRKS.Close()
8✔
261
        }
8✔
262

263
        return nil
×
264
}
265

266
// CreateUnlock calls the underlying root key store's CreateUnlock and returns
267
// the result.
268
func (svc *Service) CreateUnlock(password *[]byte) error {
6✔
269
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
12✔
270
                return boltRKS.CreateUnlock(password)
6✔
271
        }
6✔
272

273
        return nil
×
274
}
275

276
// NewMacaroon wraps around the function Oven.NewMacaroon with the defaults,
277
//   - version is always bakery.LatestVersion;
278
//   - caveats is always nil.
279
//
280
// In addition, it takes a rootKeyID parameter, and puts it into the context.
281
// The context is passed through Oven.NewMacaroon(), in which calls the function
282
// RootKey(), that reads the context for rootKeyID.
283
func (svc *Service) NewMacaroon(
284
        ctx context.Context, rootKeyID []byte,
285
        ops ...bakery.Op) (*bakery.Macaroon, error) {
10✔
286

10✔
287
        // Check rootKeyID is not called with nil or empty bytes. We want the
10✔
288
        // caller to be aware the value of root key ID used, so we won't replace
10✔
289
        // it with the DefaultRootKeyID if not specified.
10✔
290
        if len(rootKeyID) == 0 {
11✔
291
                return nil, ErrMissingRootKeyID
1✔
292
        }
1✔
293

294
        // Pass the root key ID to context.
295
        ctx = ContextWithRootKeyID(ctx, rootKeyID)
9✔
296

9✔
297
        return svc.Oven.NewMacaroon(ctx, bakery.LatestVersion, nil, ops...)
9✔
298
}
299

300
// ListMacaroonIDs returns all the root key ID values except the value of
301
// encryptedKeyID.
302
func (svc *Service) ListMacaroonIDs(ctxt context.Context) ([][]byte, error) {
2✔
303
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
4✔
304
                return boltRKS.ListMacaroonIDs(ctxt)
2✔
305
        }
2✔
306

307
        return nil, nil
×
308
}
309

310
// DeleteMacaroonID removes one specific root key ID. If the root key ID is
311
// found and deleted, it will be returned.
312
func (svc *Service) DeleteMacaroonID(ctxt context.Context,
313
        rootKeyID []byte) ([]byte, error) {
5✔
314

5✔
315
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
10✔
316
                return boltRKS.DeleteMacaroonID(ctxt, rootKeyID)
5✔
317
        }
5✔
318

319
        return nil, nil
×
320
}
321

322
// GenerateNewRootKey calls the underlying root key store's GenerateNewRootKey
323
// and returns the result.
324
func (svc *Service) GenerateNewRootKey() error {
2✔
325
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
4✔
326
                return boltRKS.GenerateNewRootKey()
2✔
327
        }
2✔
328

329
        return nil
×
330
}
331

332
// SetRootKey calls the underlying root key store's SetRootKey and returns the
333
// result.
334
func (svc *Service) SetRootKey(rootKey []byte) error {
×
335
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
×
336
                return boltRKS.SetRootKey(rootKey)
×
337
        }
×
338

339
        return nil
×
340
}
341

342
// ChangePassword calls the underlying root key store's ChangePassword and
343
// returns the result.
344
func (svc *Service) ChangePassword(oldPw, newPw []byte) error {
2✔
345
        if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
4✔
346
                return boltRKS.ChangePassword(oldPw, newPw)
2✔
347
        }
2✔
348

349
        return nil
×
350
}
351

352
// RawMacaroonFromContext is a helper function that extracts a raw macaroon
353
// from the given incoming gRPC request context.
354
func RawMacaroonFromContext(ctx context.Context) (string, error) {
3✔
355
        // Get macaroon bytes from context and unmarshal into macaroon.
3✔
356
        md, ok := metadata.FromIncomingContext(ctx)
3✔
357
        if !ok {
3✔
358
                return "", fmt.Errorf("unable to get metadata from context")
×
359
        }
×
360
        if len(md["macaroon"]) != 1 {
3✔
UNCOV
361
                return "", fmt.Errorf("expected 1 macaroon, got %d",
×
UNCOV
362
                        len(md["macaroon"]))
×
UNCOV
363
        }
×
364

365
        return md["macaroon"][0], nil
3✔
366
}
367

368
// SafeCopyMacaroon creates a copy of a macaroon that is safe to be used and
369
// modified. This is necessary because the macaroon library's own Clone() method
370
// is unsafe for certain edge cases, resulting in both the cloned and the
371
// original macaroons to be modified.
372
func SafeCopyMacaroon(mac *macaroon.Macaroon) (*macaroon.Macaroon, error) {
2✔
373
        macBytes, err := mac.MarshalBinary()
2✔
374
        if err != nil {
2✔
375
                return nil, err
×
376
        }
×
377

378
        newMac := &macaroon.Macaroon{}
2✔
379
        if err := newMac.UnmarshalBinary(macBytes); err != nil {
2✔
380
                return nil, err
×
381
        }
×
382

383
        return newMac, nil
2✔
384
}
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