diff --git a/config.go b/config.go index 155a10e9..32601f55 100644 --- a/config.go +++ b/config.go @@ -302,11 +302,6 @@ func loadConfig() (*config, error) { return nil, errors.New("the supportemail option is not set") } - // Ensure the administrator password is set. - if cfg.AdminPass == "" { - return nil, errors.New("the adminpass option is not set") - } - // Ensure the dcrd RPC username is set. if cfg.DcrdUser == "" { return nil, errors.New("the dcrduser option is not set") diff --git a/database/database.go b/database/database.go index e9b88819..3060e291 100644 --- a/database/database.go +++ b/database/database.go @@ -9,6 +9,7 @@ import ( "crypto/ed25519" "crypto/rand" "encoding/binary" + "errors" "fmt" "io" "net/http" @@ -52,6 +53,8 @@ var ( lastAddressIndexK = []byte("lastaddressindex") // altSignAddrBktK stores alternate signing addresses. altSignAddrBktK = []byte("altsigbkt") + //adminPassHashK stores the hash of cfg.AdminPass + adminPassHashK = []byte("adminPassHash") ) const ( @@ -457,3 +460,26 @@ func (vdb *VspDatabase) CheckIntegrity(ctx context.Context, params *chaincfg.Par return nil } + +// UpdateAdminPass stores admin password hash in Database. +func (vdb *VspDatabase) UpdateAdminPass(adminPassHash string) error { +return vdb.db.Update(func(tx *bolt.Tx) error { + vspBkt := tx.Bucket(vspBktK) +return vspBkt.Put(adminPassHashK, []byte(adminPassHash)) + }) +} + +// GetAdminHash retrieves admin password hash from Database. +func (vdb *VspDatabase) GetAdminHash() ([]byte, error) { + var adminPassHash []byte + err := vdb.db.View(func(tx *bolt.Tx) error { + vspBkt := tx.Bucket(vspBktK) + adminPassHash = vspBkt.Get(adminPassHashK) + if adminPassHash == nil { + return errors.New("admin password hash has not been set") + } + return nil + }) + + return adminPassHash, err +} diff --git a/go.mod b/go.mod index 7069a77d..80567bfb 100644 --- a/go.mod +++ b/go.mod @@ -22,4 +22,5 @@ require ( github.com/jrick/logrotate v1.0.0 github.com/jrick/wsrpc/v2 v2.3.4 go.etcd.io/bbolt v1.3.6 + golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect ) diff --git a/go.sum b/go.sum index b49e8f37..6e97a7ff 100644 --- a/go.sum +++ b/go.sum @@ -187,6 +187,8 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8= +golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -200,6 +202,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -220,13 +224,19 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/vspd.go b/vspd.go index f9922e15..839e749e 100644 --- a/vspd.go +++ b/vspd.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -17,6 +17,7 @@ import ( "github.com/decred/vspd/rpc" "github.com/decred/vspd/version" "github.com/decred/vspd/webapi" + "golang.org/x/crypto/bcrypt" ) // maxVoteChangeRecords defines how many vote change records will be stored for @@ -37,6 +38,12 @@ func main() { } } +// hashPassword hashes cfg.AdminPass and returns the hash. +func hashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 15) + return string(bytes), err +} + // run is the main startup and teardown logic performed by the main package. It // is responsible for parsing the config, creating a dcrwallet RPC client, // opening the database, starting the webserver, and stopping all started @@ -74,6 +81,33 @@ func run(ctx context.Context) error { } defer db.Close() + // Check if admin password hash is set in database. + hash, err := db.GetAdminHash() + if cfg.AdminPass == "" && err != nil { + log.Errorf("Admin password is not yet set: %v.", err) + return err + } + + if cfg.AdminPass != "" { + // Hash cfg.AdminPass + cfg.AdminPass, err = hashPassword(cfg.AdminPass) + if err != nil { + log.Errorf("Hashing admin password failed: %v", err) + return err + } + + // If cfg.adminPass is set, + // overwrite the saved admin password hash in database and shutdown. + db.UpdateAdminPass(cfg.AdminPass) + log.Info("Admin password has been hashed and saved successfully.\n Remove the admin password from config file and command line before running vspd again.") + log.Info("To reset admin password, provide admin password via config or commandline.") + requestShutdown() + shutdownWg.Wait() + } + + // Assign Hash Value to cfg.AdminPass + cfg.AdminPass = string(hash) + // Create RPC client for local dcrd instance (used for broadcasting and // checking the status of fee transactions). dcrd := rpc.SetupDcrd(cfg.DcrdUser, cfg.DcrdPass, cfg.DcrdHost, cfg.dcrdCert, nil) diff --git a/webapi/admin.go b/webapi/admin.go index bb3d99bd..3684e7ae 100644 --- a/webapi/admin.go +++ b/webapi/admin.go @@ -11,6 +11,7 @@ import ( "github.com/decred/vspd/rpc" "github.com/gin-gonic/gin" "github.com/gorilla/sessions" + "golang.org/x/crypto/bcrypt" ) // WalletStatus describes the current status of a single voting wallet. This is @@ -197,12 +198,19 @@ func ticketSearch(c *gin.Context) { }) } +// checkPasswordHash compares the hash value of the provided password with +// the provided hash. +func checkPasswordHash(hash, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + // adminLogin is the handler for "POST /admin". If a valid password is provided, // the current session will be authenticated as an admin. func adminLogin(c *gin.Context) { password := c.PostForm("password") - - if password != cfg.AdminPass { + ok := checkPasswordHash(cfg.AdminPass, password) + if !ok { log.Warnf("Failed login attempt from %s", c.ClientIP()) c.HTML(http.StatusUnauthorized, "login.html", gin.H{ "WebApiCache": getCache(),