Merge pull request #6818 from ellemouton/lncli-metadata-insecure
cmd/lncli: add metadata and insecure flags
This commit is contained in:
4 files changed
+143
-43
No files matched your search
@@ -2052,7 +2052,7 @@ func parseChanPoint(s string) (*lnrpc.ChannelPoint, error) {
|
|||||||
return nil, errBadChanPoint
|
return nil, errBadChanPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
index, err := strconv.ParseInt(split[1], 10, 32)
|
index, err := strconv.ParseInt(split[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decode output index: %v", err)
|
return nil, fmt.Errorf("unable to decode output index: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-30
@@ -24,6 +24,7 @@ import (
|
|||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -87,28 +88,40 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
|||||||
fatal(fmt.Errorf("could not load global options: %v", err))
|
fatal(fmt.Errorf("could not load global options: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the specified TLS certificate.
|
|
||||||
certPool, err := profile.cert()
|
|
||||||
if err != nil {
|
|
||||||
fatal(fmt.Errorf("could not create cert pool: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build transport credentials from the certificate pool. If there is no
|
|
||||||
// certificate pool, we expect the server to use a non-self-signed
|
|
||||||
// certificate such as a certificate obtained from Let's Encrypt.
|
|
||||||
var creds credentials.TransportCredentials
|
|
||||||
if certPool != nil {
|
|
||||||
creds = credentials.NewClientTLSFromCert(certPool, "")
|
|
||||||
} else {
|
|
||||||
// Fallback to the system pool. Using an empty tls config is an
|
|
||||||
// alternative to x509.SystemCertPool(). That call is not
|
|
||||||
// supported on Windows.
|
|
||||||
creds = credentials.NewTLS(&tls.Config{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a dial options array.
|
// Create a dial options array.
|
||||||
opts := []grpc.DialOption{
|
opts := []grpc.DialOption{
|
||||||
grpc.WithTransportCredentials(creds),
|
grpc.WithUnaryInterceptor(
|
||||||
|
addMetadataUnaryInterceptor(profile.Metadata),
|
||||||
|
),
|
||||||
|
grpc.WithStreamInterceptor(
|
||||||
|
addMetaDataStreamInterceptor(profile.Metadata),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
if profile.Insecure {
|
||||||
|
opts = append(opts, grpc.WithInsecure())
|
||||||
|
} else {
|
||||||
|
// Load the specified TLS certificate.
|
||||||
|
certPool, err := profile.cert()
|
||||||
|
if err != nil {
|
||||||
|
fatal(fmt.Errorf("could not create cert pool: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build transport credentials from the certificate pool. If
|
||||||
|
// there is no certificate pool, we expect the server to use a
|
||||||
|
// non-self-signed certificate such as a certificate obtained
|
||||||
|
// from Let's Encrypt.
|
||||||
|
var creds credentials.TransportCredentials
|
||||||
|
if certPool != nil {
|
||||||
|
creds = credentials.NewClientTLSFromCert(certPool, "")
|
||||||
|
} else {
|
||||||
|
// Fallback to the system pool. Using an empty tls
|
||||||
|
// config is an alternative to x509.SystemCertPool().
|
||||||
|
// That call is not supported on Windows.
|
||||||
|
creds = credentials.NewTLS(&tls.Config{})
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process macaroon credentials if --no-macaroons isn't set and
|
// Only process macaroon credentials if --no-macaroons isn't set and
|
||||||
@@ -141,16 +154,17 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macConstraints := []macaroons.Constraint{
|
macConstraints := []macaroons.Constraint{
|
||||||
// We add a time-based constraint to prevent replay of the
|
// We add a time-based constraint to prevent replay of
|
||||||
// macaroon. It's good for 60 seconds by default to make up for
|
// the macaroon. It's good for 60 seconds by default to
|
||||||
// any discrepancy between client and server clocks, but leaking
|
// make up for any discrepancy between client and server
|
||||||
// the macaroon before it becomes invalid makes it possible for
|
// clocks, but leaking the macaroon before it becomes
|
||||||
// an attacker to reuse the macaroon. In addition, the validity
|
// invalid makes it possible for an attacker to reuse
|
||||||
// time of the macaroon is extended by the time the server clock
|
// the macaroon. In addition, the validity time of the
|
||||||
// is behind the client clock, or shortened by the time 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
|
// server clock is ahead of the client clock (or invalid
|
||||||
// altogether if, in the latter case, this time is more than 60
|
// altogether if, in the latter case, this time is more
|
||||||
// seconds).
|
// than 60 seconds).
|
||||||
// TODO(aakselrod): add better anti-replay protection.
|
// TODO(aakselrod): add better anti-replay protection.
|
||||||
macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
|
macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
|
||||||
|
|
||||||
@@ -180,7 +194,9 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
|||||||
// to connect to the grpc server.
|
// to connect to the grpc server.
|
||||||
if ctx.GlobalIsSet("socksproxy") {
|
if ctx.GlobalIsSet("socksproxy") {
|
||||||
socksProxy := ctx.GlobalString("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(
|
return tor.Dial(
|
||||||
addr, socksProxy, false, false,
|
addr, socksProxy, false, false,
|
||||||
tor.DefaultConnTimeout,
|
tor.DefaultConnTimeout,
|
||||||
@@ -204,6 +220,49 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
|||||||
return conn
|
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
|
// extractPathArgs parses the TLS certificate and macaroon paths from the
|
||||||
// command.
|
// command.
|
||||||
func extractPathArgs(ctx *cli.Context) (string, string, error) {
|
func extractPathArgs(ctx *cli.Context) (string, string, error) {
|
||||||
@@ -349,6 +408,20 @@ func main() {
|
|||||||
"macaroon jar instead of the default one. " +
|
"macaroon jar instead of the default one. " +
|
||||||
"Can only be used if profiles are defined.",
|
"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\".",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "insecure",
|
||||||
|
Usage: "Connect to the rpc server without TLS " +
|
||||||
|
"authentication",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
createCommand,
|
createCommand,
|
||||||
|
|||||||
+32
-12
@@ -24,14 +24,16 @@ var (
|
|||||||
// profileEntry is a struct that represents all settings for one specific
|
// profileEntry is a struct that represents all settings for one specific
|
||||||
// profile.
|
// profile.
|
||||||
type profileEntry struct {
|
type profileEntry struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
RPCServer string `json:"rpcserver"`
|
RPCServer string `json:"rpcserver"`
|
||||||
LndDir string `json:"lnddir"`
|
LndDir string `json:"lnddir"`
|
||||||
Chain string `json:"chain"`
|
Chain string `json:"chain"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
NoMacaroons bool `json:"no-macaroons,omitempty"` // nolint:tagliatelle
|
NoMacaroons bool `json:"no-macaroons,omitempty"` // nolint:tagliatelle
|
||||||
TLSCert string `json:"tlscert"`
|
TLSCert string `json:"tlscert"`
|
||||||
Macaroons *macaroonJar `json:"macaroons"`
|
Macaroons *macaroonJar `json:"macaroons"`
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// cert returns the profile's TLS certificate as a x509 certificate pool.
|
// cert returns the profile's TLS certificate as a x509 certificate pool.
|
||||||
@@ -121,24 +123,42 @@ func profileFromContext(ctx *cli.Context, store, skipMacaroons bool) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insecure := ctx.GlobalBool("insecure")
|
||||||
|
|
||||||
// Load the certificate file now, if specified. We store it as plain PEM
|
// Load the certificate file now, if specified. We store it as plain PEM
|
||||||
// directly.
|
// directly.
|
||||||
var tlsCert []byte
|
var tlsCert []byte
|
||||||
if tlsCertPath != "" {
|
if tlsCertPath != "" && !insecure {
|
||||||
var err error
|
var err error
|
||||||
tlsCert, err = ioutil.ReadFile(tlsCertPath)
|
tlsCert, err = ioutil.ReadFile(tlsCertPath)
|
||||||
if err != nil {
|
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{
|
entry := &profileEntry{
|
||||||
RPCServer: ctx.GlobalString("rpcserver"),
|
RPCServer: ctx.GlobalString("rpcserver"),
|
||||||
LndDir: lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir")),
|
LndDir: lncfg.CleanAndExpandPath(
|
||||||
|
ctx.GlobalString("lnddir"),
|
||||||
|
),
|
||||||
Chain: ctx.GlobalString("chain"),
|
Chain: ctx.GlobalString("chain"),
|
||||||
Network: ctx.GlobalString("network"),
|
Network: ctx.GlobalString("network"),
|
||||||
NoMacaroons: ctx.GlobalBool("no-macaroons"),
|
NoMacaroons: ctx.GlobalBool("no-macaroons"),
|
||||||
TLSCert: string(tlsCert),
|
TLSCert: string(tlsCert),
|
||||||
|
Metadata: metadata,
|
||||||
|
Insecure: insecure,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we aren't using macaroons in general (flag --no-macaroons) or
|
// If we aren't using macaroons in general (flag --no-macaroons) or
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ transaction](https://github.com/lightningnetwork/lnd/pull/6730).
|
|||||||
* [The macaroon key store implementation was refactored to be more generally
|
* [The macaroon key store implementation was refactored to be more generally
|
||||||
usable](https://github.com/lightningnetwork/lnd/pull/6509).
|
usable](https://github.com/lightningnetwork/lnd/pull/6509).
|
||||||
|
|
||||||
|
## `lncli`
|
||||||
|
* [Add an `insecure` flag to skip tls auth as well as a `metadata` string slice
|
||||||
|
flag](https://github.com/lightningnetwork/lnd/pull/6818) that allows the
|
||||||
|
caller to specify key-value string pairs that should be appended to the
|
||||||
|
outgoing context.
|
||||||
|
|
||||||
## Code Health
|
## Code Health
|
||||||
|
|
||||||
### Tooling and documentation
|
### Tooling and documentation
|
||||||
@@ -31,6 +37,7 @@ transaction](https://github.com/lightningnetwork/lnd/pull/6730).
|
|||||||
|
|
||||||
* Carla Kirk-Cohen
|
* Carla Kirk-Cohen
|
||||||
* Daniel McNally
|
* Daniel McNally
|
||||||
|
* Elle Mouton
|
||||||
* ErikEk
|
* ErikEk
|
||||||
* Olaoluwa Osuntokun
|
* Olaoluwa Osuntokun
|
||||||
* Oliver Gugger
|
* Oliver Gugger
|
||||||
Reference in new issue
Block a user