diff --git a/config.go b/config.go index 0d9b18bd4..409a4fb98 100644 --- a/config.go +++ b/config.go @@ -210,6 +210,7 @@ type Config struct { ExternalIPs []net.Addr DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"` 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"` 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}."` @@ -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 // 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( - cfg.RPCListeners, !cfg.NoMacaroons, + cfg.RPCListeners, !cfg.NoMacaroons, true, ) if err != nil { return nil, err @@ -1188,7 +1190,7 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) { cfg.RESTListeners = nil } else { err = lncfg.EnforceSafeAuthentication( - cfg.RESTListeners, !cfg.NoMacaroons, + cfg.RESTListeners, !cfg.NoMacaroons, !cfg.DisableRestTLS, ) if err != nil { return nil, err diff --git a/lncfg/address.go b/lncfg/address.go index 2c1770e4f..84a90ce98 100644 --- a/lncfg/address.go +++ b/lncfg/address.go @@ -48,11 +48,13 @@ func NormalizeAddresses(addrs []string, defaultPort string, } // EnforceSafeAuthentication enforces "safe" authentication taking into account -// the interfaces that the RPC servers are listening on, and if macaroons are -// activated or not. To protect users from using dangerous config combinations, -// we'll prevent disabling authentication if the server is listening on a public -// interface. -func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error { +// the interfaces that the RPC servers are listening on, and if macaroons and +// TLS is activated or not. To protect users from using dangerous config +// combinations, we'll prevent disabling authentication if the server is +// listening on a public interface. +func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive, + tlsActive bool) error { + // 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, // otherwise, we'll return an error if macaroons are inactive. @@ -62,10 +64,17 @@ func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error { } if !macaroonsActive { - return fmt.Errorf("Detected RPC server listening on "+ + return fmt.Errorf("detected RPC server listening on "+ "publicly reachable interface %v with "+ "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) } } diff --git a/lnd.go b/lnd.go index 464d71ded..8a1c50fe4 100644 --- a/lnd.go +++ b/lnd.go @@ -270,7 +270,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { defer cleanUp() // 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 { err := fmt.Errorf("unable to load TLS credentials: %v", err) ltndLog.Error(err) @@ -279,19 +279,20 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error { defer cleanUp() - serverCreds := credentials.NewTLS(tlsCfg) - serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)} + // We use the first RPC listener as the destination for our REST proxy. + // 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 - // 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), - ), + case strings.Contains(restProxyDest, "[::]"): + restProxyDest = strings.Replace( + restProxyDest, "[::]", "[::1]", 1, + ) } // 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 { params, shutdown, err := waitForWalletPassword( cfg, cfg.RESTListeners, serverOpts, restDialOpts, - restProxyDest, tlsCfg, walletUnlockerListeners, + restProxyDest, restListen, walletUnlockerListeners, ) if err != nil { 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( cfg, server, macaroonService, cfg.SubRPCServers, serverOpts, restDialOpts, restProxyDest, atplManager, server.invoices, - tower, tlsCfg, rpcListeners, chainedAcceptor, + tower, restListen, rpcListeners, chainedAcceptor, ) if err != nil { 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 // and a proxy destination for the REST reverse proxy. -func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, - string, func(), error) { +func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption, + func(net.Addr) (net.Listener, error), func(), error) { // Ensure we create TLS key and certificate if they don't exist. if !fileExists(cfg.TLSCertPath) && !fileExists(cfg.TLSKeyPath) { @@ -844,7 +845,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, cfg.TLSDisableAutofill, cert.DefaultAutogenValidity, ) if err != nil { - return nil, nil, "", nil, err + return nil, nil, nil, nil, err } rpcsLog.Infof("Done generating TLS certificates") } @@ -853,7 +854,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, cfg.TLSCertPath, cfg.TLSKeyPath, ) 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 @@ -867,7 +868,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, cfg.TLSExtraDomains, cfg.TLSDisableAutofill, ) 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) if err != nil { - return nil, nil, "", nil, err + return nil, nil, nil, nil, err } err = os.Remove(cfg.TLSKeyPath) if err != nil { - return nil, nil, "", nil, err + return nil, nil, nil, nil, err } rpcsLog.Infof("Renewing TLS certificates...") @@ -894,7 +895,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, cfg.TLSDisableAutofill, cert.DefaultAutogenValidity, ) if err != nil { - return nil, nil, "", nil, err + return nil, nil, nil, nil, err } rpcsLog.Infof("Done renewing TLS certificates") @@ -903,7 +904,7 @@ func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, cfg.TLSCertPath, cfg.TLSKeyPath, ) 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, "") if err != nil { - return 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, - ) + return nil, nil, nil, nil, err } // 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 } - 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. @@ -1109,7 +1124,7 @@ type WalletUnlockParams struct { // the user to this RPC server. func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, 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) { chainConfig := cfg.Bitcoin @@ -1188,7 +1203,7 @@ func waitForWalletPassword(cfg *Config, restEndpoints []net.Addr, srv := &http.Server{Handler: allowCORS(mux, cfg.RestCORS)} for _, restEndpoint := range restEndpoints { - lis, err := lncfg.TLSListenOnAddress(restEndpoint, tlsConf) + lis, err := restListen(restEndpoint) if err != nil { ltndLog.Errorf("Password gRPC proxy unable to listen "+ "on %s", restEndpoint) diff --git a/rpcserver.go b/rpcserver.go index 8d39547fe..eabd9ad1e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3,12 +3,12 @@ package lnd import ( "bytes" "context" - "crypto/tls" "encoding/hex" "errors" "fmt" "io" "math" + "net" "net/http" "runtime" "sort" @@ -512,9 +512,10 @@ type rpcServer struct { // restProxyDest is the address to forward REST requests to. restProxyDest string - // tlsCfg is the TLS config that allows the REST server proxy to - // connect to the main gRPC server to proxy all incoming requests. - tlsCfg *tls.Config + // restListen is a function closure that allows the REST server proxy to + // connect to the main gRPC server to proxy all incoming requests, + // applying the current TLS configuration, if any. + restListen func(net.Addr) (net.Listener, error) // routerBackend contains the backend implementation of the router // rpc sub server. @@ -551,7 +552,8 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption, restDialOpts []grpc.DialOption, restProxyDest string, atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, - tower *watchtower.Standalone, tlsCfg *tls.Config, + tower *watchtower.Standalone, + restListen func(net.Addr) (net.Listener, error), getListeners rpcListeners, chanPredicate *chanacceptor.ChainedAcceptor) (*rpcServer, error) { @@ -754,7 +756,7 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, listenerCleanUp: []func(){cleanup}, restProxyDest: restProxyDest, subServers: subServers, - tlsCfg: tlsCfg, + restListen: restListen, grpcServer: grpcServer, server: s, routerBackend: routerBackend, @@ -901,7 +903,7 @@ func (r *rpcServer) Start() error { // Now spin up a network listener for each requested port and start a // goroutine that serves REST with the created mux there. for _, restEndpoint := range r.cfg.RESTListeners { - lis, err := lncfg.TLSListenOnAddress(restEndpoint, r.tlsCfg) + lis, err := r.restListen(restEndpoint) if err != nil { ltndLog.Errorf("gRPC proxy unable to listen on %s", restEndpoint) diff --git a/sample-lnd.conf b/sample-lnd.conf index 79977a4a1..ebf23f4fe 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -176,6 +176,9 @@ ; Disable REST API. ; norest=true +; Disable TLS for the REST API. +; no-rest-tls=true + ; Shortest backoff when reconnecting to persistent peers. Valid time units are ; {s, m, h}. ; minbackoff=1s