Skip to content

Commit 7597dd7

Browse files
committed
cmd/litcli: use macaroon instead of ui password
Remove the use of the UI password from litcl. Use the litd macaroon instead. Note, this means that in stateless mode, litcli calls wont work and all calls will need to go through tht UI.
1 parent eb216af commit 7597dd7

File tree

2 files changed

+97
-82
lines changed

2 files changed

+97
-82
lines changed

cmd/litcli/main.go

Lines changed: 90 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package main
22

33
import (
4-
"context"
5-
"encoding/base64"
6-
"encoding/hex"
74
"fmt"
5+
"io/ioutil"
86
"os"
97
"path/filepath"
108
"strings"
11-
"syscall"
129

1310
terminal "github.com/lightninglabs/lightning-terminal"
1411
"github.com/lightninglabs/lightning-terminal/litrpc"
@@ -17,17 +14,17 @@ import (
1714
"github.com/lightninglabs/protobuf-hex-display/proto"
1815
"github.com/lightningnetwork/lnd"
1916
"github.com/lightningnetwork/lnd/lncfg"
17+
"github.com/lightningnetwork/lnd/macaroons"
2018
"github.com/urfave/cli"
21-
"golang.org/x/term"
2219
"google.golang.org/grpc"
2320
"google.golang.org/grpc/credentials"
24-
"google.golang.org/grpc/metadata"
21+
"gopkg.in/macaroon.v2"
2522
)
2623

2724
const (
28-
// uiPasswordEnvName is the name of the environment variable under which
29-
// we look for the UI password for litcli.
30-
uiPasswordEnvName = "UI_PASSWORD"
25+
// defaultMacaroonTimeout is the default macaroon timeout in seconds
26+
// that we set when sending it over the line.
27+
defaultMacaroonTimeout int64 = 60
3128
)
3229

3330
var (
@@ -61,12 +58,10 @@ var (
6158
Usage: "path to lnd's TLS certificate",
6259
Value: lnd.DefaultConfig().TLSCertPath,
6360
}
64-
uiPasswordFlag = cli.StringFlag{
65-
Name: "uipassword",
66-
Usage: "the UI password for authenticating against LiT; if " +
67-
"not specified will read from environment variable " +
68-
uiPasswordEnvName + " or prompt on terminal if both " +
69-
"values are empty",
61+
macaroonPathFlag = cli.StringFlag{
62+
Name: "macaroonpath",
63+
Usage: "path to lit's macaroon file",
64+
Value: terminal.DefaultMacaroonPath,
7065
}
7166
)
7267

@@ -86,7 +81,7 @@ func main() {
8681
lndMode,
8782
tlsCertFlag,
8883
lndTlsCertFlag,
89-
uiPasswordFlag,
84+
macaroonPathFlag,
9085
}
9186
app.Commands = append(app.Commands, sessionCommands...)
9287

@@ -103,11 +98,11 @@ func fatal(err error) {
10398

10499
func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
105100
rpcServer := ctx.GlobalString("rpcserver")
106-
tlsCertPath, err := extractPathArgs(ctx)
101+
tlsCertPath, macPath, err := extractPathArgs(ctx)
107102
if err != nil {
108103
return nil, nil, err
109104
}
110-
conn, err := getClientConn(rpcServer, tlsCertPath)
105+
conn, err := getClientConn(rpcServer, tlsCertPath, macPath)
111106
if err != nil {
112107
return nil, nil, err
113108
}
@@ -117,9 +112,18 @@ func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
117112
return sessionsClient, cleanup, nil
118113
}
119114

120-
func getClientConn(address, tlsCertPath string) (*grpc.ClientConn, error) {
115+
func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
116+
error) {
117+
118+
// We always need to send a macaroon.
119+
macOption, err := readMacaroon(macaroonPath)
120+
if err != nil {
121+
return nil, err
122+
}
123+
121124
opts := []grpc.DialOption{
122125
grpc.WithDefaultCallOptions(maxMsgRecvSize),
126+
macOption,
123127
}
124128

125129
// TLS cannot be disabled, we'll always have a cert file to read.
@@ -139,15 +143,33 @@ func getClientConn(address, tlsCertPath string) (*grpc.ClientConn, error) {
139143
return conn, nil
140144
}
141145

142-
// extractPathArgs parses the TLS certificate from the command.
143-
func extractPathArgs(ctx *cli.Context) (string, error) {
146+
// extractPathArgs parses the TLS certificate and macaroon paths from the
147+
// command.
148+
func extractPathArgs(ctx *cli.Context) (string, string, error) {
144149
// We'll start off by parsing the network. This is needed to determine
145150
// the correct path to the TLS certificate and macaroon when not
146151
// specified.
147152
networkStr := strings.ToLower(ctx.GlobalString("network"))
148153
_, err := lndclient.Network(networkStr).ChainParams()
149154
if err != nil {
150-
return "", err
155+
return "", "", err
156+
}
157+
158+
// Get the base dir so that we can reconstruct the default tls and
159+
// macaroon paths if needed.
160+
baseDir := lncfg.CleanAndExpandPath(ctx.GlobalString(baseDirFlag.Name))
161+
162+
macaroonPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
163+
macaroonPathFlag.Name,
164+
))
165+
166+
// If the macaroon path flag has not been set to a custom value,
167+
// then reconstruct it with the possibly new base dir and network
168+
// values.
169+
if macaroonPath == terminal.DefaultMacaroonPath {
170+
macaroonPath = filepath.Join(
171+
baseDir, networkStr, terminal.DefaultMacaroonFilename,
172+
)
151173
}
152174

153175
// Get the LND mode. If Lit is in integrated LND mode, then LND's tls
@@ -158,7 +180,7 @@ func extractPathArgs(ctx *cli.Context) (string, error) {
158180
lndTlsCertFlag.Name,
159181
))
160182

161-
return tlsCertPath, nil
183+
return tlsCertPath, macaroonPath, nil
162184
}
163185

164186
// Lit is in remote LND mode. So we need Lit's tls cert.
@@ -168,89 +190,78 @@ func extractPathArgs(ctx *cli.Context) (string, error) {
168190

169191
// If a custom TLS path was set, use it as is.
170192
if tlsCertPath != terminal.DefaultTLSCertPath {
171-
return tlsCertPath, nil
193+
return tlsCertPath, macaroonPath, nil
172194
}
173195

174196
// If a custom base directory was set, we'll also check if custom paths
175197
// for the TLS cert file was set as well. If not, we'll override the
176198
// paths so they can be found within the custom base directory set.
177199
// This allows us to set a custom base directory, along with custom
178200
// paths to the TLS cert file.
179-
baseDir := lncfg.CleanAndExpandPath(ctx.GlobalString(baseDirFlag.Name))
180201
if baseDir != terminal.DefaultLitDir {
181202
tlsCertPath = filepath.Join(
182203
baseDir, terminal.DefaultTLSCertFilename,
183204
)
184205
}
185206

186-
return tlsCertPath, nil
207+
return tlsCertPath, macaroonPath, nil
187208
}
188209

189-
func printRespJSON(resp proto.Message) { // nolint
190-
jsonMarshaler := &jsonpb.Marshaler{
191-
EmitDefaults: true,
192-
OrigName: true,
193-
Indent: "\t", // Matches indentation of printJSON.
210+
// readMacaroon tries to read the macaroon file at the specified path and create
211+
// gRPC dial options from it.
212+
func readMacaroon(macPath string) (grpc.DialOption, error) {
213+
// Load the specified macaroon file.
214+
macBytes, err := ioutil.ReadFile(macPath)
215+
if err != nil {
216+
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
194217
}
195218

196-
jsonStr, err := jsonMarshaler.MarshalToString(resp)
197-
if err != nil {
198-
fmt.Println("unable to decode response: ", err)
199-
return
219+
mac := &macaroon.Macaroon{}
220+
if err = mac.UnmarshalBinary(macBytes); err != nil {
221+
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
200222
}
201223

202-
fmt.Println(jsonStr)
203-
}
224+
macConstraints := []macaroons.Constraint{
225+
// We add a time-based constraint to prevent replay of the
226+
// macaroon. It's good for 60 seconds by default to make up for
227+
// any discrepancy between client and server clocks, but leaking
228+
// the macaroon before it becomes invalid makes it possible for
229+
// an attacker to reuse the macaroon. In addition, the validity
230+
// time of the macaroon is extended by the time the server clock
231+
// is behind the client clock, or shortened by the time the
232+
// server clock is ahead of the client clock (or invalid
233+
// altogether if, in the latter case, this time is more than 60
234+
// seconds).
235+
macaroons.TimeoutConstraint(defaultMacaroonTimeout),
236+
}
204237

205-
func getAuthContext(cliCtx *cli.Context) context.Context {
206-
uiPassword, err := getUIPassword(cliCtx)
238+
// Apply constraints to the macaroon.
239+
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
207240
if err != nil {
208-
fatal(err)
241+
return nil, err
209242
}
210243

211-
basicAuth := base64.StdEncoding.EncodeToString(
212-
[]byte(fmt.Sprintf("%s:%s", uiPassword, uiPassword)),
213-
)
214-
215-
ctxb := context.Background()
216-
md := metadata.MD{}
217-
218-
md.Set("macaroon", hex.EncodeToString(terminal.EmptyMacaroonBytes))
219-
md.Set("authorization", fmt.Sprintf("Basic %s", basicAuth))
220-
221-
return metadata.NewOutgoingContext(ctxb, md)
222-
}
223-
224-
func getUIPassword(ctx *cli.Context) (string, error) {
225-
// The command line flag has precedence.
226-
uiPassword := strings.TrimSpace(ctx.GlobalString(uiPasswordFlag.Name))
227-
228-
// To automate things with litcli, we also offer reading the password
229-
// from environment variables if the flag wasn't specified.
230-
if uiPassword == "" {
231-
uiPassword = strings.TrimSpace(os.Getenv(uiPasswordEnvName))
244+
// Now we append the macaroon credentials to the dial options.
245+
cred, err := macaroons.NewMacaroonCredential(constrainedMac)
246+
if err != nil {
247+
return nil, fmt.Errorf("error creating macaroon credential: %v",
248+
err)
232249
}
250+
return grpc.WithPerRPCCredentials(cred), nil
251+
}
233252

234-
if uiPassword == "" {
235-
// If there's no value in the environment, we'll now prompt the
236-
// user to enter their password on the terminal.
237-
fmt.Printf("Input your LiT UI password: ")
238-
239-
// The variable syscall.Stdin is of a different type in the
240-
// Windows API that's why we need the explicit cast. And of
241-
// course the linter doesn't like it either.
242-
pw, err := term.ReadPassword(int(syscall.Stdin)) // nolint:unconvert
243-
fmt.Println()
244-
245-
if err != nil {
246-
return "", err
247-
}
248-
uiPassword = strings.TrimSpace(string(pw))
253+
func printRespJSON(resp proto.Message) { // nolint
254+
jsonMarshaler := &jsonpb.Marshaler{
255+
EmitDefaults: true,
256+
OrigName: true,
257+
Indent: "\t", // Matches indentation of printJSON.
249258
}
250259

251-
if uiPassword == "" {
252-
return "", fmt.Errorf("no UI password provided")
260+
jsonStr, err := jsonMarshaler.MarshalToString(resp)
261+
if err != nil {
262+
fmt.Println("unable to decode response: ", err)
263+
return
253264
}
254265

255-
return uiPassword, nil
266+
fmt.Println(jsonStr)
256267
}

cmd/litcli/sessions.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"encoding/hex"
56
"fmt"
67
"time"
@@ -88,8 +89,9 @@ func addSession(ctx *cli.Context) error {
8889
sessionLength := time.Second * time.Duration(ctx.Uint64("expiry"))
8990
sessionExpiry := time.Now().Add(sessionLength).Unix()
9091

92+
ctxb := context.Background()
9193
resp, err := client.AddSession(
92-
getAuthContext(ctx), &litrpc.AddSessionRequest{
94+
ctxb, &litrpc.AddSessionRequest{
9395
Label: label,
9496
SessionType: sessType,
9597
ExpiryTimestampSeconds: uint64(sessionExpiry),
@@ -196,8 +198,9 @@ func listSessions(filter sessionFilter) func(ctx *cli.Context) error {
196198
}
197199
defer cleanup()
198200

201+
ctxb := context.Background()
199202
resp, err := client.ListSessions(
200-
getAuthContext(ctx), &litrpc.ListSessionsRequest{},
203+
ctxb, &litrpc.ListSessionsRequest{},
201204
)
202205
if err != nil {
203206
return err
@@ -248,8 +251,9 @@ func revokeSession(ctx *cli.Context) error {
248251
return err
249252
}
250253

254+
ctxb := context.Background()
251255
resp, err := client.RevokeSession(
252-
getAuthContext(ctx), &litrpc.RevokeSessionRequest{
256+
ctxb, &litrpc.RevokeSessionRequest{
253257
LocalPublicKey: pubkey,
254258
},
255259
)

0 commit comments

Comments
 (0)