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,11 @@ 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+ conn , err := getClientConn (rpcServer , tlsCertPath , macPath )
110105 if err != nil {
111106 return nil , nil , err
112107 }
@@ -116,9 +111,18 @@ func getClient(ctx *cli.Context) (litrpc.SessionsClient, func(), error) {
116111 return sessionsClient , cleanup , nil
117112}
118113
119- func getClientConn (address , tlsCertPath string ) (* grpc.ClientConn , error ) {
114+ func getClientConn (address , tlsCertPath , macaroonPath string ) (* grpc.ClientConn ,
115+ error ) {
116+
117+ // We always need to send a macaroon.
118+ macOption , err := readMacaroon (macaroonPath )
119+ if err != nil {
120+ return nil , err
121+ }
122+
120123 opts := []grpc.DialOption {
121124 grpc .WithDefaultCallOptions (maxMsgRecvSize ),
125+ macOption ,
122126 }
123127
124128 // TLS cannot be disabled, we'll always have a cert file to read.
@@ -138,15 +142,33 @@ func getClientConn(address, tlsCertPath string) (*grpc.ClientConn, error) {
138142 return conn , nil
139143}
140144
141- // extractPathArgs parses the TLS certificate from the command.
142- func extractPathArgs (ctx * cli.Context ) (string , error ) {
145+ // extractPathArgs parses the TLS certificate and macaroon paths from the
146+ // command.
147+ func extractPathArgs (ctx * cli.Context ) (string , string , error ) {
143148 // We'll start off by parsing the network. This is needed to determine
144149 // the correct path to the TLS certificate and macaroon when not
145150 // specified.
146151 networkStr := strings .ToLower (ctx .GlobalString ("network" ))
147152 _ , err := lndclient .Network (networkStr ).ChainParams ()
148153 if err != nil {
149- return "" , err
154+ return "" , "" , err
155+ }
156+
157+ // Get the base dir so that we can reconstruct the default tls and
158+ // macaroon paths if needed.
159+ baseDir := lncfg .CleanAndExpandPath (ctx .GlobalString (baseDirFlag .Name ))
160+
161+ macaroonPath := lncfg .CleanAndExpandPath (ctx .GlobalString (
162+ macaroonPathFlag .Name ,
163+ ))
164+
165+ // If the macaroon path flag has not been set to a custom value,
166+ // then reconstruct it with the possibly new base dir and network
167+ // values.
168+ if macaroonPath == terminal .DefaultMacaroonPath {
169+ macaroonPath = filepath .Join (
170+ baseDir , networkStr , terminal .DefaultMacaroonFilename ,
171+ )
150172 }
151173
152174 // Get the LND mode. If Lit is in integrated LND mode, then LND's tls
@@ -157,7 +179,7 @@ func extractPathArgs(ctx *cli.Context) (string, error) {
157179 lndTlsCertFlag .Name ,
158180 ))
159181
160- return tlsCertPath , nil
182+ return tlsCertPath , macaroonPath , nil
161183 }
162184
163185 // Lit is in remote LND mode. So we need Lit's tls cert.
@@ -167,89 +189,78 @@ func extractPathArgs(ctx *cli.Context) (string, error) {
167189
168190 // If a custom TLS path was set, use it as is.
169191 if tlsCertPath != terminal .DefaultTLSCertPath {
170- return tlsCertPath , nil
192+ return tlsCertPath , macaroonPath , nil
171193 }
172194
173195 // If a custom base directory was set, we'll also check if custom paths
174196 // for the TLS cert file was set as well. If not, we'll override the
175197 // paths so they can be found within the custom base directory set.
176198 // This allows us to set a custom base directory, along with custom
177199 // paths to the TLS cert file.
178- baseDir := lncfg .CleanAndExpandPath (ctx .GlobalString (baseDirFlag .Name ))
179200 if baseDir != terminal .DefaultLitDir {
180201 tlsCertPath = filepath .Join (
181202 baseDir , terminal .DefaultTLSCertFilename ,
182203 )
183204 }
184205
185- return tlsCertPath , nil
206+ return tlsCertPath , macaroonPath , nil
186207}
187208
188- func printRespJSON (resp proto.Message ) { // nolint
189- jsonMarshaler := & jsonpb.Marshaler {
190- EmitDefaults : true ,
191- OrigName : true ,
192- Indent : "\t " , // Matches indentation of printJSON.
209+ // readMacaroon tries to read the macaroon file at the specified path and create
210+ // gRPC dial options from it.
211+ func readMacaroon (macPath string ) (grpc.DialOption , error ) {
212+ // Load the specified macaroon file.
213+ macBytes , err := ioutil .ReadFile (macPath )
214+ if err != nil {
215+ return nil , fmt .Errorf ("unable to read macaroon path : %v" , err )
193216 }
194217
195- jsonStr , err := jsonMarshaler .MarshalToString (resp )
196- if err != nil {
197- fmt .Println ("unable to decode response: " , err )
198- return
218+ mac := & macaroon.Macaroon {}
219+ if err = mac .UnmarshalBinary (macBytes ); err != nil {
220+ return nil , fmt .Errorf ("unable to decode macaroon: %v" , err )
199221 }
200222
201- fmt .Println (jsonStr )
202- }
223+ macConstraints := []macaroons.Constraint {
224+ // We add a time-based constraint to prevent replay of the
225+ // macaroon. It's good for 60 seconds by default to make up for
226+ // any discrepancy between client and server clocks, but leaking
227+ // the macaroon before it becomes invalid makes it possible for
228+ // an attacker to reuse the macaroon. In addition, the validity
229+ // time of the macaroon is extended by the time the server clock
230+ // is behind the client clock, or shortened by the time the
231+ // server clock is ahead of the client clock (or invalid
232+ // altogether if, in the latter case, this time is more than 60
233+ // seconds).
234+ macaroons .TimeoutConstraint (defaultMacaroonTimeout ),
235+ }
203236
204- func getAuthContext ( cliCtx * cli. Context ) context. Context {
205- uiPassword , err := getUIPassword ( cliCtx )
237+ // Apply constraints to the macaroon.
238+ constrainedMac , err := macaroons . AddConstraints ( mac , macConstraints ... )
206239 if err != nil {
207- fatal ( err )
240+ return nil , err
208241 }
209242
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 ))
243+ // Now we append the macaroon credentials to the dial options.
244+ cred , err := macaroons .NewMacaroonCredential (constrainedMac )
245+ if err != nil {
246+ return nil , fmt .Errorf ("error creating macaroon credential: %v" ,
247+ err )
231248 }
249+ return grpc .WithPerRPCCredentials (cred ), nil
250+ }
232251
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 ))
252+ func printRespJSON (resp proto.Message ) { // nolint
253+ jsonMarshaler := & jsonpb.Marshaler {
254+ EmitDefaults : true ,
255+ OrigName : true ,
256+ Indent : "\t " , // Matches indentation of printJSON.
248257 }
249258
250- if uiPassword == "" {
251- return "" , fmt .Errorf ("no UI password provided" )
259+ jsonStr , err := jsonMarshaler .MarshalToString (resp )
260+ if err != nil {
261+ fmt .Println ("unable to decode response: " , err )
262+ return
252263 }
253264
254- return uiPassword , nil
265+ fmt . Println ( jsonStr )
255266}
0 commit comments