11package main
22
33import (
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
2623const (
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
3229var (
@@ -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
10398func 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
188210func 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}
0 commit comments