@@ -19,12 +19,36 @@ import (
19
19
"gopkg.in/macaroon.v2"
20
20
)
21
21
22
+ var (
23
+ macTimeoutFlag = cli.Uint64Flag {
24
+ Name : "timeout" ,
25
+ Usage : "the number of seconds the macaroon will be " +
26
+ "valid before it times out" ,
27
+ }
28
+ macIPAddressFlag = cli.StringFlag {
29
+ Name : "ip_address" ,
30
+ Usage : "the IP address the macaroon will be bound to" ,
31
+ }
32
+ macCustomCaveatNameFlag = cli.StringFlag {
33
+ Name : "custom_caveat_name" ,
34
+ Usage : "the name of the custom caveat to add" ,
35
+ }
36
+ macCustomCaveatConditionFlag = cli.StringFlag {
37
+ Name : "custom_caveat_condition" ,
38
+ Usage : "the condition of the custom caveat to add, can be " +
39
+ "empty if custom caveat doesn't need a value" ,
40
+ }
41
+ )
42
+
22
43
var bakeMacaroonCommand = cli.Command {
23
44
Name : "bakemacaroon" ,
24
45
Category : "Macaroons" ,
25
46
Usage : "Bakes a new macaroon with the provided list of permissions " +
26
47
"and restrictions." ,
27
- ArgsUsage : "[--save_to=] [--timeout=] [--ip_address=] [--allow_external_permissions] permissions..." ,
48
+ ArgsUsage : "[--save_to=] [--timeout=] [--ip_address=] " +
49
+ "[--custom_caveat_name= [--custom_caveat_condition=]] " +
50
+ "[--root_key_id=] [--allow_external_permissions] " +
51
+ "permissions..." ,
28
52
Description : `
29
53
Bake a new macaroon that grants the provided permissions and
30
54
optionally adds restrictions (timeout, IP address) to it.
@@ -57,32 +81,19 @@ var bakeMacaroonCommand = cli.Command{
57
81
Usage : "save the created macaroon to this file " +
58
82
"using the default binary format" ,
59
83
},
84
+ macTimeoutFlag ,
85
+ macIPAddressFlag ,
86
+ macCustomCaveatNameFlag ,
87
+ macCustomCaveatConditionFlag ,
60
88
cli.Uint64Flag {
61
- Name : "timeout" ,
62
- Usage : "the number of seconds the macaroon will be " +
63
- "valid before it times out" ,
64
- },
65
- cli.StringFlag {
66
- Name : "ip_address" ,
67
- Usage : "the IP address the macaroon will be bound to" ,
68
- },
69
- cli.StringFlag {
70
- Name : "custom_caveat_name" ,
71
- Usage : "the name of the custom caveat to add" ,
72
- },
73
- cli.StringFlag {
74
- Name : "custom_caveat_condition" ,
75
- Usage : "the condition of the custom caveat to add, " +
76
- "can be empty if custom caveat doesn't need " +
77
- "a value" ,
78
- },
79
- cli.Uint64Flag {
80
- Name : "root_key_id" ,
81
- Usage : "the numerical root key ID used to create the macaroon" ,
89
+ Name : "root_key_id" ,
90
+ Usage : "the numerical root key ID used to create the " +
91
+ "macaroon" ,
82
92
},
83
93
cli.BoolFlag {
84
- Name : "allow_external_permissions" ,
85
- Usage : "whether permissions lnd is not familiar with are allowed" ,
94
+ Name : "allow_external_permissions" ,
95
+ Usage : "whether permissions lnd is not familiar with " +
96
+ "are allowed" ,
86
97
},
87
98
},
88
99
Action : actionDecorator (bakeMacaroon ),
@@ -101,10 +112,6 @@ func bakeMacaroon(ctx *cli.Context) error {
101
112
102
113
var (
103
114
savePath string
104
- timeout int64
105
- ipAddress net.IP
106
- customCaveatName string
107
- customCaveatCond string
108
115
rootKeyID uint64
109
116
parsedPermissions []* lnrpc.MacaroonPermission
110
117
err error
@@ -114,47 +121,6 @@ func bakeMacaroon(ctx *cli.Context) error {
114
121
savePath = lncfg .CleanAndExpandPath (ctx .String ("save_to" ))
115
122
}
116
123
117
- if ctx .IsSet ("timeout" ) {
118
- timeout = ctx .Int64 ("timeout" )
119
- if timeout <= 0 {
120
- return fmt .Errorf ("timeout must be greater than 0" )
121
- }
122
- }
123
-
124
- if ctx .IsSet ("ip_address" ) {
125
- ipAddress = net .ParseIP (ctx .String ("ip_address" ))
126
- if ipAddress == nil {
127
- return fmt .Errorf ("unable to parse ip_address: %s" ,
128
- ctx .String ("ip_address" ))
129
- }
130
- }
131
-
132
- if ctx .IsSet ("custom_caveat_name" ) {
133
- customCaveatName = ctx .String ("custom_caveat_name" )
134
- if containsWhiteSpace (customCaveatName ) {
135
- return fmt .Errorf ("unexpected white space found in " +
136
- "custom caveat name" )
137
- }
138
- if customCaveatName == "" {
139
- return fmt .Errorf ("invalid custom caveat name" )
140
- }
141
- }
142
-
143
- if ctx .IsSet ("custom_caveat_condition" ) {
144
- customCaveatCond = ctx .String ("custom_caveat_condition" )
145
- if containsWhiteSpace (customCaveatCond ) {
146
- return fmt .Errorf ("unexpected white space found in " +
147
- "custom caveat condition" )
148
- }
149
- if customCaveatCond == "" {
150
- return fmt .Errorf ("invalid custom caveat condition" )
151
- }
152
- if customCaveatCond != "" && customCaveatName == "" {
153
- return fmt .Errorf ("cannot set custom caveat " +
154
- "condition without custom caveat name" )
155
- }
156
- }
157
-
158
124
if ctx .IsSet ("root_key_id" ) {
159
125
rootKeyID = ctx .Uint64 ("root_key_id" )
160
126
}
@@ -213,32 +179,7 @@ func bakeMacaroon(ctx *cli.Context) error {
213
179
214
180
// Now apply the desired constraints to the macaroon. This will always
215
181
// create a new macaroon object, even if no constraints are added.
216
- macConstraints := make ([]macaroons.Constraint , 0 )
217
- if timeout > 0 {
218
- macConstraints = append (
219
- macConstraints , macaroons .TimeoutConstraint (timeout ),
220
- )
221
- }
222
- if ipAddress != nil {
223
- macConstraints = append (
224
- macConstraints ,
225
- macaroons .IPLockConstraint (ipAddress .String ()),
226
- )
227
- }
228
-
229
- // The custom caveat condition is optional, it could just be a marker
230
- // tag in the macaroon with just a name. The interceptor itself doesn't
231
- // care about the value anyway.
232
- if customCaveatName != "" {
233
- macConstraints = append (
234
- macConstraints , macaroons .CustomConstraint (
235
- customCaveatName , customCaveatCond ,
236
- ),
237
- )
238
- }
239
- constrainedMac , err := macaroons .AddConstraints (
240
- unmarshalMac , macConstraints ... ,
241
- )
182
+ constrainedMac , err := applyMacaroonConstraints (ctx , unmarshalMac )
242
183
if err != nil {
243
184
return err
244
185
}
@@ -470,6 +411,151 @@ func printMacaroon(ctx *cli.Context) error {
470
411
return nil
471
412
}
472
413
414
+ var constrainMacaroonCommand = cli.Command {
415
+ Name : "constrainmacaroon" ,
416
+ Category : "Macaroons" ,
417
+ Usage : "Adds one or more restriction(s) to an existing macaroon" ,
418
+ ArgsUsage : "[--timeout=] [--ip_address=] [--custom_caveat_name= " +
419
+ "[--custom_caveat_condition=]] input-macaroon-file " +
420
+ "constrained-macaroon-file" ,
421
+ Description : `
422
+ Add one or more first-party caveat(s) (a.k.a. constraints/restrictions)
423
+ to an existing macaroon.
424
+ ` ,
425
+ Flags : []cli.Flag {
426
+ macTimeoutFlag ,
427
+ macIPAddressFlag ,
428
+ macCustomCaveatNameFlag ,
429
+ macCustomCaveatConditionFlag ,
430
+ },
431
+ Action : actionDecorator (constrainMacaroon ),
432
+ }
433
+
434
+ func constrainMacaroon (ctx * cli.Context ) error {
435
+ // Show command help if not enough arguments.
436
+ if ctx .NArg () != 2 {
437
+ return cli .ShowCommandHelp (ctx , "constrainmacaroon" )
438
+ }
439
+ args := ctx .Args ()
440
+
441
+ sourceMacFile := lncfg .CleanAndExpandPath (args .First ())
442
+ args = args .Tail ()
443
+
444
+ sourceMacBytes , err := ioutil .ReadFile (sourceMacFile )
445
+ if err != nil {
446
+ return fmt .Errorf ("error trying to read source macaroon file " +
447
+ "%s: %v" , sourceMacFile , err )
448
+ }
449
+
450
+ destMacFile := lncfg .CleanAndExpandPath (args .First ())
451
+
452
+ // Now we should have gotten a valid macaroon. Unmarshal it so we can
453
+ // add first-party caveats (if necessary) to it.
454
+ sourceMac := & macaroon.Macaroon {}
455
+ if err = sourceMac .UnmarshalBinary (sourceMacBytes ); err != nil {
456
+ return fmt .Errorf ("error unmarshaling source macaroon file " +
457
+ "%s: %v" , sourceMacFile , err )
458
+ }
459
+
460
+ // Now apply the desired constraints to the macaroon. This will always
461
+ // create a new macaroon object, even if no constraints are added.
462
+ constrainedMac , err := applyMacaroonConstraints (ctx , sourceMac )
463
+ if err != nil {
464
+ return err
465
+ }
466
+
467
+ destMacBytes , err := constrainedMac .MarshalBinary ()
468
+ if err != nil {
469
+ return fmt .Errorf ("error marshaling destination macaroon " +
470
+ "file: %v" , err )
471
+ }
472
+
473
+ // Now we can output the result.
474
+ err = ioutil .WriteFile (destMacFile , destMacBytes , 0644 )
475
+ if err != nil {
476
+ return fmt .Errorf ("error writing destination macaroon file " +
477
+ "%s: %v" , destMacFile , err )
478
+ }
479
+ fmt .Printf ("Macaroon saved to %s\n " , destMacFile )
480
+
481
+ return nil
482
+ }
483
+
484
+ // applyMacaroonConstraints parses and applies all currently supported macaroon
485
+ // condition flags from the command line to the given macaroon and returns a new
486
+ // macaroon instance.
487
+ func applyMacaroonConstraints (ctx * cli.Context ,
488
+ mac * macaroon.Macaroon ) (* macaroon.Macaroon , error ) {
489
+
490
+ macConstraints := make ([]macaroons.Constraint , 0 )
491
+
492
+ if ctx .IsSet (macTimeoutFlag .Name ) {
493
+ timeout := ctx .Int64 (macTimeoutFlag .Name )
494
+ if timeout <= 0 {
495
+ return nil , fmt .Errorf ("timeout must be greater than 0" )
496
+ }
497
+ macConstraints = append (
498
+ macConstraints , macaroons .TimeoutConstraint (timeout ),
499
+ )
500
+ }
501
+
502
+ if ctx .IsSet (macIPAddressFlag .Name ) {
503
+ ipAddress := net .ParseIP (ctx .String (macIPAddressFlag .Name ))
504
+ if ipAddress == nil {
505
+ return nil , fmt .Errorf ("unable to parse ip_address: %s" ,
506
+ ctx .String ("ip_address" ))
507
+ }
508
+
509
+ macConstraints = append (
510
+ macConstraints ,
511
+ macaroons .IPLockConstraint (ipAddress .String ()),
512
+ )
513
+ }
514
+
515
+ if ctx .IsSet (macCustomCaveatNameFlag .Name ) {
516
+ customCaveatName := ctx .String (macCustomCaveatNameFlag .Name )
517
+ if containsWhiteSpace (customCaveatName ) {
518
+ return nil , fmt .Errorf ("unexpected white space found " +
519
+ "in custom caveat name" )
520
+ }
521
+ if customCaveatName == "" {
522
+ return nil , fmt .Errorf ("invalid custom caveat name" )
523
+ }
524
+
525
+ var customCaveatCond string
526
+ if ctx .IsSet (macCustomCaveatConditionFlag .Name ) {
527
+ customCaveatCond = ctx .String (
528
+ macCustomCaveatConditionFlag .Name ,
529
+ )
530
+ if containsWhiteSpace (customCaveatCond ) {
531
+ return nil , fmt .Errorf ("unexpected white " +
532
+ "space found in custom caveat " +
533
+ "condition" )
534
+ }
535
+ if customCaveatCond == "" {
536
+ return nil , fmt .Errorf ("invalid custom " +
537
+ "caveat condition" )
538
+ }
539
+ }
540
+
541
+ // The custom caveat condition is optional, it could just be a
542
+ // marker tag in the macaroon with just a name. The interceptor
543
+ // itself doesn't care about the value anyway.
544
+ macConstraints = append (
545
+ macConstraints , macaroons .CustomConstraint (
546
+ customCaveatName , customCaveatCond ,
547
+ ),
548
+ )
549
+ }
550
+
551
+ constrainedMac , err := macaroons .AddConstraints (mac , macConstraints ... )
552
+ if err != nil {
553
+ return nil , fmt .Errorf ("error adding constraints: %v" , err )
554
+ }
555
+
556
+ return constrainedMac , nil
557
+ }
558
+
473
559
// containsWhiteSpace returns true if the given string contains any character
474
560
// that is considered to be a white space or non-printable character such as
475
561
// space, tabulator, newline, carriage return and some more exotic ones.
0 commit comments