diff --git a/cmd/vspd/main.go b/cmd/vspd/main.go index 796a9bd9..49375765 100644 --- a/cmd/vspd/main.go +++ b/cmd/vspd/main.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 The Decred developers +// Copyright (c) 2020-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -158,27 +158,21 @@ func run() int { var wg sync.WaitGroup // Start the webapi server. - wg.Add(1) - go func() { + wg.Go(func() { api.Run(ctx) - wg.Done() - }() + }) // Start vspd. vspd := vspd.New(network, log, db, dcrd, wallets, blockNotifChan) - wg.Add(1) - go func() { + wg.Go(func() { vspd.Run(ctx) - wg.Done() - }() + }) // Periodically write a database backup file. - wg.Add(1) - go func() { + wg.Go(func() { for { select { case <-ctx.Done(): - wg.Done() return case <-time.After(cfg.BackupInterval): err := db.WriteHotBackupFile() @@ -187,7 +181,7 @@ func run() int { } } } - }() + }) // Wait for shutdown tasks to complete before running deferred tasks and // returning. diff --git a/database/database.go b/database/database.go index f7409ad7..8c91de27 100644 --- a/database/database.go +++ b/database/database.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 The Decred developers +// Copyright (c) 2020-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -126,11 +126,10 @@ func CreateNew(dbFile, feeXPub string) error { } // Generate a secret key for initializing the cookie store. + // Since go 1.24, crypto/rand.Read will never return an error. secret := make([]byte, 32) - _, err = rand.Read(secret) - if err != nil { - return err - } + _, _ = rand.Read(secret) + err = vspBkt.Put(cookieSecretK, secret) if err != nil { return err diff --git a/go.mod b/go.mod index 05550c5b..76bf0eb2 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,20 @@ module github.com/decred/vspd -go 1.24.0 +go 1.25.0 require ( - decred.org/dcrwallet/v5 v5.0.1 + decred.org/dcrwallet/v5 v5.1.0 github.com/decred/dcrd/blockchain/stake/v5 v5.0.2 - github.com/decred/dcrd/blockchain/standalone/v2 v2.2.2 + github.com/decred/dcrd/blockchain/standalone/v2 v2.3.0 github.com/decred/dcrd/chaincfg/chainhash v1.0.5 github.com/decred/dcrd/chaincfg/v3 v3.3.0 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 github.com/decred/dcrd/dcrutil/v4 v4.0.3 github.com/decred/dcrd/gcs/v4 v4.1.1 github.com/decred/dcrd/hdkeychain/v3 v3.1.3 github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.4.0 github.com/decred/dcrd/txscript/v4 v4.1.2 - github.com/decred/dcrd/wire v1.7.1 + github.com/decred/dcrd/wire v1.7.5 github.com/decred/slog v1.2.0 github.com/decred/vspd/client/v4 v4.0.2 github.com/decred/vspd/types/v3 v3.0.0 diff --git a/go.sum b/go.sum index b7be2250..905c4d21 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -decred.org/dcrwallet/v5 v5.0.1 h1:0n4HyMyjdsxiwDMyPx4ANRqadgVaNoiyqMU7T/pe7fs= -decred.org/dcrwallet/v5 v5.0.1/go.mod h1:SIFVCQX7qSGccZRG7sbm4NyA49WzXIXuXFll/NItSNo= +decred.org/dcrwallet/v5 v5.1.0 h1:bgd2D/X6OY/+oJoX6bE4rKAV/1JxKd8sETfdAMduO5c= +decred.org/dcrwallet/v5 v5.1.0/go.mod h1:ey7FaK4F6dmyHa3gmOC1XUShtI5jr5/zsYLdqyyVThc= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= @@ -17,8 +17,8 @@ github.com/decred/base58 v1.0.6 h1:NXndBcO+ubGZORV3EulvqeBcMuQM7doqVGa7pBhMOs4= github.com/decred/base58 v1.0.6/go.mod h1:KR7Oh9njDPXTagD4P67KJZwroL8jT653u8CffkYqhcQ= github.com/decred/dcrd/blockchain/stake/v5 v5.0.2 h1:5SZwlt0oJRuj64MqSFyQItSvfnd6hEgkkx4LySK9Zxo= github.com/decred/dcrd/blockchain/stake/v5 v5.0.2/go.mod h1:h6Xw2ECYewEb27i8yVsjOKktVIIu0TTS6EKhqfxfnbA= -github.com/decred/dcrd/blockchain/standalone/v2 v2.2.2 h1:mQA1cEzMS6EHbgV50wb2VFx4VQjNE8+n/B5BKvp5HWk= -github.com/decred/dcrd/blockchain/standalone/v2 v2.2.2/go.mod h1:lkBXrIA42dLYG4nDKAf08RlAeO4c4FC5YeDh8if91Hc= +github.com/decred/dcrd/blockchain/standalone/v2 v2.3.0 h1:AbHhFQE0paPmhRumRkoeRUTrWh0Gb0409exls+yU3VM= +github.com/decred/dcrd/blockchain/standalone/v2 v2.3.0/go.mod h1:9r5PlToh/WNrP3UJ4mG7Hq1U2WQ6I0uu9qMCpsRw22s= github.com/decred/dcrd/chaincfg/chainhash v1.0.5 h1:GwzXLsZoemdDxFdtj3GdW25Z+NXdN6nD3OjVtA+UwiE= github.com/decred/dcrd/chaincfg/chainhash v1.0.5/go.mod h1:vCqZMGtKbyxJkdcVzP3Ryc9IvaspVUb7hO6t+rjxmcs= github.com/decred/dcrd/chaincfg/v3 v3.3.0 h1:Plu5nS4ctf2V+8PMyannHRtcgexjY1EnFLTt3zfgGI8= @@ -35,8 +35,8 @@ github.com/decred/dcrd/dcrec v1.0.1 h1:gDzlndw0zYxM5BlaV17d7ZJV6vhRe9njPBFeg4Db2 github.com/decred/dcrd/dcrec v1.0.1/go.mod h1:CO+EJd8eHFb8WHa84C7ZBkXsNUIywaTHb+UAuI5uo6o= github.com/decred/dcrd/dcrec/edwards/v2 v2.0.4 h1:xmmdtnGxF/Od2doiP56zBv5a3LgJ3PA6mlx3Luf622I= github.com/decred/dcrd/dcrec/edwards/v2 v2.0.4/go.mod h1:07Ke2V+uJkG72M1Eiek8CF6NUB3XPlZ38cIit57R0UU= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/dcrjson/v4 v4.2.0 h1:VZOitxS5/J1Gr8V/rUcVnvbtTvijxrVy7i2A3nu9wto= github.com/decred/dcrd/dcrjson/v4 v4.2.0/go.mod h1:1YuURV3cVmko3lmBlkKc6Y2iHwHJJXATdiLULBS9D+Q= github.com/decred/dcrd/dcrutil/v4 v4.0.3 h1:uUgSBB4ZFHeKQFrdUgKv3PvVJ3YpBpFeXMgXZsa0790= @@ -49,8 +49,8 @@ github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.4.0 h1:BBVaYemabsFsaqNVlCmacoZlSs github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.4.0/go.mod h1:w4C6hZ7ywpc8/YNkiPAknCaqKofF68cRhUiTglEIc7s= github.com/decred/dcrd/txscript/v4 v4.1.2 h1:1EP7ZmBDl2LBeAMTEygxY8rVNN3+lkGqrsb4u64x+II= github.com/decred/dcrd/txscript/v4 v4.1.2/go.mod h1:r5/8qfCnl6TFrE369gggUayVIryM1oC7BLoRfa27Ckw= -github.com/decred/dcrd/wire v1.7.1 h1:kDuHBiY1Qv9rBxYKgC2RgyPy7IOA2WRf00jqHwpr16I= -github.com/decred/dcrd/wire v1.7.1/go.mod h1:eP9XRsMloy+phlntkTAaAm611JgLv8NqY1YJoRxkNKU= +github.com/decred/dcrd/wire v1.7.5 h1:fRaaB5CrwYWGI3YVv50XHm54lsU1TB40WnnIJ4W6aGM= +github.com/decred/dcrd/wire v1.7.5/go.mod h1:NZK8QD5W2ObX6p+Q0TUzYNpQtk4Ov3pBIvc6ZUK88FU= github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= github.com/decred/vspd/client/v4 v4.0.2 h1:yvQaJFy3UdQMcRzjAyvuKPpr2G2Rn6Bbha+HH4TwItQ= diff --git a/internal/webapi/middleware.go b/internal/webapi/middleware.go index 6a4915e8..f0b3957a 100644 --- a/internal/webapi/middleware.go +++ b/internal/webapi/middleware.go @@ -111,6 +111,26 @@ func (w *WebAPI) requireWebCache(c *gin.Context) { c.Set(cacheKey, w.cache.getData()) } +// csrf provides protection against Cross-Site Request Forgery (CSRF) attacks. +func (w *WebAPI) csrf() gin.HandlerFunc { + // The protection offered by http.CrossOriginProtection is not as robust as + // traditional token based solutions, however it is good enough for the vspd + // website which has no particularly sensitive form actions. + // + // Alex Edwards's blog has a good overview of the limitations: + // https://www.alexedwards.net/blog/preventing-csrf-in-go + csrf := http.NewCrossOriginProtection() + return func(c *gin.Context) { + err := csrf.Check(c.Request) + if err != nil { + w.log.Warnf("CSRF failure (clientIP=%s): %v", c.ClientIP(), err) + c.String(http.StatusBadRequest, "CSRF failure") + c.Abort() + return + } + } +} + // requireAdmin will only allow the request to proceed if the current session is // authenticated as an admin, otherwise it will render the login template. func (w *WebAPI) requireAdmin(c *gin.Context) { diff --git a/internal/webapi/webapi.go b/internal/webapi/webapi.go index f7a1fefb..5817bda8 100644 --- a/internal/webapi/webapi.go +++ b/internal/webapi/webapi.go @@ -147,8 +147,7 @@ func (w *WebAPI) Run(ctx context.Context) { var wg sync.WaitGroup // Add the graceful shutdown to the waitgroup. - wg.Add(1) - go func() { + wg.Go(func() { // Wait until context is canceled before shutting down the server. <-ctx.Done() @@ -159,13 +158,10 @@ func (w *WebAPI) Run(ctx context.Context) { cancel() w.log.Debug("Webserver stopped") - - wg.Done() - }() + }) // Start webserver. - wg.Add(1) - go func() { + wg.Go(func() { w.log.Infof("Listening on %s", w.listener.Addr()) err := w.server.Serve(w.listener) // ErrServerClosed is expected from a graceful server shutdown, it can @@ -173,12 +169,10 @@ func (w *WebAPI) Run(ctx context.Context) { if err != nil && !errors.Is(err, http.ErrServerClosed) { w.log.Errorf("Unexpected webserver error: %v", err) } - wg.Done() - }() + }) // Periodically update cached VSP stats. - wg.Add(1) - go func() { + wg.Go(func() { refresh := 1 * time.Minute if w.cfg.Debug { refresh = 1 * time.Second @@ -186,7 +180,6 @@ func (w *WebAPI) Run(ctx context.Context) { for { select { case <-ctx.Done(): - wg.Done() return case <-time.After(refresh): err := w.cache.update() @@ -195,7 +188,7 @@ func (w *WebAPI) Run(ctx context.Context) { } } } - }() + }) wg.Wait() } @@ -239,6 +232,8 @@ func (w *WebAPI) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W router.Use(gin.Logger()) } + router.Use(w.csrf()) + // Serve static web resources router.Static("/public", "internal/webapi/public/")