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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 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