Skip to content

Commit b99c5c0

Browse files
authored
Merge pull request #319 from lightninglabs/macaroon-remote-mode
Support super macaroon in remote lnd mode
2 parents 59cc505 + c3ff7e4 commit b99c5c0

File tree

10 files changed

+480
-132
lines changed

10 files changed

+480
-132
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,29 @@ Lightning Terminal is backwards compatible with `lnd` back to version v0.13.3-be
8686
| **v0.3.0-alpha** | v0.11.1-beta |
8787
| **v0.2.0-alpha** | v0.11.0-beta |
8888

89+
LiT offers two main operating modes, one in which [`lnd` is running inside the
90+
LiT process (called "lnd integrated mode", set by `lnd-mode=integrated` config
91+
option)](doc/config-lnd-integrated.md) and one in which [`lnd` is running in
92+
a standalone process on the same or remote machine (called "lnd remote mode",
93+
set by `lnd-mode=remote` config option)](doc/config-lnd-remote.md).
94+
95+
In addition to those main modes, the individual bundled daemons (Faraday, Loop
96+
and Pool) can be toggled to be integrated or remote as well. This offers a
97+
large number of possible configuration combinations, of which not all are
98+
fully supported due to technical reasons.
99+
100+
The following table shows the supported combinations:
101+
102+
| | `lnd-mode=integrated` | `lnd-mode=remote` |
103+
|----------------------------------------|-----------------------|-------------------|
104+
| `faraday-mode=integrated` | X | X |
105+
| `loop-mode=integrated` | X | X |
106+
| `pool-mode=integrated` | X | X |
107+
| `faraday-mode=remote` | | X |
108+
| `loop-mode=remote` | | X |
109+
| `pool-mode=remote` | | X |
110+
| `lnd` running in "stateless init" mode | X | |
111+
89112
## Daemon Versions packaged with LiT
90113

91114
| LiT | LND | Loop | Faraday | Pool |

cmd/litcli/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"encoding/base64"
6+
"encoding/hex"
67
"fmt"
78
"os"
89
"path/filepath"
@@ -210,7 +211,7 @@ func getAuthContext(cliCtx *cli.Context) context.Context {
210211
ctxb := context.Background()
211212
md := metadata.MD{}
212213

213-
md.Set("macaroon", "no-macaroons-for-litcli")
214+
md.Set("macaroon", hex.EncodeToString(terminal.EmptyMacaroonBytes))
214215
md.Set("authorization", fmt.Sprintf("Basic %s", basicAuth))
215216

216217
return metadata.NewOutgoingContext(ctxb, md)

itest/litd_mode_integrated_test.go

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import (
1010
"fmt"
1111
"io/ioutil"
1212
"net/http"
13+
"os"
1314
"strings"
1415
"testing"
1516
"time"
1617

1718
"github.com/btcsuite/btcutil"
1819
"github.com/lightninglabs/faraday/frdrpc"
20+
terminal "github.com/lightninglabs/lightning-terminal"
1921
"github.com/lightninglabs/lightning-terminal/litrpc"
22+
"github.com/lightninglabs/lightning-terminal/session"
2023
"github.com/lightninglabs/loop/looprpc"
2124
"github.com/lightninglabs/pool/poolrpc"
2225
"github.com/lightningnetwork/lnd/lnrpc"
@@ -135,6 +138,7 @@ var (
135138
supportsUIPasswordOnLndPort bool
136139
supportsUIPasswordOnLitPort bool
137140
grpcWebURI string
141+
restWebURI string
138142
}{{
139143
name: "lnrpc",
140144
macaroonFn: lndMacaroonFn,
@@ -145,6 +149,7 @@ var (
145149
supportsUIPasswordOnLndPort: false,
146150
supportsUIPasswordOnLitPort: true,
147151
grpcWebURI: "/lnrpc.Lightning/GetInfo",
152+
restWebURI: "/v1/getinfo",
148153
}, {
149154
name: "frdrpc",
150155
macaroonFn: faradayMacaroonFn,
@@ -155,6 +160,7 @@ var (
155160
supportsUIPasswordOnLndPort: false,
156161
supportsUIPasswordOnLitPort: true,
157162
grpcWebURI: "/frdrpc.FaradayServer/RevenueReport",
163+
restWebURI: "/v1/faraday/revenue",
158164
}, {
159165
name: "looprpc",
160166
macaroonFn: loopMacaroonFn,
@@ -165,6 +171,7 @@ var (
165171
supportsUIPasswordOnLndPort: false,
166172
supportsUIPasswordOnLitPort: true,
167173
grpcWebURI: "/looprpc.SwapClient/ListSwaps",
174+
restWebURI: "/v1/loop/swaps",
168175
}, {
169176
name: "poolrpc",
170177
macaroonFn: poolMacaroonFn,
@@ -175,6 +182,7 @@ var (
175182
supportsUIPasswordOnLndPort: false,
176183
supportsUIPasswordOnLitPort: true,
177184
grpcWebURI: "/poolrpc.Trader/GetInfo",
185+
restWebURI: "/v1/pool/info",
178186
}, {
179187
name: "litrpc",
180188
macaroonFn: nil,
@@ -283,6 +291,67 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
283291
})
284292
}
285293
})
294+
295+
t.t.Run("gRPC super macaroon auth check", func(tt *testing.T) {
296+
cfg := net.Alice.Cfg
297+
298+
superMacFile, err := bakeSuperMacaroon(cfg, true)
299+
require.NoError(tt, err)
300+
301+
defer func() {
302+
_ = os.Remove(superMacFile)
303+
}()
304+
305+
for _, endpoint := range endpoints {
306+
endpoint := endpoint
307+
tt.Run(endpoint.name+" lnd port", func(ttt *testing.T) {
308+
if !endpoint.supportsMacAuthOnLndPort {
309+
return
310+
}
311+
312+
runGRPCAuthTest(
313+
ttt, cfg.RPCAddr(), cfg.TLSCertPath,
314+
superMacFile,
315+
endpoint.requestFn,
316+
endpoint.successPattern,
317+
)
318+
})
319+
320+
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
321+
if !endpoint.supportsMacAuthOnLitPort {
322+
return
323+
}
324+
325+
runGRPCAuthTest(
326+
ttt, cfg.LitAddr(), cfg.TLSCertPath,
327+
superMacFile,
328+
endpoint.requestFn,
329+
endpoint.successPattern,
330+
)
331+
})
332+
}
333+
})
334+
335+
t.t.Run("REST auth", func(tt *testing.T) {
336+
cfg := net.Alice.Cfg
337+
338+
for _, endpoint := range endpoints {
339+
endpoint := endpoint
340+
341+
if endpoint.restWebURI == "" {
342+
continue
343+
}
344+
345+
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
346+
runRESTAuthTest(
347+
ttt, cfg.LitAddr(), cfg.UIPassword,
348+
endpoint.macaroonFn(cfg),
349+
endpoint.restWebURI,
350+
endpoint.successPattern,
351+
)
352+
})
353+
}
354+
})
286355
}
287356

288357
// runCertificateCheck checks that the TLS certificates presented to clients are
@@ -470,6 +539,58 @@ func runGRPCWebAuthTest(t *testing.T, hostPort, uiPassword, grpcWebURI string) {
470539
require.Contains(t, body, "grpc-status: 0")
471540
}
472541

542+
// runRESTAuthTest tests authentication of the given REST interface.
543+
func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,
544+
successPattern string) {
545+
546+
basicAuth := base64.StdEncoding.EncodeToString(
547+
[]byte(fmt.Sprintf("%s:%s", uiPassword, uiPassword)),
548+
)
549+
basicAuthHeader := http.Header{
550+
"authorization": []string{fmt.Sprintf("Basic %s", basicAuth)},
551+
}
552+
url := fmt.Sprintf("https://%s%s", hostPort, restURI)
553+
554+
// First test a REST call without authorization, which should fail.
555+
body, responseHeader, err := callURL(url, "GET", nil, nil, false)
556+
require.NoError(t, err)
557+
558+
require.Equal(
559+
t, "application/grpc",
560+
responseHeader.Get("grpc-metadata-content-type"),
561+
)
562+
require.Equal(
563+
t, "application/json",
564+
responseHeader.Get("content-type"),
565+
)
566+
require.Contains(
567+
t, body,
568+
"expected 1 macaroon, got 0",
569+
)
570+
571+
// Now add the UI password which should make the request succeed.
572+
body, responseHeader, err = callURL(
573+
url, "GET", nil, basicAuthHeader, false,
574+
)
575+
require.NoError(t, err)
576+
require.Contains(t, body, successPattern)
577+
578+
// And finally, try with the given macaroon.
579+
macBytes, err := ioutil.ReadFile(macaroonPath)
580+
require.NoError(t, err)
581+
582+
macaroonHeader := http.Header{
583+
"grpc-metadata-macaroon": []string{
584+
hex.EncodeToString(macBytes),
585+
},
586+
}
587+
body, responseHeader, err = callURL(
588+
url, "GET", nil, macaroonHeader, false,
589+
)
590+
require.NoError(t, err)
591+
require.Contains(t, body, successPattern)
592+
}
593+
473594
// getURL retrieves the body of a given URL, ignoring any TLS certificate the
474595
// server might present.
475596
func getURL(url string) (string, error) {
@@ -495,7 +616,15 @@ func getURL(url string) (string, error) {
495616
func postURL(url string, postBody []byte, header http.Header) (string,
496617
http.Header, error) {
497618

498-
req, err := http.NewRequest("POST", url, bytes.NewReader(postBody))
619+
return callURL(url, "POST", postBody, header, true)
620+
}
621+
622+
// callURL does a HTTP call to the given URL, ignoring any TLS certificate the
623+
// server might present.
624+
func callURL(url, method string, postBody []byte, header http.Header,
625+
expectOk bool) (string, http.Header, error) {
626+
627+
req, err := http.NewRequest(method, url, bytes.NewReader(postBody))
499628
if err != nil {
500629
return "", nil, err
501630
}
@@ -509,7 +638,7 @@ func postURL(url string, postBody []byte, header http.Header) (string,
509638
return "", nil, err
510639
}
511640

512-
if resp.StatusCode != 200 {
641+
if expectOk && resp.StatusCode != 200 {
513642
return "", nil, fmt.Errorf("request failed, got status code "+
514643
"%d (%s)", resp.StatusCode, resp.Status)
515644
}
@@ -601,3 +730,51 @@ func connectRPC(ctx context.Context, hostPort,
601730

602731
return grpc.DialContext(ctx, hostPort, opts...)
603732
}
733+
734+
func bakeSuperMacaroon(cfg *LitNodeConfig, readOnly bool) (string, error) {
735+
lndAdminMac := lndMacaroonFn(cfg)
736+
737+
ctxb := context.Background()
738+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
739+
defer cancel()
740+
741+
rawConn, err := connectRPC(ctxt, cfg.RPCAddr(), cfg.TLSCertPath)
742+
if err != nil {
743+
return "", err
744+
}
745+
746+
lndAdminMacBytes, err := ioutil.ReadFile(lndAdminMac)
747+
if err != nil {
748+
return "", err
749+
}
750+
lndAdminCtx := macaroonContext(ctxt, lndAdminMacBytes)
751+
lndConn := lnrpc.NewLightningClient(rawConn)
752+
753+
superMacPermissions := terminal.GetAllPermissions(readOnly)
754+
nullID := [4]byte{}
755+
superMacHex, err := terminal.BakeSuperMacaroon(
756+
lndAdminCtx, lndConn, session.NewSuperMacaroonRootKeyID(nullID),
757+
superMacPermissions, nil,
758+
)
759+
if err != nil {
760+
return "", err
761+
}
762+
763+
// The BakeSuperMacaroon function just hex encoded the macaroon, we know
764+
// it's valid.
765+
superMacBytes, _ := hex.DecodeString(superMacHex)
766+
767+
tempFile, err := ioutil.TempFile("", "lit-super-macaroon")
768+
if err != nil {
769+
_ = os.Remove(tempFile.Name())
770+
return "", err
771+
}
772+
773+
err = ioutil.WriteFile(tempFile.Name(), superMacBytes, 0644)
774+
if err != nil {
775+
_ = os.Remove(tempFile.Name())
776+
return "", err
777+
}
778+
779+
return tempFile.Name(), nil
780+
}

itest/litd_mode_remote_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package itest
22

33
import (
44
"context"
5+
"os"
56
"testing"
67

78
"github.com/btcsuite/btcutil"
@@ -90,4 +91,52 @@ func testModeRemote(net *NetworkHarness, t *harnessTest) {
9091
})
9192
}
9293
})
94+
95+
t.t.Run("gRPC super macaroon auth check", func(tt *testing.T) {
96+
cfg := net.Bob.Cfg
97+
98+
superMacFile, err := bakeSuperMacaroon(cfg, true)
99+
require.NoError(tt, err)
100+
101+
defer func() {
102+
_ = os.Remove(superMacFile)
103+
}()
104+
105+
for _, endpoint := range endpoints {
106+
endpoint := endpoint
107+
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
108+
if !endpoint.supportsMacAuthOnLitPort {
109+
return
110+
}
111+
112+
runGRPCAuthTest(
113+
ttt, cfg.LitAddr(), cfg.LitTLSCertPath,
114+
superMacFile,
115+
endpoint.requestFn,
116+
endpoint.successPattern,
117+
)
118+
})
119+
}
120+
})
121+
122+
t.t.Run("REST auth", func(tt *testing.T) {
123+
cfg := net.Bob.Cfg
124+
125+
for _, endpoint := range endpoints {
126+
endpoint := endpoint
127+
128+
if endpoint.restWebURI == "" {
129+
continue
130+
}
131+
132+
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
133+
runRESTAuthTest(
134+
ttt, cfg.LitAddr(), cfg.UIPassword,
135+
endpoint.macaroonFn(cfg),
136+
endpoint.restWebURI,
137+
endpoint.successPattern,
138+
)
139+
})
140+
}
141+
})
93142
}

itest/litd_node.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func (cfg *LitNodeConfig) GenArgs() []string {
109109
fmt.Sprintf("--loop.loopdir=%s", cfg.LoopDir),
110110
fmt.Sprintf("--pool.basedir=%s", cfg.PoolDir),
111111
fmt.Sprintf("--uipassword=%s", cfg.UIPassword),
112+
"--enablerest",
112113
"--restcors=*",
113114
}
114115
)
@@ -139,7 +140,7 @@ func (cfg *LitNodeConfig) GenArgs() []string {
139140

140141
return litArgs
141142
}
142-
143+
143144
// All arguments so far were for lnd. Let's namespace them now so we can
144145
// add args for the other daemons and LiT itself afterwards.
145146
litArgs = append(litArgs, cfg.LitArgs...)

0 commit comments

Comments
 (0)