Skip to content

Commit 6b57e92

Browse files
authored
Merge pull request #6529 from guggero/constrainmacaroon
lncli: add constrainmacaroon command
2 parents 4b313a5 + 8f94aac commit 6b57e92

File tree

3 files changed

+188
-96
lines changed

3 files changed

+188
-96
lines changed

cmd/lncli/cmd_macaroon.go

+181-95
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,36 @@ import (
1919
"gopkg.in/macaroon.v2"
2020
)
2121

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+
2243
var bakeMacaroonCommand = cli.Command{
2344
Name: "bakemacaroon",
2445
Category: "Macaroons",
2546
Usage: "Bakes a new macaroon with the provided list of permissions " +
2647
"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...",
2852
Description: `
2953
Bake a new macaroon that grants the provided permissions and
3054
optionally adds restrictions (timeout, IP address) to it.
@@ -57,32 +81,19 @@ var bakeMacaroonCommand = cli.Command{
5781
Usage: "save the created macaroon to this file " +
5882
"using the default binary format",
5983
},
84+
macTimeoutFlag,
85+
macIPAddressFlag,
86+
macCustomCaveatNameFlag,
87+
macCustomCaveatConditionFlag,
6088
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",
8292
},
8393
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",
8697
},
8798
},
8899
Action: actionDecorator(bakeMacaroon),
@@ -101,10 +112,6 @@ func bakeMacaroon(ctx *cli.Context) error {
101112

102113
var (
103114
savePath string
104-
timeout int64
105-
ipAddress net.IP
106-
customCaveatName string
107-
customCaveatCond string
108115
rootKeyID uint64
109116
parsedPermissions []*lnrpc.MacaroonPermission
110117
err error
@@ -114,47 +121,6 @@ func bakeMacaroon(ctx *cli.Context) error {
114121
savePath = lncfg.CleanAndExpandPath(ctx.String("save_to"))
115122
}
116123

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-
158124
if ctx.IsSet("root_key_id") {
159125
rootKeyID = ctx.Uint64("root_key_id")
160126
}
@@ -213,32 +179,7 @@ func bakeMacaroon(ctx *cli.Context) error {
213179

214180
// Now apply the desired constraints to the macaroon. This will always
215181
// 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)
242183
if err != nil {
243184
return err
244185
}
@@ -470,6 +411,151 @@ func printMacaroon(ctx *cli.Context) error {
470411
return nil
471412
}
472413

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+
473559
// containsWhiteSpace returns true if the given string contains any character
474560
// that is considered to be a white space or non-printable character such as
475561
// space, tabulator, newline, carriage return and some more exotic ones.

cmd/lncli/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ func main() {
405405
deleteMacaroonIDCommand,
406406
listPermissionsCommand,
407407
printMacaroonCommand,
408+
constrainMacaroonCommand,
408409
trackPaymentCommand,
409410
versionCommand,
410411
profileSubCommand,

docs/release-notes/release-notes-0.15.0.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,14 @@ releases. Backward compatibility is not guaranteed!
6464
to allow for RPC calls via Tor.
6565

6666
* [Hop hints are now opt in when using `lncli
67-
addinvoice`]https://github.com/lightningnetwork/lnd/pull/6523). Users now
67+
addinvoice`](https://github.com/lightningnetwork/lnd/pull/6523). Users now
6868
need to explicitly specify the `--private` flag.
6969

70+
* A new [`constrainmacaroon` command was
71+
added](https://github.com/lightningnetwork/lnd/pull/6529) that allows
72+
caveats/restrictions to be added to an existing macaroon (instead of needing
73+
to bake a new one).
74+
7075
## Neutrino
7176

7277
[Neutrino now suports BIP

0 commit comments

Comments
 (0)