Skip to content

Commit ef27e42

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 b1f6599 commit ef27e42

File tree

2 files changed

+90
-74
lines changed

2 files changed

+90
-74
lines changed

cmd/litcli/main.go

Lines changed: 83 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
package main
22

33
import (
4-
"context"
5-
"encoding/base64"
64
"fmt"
7-
"os"
8-
"path/filepath"
9-
"strings"
10-
"syscall"
11-
125
terminal "github.com/lightninglabs/lightning-terminal"
136
"github.com/lightninglabs/lightning-terminal/litrpc"
147
"github.com/lightninglabs/lndclient"
158
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
169
"github.com/lightninglabs/protobuf-hex-display/proto"
1710
"github.com/lightningnetwork/lnd"
1811
"github.com/lightningnetwork/lnd/lncfg"
12+
"github.com/lightningnetwork/lnd/macaroons"
1913
"github.com/urfave/cli"
20-
"golang.org/x/term"
2114
"google.golang.org/grpc"
2215
"google.golang.org/grpc/credentials"
23-
"google.golang.org/grpc/metadata"
16+
"gopkg.in/macaroon.v2"
17+
"io/ioutil"
18+
"os"
19+
"path/filepath"
20+
"strings"
2421
)
2522

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

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

@@ -85,7 +80,7 @@ func main() {
8580
lndMode,
8681
tlsCertFlag,
8782
lndTlsCertFlag,
88-
uiPasswordFlag,
83+
macaroonPathFlag,
8984
}
9085
app.Commands = append(app.Commands, sessionCommands...)
9186

@@ -102,11 +97,12 @@ func fatal(err error) {
10297

10398
func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
10499
rpcServer := ctx.GlobalString("rpcserver")
105-
tlsCertPath, err := extractPathArgs(ctx)
100+
tlsCertPath, macPath, err := extractPathArgs(ctx)
106101
if err != nil {
107102
return nil, nil, err
108103
}
109-
conn, err := getClientConn(rpcServer, tlsCertPath)
104+
fmt.Println(macPath)
105+
conn, err := getClientConn(rpcServer, tlsCertPath, macPath)
110106
if err != nil {
111107
return nil, nil, err
112108
}
@@ -116,9 +112,18 @@ func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
116112
return sessionsClient, cleanup, nil
117113
}
118114

119-
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+
120124
opts := []grpc.DialOption{
121125
grpc.WithDefaultCallOptions(maxMsgRecvSize),
126+
macOption,
122127
}
123128

124129
// TLS cannot be disabled, we'll always have a cert file to read.
@@ -138,15 +143,33 @@ func getClientConn(address, tlsCertPath string) (*grpc.ClientConn, error) {
138143
return conn, nil
139144
}
140145

141-
// extractPathArgs parses the TLS certificate from the command.
142-
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) {
143149
// We'll start off by parsing the network. This is needed to determine
144150
// the correct path to the TLS certificate and macaroon when not
145151
// specified.
146152
networkStr := strings.ToLower(ctx.GlobalString("network"))
147153
_, err := lndclient.Network(networkStr).ChainParams()
148154
if err != nil {
149-
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+
)
150173
}
151174

152175
// Get the LND mode. If Lit is in integrated LND mode, then LND's tls
@@ -157,7 +180,7 @@ func extractPathArgs(ctx *cli.Context) (string, error) {
157180
lndTlsCertFlag.Name,
158181
))
159182

160-
return tlsCertPath, nil
183+
return tlsCertPath, macaroonPath, nil
161184
}
162185

163186
// Lit is in remote LND mode. So we need Lit's tls cert.
@@ -167,22 +190,21 @@ func extractPathArgs(ctx *cli.Context) (string, error) {
167190

168191
// If a custom TLS path was set, use it as is.
169192
if tlsCertPath != terminal.DefaultTLSCertPath {
170-
return tlsCertPath, nil
193+
return tlsCertPath, macaroonPath, nil
171194
}
172195

173196
// If a custom base directory was set, we'll also check if custom paths
174197
// for the TLS cert file was set as well. If not, we'll override the
175198
// paths so they can be found within the custom base directory set.
176199
// This allows us to set a custom base directory, along with custom
177200
// paths to the TLS cert file.
178-
baseDir := lncfg.CleanAndExpandPath(ctx.GlobalString(baseDirFlag.Name))
179201
if baseDir != terminal.DefaultLitDir {
180202
tlsCertPath = filepath.Join(
181203
baseDir, terminal.DefaultTLSCertFilename,
182204
)
183205
}
184206

185-
return tlsCertPath, nil
207+
return tlsCertPath, macaroonPath, nil
186208
}
187209

188210
func printRespJSON(resp proto.Message) { // nolint
@@ -201,55 +223,45 @@ func printRespJSON(resp proto.Message) { // nolint
201223
fmt.Println(jsonStr)
202224
}
203225

204-
func getAuthContext(cliCtx *cli.Context) context.Context {
205-
uiPassword, err := getUIPassword(cliCtx)
226+
// readMacaroon tries to read the macaroon file at the specified path and create
227+
// gRPC dial options from it.
228+
func readMacaroon(macPath string) (grpc.DialOption, error) {
229+
// Load the specified macaroon file.
230+
macBytes, err := ioutil.ReadFile(macPath)
206231
if err != nil {
207-
fatal(err)
232+
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
208233
}
209234

210-
basicAuth := base64.StdEncoding.EncodeToString(
211-
[]byte(fmt.Sprintf("%s:%s", uiPassword, uiPassword)),
212-
)
213-
214-
ctxb := context.Background()
215-
md := metadata.MD{}
216-
217-
md.Set("macaroon", "no-macaroons-for-litcli")
218-
md.Set("authorization", fmt.Sprintf("Basic %s", basicAuth))
219-
220-
return metadata.NewOutgoingContext(ctxb, md)
221-
}
222-
223-
func getUIPassword(ctx *cli.Context) (string, error) {
224-
// The command line flag has precedence.
225-
uiPassword := strings.TrimSpace(ctx.GlobalString(uiPasswordFlag.Name))
226-
227-
// To automate things with litcli, we also offer reading the password
228-
// from environment variables if the flag wasn't specified.
229-
if uiPassword == "" {
230-
uiPassword = strings.TrimSpace(os.Getenv(uiPasswordEnvName))
235+
mac := &macaroon.Macaroon{}
236+
if err = mac.UnmarshalBinary(macBytes); err != nil {
237+
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
231238
}
232239

233-
if uiPassword == "" {
234-
// If there's no value in the environment, we'll now prompt the
235-
// user to enter their password on the terminal.
236-
fmt.Printf("Input your LiT UI password: ")
237-
238-
// The variable syscall.Stdin is of a different type in the
239-
// Windows API that's why we need the explicit cast. And of
240-
// course the linter doesn't like it either.
241-
pw, err := term.ReadPassword(int(syscall.Stdin)) // nolint:unconvert
242-
fmt.Println()
243-
244-
if err != nil {
245-
return "", err
246-
}
247-
uiPassword = strings.TrimSpace(string(pw))
240+
macConstraints := []macaroons.Constraint{
241+
// We add a time-based constraint to prevent replay of the
242+
// macaroon. It's good for 60 seconds by default to make up for
243+
// any discrepancy between client and server clocks, but leaking
244+
// the macaroon before it becomes invalid makes it possible for
245+
// an attacker to reuse the macaroon. In addition, the validity
246+
// time of the macaroon is extended by the time the server clock
247+
// is behind the client clock, or shortened by the time the
248+
// server clock is ahead of the client clock (or invalid
249+
// altogether if, in the latter case, this time is more than 60
250+
// seconds).
251+
macaroons.TimeoutConstraint(defaultMacaroonTimeout),
248252
}
249253

250-
if uiPassword == "" {
251-
return "", fmt.Errorf("no UI password provided")
254+
// Apply constraints to the macaroon.
255+
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
256+
if err != nil {
257+
return nil, err
252258
}
253259

254-
return uiPassword, nil
260+
// Now we append the macaroon credentials to the dial options.
261+
cred, err := macaroons.NewMacaroonCredential(constrainedMac)
262+
if err != nil {
263+
return nil, fmt.Errorf("error creating macaroon credential: %v",
264+
err)
265+
}
266+
return grpc.WithPerRPCCredentials(cred), nil
255267
}

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"
@@ -68,8 +69,9 @@ func addSession(ctx *cli.Context) error {
6869
sessionLength := time.Second * time.Duration(ctx.Uint64("expiry"))
6970
sessionExpiry := time.Now().Add(sessionLength).Unix()
7071

72+
ctxb := context.Background()
7173
resp, err := client.AddSession(
72-
getAuthContext(ctx), &litrpc.AddSessionRequest{
74+
ctxb, &litrpc.AddSessionRequest{
7375
Label: label,
7476
SessionType: litrpc.SessionType_TYPE_UI_PASSWORD,
7577
ExpiryTimestampSeconds: uint64(sessionExpiry),
@@ -165,8 +167,9 @@ func listSessions(filter sessionFilter) func(ctx *cli.Context) error {
165167
}
166168
defer cleanup()
167169

170+
ctxb := context.Background()
168171
resp, err := client.ListSessions(
169-
getAuthContext(ctx), &litrpc.ListSessionsRequest{},
172+
ctxb, &litrpc.ListSessionsRequest{},
170173
)
171174
if err != nil {
172175
return err
@@ -217,8 +220,9 @@ func revokeSession(ctx *cli.Context) error {
217220
return err
218221
}
219222

223+
ctxb := context.Background()
220224
resp, err := client.RevokeSession(
221-
getAuthContext(ctx), &litrpc.RevokeSessionRequest{
225+
ctxb, &litrpc.RevokeSessionRequest{
222226
LocalPublicKey: pubkey,
223227
},
224228
)

0 commit comments

Comments
 (0)