Skip to content

Commit 7a4d84a

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 won't have a macaroon to use on disk and one must be baked specifically.
1 parent f011bc8 commit 7a4d84a

File tree

3 files changed

+97
-91
lines changed

3 files changed

+97
-91
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

@@ -87,7 +82,7 @@ func main() {
8782
lndMode,
8883
tlsCertFlag,
8984
lndTlsCertFlag,
90-
uiPasswordFlag,
85+
macaroonPathFlag,
9186
}
9287
app.Commands = append(app.Commands, sessionCommands...)
9388

@@ -104,11 +99,11 @@ func fatal(err error) {
10499

105100
func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
106101
rpcServer := ctx.GlobalString("rpcserver")
107-
tlsCertPath, err := extractPathArgs(ctx)
102+
tlsCertPath, macPath, err := extractPathArgs(ctx)
108103
if err != nil {
109104
return nil, nil, err
110105
}
111-
conn, err := getClientConn(rpcServer, tlsCertPath)
106+
conn, err := getClientConn(rpcServer, tlsCertPath, macPath)
112107
if err != nil {
113108
return nil, nil, err
114109
}
@@ -118,9 +113,18 @@ func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
118113
return sessionsClient, cleanup, nil
119114
}
120115

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

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

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

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

162-
return tlsCertPath, nil
184+
return tlsCertPath, macaroonPath, nil
163185
}
164186

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

170192
// If a custom TLS path was set, use it as is.
171193
if tlsCertPath != terminal.DefaultTLSCertPath {
172-
return tlsCertPath, nil
194+
return tlsCertPath, macaroonPath, nil
173195
}
174196

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

187-
return tlsCertPath, nil
208+
return tlsCertPath, macaroonPath, nil
188209
}
189210

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

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

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

206-
func getAuthContext(cliCtx *cli.Context) context.Context {
207-
uiPassword, err := getUIPassword(cliCtx)
239+
// Apply constraints to the macaroon.
240+
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
208241
if err != nil {
209-
fatal(err)
242+
return nil, err
210243
}
211244

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

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

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

256-
return uiPassword, nil
267+
fmt.Println(jsonStr)
257268
}

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
)

rpc_proxy.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,6 @@ const (
3636
HeaderMacaroon = "Macaroon"
3737
)
3838

39-
var (
40-
// EmptyMacaroonBytes is the byte representation of an empty but
41-
// formally valid macaroon.
42-
EmptyMacaroonBytes, _ = hex.DecodeString(
43-
"020205656d7074790000062062083e2ea599285ac29350abb4ea21fd7c5a" +
44-
"15aca8b4c0d38e6c058829369e50",
45-
)
46-
)
47-
4839
// proxyErr is an error type that adds more context to an error occurring in the
4940
// proxy.
5041
type proxyErr struct {

0 commit comments

Comments
 (0)