Skip to content

Commit

Permalink
Merge 850f64b into 26636ce
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Jun 13, 2018
2 parents 26636ce + 850f64b commit 6011fa0
Show file tree
Hide file tree
Showing 10 changed files with 936 additions and 394 deletions.
229 changes: 229 additions & 0 deletions cmd/lncli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
"io/ioutil"
"math"
"net"
"os"
"os/exec"
"strconv"
Expand All @@ -21,13 +22,16 @@ import (
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcutil"
"github.com/urfave/cli"

"golang.org/x/crypto/ssh/terminal"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gopkg.in/macaroon.v2"
)

// TODO(roasbeef): cli logic for supporting both positional and unix style
Expand Down Expand Up @@ -64,6 +68,17 @@ func printRespJSON(resp proto.Message) {
fmt.Println(jsonStr)
}

// stringInSlice looks for a string in a slice of strings and returns true
// if it is found.
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

// actionDecorator is used to add additional information and error handling
// to command actions.
func actionDecorator(f func(*cli.Context) error) func(*cli.Context) error {
Expand Down Expand Up @@ -3193,3 +3208,217 @@ func forwardingHistory(ctx *cli.Context) error {
printRespJSON(resp)
return nil
}

var newMacaroonCommand = cli.Command{
Name: "newmacaroon",
Category: "Macaroons",
Usage: "Bakes a new macaroon with the provided list of " +
"permissions and restrictions",
ArgsUsage: "--permission=entity/action [--output=] [--timeout=] " +
"[--ip_address=]",
Description: `
Create a new macaroon that grants the provided permissions and
optionally adds restrictions (timeout, IP address) to it.
If the --output argument is 'json' or 'binary', the new macaroon is
printed to the standard output marshaled in that format. Otherwise it
is assumed to be a file name and the macaroon is written to that file
in the binary format.
A permission is a tuple of an entity and an action.
The following entities are available in lnd:
* onchain
* offchain
* address
* message
* peers
* info
* invoices
* macaroon
The action can be either 'read' or 'write'.
Multiple operations can be added, for example:
--permission=info/read --permission=invoices/write --permission=...
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "output",
Usage: "the output format to serialize the macaroon " +
"in, must be either 'json' or 'binary', " +
"otherwise it is assumed to be a file name",
Value: "binary",
},
cli.Uint64Flag{
Name: "timeout",
Usage: "the number of seconds the macaroon will be " +
"valid before it times out",
},
cli.StringFlag{
Name: "ip_address",
Usage: "the IP address the maracoon will be bound to",
},
cli.StringSliceFlag{
Name: "permission",
Usage: "permission to include in the macaroon, " +
"expressed as a tuple of entity/action",
},
},
Action: actionDecorator(newMacaroon),
}

func newMacaroon(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getClient(ctx)
defer cleanUp()

var (
output string
timeout int64
ipAddress net.IP
permissionSlice []string
parsedPermissions []*lnrpc.MacaroonPermission
err error
validActions = []string{"read", "write"}
validEntities = []string{
"onchain", "offchain", "address", "message",
"peers", "info", "invoices", "macaroon",
}
)
args := ctx.Args()

// Parse command line arguments. Positional arguments cannot be
// implemented because we have a dynamic number of --permission
// arguments and all other flags are optional. So we wouldn't really
// know what is in what position.
if args.Present() {
return fmt.Errorf("positional arguments are not supported by " +
"this command, please use named arguments (for " +
"example --permission=xyz)")
}
if ctx.IsSet("output") || ctx.String("output") != "" {
output = ctx.String("output")
}
if ctx.IsSet("timeout") {
timeout = ctx.Int64("timeout")
if timeout <= 0 {
return fmt.Errorf("timeout must be greater than 0")
}
}
if ctx.IsSet("ip_address") {
ipAddress = net.ParseIP(ctx.String("ip_address"))
if ipAddress == nil {
return fmt.Errorf("unable to parse ip_address: %s",
ctx.String("ip_address"))
}
}
if ctx.IsSet("permission") {
permissionSlice = ctx.StringSlice("permission")
}
if len(permissionSlice) > 0 {
for _, permission := range permissionSlice {
if len(permission) == 0 {
return fmt.Errorf("unable to use empty "+
"permission argument: %v",
err)
}
tuple := strings.Split(permission, "/")
if len(tuple) != 2 {
return fmt.Errorf("unable to parse "+
"permission tuple: %s",
permission)
}
entity, action := tuple[0], tuple[1]
if !stringInSlice(entity, validEntities) {
return fmt.Errorf("invalid entity: %s", entity)
}
if !stringInSlice(action, validActions) {
return fmt.Errorf("invalid action: %s", action)
}

// No we can assume that we have a valid entity/action
// tuple.
parsedPermissions = append(parsedPermissions,
&lnrpc.MacaroonPermission{
Entity: entity,
Action: action,
})
}
} else {
return fmt.Errorf("missing at least one permission argument")
}

// Now we have gathered all the input we need and can do the actual
// RPC call.
req := &lnrpc.NewMacaroonRequest{parsedPermissions}
resp, err := client.NewMacaroon(ctxb, req)
if err != nil {
return err
}

// Now we should have gotten a valid macaroon. Unmarshal it so we
// can add first-party caveats (if necessary) to it.
macBytes, err := hex.DecodeString(resp.Macaroon)
if err != nil {
return err
}
deserializedMac := &macaroon.Macaroon{}
if err = deserializedMac.UnmarshalBinary(macBytes); err != nil {
return err
}

// Now apply the desired constraints to the macaroon.
// This will always create a new macaroon object, even if no constraints
// are added.
macConstraints := []macaroons.Constraint{}
if timeout > 0 {
macConstraints = append(macConstraints,
macaroons.TimeoutConstraint(timeout))
}
if ipAddress != nil {
macConstraints = append(macConstraints,
macaroons.IPLockConstraint(ipAddress.String()))
}
constrainedMac, err := macaroons.AddConstraints(deserializedMac,
macConstraints...)
if err != nil {
fatal(err)
}
binaryString, err := constrainedMac.MarshalBinary()
if err != nil {
fatal(err)
}

// Serialize the macaroon using the requested output format and then
// print it to standard output. Both of the following formats
// produce JSON as an output:
// - 'binary': Prints a JSON object with one single string
// property 'macaroon'.
// - 'json': Directly prints the JSON serialized macaroon.
//
// If the output format is neither 'binary' nor 'json', then the output
// is assumed to be a file and the macaroon is written to that file in
// the default binary format.
if output == "binary" {
printJSON(map[string]string{
"macaroon": hex.EncodeToString(binaryString),
})
} else if output == "json" {
jsonBytes, err := constrainedMac.MarshalJSON()
if err != nil {
fatal(err)
}
var out bytes.Buffer
json.Indent(&out, jsonBytes, "", "\t")
out.WriteString("\n")
out.WriteTo(os.Stdout)
} else {
outputPath := cleanAndExpandPath(output)
err = ioutil.WriteFile(outputPath, []byte(binaryString), 0644)
if err != nil {
os.Remove(outputPath)
fatal(err)
}
}
return nil
}
1 change: 1 addition & 0 deletions cmd/lncli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ func main() {
feeReportCommand,
updateChannelPolicyCommand,
forwardingHistoryCommand,
newMacaroonCommand,
}

if err := app.Run(os.Args); err != nil {
Expand Down
1 change: 1 addition & 0 deletions lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ func lndMain() error {
// connections.
server, err := newServer(
cfg.Listeners, chanDB, activeChainControl, idPrivKey,
macaroonService,
)
if err != nil {
srvrLog.Errorf("unable to create server: %v\n", err)
Expand Down
8 changes: 7 additions & 1 deletion lnrpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ description):
enforced by the node globally for each channel.
* UpdateChannelPolicy
* Allows the caller to update the fee schedule and channel policies for all channels
globally, or a particular channel
globally, or a particular channel.
* ForwardingHistory
* ForwardingHistory allows the caller to query the htlcswitch for a
record of all HTLCs forwarded.
* NewMacaroon
* Bakes a new macaroon with the provided list of permissions and
restrictions

## Service: WalletUnlocker

Expand Down

0 comments on commit 6011fa0

Please sign in to comment.