@@ -3,6 +3,7 @@ package shushtar
3
3
import (
4
4
"context"
5
5
"crypto/tls"
6
+ "encoding/base64"
6
7
"errors"
7
8
"fmt"
8
9
"io/ioutil"
@@ -30,9 +31,11 @@ import (
30
31
"github.com/rakyll/statik/fs"
31
32
"google.golang.org/grpc"
32
33
"google.golang.org/grpc/backoff"
34
+ "google.golang.org/grpc/codes"
33
35
"google.golang.org/grpc/credentials"
34
36
"google.golang.org/grpc/grpclog"
35
37
"google.golang.org/grpc/metadata"
38
+ "google.golang.org/grpc/status"
36
39
"gopkg.in/macaroon.v2"
37
40
38
41
// Import generated go package that contains all static files for the
@@ -41,7 +44,6 @@ import (
41
44
)
42
45
43
46
const (
44
- defaultHTTPSListen = "127.0.0.1:8443"
45
47
defaultServerTimeout = 10 * time .Second
46
48
defaultStartupTimeout = 5 * time .Second
47
49
)
51
53
// set this to 200MiB atm.
52
54
maxMsgRecvSize = grpc .MaxCallRecvMsgSize (1 * 1024 * 1024 * 200 )
53
55
56
+ authError = status .Error (
57
+ codes .Unauthenticated , "authentication required" ,
58
+ )
59
+
54
60
lndDefaultConfig = lnd .DefaultConfig ()
55
61
faradayDefaultConfig = faraday .DefaultConfig ()
56
62
loopDefaultConfig = loopd .DefaultConfig ()
@@ -102,6 +108,15 @@ func (g *Shushtar) Run() error {
102
108
return err
103
109
}
104
110
111
+ err = readUIPassword (g .cfg )
112
+ if err != nil {
113
+ return fmt .Errorf ("could not read UI password: %v" , err )
114
+ }
115
+ if len (g .cfg .UIPassword ) < uiPasswordMinLength {
116
+ return fmt .Errorf ("please set a strong password for the UI, " +
117
+ "at least %d characters long" , uiPasswordMinLength )
118
+ }
119
+
105
120
// Load the configuration, and parse any command line options. This
106
121
// function will also set up logging properly.
107
122
g .cfg .Lnd , err = loadLndConfig (g .cfg )
@@ -416,7 +431,7 @@ func (g *Shushtar) startGrpcWebProxy() error {
416
431
// admin macaroon and converts the browser's gRPC web calls into native
417
432
// gRPC.
418
433
lndGrpcServer , grpcServer , err := buildGrpcWebProxyServer (
419
- g .lndAddr , g .cfg .Lnd ,
434
+ g .lndAddr , g .cfg .UIPassword , g . cfg . Lnd ,
420
435
)
421
436
if err != nil {
422
437
return fmt .Errorf ("could not create gRPC web proxy: %v" , err )
@@ -480,7 +495,7 @@ func (g *Shushtar) startGrpcWebProxy() error {
480
495
// buildGrpcWebProxyServer creates a gRPC server that will serve gRPC web to the
481
496
// browser and translate all incoming gRPC web calls into native gRPC that are
482
497
// then forwarded to lnd's RPC interface.
483
- func buildGrpcWebProxyServer (lndAddr string ,
498
+ func buildGrpcWebProxyServer (lndAddr , uiPassword string ,
484
499
config * lnd.Config ) (* grpcweb.WrappedGrpcServer , * grpc.Server , error ) {
485
500
486
501
// Apply gRPC-wide changes.
@@ -489,14 +504,21 @@ func buildGrpcWebProxyServer(lndAddr string,
489
504
config .LogWriter , GrpcLogSubsystem ,
490
505
))
491
506
507
+ // The gRPC web calls are protected by HTTP basic auth which is defined
508
+ // by base64(username:password). Because we only have a password, we
509
+ // just use base64(password:password).
510
+ basicAuth := base64 .StdEncoding .EncodeToString ([]byte (fmt .Sprintf (
511
+ "%s:%s" , uiPassword , uiPassword ,
512
+ )))
513
+
492
514
// Setup the connection to lnd. GRPC web has a few kinks that need to be
493
515
// addressed with a custom director that just takes care of a few HTTP
494
516
// header fields.
495
517
backendConn , err := dialLnd (lndAddr , config )
496
518
if err != nil {
497
519
return nil , nil , fmt .Errorf ("could not dial lnd: %v" , err )
498
520
}
499
- director := newDirector (backendConn )
521
+ director := newDirector (backendConn , basicAuth )
500
522
501
523
// Set up the final gRPC server that will serve gRPC web to the browser
502
524
// and translate all incoming gRPC web calls into native gRPC that are
@@ -515,16 +537,30 @@ func buildGrpcWebProxyServer(lndAddr string,
515
537
516
538
// newDirector returns a new director function that fixes some common known
517
539
// issues when using gRPC web from the browser.
518
- func newDirector (backendConn * grpc.ClientConn ) proxy.StreamDirector {
540
+ func newDirector (backendConn * grpc.ClientConn ,
541
+ basicAuth string ) proxy.StreamDirector {
542
+
519
543
return func (ctx context.Context , fullMethodName string ) (context.Context ,
520
544
* grpc.ClientConn , error ) {
521
545
522
546
md , _ := metadata .FromIncomingContext (ctx )
523
- mdCopy := md .Copy ()
547
+
548
+ authHeaders := md .Get ("authorization" )
549
+ if len (authHeaders ) == 0 {
550
+ return nil , nil , authError
551
+ }
552
+ authHeaderParts := strings .Split (authHeaders [0 ], " " )
553
+ if len (authHeaderParts ) != 2 {
554
+ return nil , nil , authError
555
+ }
556
+ if authHeaderParts [1 ] != basicAuth {
557
+ return nil , nil , authError
558
+ }
524
559
525
560
// If this header is present in the request from the web client,
526
561
// the actual connection to the backend will not be established.
527
562
// https://github.com/improbable-eng/grpc-web/issues/568
563
+ mdCopy := md .Copy ()
528
564
delete (mdCopy , "connection" )
529
565
530
566
outCtx := metadata .NewOutgoingContext (ctx , mdCopy )
0 commit comments