11package main
22
33import (
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
2724const (
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
3330var (
@@ -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
10499func 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}
0 commit comments