Skip to content

Stateless integrated mode #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ type Config struct {
faradayRemote bool
loopRemote bool
poolRemote bool

// lndAdminMacaroon is the admin macaroon that is given to us by lnd
// over an in-memory connection on startup. This is only set in
// integrated lnd mode.
lndAdminMacaroon []byte
}

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

// In remote lnd mode, we just pass along what was configured in the
// remote section of the lnd config.
if c.LndMode == ModeRemote {
return c.Remote.Lnd.RPCServer,
lndclient.Network(c.Network),
lncfg.CleanAndExpandPath(c.Remote.Lnd.TLSCertPath),
lncfg.CleanAndExpandPath(c.Remote.Lnd.MacaroonPath)
lncfg.CleanAndExpandPath(c.Remote.Lnd.MacaroonPath),
nil
}

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

return lndDialAddr, lndclient.Network(c.Network),
c.Lnd.TLSCertPath, c.Lnd.AdminMacPath
return lndDialAddr, lndclient.Network(c.Network), "", "",
c.lndAdminMacaroon
}

// defaultConfig returns a configuration struct with all default values set.
Expand Down
91 changes: 78 additions & 13 deletions rpc_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package terminal

import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"time"
Expand All @@ -18,6 +20,7 @@ import (
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/test/bufconn"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
Expand All @@ -34,7 +37,8 @@ const (
// or REST request and delegate (and convert if necessary) it to the correct
// component.
func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
permissionMap map[string][]bakery.Op) *rpcProxy {
permissionMap map[string][]bakery.Op,
bufListener *bufconn.Listener) *rpcProxy {

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

macValidator macaroons.MacaroonValidator
bufListener *bufconn.Listener

superMacaroon string

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

// Setup the connection to lnd.
host, _, tlsPath, _ := p.cfg.lndConnectParams()
p.lndConn, err = dialBackend("lnd", host, tlsPath)
host, _, tlsPath, _, _ := p.cfg.lndConnectParams()

// We use a bufconn to connect to lnd in integrated mode.
if p.cfg.LndMode == ModeIntegrated {
p.lndConn, err = dialBufConnBackend(p.bufListener)
} else {
p.lndConn, err = dialBackend("lnd", host, tlsPath)
}
if err != nil {
return fmt.Errorf("could not dial lnd: %v", err)
}
Expand Down Expand Up @@ -390,10 +404,13 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context,
return ctx, nil
}

var macPath string
var (
macPath string
macData []byte
)
switch {
case isLndURI(requestURI):
_, _, _, macPath = p.cfg.lndConnectParams()
_, _, _, macPath, macData = p.cfg.lndConnectParams()

case isFaradayURI(requestURI):
if p.cfg.faradayRemote {
Expand Down Expand Up @@ -421,16 +438,64 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context,
requestURI)
}

// Now that we know which macaroon to load, do it and attach it to the
// request context.
macBytes, err := readMacaroon(lncfg.CleanAndExpandPath(macPath))
if err != nil {
return ctx, fmt.Errorf("error reading macaroon: %v", err)
switch {
// If we have a super macaroon, we can use that one directly since it
// will contain all permissions we need.
case len(p.superMacaroon) > 0:
md.Set(HeaderMacaroon, p.superMacaroon)

// If we have macaroon data directly, just encode them. This could be
// for initial requests to lnd while we don't have the super macaroon
// just yet (or are actually calling to bake the super macaroon).
case len(macData) > 0:
md.Set(HeaderMacaroon, hex.EncodeToString(macData))

// The fall back is to read the macaroon from a path. This is in remote
// mode.
case len(macPath) > 0:
// Now that we know which macaroon to load, do it and attach it
// to the request context.
macBytes, err := readMacaroon(lncfg.CleanAndExpandPath(macPath))
if err != nil {
return ctx, fmt.Errorf("error reading macaroon %s: %v",
lncfg.CleanAndExpandPath(macPath), err)
}

md.Set(HeaderMacaroon, hex.EncodeToString(macBytes))
}
md.Set(HeaderMacaroon, hex.EncodeToString(macBytes))

return metadata.NewIncomingContext(ctx, md), nil
}

// dialBufConnBackend dials an in-memory connection to an RPC listener and
// ignores any TLS certificate mismatches.
func dialBufConnBackend(listener *bufconn.Listener) (*grpc.ClientConn, error) {
tlsConfig := credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})
conn, err := grpc.Dial(
"",
grpc.WithContextDialer(
func(context.Context, string) (net.Conn, error) {
return listener.Dial()
},
),
grpc.WithTransportCredentials(tlsConfig),

// From the grpcProxy doc: This codec is *crucial* to the
// functioning of the proxy.
grpc.WithCodec(grpcProxy.Codec()), // nolint
grpc.WithTransportCredentials(tlsConfig),
grpc.WithDefaultCallOptions(maxMsgRecvSize),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
MinConnectTimeout: defaultConnectTimeout,
}),
)

return conn, err
}

// dialBackend connects to a gRPC backend through the given address and uses the
// given TLS certificate to authenticate the connection.
func dialBackend(name, dialAddr, tlsCertPath string) (*grpc.ClientConn, error) {
Expand Down Expand Up @@ -470,12 +535,12 @@ func readMacaroon(macPath string) ([]byte, error) {
// Load the specified macaroon file.
macBytes, err := ioutil.ReadFile(macPath)
if err != nil {
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
return nil, fmt.Errorf("unable to read macaroon path: %v", err)
}

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

Expand Down
35 changes: 32 additions & 3 deletions subserver_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func getSubserverPermissions() map[string][]bakery.Op {
return result
}

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

// getAllPermissions retrieves all the permissions needed to bake a super
// macaroon.
func getAllPermissions() []bakery.Op {
dedupMap := make(map[string]map[string]bool)

for _, methodPerms := range getAllMethodPermissions() {
for _, methodPerm := range methodPerms {
if dedupMap[methodPerm.Entity] == nil {
dedupMap[methodPerm.Entity] = make(
map[string]bool,
)
}
dedupMap[methodPerm.Entity][methodPerm.Action] = true
}
}

result := make([]bakery.Op, 0, len(dedupMap))
for entity, actions := range dedupMap {
for action := range actions {
result = append(result, bakery.Op{
Entity: entity,
Action: action,
})
}
}

return result
}

// isLndURI returns true if the given URI belongs to an RPC of lnd.
func isLndURI(uri string) bool {
_, ok := lnd.MainRPCServerPermissions()[uri]
Expand Down
Loading