Skip to content

Commit

Permalink
Support reverse proxy deployments (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
jholdstock committed Feb 10, 2021
1 parent 9f8e76b commit 4ba2828
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 73 deletions.
59 changes: 37 additions & 22 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const (
defaultLogDirname = "log"
defaultLogFilename = "dcrpool.log"
defaultDBFilename = "dcrpool.kv"
defaultTLSCertFilename = "dcrpool.cert"
defaultTLSKeyFilename = "dcrpool.key"
defaultGUITLSCertFilename = "dcrpool.cert"
defaultGUITLSKeyFilename = "dcrpool.key"
defaultWalletTLSCertFilename = "wallet.cert"
defaultWalletTLSKeyFilename = "wallet.key"
defaultDcrdRPCHost = "127.0.0.1"
Expand All @@ -45,10 +45,12 @@ const (
defaultPoolFee = 0.01
defaultLastNPeriod = time.Hour * 24
defaultSoloPool = false
defaultGUIPort = 8080
defaultGUIPort = "8080"
defaultGUIListen = "0.0.0.0"
defaultGUIDir = "gui"
defaultUseLEHTTPS = false
defaultMinerPort = 5550
defaultMinerPort = "5550"
defaultMinerListen = "0.0.0.0"
defaultDesignation = "YourPoolNameHere"
defaultMaxConnectionsPerHost = 100 // 100 connected clients per host
defaultWalletAccount = 0
Expand All @@ -61,6 +63,7 @@ const (
defaultPGDBName = "dcrpooldb"
defaultMonitorCycle = time.Minute * 2
defaultMaxUpgradeTries = 10
defaultNoGUITLS = false
)

var (
Expand All @@ -74,8 +77,8 @@ var (

// This keypair is solely for enabling HTTPS connections to the pool's
// web interface.
defaultTLSCertFile = filepath.Join(dcrpoolHomeDir, defaultTLSCertFilename)
defaultTLSKeyFile = filepath.Join(dcrpoolHomeDir, defaultTLSKeyFilename)
defaultGUITLSCertFile = filepath.Join(dcrpoolHomeDir, defaultGUITLSCertFilename)
defaultGUITLSKeyFile = filepath.Join(dcrpoolHomeDir, defaultGUITLSKeyFilename)

// This keypair is solely for client authentication to the wallet.
defaultWalletTLSCertFile = filepath.Join(dcrpoolHomeDir, defaultWalletTLSCertFilename)
Expand All @@ -93,7 +96,7 @@ type config struct {
ConfigFile string `long:"configfile" ini-name:"configfile" description:"Path to configuration file."`
DataDir string `long:"datadir" ini-name:"datadir" description:"The data directory."`
ActiveNet string `long:"activenet" ini-name:"activenet" description:"The active network being mined on. {testnet3, mainnet, simnet}"`
GUIPort uint32 `long:"guiport" ini-name:"guiport" description:"The pool GUI port."`
GUIListen string `long:"guilisten" ini-name:"guilisten" description:"The address:port for pool GUI listening."`
DebugLevel string `long:"debuglevel" ini-name:"debuglevel" description:"Logging level for all subsystems. {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
LogDir string `long:"logdir" ini-name:"logdir" description:"Directory to log output."`
DBFile string `long:"dbfile" ini-name:"dbfile" description:"Path to the database file."`
Expand All @@ -117,14 +120,14 @@ type config struct {
GUIDir string `long:"guidir" ini-name:"guidir" description:"The path to the directory containing the pool's user interface assets (templates, css etc.)"`
Domain string `long:"domain" ini-name:"domain" description:"The domain of the mining pool, required for TLS."`
UseLEHTTPS bool `long:"uselehttps" ini-name:"uselehttps" description:"This enables HTTPS using a Letsencrypt certificate. By default the pool uses a self-signed certificate for HTTPS."`
TLSCert string `long:"tlscert" ini-name:"tlscert" description:"Path to the TLS cert file."`
TLSKey string `long:"tlskey" ini-name:"tlskey" description:"Path to the TLS key file."`
GUITLSCert string `long:"tlscert" ini-name:"tlscert" description:"Path to the TLS cert file (for running GUI on https)."`
GUITLSKey string `long:"tlskey" ini-name:"tlskey" description:"Path to the TLS key file (for running GUI on https)."`
WalletTLSCert string `long:"wallettlscert" ini-name:"wallettlscert" description:"Path to the wallet client TLS cert file."`
WalletTLSKey string `long:"wallettlskey" ini-name:"wallettlskey" description:"Path to the wallet client TLS key file."`
Designation string `long:"designation" ini-name:"designation" description:"The designated codename for this pool. Customises the logo in the top toolbar."`
MaxConnectionsPerHost uint32 `long:"maxconnperhost" ini-name:"maxconnperhost" description:"The maximum number of connections allowed per host."`
Profile string `long:"profile" ini-name:"profile" description:"Enable HTTP profiling on given [addr:]port -- NOTE port must be between 1024 and 65536"`
MinerPort uint32 `long:"minerport" ini-name:"minerport" description:"Miner connection port."`
MinerListen string `long:"minerlisten" ini-name:"minerlisten" description:"The address:port for miner connections."`
CoinbaseConfTimeout time.Duration `long:"conftimeout" ini-name:"conftimeout" description:"The duration to wait for coinbase confirmations."`
GenCertsOnly bool `long:"gencertsonly" ini-name:"gencertsonly" description:"Only generate needed TLS key pairs and terminate."`
UsePostgres bool `long:"postgres" ini-name:"postgres" description:"Use postgres database instead of bolt."`
Expand All @@ -136,6 +139,7 @@ type config struct {
PurgeDB bool `long:"purgedb" ini-name:"purgedb" description:"Wipes all existing data on startup for a postgres backend. This intended for simnet testing purposes only."`
MonitorCycle time.Duration `long:"monitorcycle" ini-name:"monitorcycle" description:"Time spent monitoring a mining client for possible upgrades."`
MaxUpgradeTries uint32 `long:"maxupgradetries" ini-name:"maxupgradetries" description:"Maximum consecuctive miner monitoring and upgrade tries."`
NoGUITLS bool `long:"noguitls" ini-name:"noguitls" description:"Disable TLS on GUI endpoint (eg. for reverse proxy with a dedicated webserver)."`
poolFeeAddrs []dcrutil.Address
dcrdRPCCerts []byte
net *params
Expand Down Expand Up @@ -353,16 +357,16 @@ func loadConfig() (*config, []string, error) {
PaymentMethod: defaultPaymentMethod,
LastNPeriod: defaultLastNPeriod,
SoloPool: defaultSoloPool,
GUIPort: defaultGUIPort,
GUIListen: defaultGUIListen,
GUIDir: defaultGUIDir,
UseLEHTTPS: defaultUseLEHTTPS,
TLSCert: defaultTLSCertFile,
TLSKey: defaultTLSKeyFile,
GUITLSCert: defaultGUITLSCertFile,
GUITLSKey: defaultGUITLSKeyFile,
WalletTLSCert: defaultWalletTLSCertFile,
WalletTLSKey: defaultWalletTLSKeyFile,
Designation: defaultDesignation,
MaxConnectionsPerHost: defaultMaxConnectionsPerHost,
MinerPort: defaultMinerPort,
MinerListen: defaultMinerListen,
WalletAccount: defaultWalletAccount,
CoinbaseConfTimeout: defaultCoinbaseConfTimeout,
UsePostgres: defaultUsePostgres,
Expand All @@ -373,6 +377,7 @@ func loadConfig() (*config, []string, error) {
PGDBName: defaultPGDBName,
MonitorCycle: defaultMonitorCycle,
MaxUpgradeTries: defaultMaxUpgradeTries,
NoGUITLS: defaultNoGUITLS,
}

// Service options which are only added on Windows.
Expand Down Expand Up @@ -455,15 +460,15 @@ func loadConfig() (*config, []string, error) {
} else {
cfg.DBFile = preCfg.DBFile
}
if preCfg.TLSCert == defaultTLSCertFile {
cfg.TLSCert = filepath.Join(cfg.HomeDir, defaultTLSCertFilename)
if preCfg.GUITLSCert == defaultGUITLSCertFile {
cfg.GUITLSCert = filepath.Join(cfg.HomeDir, defaultGUITLSCertFilename)
} else {
cfg.TLSCert = preCfg.TLSCert
cfg.GUITLSCert = preCfg.GUITLSCert
}
if preCfg.TLSKey == defaultTLSKeyFile {
cfg.TLSKey = filepath.Join(cfg.HomeDir, defaultTLSKeyFilename)
if preCfg.GUITLSKey == defaultGUITLSKeyFile {
cfg.GUITLSKey = filepath.Join(cfg.HomeDir, defaultGUITLSKeyFilename)
} else {
cfg.TLSKey = preCfg.TLSKey
cfg.GUITLSKey = preCfg.GUITLSKey
}
if preCfg.WalletTLSCert == defaultWalletTLSCertFile {
cfg.WalletTLSCert = filepath.Join(cfg.HomeDir,
Expand Down Expand Up @@ -641,6 +646,9 @@ func loadConfig() (*config, []string, error) {
cfg.DcrdRPCHost = normalizeAddress(cfg.DcrdRPCHost, cfg.net.DcrdRPCServerPort)
cfg.WalletGRPCHost = normalizeAddress(cfg.WalletGRPCHost, cfg.net.WalletGRPCServerPort)

cfg.MinerListen = normalizeAddress(cfg.MinerListen, defaultMinerPort)
cfg.GUIListen = normalizeAddress(cfg.GUIListen, defaultGUIPort)

if !cfg.SoloPool {
// Ensure a valid payment method is set.
if cfg.PaymentMethod != pool.PPS && cfg.PaymentMethod != pool.PPLNS {
Expand Down Expand Up @@ -723,6 +731,13 @@ func loadConfig() (*config, []string, error) {
mpLog.Warnf("%v", configFileError)
}

if cfg.NoGUITLS && cfg.UseLEHTTPS {
err := fmt.Errorf("only one of uselehttps and noguitls can be specified")
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
}

// Ensure a domain is set if HTTPS via letsencrypt is preferred.
if cfg.UseLEHTTPS && cfg.Domain == "" {
err := fmt.Errorf("a valid domain is required for HTTPS " +
Expand All @@ -735,8 +750,8 @@ func loadConfig() (*config, []string, error) {
// Generate self-signed TLS cert and key if they do not already exist. This
// keypair is solely for enabling HTTPS connections to the pool's
// web interface.
if !cfg.UseLEHTTPS && (!fileExists(cfg.TLSCert) || !fileExists(cfg.TLSKey)) {
err := genCertPair(cfg.TLSCert, cfg.TLSKey)
if !cfg.UseLEHTTPS && (!fileExists(cfg.GUITLSCert) || !fileExists(cfg.GUITLSKey)) {
err := genCertPair(cfg.GUITLSCert, cfg.GUITLSKey)
if err != nil {
str := "%s: unable to generate dcrpool's TLS cert/key: %v"
err := fmt.Errorf(str, funcName, err)
Expand Down
11 changes: 6 additions & 5 deletions dcrpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func newPool(db pool.Database, cfg *config) (*miningPool, error) {
PoolFeeAddrs: cfg.poolFeeAddrs,
SoloPool: cfg.SoloPool,
NonceIterations: iterations,
MinerPort: cfg.MinerPort,
MinerListen: cfg.MinerListen,
MaxConnectionsPerHost: cfg.MaxConnectionsPerHost,
WalletAccount: cfg.WalletAccount,
CoinbaseConfTimeout: cfg.CoinbaseConfTimeout,
Expand Down Expand Up @@ -92,17 +92,18 @@ func newPool(db pool.Database, cfg *config) (*miningPool, error) {
SoloPool: cfg.SoloPool,
GUIDir: cfg.GUIDir,
AdminPass: cfg.AdminPass,
GUIPort: cfg.GUIPort,
GUIListen: cfg.GUIListen,
UseLEHTTPS: cfg.UseLEHTTPS,
NoGUITLS: cfg.NoGUITLS,
Domain: cfg.Domain,
TLSCertFile: cfg.TLSCert,
TLSKeyFile: cfg.TLSKey,
TLSCertFile: cfg.GUITLSCert,
TLSKeyFile: cfg.GUITLSKey,
ActiveNet: cfg.net.Params,
PaymentMethod: cfg.PaymentMethod,
Designation: cfg.Designation,
PoolFee: cfg.PoolFee,
CSRFSecret: csrfSecret,
MinerPort: cfg.MinerPort,
MinerListen: cfg.MinerListen,
WithinLimit: p.hub.WithinLimit,
FetchLastWorkHeight: p.hub.FetchLastWorkHeight,
FetchLastPaymentInfo: p.hub.FetchLastPaymentInfo,
Expand Down
60 changes: 37 additions & 23 deletions gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"html/template"
"net/http"
"os"
Expand Down Expand Up @@ -40,14 +39,16 @@ type Config struct {
CSRFSecret []byte
// AdminPass represents the admin password.
AdminPass string
// GUIPort represents the port the frontend is served on.
GUIPort uint32
// GUIListen represents the listening address the frontend is served on.
GUIListen string
// TLSCertFile represents the TLS certificate file path.
TLSCertFile string
// TLSKeyFile represents the TLS key file path.
TLSKeyFile string
// UseLEHTTPS represents Letsencrypt HTTPS mode.
UseLEHTTPS bool
// NoGUITLS starts the webserver listening for plain HTTP.
NoGUITLS bool
// Domain represents the domain name of the pool.
Domain string
// ActiveNet represents the active network being mined on.
Expand All @@ -58,8 +59,8 @@ type Config struct {
Designation string
// PoolFee represents the fee charged to participating accounts of the pool.
PoolFee float64
// MinerPort represents the miner connection port for the pool.
MinerPort uint32
// MinerListen represents the listening address for miner connections.
MinerListen string
// WithinLimit returns if a client is within its request limits.
WithinLimit func(string, int) bool
// FetchLastWorkHeight returns the last work height of the pool.
Expand Down Expand Up @@ -247,24 +248,8 @@ func (ui *GUI) loadTemplates() error {
// Run starts the user interface.
func (ui *GUI) Run(ctx context.Context) {
go func() {
if !ui.cfg.UseLEHTTPS {
log.Infof("Starting GUI server on port %d (https)", ui.cfg.GUIPort)
ui.server = &http.Server{
WriteTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
IdleTimeout: time.Second * 30,
Addr: fmt.Sprintf("0.0.0.0:%v", ui.cfg.GUIPort),
Handler: ui.router,
}

if err := ui.server.ListenAndServeTLS(ui.cfg.TLSCertFile,
ui.cfg.TLSKeyFile); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}

if ui.cfg.UseLEHTTPS {
switch {
case ui.cfg.UseLEHTTPS:
certMgr := &autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache("certs"),
Expand Down Expand Up @@ -295,6 +280,35 @@ func (ui *GUI) Run(ctx context.Context) {
if err := ui.server.ListenAndServeTLS("", ""); err != nil {
log.Error(err)
}
case ui.cfg.NoGUITLS:
log.Infof("Starting GUI server on %s (http)", ui.cfg.GUIListen)
ui.server = &http.Server{
WriteTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
IdleTimeout: time.Second * 30,
Addr: ui.cfg.GUIListen,
Handler: ui.router,
}

if err := ui.server.ListenAndServe(); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
default:
log.Infof("Starting GUI server on %s (https)", ui.cfg.GUIListen)
ui.server = &http.Server{
WriteTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
IdleTimeout: time.Second * 30,
Addr: ui.cfg.GUIListen,
Handler: ui.router,
}

if err := ui.server.ListenAndServeTLS(ui.cfg.TLSCertFile,
ui.cfg.TLSKeyFile); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}
}()

Expand Down
11 changes: 9 additions & 2 deletions gui/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package gui

import (
"net"
"net/http"

"github.com/gorilla/csrf"
Expand All @@ -15,7 +16,7 @@ import (
type indexPageData struct {
HeaderData headerData
PoolStatsData poolStatsData
MinerPort uint32
MinerPort string
MinedWork []*minedWork
RewardQuotas []*rewardQuota
Address string
Expand Down Expand Up @@ -43,6 +44,12 @@ func (ui *GUI) renderIndex(w http.ResponseWriter, r *http.Request, modalError st
address = ui.cfg.Domain
}

_, minerPort, err := net.SplitHostPort(ui.cfg.MinerListen)
if err != nil {
log.Errorf("failed to parse port from miner listening address %q: %v",
ui.cfg.MinerListen, err)
}

data := indexPageData{
HeaderData: headerData{
CSRF: csrf.TemplateField(r),
Expand All @@ -60,7 +67,7 @@ func (ui *GUI) renderIndex(w http.ResponseWriter, r *http.Request, modalError st
},
RewardQuotas: rewardQuotas,
MinedWork: confirmedWork,
MinerPort: ui.cfg.MinerPort,
MinerPort: minerPort,
ModalError: modalError,
Address: address,
}
Expand Down
20 changes: 10 additions & 10 deletions pool/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type connection struct {

// Endpoint represents a stratum endpoint.
type Endpoint struct {
port uint32
listenAddr string
connCh chan *connection
discCh chan struct{}
listener net.Listener
Expand All @@ -82,17 +82,17 @@ type Endpoint struct {
}

// NewEndpoint creates an new miner endpoint.
func NewEndpoint(eCfg *EndpointConfig, port uint32) (*Endpoint, error) {
func NewEndpoint(eCfg *EndpointConfig, listenAddr string) (*Endpoint, error) {
endpoint := &Endpoint{
port: port,
cfg: eCfg,
clients: make(map[string]*Client),
connCh: make(chan *connection, bufferSize),
discCh: make(chan struct{}, bufferSize),
listenAddr: listenAddr,
cfg: eCfg,
clients: make(map[string]*Client),
connCh: make(chan *connection, bufferSize),
discCh: make(chan struct{}, bufferSize),
}
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "0.0.0.0", endpoint.port))
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
desc := fmt.Sprintf("unable to create endpoint on port %d", port)
desc := fmt.Sprintf("unable to create endpoint on %s", listenAddr)
return nil, errs.PoolError(errs.Listener, desc)
}
endpoint.listener = listener
Expand All @@ -110,7 +110,7 @@ func (e *Endpoint) removeClient(c *Client) {
// listen accepts incoming client connections on the endpoint.
// It must be run as a goroutine.
func (e *Endpoint) listen() {
log.Infof("listening on :%d", e.port)
log.Infof("listening on %s", e.listenAddr)
for {
conn, err := e.listener.Accept()
if err != nil {
Expand Down
Loading

0 comments on commit 4ba2828

Please sign in to comment.