mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-18 21:35:24 +01:00
cmd/lncli: add metadata kvpair flag
Add a new `metadata` string slice flag to lncli that allows the caller to specify multiple key-value string pairs that should be appended to the outgoing context.
This commit is contained in:
parent
e488bbfc9d
commit
1dffaf10e2
@ -24,6 +24,7 @@ import (
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -109,6 +110,12 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
||||
// Create a dial options array.
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(creds),
|
||||
grpc.WithUnaryInterceptor(
|
||||
addMetadataUnaryInterceptor(profile.Metadata),
|
||||
),
|
||||
grpc.WithStreamInterceptor(
|
||||
addMetaDataStreamInterceptor(profile.Metadata),
|
||||
),
|
||||
}
|
||||
|
||||
// Only process macaroon credentials if --no-macaroons isn't set and
|
||||
@ -141,16 +148,17 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
||||
}
|
||||
|
||||
macConstraints := []macaroons.Constraint{
|
||||
// We add a time-based constraint to prevent replay of the
|
||||
// macaroon. It's good for 60 seconds by default to make up for
|
||||
// any discrepancy between client and server clocks, but leaking
|
||||
// the macaroon before it becomes invalid makes it possible for
|
||||
// an attacker to reuse the macaroon. In addition, the validity
|
||||
// time of the macaroon is extended by the time the server clock
|
||||
// is behind the client clock, or shortened by the time the
|
||||
// We add a time-based constraint to prevent replay of
|
||||
// the macaroon. It's good for 60 seconds by default to
|
||||
// make up for any discrepancy between client and server
|
||||
// clocks, but leaking the macaroon before it becomes
|
||||
// invalid makes it possible for an attacker to reuse
|
||||
// the macaroon. In addition, the validity time of the
|
||||
// macaroon is extended by the time the server clock is
|
||||
// behind the client clock, or shortened by the time the
|
||||
// server clock is ahead of the client clock (or invalid
|
||||
// altogether if, in the latter case, this time is more than 60
|
||||
// seconds).
|
||||
// altogether if, in the latter case, this time is more
|
||||
// than 60 seconds).
|
||||
// TODO(aakselrod): add better anti-replay protection.
|
||||
macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
|
||||
|
||||
@ -180,7 +188,9 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
||||
// to connect to the grpc server.
|
||||
if ctx.GlobalIsSet("socksproxy") {
|
||||
socksProxy := ctx.GlobalString("socksproxy")
|
||||
torDialer := func(_ context.Context, addr string) (net.Conn, error) {
|
||||
torDialer := func(_ context.Context, addr string) (net.Conn,
|
||||
error) {
|
||||
|
||||
return tor.Dial(
|
||||
addr, socksProxy, false, false,
|
||||
tor.DefaultConnTimeout,
|
||||
@ -204,6 +214,49 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
||||
return conn
|
||||
}
|
||||
|
||||
// addMetadataUnaryInterceptor returns a grpc client side interceptor that
|
||||
// appends any key-value metadata strings to the outgoing context of a grpc
|
||||
// unary call.
|
||||
func addMetadataUnaryInterceptor(
|
||||
md map[string]string) grpc.UnaryClientInterceptor {
|
||||
|
||||
return func(ctx context.Context, method string, req, reply interface{},
|
||||
cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
|
||||
opts ...grpc.CallOption) error {
|
||||
|
||||
outCtx := contextWithMetadata(ctx, md)
|
||||
return invoker(outCtx, method, req, reply, cc, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// addMetaDataStreamInterceptor returns a grpc client side interceptor that
|
||||
// appends any key-value metadata strings to the outgoing context of a grpc
|
||||
// stream call.
|
||||
func addMetaDataStreamInterceptor(
|
||||
md map[string]string) grpc.StreamClientInterceptor {
|
||||
|
||||
return func(ctx context.Context, desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn, method string, streamer grpc.Streamer,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
|
||||
outCtx := contextWithMetadata(ctx, md)
|
||||
return streamer(outCtx, desc, cc, method, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// contextWithMetaData appends the given metadata key-value pairs to the given
|
||||
// context.
|
||||
func contextWithMetadata(ctx context.Context,
|
||||
md map[string]string) context.Context {
|
||||
|
||||
kvPairs := make([]string, 0, 2*len(md))
|
||||
for k, v := range md {
|
||||
kvPairs = append(kvPairs, k, v)
|
||||
}
|
||||
|
||||
return metadata.AppendToOutgoingContext(ctx, kvPairs...)
|
||||
}
|
||||
|
||||
// extractPathArgs parses the TLS certificate and macaroon paths from the
|
||||
// command.
|
||||
func extractPathArgs(ctx *cli.Context) (string, string, error) {
|
||||
@ -349,6 +402,14 @@ func main() {
|
||||
"macaroon jar instead of the default one. " +
|
||||
"Can only be used if profiles are defined.",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "metadata",
|
||||
Usage: "This flag can be used to specify a key-value " +
|
||||
"pair that should be appended to the " +
|
||||
"outgoing context before the request is sent " +
|
||||
"to lnd. This flag may be specified multiple " +
|
||||
"times. The format is: \"key:value\".",
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
createCommand,
|
||||
|
@ -24,14 +24,15 @@ var (
|
||||
// profileEntry is a struct that represents all settings for one specific
|
||||
// profile.
|
||||
type profileEntry struct {
|
||||
Name string `json:"name"`
|
||||
RPCServer string `json:"rpcserver"`
|
||||
LndDir string `json:"lnddir"`
|
||||
Chain string `json:"chain"`
|
||||
Network string `json:"network"`
|
||||
NoMacaroons bool `json:"no-macaroons,omitempty"` // nolint:tagliatelle
|
||||
TLSCert string `json:"tlscert"`
|
||||
Macaroons *macaroonJar `json:"macaroons"`
|
||||
Name string `json:"name"`
|
||||
RPCServer string `json:"rpcserver"`
|
||||
LndDir string `json:"lnddir"`
|
||||
Chain string `json:"chain"`
|
||||
Network string `json:"network"`
|
||||
NoMacaroons bool `json:"no-macaroons,omitempty"` // nolint:tagliatelle
|
||||
TLSCert string `json:"tlscert"`
|
||||
Macaroons *macaroonJar `json:"macaroons"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// cert returns the profile's TLS certificate as a x509 certificate pool.
|
||||
@ -128,17 +129,32 @@ func profileFromContext(ctx *cli.Context, store, skipMacaroons bool) (
|
||||
var err error
|
||||
tlsCert, err = ioutil.ReadFile(tlsCertPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load TLS cert file: %v", err)
|
||||
return nil, fmt.Errorf("could not load TLS cert "+
|
||||
"file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
metadata := make(map[string]string)
|
||||
for _, m := range ctx.GlobalStringSlice("metadata") {
|
||||
pair := strings.Split(m, ":")
|
||||
if len(pair) != 2 {
|
||||
return nil, fmt.Errorf("invalid format for metadata " +
|
||||
"flag; expected \"key:value\"")
|
||||
}
|
||||
|
||||
metadata[pair[0]] = pair[1]
|
||||
}
|
||||
|
||||
entry := &profileEntry{
|
||||
RPCServer: ctx.GlobalString("rpcserver"),
|
||||
LndDir: lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir")),
|
||||
RPCServer: ctx.GlobalString("rpcserver"),
|
||||
LndDir: lncfg.CleanAndExpandPath(
|
||||
ctx.GlobalString("lnddir"),
|
||||
),
|
||||
Chain: ctx.GlobalString("chain"),
|
||||
Network: ctx.GlobalString("network"),
|
||||
NoMacaroons: ctx.GlobalBool("no-macaroons"),
|
||||
TLSCert: string(tlsCert),
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
// If we aren't using macaroons in general (flag --no-macaroons) or
|
||||
|
Loading…
Reference in New Issue
Block a user