Merge pull request #4648 from halseth/option-no-tls

RPC: add --no-rest-tls option
This commit is contained in:
Johan T. Halseth 2020-11-11 11:52:23 +01:00 committed by GitHub
commit b525c04432
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 58 deletions

@ -210,6 +210,7 @@ type Config struct {
ExternalIPs []net.Addr ExternalIPs []net.Addr
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"` DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
DisableRest bool `long:"norest" description:"Disable REST API"` DisableRest bool `long:"norest" description:"Disable REST API"`
DisableRestTLS bool `long:"no-rest-tls" description:"Disable TLS for REST connections"`
NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"` NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"`
MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."` MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."` MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
@ -1175,9 +1176,10 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) {
// For each of the RPC listeners (REST+gRPC), we'll ensure that users // For each of the RPC listeners (REST+gRPC), we'll ensure that users
// have specified a safe combo for authentication. If not, we'll bail // have specified a safe combo for authentication. If not, we'll bail
// out with an error. // out with an error. Since we don't allow disabling TLS for gRPC
// connections we pass in tlsActive=true.
err = lncfg.EnforceSafeAuthentication( err = lncfg.EnforceSafeAuthentication(
cfg.RPCListeners, !cfg.NoMacaroons, cfg.RPCListeners, !cfg.NoMacaroons, true,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1188,7 +1190,7 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) {
cfg.RESTListeners = nil cfg.RESTListeners = nil
} else { } else {
err = lncfg.EnforceSafeAuthentication( err = lncfg.EnforceSafeAuthentication(
cfg.RESTListeners, !cfg.NoMacaroons, cfg.RESTListeners, !cfg.NoMacaroons, !cfg.DisableRestTLS,
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -48,11 +48,13 @@ func NormalizeAddresses(addrs []string, defaultPort string,
} }
// EnforceSafeAuthentication enforces "safe" authentication taking into account // EnforceSafeAuthentication enforces "safe" authentication taking into account
// the interfaces that the RPC servers are listening on, and if macaroons are // the interfaces that the RPC servers are listening on, and if macaroons and
// activated or not. To protect users from using dangerous config combinations, // TLS is activated or not. To protect users from using dangerous config
// we'll prevent disabling authentication if the server is listening on a public // combinations, we'll prevent disabling authentication if the server is
// interface. // listening on a public interface.
func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error { func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive,
tlsActive bool) error {
// We'll now examine all addresses that this RPC server is listening // We'll now examine all addresses that this RPC server is listening
// on. If it's a localhost address or a private address, we'll skip it, // on. If it's a localhost address or a private address, we'll skip it,
// otherwise, we'll return an error if macaroons are inactive. // otherwise, we'll return an error if macaroons are inactive.
@ -62,10 +64,17 @@ func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error {
} }
if !macaroonsActive { if !macaroonsActive {
return fmt.Errorf("Detected RPC server listening on "+ return fmt.Errorf("detected RPC server listening on "+
"publicly reachable interface %v with "+ "publicly reachable interface %v with "+
"authentication disabled! Refusing to start "+ "authentication disabled! Refusing to start "+
"with --no-macaroons specified.", addr) "with --no-macaroons specified", addr)
}
if !tlsActive {
return fmt.Errorf("detected RPC server listening on "+
"publicly reachable interface %v with "+
"encryption disabled! Refusing to start "+
"with --notls specified", addr)
} }
} }

97
lnd.go

@ -270,7 +270,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
defer cleanUp() defer cleanUp()
// Only process macaroons if --no-macaroons isn't set. // Only process macaroons if --no-macaroons isn't set.
tlsCfg, restCreds, restProxyDest, cleanUp, err := getTLSConfig(cfg) serverOpts, restDialOpts, restListen, cleanUp, err := getTLSConfig(cfg)
if err != nil { if err != nil {
err := fmt.Errorf("unable to load TLS credentials: %v", err) err := fmt.Errorf("unable to load TLS credentials: %v", err)
ltndLog.Error(err) ltndLog.Error(err)
@ -279,19 +279,20 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
defer cleanUp() defer cleanUp()
serverCreds := credentials.NewTLS(tlsCfg) // We use the first RPC listener as the destination for our REST proxy.
serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)} // If the listener is set to listen on all interfaces, we replace it
// with localhost, as we cannot dial it directly.
restProxyDest := cfg.RPCListeners[0].String()
switch {
case strings.Contains(restProxyDest, "0.0.0.0"):
restProxyDest = strings.Replace(
restProxyDest, "0.0.0.0", "127.0.0.1", 1,
)
// For our REST dial options, we'll still use TLS, but also increase case strings.Contains(restProxyDest, "[::]"):
// the max message size that we'll decode to allow clients to hit restProxyDest = strings.Replace(
// endpoints which return more data such as the DescribeGraph call. restProxyDest, "[::]", "[::1]", 1,
// We set this to 200MiB atm. Should be the same value as maxMsgRecvSize )
// in cmd/lncli/main.go.
restDialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(*restCreds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200),
),
} }
// Before starting the wallet, we'll create and start our Neutrino // Before starting the wallet, we'll create and start our Neutrino
@ -380,7 +381,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
if !cfg.NoSeedBackup { if !cfg.NoSeedBackup {
params, shutdown, err := waitForWalletPassword( params, shutdown, err := waitForWalletPassword(
cfg, cfg.RESTListeners, serverOpts, restDialOpts, cfg, cfg.RESTListeners, serverOpts, restDialOpts,
restProxyDest, tlsCfg, walletUnlockerListeners, restProxyDest, restListen, walletUnlockerListeners,
) )
if err != nil { if err != nil {
err := fmt.Errorf("unable to set up wallet password "+ err := fmt.Errorf("unable to set up wallet password "+
@ -730,7 +731,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
rpcServer, err := newRPCServer( rpcServer, err := newRPCServer(
cfg, server, macaroonService, cfg.SubRPCServers, serverOpts, cfg, server, macaroonService, cfg.SubRPCServers, serverOpts,
restDialOpts, restProxyDest, atplManager, server.invoices, restDialOpts, restProxyDest, atplManager, server.invoices,
tower, tlsCfg, rpcListeners, chainedAcceptor, tower, restListen, rpcListeners, chainedAcceptor,
) )
if err != nil { if err != nil {
err := fmt.Errorf("unable to create RPC server: %v", err) err := fmt.Errorf("unable to create RPC server: %v", err)
@ -832,8 +833,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
// getTLSConfig returns a TLS configuration for the gRPC server and credentials // getTLSConfig returns a TLS configuration for the gRPC server and credentials
// and a proxy destination for the REST reverse proxy. // and a proxy destination for the REST reverse proxy.
func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption,
string, func(), error) { func(net.Addr) (net.Listener, error), func(), error) {
// Ensure we create TLS key and certificate if they don't exist. // Ensure we create TLS key and certificate if they don't exist.
if !fileExists(cfg.TLSCertPath) && !fileExists(cfg.TLSKeyPath) { if !fileExists(cfg.TLSCertPath) && !fileExists(cfg.TLSKeyPath) {
@ -844,7 +845,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
cfg.TLSDisableAutofill, cert.DefaultAutogenValidity, cfg.TLSDisableAutofill, cert.DefaultAutogenValidity,
) )
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
rpcsLog.Infof("Done generating TLS certificates") rpcsLog.Infof("Done generating TLS certificates")
} }
@ -853,7 +854,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
cfg.TLSCertPath, cfg.TLSKeyPath, cfg.TLSCertPath, cfg.TLSKeyPath,
) )
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
// We check whether the certifcate we have on disk match the IPs and // We check whether the certifcate we have on disk match the IPs and
@ -867,7 +868,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
cfg.TLSExtraDomains, cfg.TLSDisableAutofill, cfg.TLSExtraDomains, cfg.TLSDisableAutofill,
) )
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
} }
@ -879,12 +880,12 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
err := os.Remove(cfg.TLSCertPath) err := os.Remove(cfg.TLSCertPath)
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
err = os.Remove(cfg.TLSKeyPath) err = os.Remove(cfg.TLSKeyPath)
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
rpcsLog.Infof("Renewing TLS certificates...") rpcsLog.Infof("Renewing TLS certificates...")
@ -894,7 +895,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
cfg.TLSDisableAutofill, cert.DefaultAutogenValidity, cfg.TLSDisableAutofill, cert.DefaultAutogenValidity,
) )
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
rpcsLog.Infof("Done renewing TLS certificates") rpcsLog.Infof("Done renewing TLS certificates")
@ -903,7 +904,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
cfg.TLSCertPath, cfg.TLSKeyPath, cfg.TLSCertPath, cfg.TLSKeyPath,
) )
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
} }
} }
@ -911,20 +912,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
restCreds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "") restCreds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "")
if err != nil { if err != nil {
return nil, nil, "", nil, err return nil, nil, nil, nil, err
}
restProxyDest := cfg.RPCListeners[0].String()
switch {
case strings.Contains(restProxyDest, "0.0.0.0"):
restProxyDest = strings.Replace(
restProxyDest, "0.0.0.0", "127.0.0.1", 1,
)
case strings.Contains(restProxyDest, "[::]"):
restProxyDest = strings.Replace(
restProxyDest, "[::]", "[::1]", 1,
)
} }
// If Let's Encrypt is enabled, instantiate autocert to request/renew // If Let's Encrypt is enabled, instantiate autocert to request/renew
@ -984,7 +972,34 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
tlsCfg.GetCertificate = getCertificate tlsCfg.GetCertificate = getCertificate
} }
return tlsCfg, &restCreds, restProxyDest, cleanUp, nil serverCreds := credentials.NewTLS(tlsCfg)
serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)}
// For our REST dial options, we'll still use TLS, but also increase
// the max message size that we'll decode to allow clients to hit
// endpoints which return more data such as the DescribeGraph call.
// We set this to 200MiB atm. Should be the same value as maxMsgRecvSize
// in cmd/lncli/main.go.
restDialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(restCreds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200),
),
}
// Return a function closure that can be used to listen on a given
// address with the current TLS config.
restListen := func(addr net.Addr) (net.Listener, error) {
// For restListen we will call ListenOnAddress if TLS is
// disabled.
if cfg.DisableRestTLS {
return lncfg.ListenOnAddress(addr)
}
return lncfg.TLSListenOnAddress(addr, tlsCfg)
}
return serverOpts, restDialOpts, restListen, cleanUp, nil
} }
// fileExists reports whether the named file or directory exists. // fileExists reports whether the named file or directory exists.
@ -1109,7 +1124,7 @@ type WalletUnlockParams struct {
// the user to this RPC server. // the user to this RPC server.
func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr,
serverOpts []grpc.ServerOption, restDialOpts []grpc.DialOption, serverOpts []grpc.ServerOption, restDialOpts []grpc.DialOption,
restProxyDest string, tlsConf *tls.Config, restProxyDest string, restListen func(net.Addr) (net.Listener, error),
getListeners rpcListeners) (*WalletUnlockParams, func(), error) { getListeners rpcListeners) (*WalletUnlockParams, func(), error) {
chainConfig := cfg.Bitcoin chainConfig := cfg.Bitcoin
@ -1188,7 +1203,7 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr,
srv := &http.Server{Handler: allowCORS(mux, cfg.RestCORS)} srv := &http.Server{Handler: allowCORS(mux, cfg.RestCORS)}
for _, restEndpoint := range restEndpoints { for _, restEndpoint := range restEndpoints {
lis, err := lncfg.TLSListenOnAddress(restEndpoint, tlsConf) lis, err := restListen(restEndpoint)
if err != nil { if err != nil {
ltndLog.Errorf("Password gRPC proxy unable to listen "+ ltndLog.Errorf("Password gRPC proxy unable to listen "+
"on %s", restEndpoint) "on %s", restEndpoint)

@ -3,12 +3,12 @@ package lnd
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math" "math"
"net"
"net/http" "net/http"
"runtime" "runtime"
"sort" "sort"
@ -512,9 +512,10 @@ type rpcServer struct {
// restProxyDest is the address to forward REST requests to. // restProxyDest is the address to forward REST requests to.
restProxyDest string restProxyDest string
// tlsCfg is the TLS config that allows the REST server proxy to // restListen is a function closure that allows the REST server proxy to
// connect to the main gRPC server to proxy all incoming requests. // connect to the main gRPC server to proxy all incoming requests,
tlsCfg *tls.Config // applying the current TLS configuration, if any.
restListen func(net.Addr) (net.Listener, error)
// routerBackend contains the backend implementation of the router // routerBackend contains the backend implementation of the router
// rpc sub server. // rpc sub server.
@ -551,7 +552,8 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service,
subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption, subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption,
restDialOpts []grpc.DialOption, restProxyDest string, restDialOpts []grpc.DialOption, restProxyDest string,
atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry,
tower *watchtower.Standalone, tlsCfg *tls.Config, tower *watchtower.Standalone,
restListen func(net.Addr) (net.Listener, error),
getListeners rpcListeners, getListeners rpcListeners,
chanPredicate *chanacceptor.ChainedAcceptor) (*rpcServer, error) { chanPredicate *chanacceptor.ChainedAcceptor) (*rpcServer, error) {
@ -754,7 +756,7 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service,
listenerCleanUp: []func(){cleanup}, listenerCleanUp: []func(){cleanup},
restProxyDest: restProxyDest, restProxyDest: restProxyDest,
subServers: subServers, subServers: subServers,
tlsCfg: tlsCfg, restListen: restListen,
grpcServer: grpcServer, grpcServer: grpcServer,
server: s, server: s,
routerBackend: routerBackend, routerBackend: routerBackend,
@ -901,7 +903,7 @@ func (r *rpcServer) Start() error {
// Now spin up a network listener for each requested port and start a // Now spin up a network listener for each requested port and start a
// goroutine that serves REST with the created mux there. // goroutine that serves REST with the created mux there.
for _, restEndpoint := range r.cfg.RESTListeners { for _, restEndpoint := range r.cfg.RESTListeners {
lis, err := lncfg.TLSListenOnAddress(restEndpoint, r.tlsCfg) lis, err := r.restListen(restEndpoint)
if err != nil { if err != nil {
ltndLog.Errorf("gRPC proxy unable to listen on %s", ltndLog.Errorf("gRPC proxy unable to listen on %s",
restEndpoint) restEndpoint)

@ -176,6 +176,9 @@
; Disable REST API. ; Disable REST API.
; norest=true ; norest=true
; Disable TLS for the REST API.
; no-rest-tls=true
; Shortest backoff when reconnecting to persistent peers. Valid time units are ; Shortest backoff when reconnecting to persistent peers. Valid time units are
; {s, m, h}. ; {s, m, h}.
; minbackoff=1s ; minbackoff=1s