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
@@ -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
105100func 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}
0 commit comments