Skip to content

Commit a25426d

Browse files
orbitalturtleguggero
authored andcommitted
multi: bake super macaroon in integrated mode
In integrated mode we hook directly into lnd's bufconn listener for any connections to it. So we don't need any TLS setup and can bake a single super macaroon that is used for all RPC calls.
1 parent d47ecee commit a25426d

File tree

4 files changed

+251
-28
lines changed

4 files changed

+251
-28
lines changed

config.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ type Config struct {
177177
faradayRemote bool
178178
loopRemote bool
179179
poolRemote bool
180+
181+
// lndAdminMacaroon is the admin macaroon that is given to us by lnd
182+
// over an in-memory connection on startup. This is only set in
183+
// integrated lnd mode.
184+
lndAdminMacaroon []byte
180185
}
181186

182187
// RemoteConfig holds the configuration parameters that are needed when running
@@ -219,15 +224,16 @@ type RemoteDaemonConfig struct {
219224
// lndConnectParams returns the connection parameters to connect to the local
220225
// lnd instance.
221226
func (c *Config) lndConnectParams() (string, lndclient.Network, string,
222-
string) {
227+
string, []byte) {
223228

224229
// In remote lnd mode, we just pass along what was configured in the
225230
// remote section of the lnd config.
226231
if c.LndMode == ModeRemote {
227232
return c.Remote.Lnd.RPCServer,
228233
lndclient.Network(c.Network),
229234
lncfg.CleanAndExpandPath(c.Remote.Lnd.TLSCertPath),
230-
lncfg.CleanAndExpandPath(c.Remote.Lnd.MacaroonPath)
235+
lncfg.CleanAndExpandPath(c.Remote.Lnd.MacaroonPath),
236+
nil
231237
}
232238

233239
// When we start lnd internally, we take the listen address as
@@ -248,8 +254,8 @@ func (c *Config) lndConnectParams() (string, lndclient.Network, string,
248254
)
249255
}
250256

251-
return lndDialAddr, lndclient.Network(c.Network),
252-
c.Lnd.TLSCertPath, c.Lnd.AdminMacPath
257+
return lndDialAddr, lndclient.Network(c.Network), "", "",
258+
c.lndAdminMacaroon
253259
}
254260

255261
// defaultConfig returns a configuration struct with all default values set.

rpc_proxy.go

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package terminal
22

33
import (
44
"context"
5+
"crypto/tls"
56
"encoding/base64"
67
"encoding/hex"
78
"fmt"
89
"io/ioutil"
10+
"net"
911
"net/http"
1012
"strings"
1113
"time"
@@ -18,6 +20,7 @@ import (
1820
"google.golang.org/grpc/backoff"
1921
"google.golang.org/grpc/credentials"
2022
"google.golang.org/grpc/metadata"
23+
"google.golang.org/grpc/test/bufconn"
2124
"gopkg.in/macaroon-bakery.v2/bakery"
2225
"gopkg.in/macaroon.v2"
2326
)
@@ -34,7 +37,8 @@ const (
3437
// or REST request and delegate (and convert if necessary) it to the correct
3538
// component.
3639
func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
37-
permissionMap map[string][]bakery.Op) *rpcProxy {
40+
permissionMap map[string][]bakery.Op,
41+
bufListener *bufconn.Listener) *rpcProxy {
3842

3943
// The gRPC web calls are protected by HTTP basic auth which is defined
4044
// by base64(username:password). Because we only have a password, we
@@ -52,6 +56,7 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
5256
cfg: cfg,
5357
basicAuth: basicAuth,
5458
macValidator: validator,
59+
bufListener: bufListener,
5560
}
5661
p.grpcServer = grpc.NewServer(
5762
// From the grpxProxy doc: This codec is *crucial* to the
@@ -125,6 +130,9 @@ type rpcProxy struct {
125130
basicAuth string
126131

127132
macValidator macaroons.MacaroonValidator
133+
bufListener *bufconn.Listener
134+
135+
superMacaroon string
128136

129137
lndConn *grpc.ClientConn
130138
faradayConn *grpc.ClientConn
@@ -140,8 +148,14 @@ func (p *rpcProxy) Start() error {
140148
var err error
141149

142150
// Setup the connection to lnd.
143-
host, _, tlsPath, _ := p.cfg.lndConnectParams()
144-
p.lndConn, err = dialBackend("lnd", host, tlsPath)
151+
host, _, tlsPath, _, _ := p.cfg.lndConnectParams()
152+
153+
// We use a bufconn to connect to lnd in integrated mode.
154+
if p.cfg.LndMode == ModeIntegrated {
155+
p.lndConn, err = dialBufConnBackend(p.bufListener)
156+
} else {
157+
p.lndConn, err = dialBackend("lnd", host, tlsPath)
158+
}
145159
if err != nil {
146160
return fmt.Errorf("could not dial lnd: %v", err)
147161
}
@@ -390,10 +404,13 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context,
390404
return ctx, nil
391405
}
392406

393-
var macPath string
407+
var (
408+
macPath string
409+
macData []byte
410+
)
394411
switch {
395412
case isLndURI(requestURI):
396-
_, _, _, macPath = p.cfg.lndConnectParams()
413+
_, _, _, macPath, macData = p.cfg.lndConnectParams()
397414

398415
case isFaradayURI(requestURI):
399416
if p.cfg.faradayRemote {
@@ -421,16 +438,64 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context,
421438
requestURI)
422439
}
423440

424-
// Now that we know which macaroon to load, do it and attach it to the
425-
// request context.
426-
macBytes, err := readMacaroon(lncfg.CleanAndExpandPath(macPath))
427-
if err != nil {
428-
return ctx, fmt.Errorf("error reading macaroon: %v", err)
441+
switch {
442+
// If we have a super macaroon, we can use that one directly since it
443+
// will contain all permissions we need.
444+
case len(p.superMacaroon) > 0:
445+
md.Set(HeaderMacaroon, p.superMacaroon)
446+
447+
// If we have macaroon data directly, just encode them. This could be
448+
// for initial requests to lnd while we don't have the super macaroon
449+
// just yet (or are actually calling to bake the super macaroon).
450+
case len(macData) > 0:
451+
md.Set(HeaderMacaroon, hex.EncodeToString(macData))
452+
453+
// The fall back is to read the macaroon from a path. This is in remote
454+
// mode.
455+
case len(macPath) > 0:
456+
// Now that we know which macaroon to load, do it and attach it
457+
// to the request context.
458+
macBytes, err := readMacaroon(lncfg.CleanAndExpandPath(macPath))
459+
if err != nil {
460+
return ctx, fmt.Errorf("error reading macaroon %s: %v",
461+
lncfg.CleanAndExpandPath(macPath), err)
462+
}
463+
464+
md.Set(HeaderMacaroon, hex.EncodeToString(macBytes))
429465
}
430-
md.Set(HeaderMacaroon, hex.EncodeToString(macBytes))
466+
431467
return metadata.NewIncomingContext(ctx, md), nil
432468
}
433469

470+
// dialBufConnBackend dials an in-memory connection to an RPC listener and
471+
// ignores any TLS certificate mismatches.
472+
func dialBufConnBackend(listener *bufconn.Listener) (*grpc.ClientConn, error) {
473+
tlsConfig := credentials.NewTLS(&tls.Config{
474+
InsecureSkipVerify: true,
475+
})
476+
conn, err := grpc.Dial(
477+
"",
478+
grpc.WithContextDialer(
479+
func(context.Context, string) (net.Conn, error) {
480+
return listener.Dial()
481+
},
482+
),
483+
grpc.WithTransportCredentials(tlsConfig),
484+
485+
// From the grpcProxy doc: This codec is *crucial* to the
486+
// functioning of the proxy.
487+
grpc.WithCodec(grpcProxy.Codec()), // nolint
488+
grpc.WithTransportCredentials(tlsConfig),
489+
grpc.WithDefaultCallOptions(maxMsgRecvSize),
490+
grpc.WithConnectParams(grpc.ConnectParams{
491+
Backoff: backoff.DefaultConfig,
492+
MinConnectTimeout: defaultConnectTimeout,
493+
}),
494+
)
495+
496+
return conn, err
497+
}
498+
434499
// dialBackend connects to a gRPC backend through the given address and uses the
435500
// given TLS certificate to authenticate the connection.
436501
func dialBackend(name, dialAddr, tlsCertPath string) (*grpc.ClientConn, error) {
@@ -470,12 +535,12 @@ func readMacaroon(macPath string) ([]byte, error) {
470535
// Load the specified macaroon file.
471536
macBytes, err := ioutil.ReadFile(macPath)
472537
if err != nil {
473-
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
538+
return nil, fmt.Errorf("unable to read macaroon path: %v", err)
474539
}
475540

476541
// Make sure it actually is a macaroon by parsing it.
477542
mac := &macaroon.Macaroon{}
478-
if err = mac.UnmarshalBinary(macBytes); err != nil {
543+
if err := mac.UnmarshalBinary(macBytes); err != nil {
479544
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
480545
}
481546

subserver_permissions.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ func getSubserverPermissions() map[string][]bakery.Op {
2626
return result
2727
}
2828

29-
// getAllPermissions returns a merged map of lnd's and all subservers' macaroon
30-
// permissions.
31-
func getAllPermissions() map[string][]bakery.Op {
29+
// getAllMethodPermissions returns a merged map of lnd's and all subservers'
30+
// method macaroon permissions.
31+
func getAllMethodPermissions() map[string][]bakery.Op {
3232
subserverPermissions := getSubserverPermissions()
3333
lndPermissions := lnd.MainRPCServerPermissions()
3434
mapSize := len(subserverPermissions) + len(lndPermissions)
@@ -42,6 +42,35 @@ func getAllPermissions() map[string][]bakery.Op {
4242
return result
4343
}
4444

45+
// getAllPermissions retrieves all the permissions needed to bake a super
46+
// macaroon.
47+
func getAllPermissions() []bakery.Op {
48+
dedupMap := make(map[string]map[string]bool)
49+
50+
for _, methodPerms := range getAllMethodPermissions() {
51+
for _, methodPerm := range methodPerms {
52+
if dedupMap[methodPerm.Entity] == nil {
53+
dedupMap[methodPerm.Entity] = make(
54+
map[string]bool,
55+
)
56+
}
57+
dedupMap[methodPerm.Entity][methodPerm.Action] = true
58+
}
59+
}
60+
61+
result := make([]bakery.Op, 0, len(dedupMap))
62+
for entity, actions := range dedupMap {
63+
for action := range actions {
64+
result = append(result, bakery.Op{
65+
Entity: entity,
66+
Action: action,
67+
})
68+
}
69+
}
70+
71+
return result
72+
}
73+
4574
// isLndURI returns true if the given URI belongs to an RPC of lnd.
4675
func isLndURI(uri string) bool {
4776
_, ok := lnd.MainRPCServerPermissions()[uri]

0 commit comments

Comments
 (0)