diff --git a/cmd/vspd/config.go b/cmd/vspd/config.go index aecedbae..b4a4866a 100644 --- a/cmd/vspd/config.go +++ b/cmd/vspd/config.go @@ -66,7 +66,6 @@ type config struct { BackupInterval time.Duration `long:"backupinterval" ini-name:"backupinterval" description:"Time period between automatic database backups. Valid time units are {s,m,h}. Minimum 30 seconds."` VspClosed bool `long:"vspclosed" ini-name:"vspclosed" description:"Closed prevents the VSP from accepting new tickets."` VspClosedMsg string `long:"vspclosedmsg" ini-name:"vspclosedmsg" description:"A short message displayed on the webpage and returned by the status API endpoint if vspclosed is true."` - AdminPass string `long:"adminpass" ini-name:"adminpass" description:"Password for accessing admin page."` Designation string `long:"designation" ini-name:"designation" description:"Short name for the VSP. Customizes the logo in the top toolbar."` // The following flags should be set on CLI only, not via config file. @@ -170,7 +169,6 @@ func normalizeAddress(addr, defaultPort string) string { // while still allowing the user to override settings with config files and // command line options. Command line options always take precedence. func loadConfig() (*config, error) { - // Default config. cfg := config{ Listen: defaultListen, @@ -307,11 +305,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/cmd/vspd/main.go b/cmd/vspd/main.go index 4831b603..2e5bf4ff 100644 --- a/cmd/vspd/main.go +++ b/cmd/vspd/main.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "os" + "path/filepath" "runtime" "sync" "time" @@ -19,11 +20,15 @@ import ( "github.com/decred/vspd/webapi" ) -// maxVoteChangeRecords defines how many vote change records will be stored for -// each ticket. The limit is in place to mitigate DoS attacks on server storage -// space. When storing a new record breaches this limit, the oldest record in -// the database is deleted. -const maxVoteChangeRecords = 10 +const ( + // maxVoteChangeRecords defines how many vote change records will be stored for + // each ticket. The limit is in place to mitigate DoS attacks on server storage + // space. When storing a new record breaches this limit, the oldest record in + // the database is deleted. + maxVoteChangeRecords = 10 + // passwordHashFileName is the name of the file containing admin password hash. + passwordHashFileName = "password.hash" +) // consistencyInterval is the time period between wallet consistency checks. const consistencyInterval = 30 * time.Minute @@ -56,6 +61,23 @@ func run() int { shutdownCtx := withShutdownCancel(context.Background()) go shutdownListener(log) + // Request admin password if admin password hash file is not found. + var adminAuthHash []byte + passwordDir := filepath.Join(cfg.HomeDir, passwordHashFileName) + if fileExists(passwordDir) { + adminAuthHash, err = readPassHashFromFile(passwordDir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot use password: %v\n", err) + return 1 + } + } else { + adminAuthHash, err = createPassHashFile(shutdownCtx, passwordDir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot use password: %v\n", err) + return 1 + } + } + // Show version at startup. log.Criticalf("Version %s (Go version %s %s/%s)", version.String(), runtime.Version(), runtime.GOOS, runtime.GOARCH) @@ -175,7 +197,7 @@ func run() int { SupportEmail: cfg.SupportEmail, VspClosed: cfg.VspClosed, VspClosedMsg: cfg.VspClosedMsg, - AdminPass: cfg.AdminPass, + AdminAuthHash: adminAuthHash, Debug: cfg.WebServerDebug, Designation: cfg.Designation, MaxVoteChangeRecords: maxVoteChangeRecords, diff --git a/cmd/vspd/prompt.go b/cmd/vspd/prompt.go new file mode 100644 index 00000000..43c45cc1 --- /dev/null +++ b/cmd/vspd/prompt.go @@ -0,0 +1,118 @@ +// Copyright (c) 2022 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "context" + "crypto/sha256" + "fmt" + "os" + + "golang.org/x/term" +) + +type passwordReadResponse struct { + password []byte + err error +} + +// clearBytes zeroes the byte slice. +func clearBytes(b []byte) { + for i := range b { + b[i] = 0 + } +} + +// passwordPrompt prompts the user to enter a password. Password must not be an +// empty string. +func passwordPrompt(ctx context.Context, prompt string) ([]byte, error) { + // Get the initial state of the terminal. + initialTermState, err := term.GetState(int(os.Stdin.Fd())) + if err != nil { + return nil, err + } + + passwordReadChan := make(chan passwordReadResponse, 1) + + go func() { + fmt.Print(prompt) + pass, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + passwordReadChan <- passwordReadResponse{ + password: pass, + err: err, + } + }() + + select { + case <-ctx.Done(): + _ = term.Restore(int(os.Stdin.Fd()), initialTermState) + return nil, ctx.Err() + + case res := <-passwordReadChan: + if res.err != nil { + return nil, res.err + } + return res.password, nil + } +} + +// passwordHashPrompt prompts the user to enter a password and returns its +// SHA256 hash. Password must not be an empty string. +func passwordHashPrompt(ctx context.Context, prompt string) ([]byte, error) { + var passBytes []byte + var err error + + // Ensure passBytes is not empty. + for len(passBytes) == 0 { + passBytes, err = passwordPrompt(ctx, prompt) + if err != nil { + return nil, err + } + } + + authHash := sha256.Sum256(passBytes) + // Zero password bytes. + clearBytes(passBytes) + return authHash[:], nil +} + +// readPassHashFromFile reads admin password hash from provided file. +func readPassHashFromFile(passwordDir string) ([]byte, error) { + passwordFile, err := os.Open(passwordDir) + if err != nil { + return nil, err + } + defer passwordFile.Close() + + reader := bufio.NewReader(passwordFile) + adminAuthHash, _, err := reader.ReadLine() + if err != nil { + return nil, err + } + + return adminAuthHash, nil +} + +// createPassHashFile prompts user for password, +// hashes the provided password and saves the hashed password to a file. +func createPassHashFile(ctx context.Context, passwordDir string) ([]byte, error) { + adminAuthHash, err := passwordHashPrompt(ctx, "Enter admin Password:") + if err != nil { + return nil, err + } + passwordFile, err := os.Create(passwordDir) + if err != nil { + return nil, err + } + defer passwordFile.Close() + // Length of byte is ignored + _, err = passwordFile.Write(adminAuthHash) + if err != nil { + return nil, err + } + return adminAuthHash, nil +} diff --git a/go.mod b/go.mod index 5c2bf0c4..6cefe75b 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,8 @@ require ( github.com/jrick/logrotate v1.0.0 github.com/jrick/wsrpc/v2 v2.3.5 go.etcd.io/bbolt v1.3.6 + golang.org/x/crypto v0.3.0 // indirect + golang.org/x/term v0.2.0 ) require ( @@ -55,7 +57,6 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/ugorji/go/codec v1.2.7 // indirect - golang.org/x/crypto v0.3.0 // indirect golang.org/x/net v0.2.0 // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect @@ -63,6 +64,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace ( - github.com/decred/vspd/types => ./types -) +replace github.com/decred/vspd/types => ./types diff --git a/go.sum b/go.sum index 62da0ed8..f036f91e 100644 --- a/go.sum +++ b/go.sum @@ -13,11 +13,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I= github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= -github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA= github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= @@ -89,12 +87,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -150,11 +144,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -169,8 +160,6 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -186,14 +175,12 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -202,7 +189,6 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= @@ -219,7 +205,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= @@ -245,18 +230,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w 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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0= -golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= @@ -284,8 +267,6 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -301,8 +282,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/webapi/admin.go b/webapi/admin.go index 5cd4c598..d55dcf02 100644 --- a/webapi/admin.go +++ b/webapi/admin.go @@ -5,6 +5,8 @@ package webapi import ( + "crypto/sha256" + "crypto/subtle" "net/http" "github.com/decred/vspd/database" @@ -196,8 +198,8 @@ func (s *Server) ticketSearch(c *gin.Context) { // the current session will be authenticated as an admin. func (s *Server) adminLogin(c *gin.Context) { password := c.PostForm("password") - - if password != s.cfg.AdminPass { + passwordHash := sha256.Sum256([]byte(password)) + if subtle.ConstantTimeCompare(s.cfg.AdminAuthHash[:], passwordHash[:]) != 1 { s.log.Warnf("Failed login attempt from %s", c.ClientIP()) c.HTML(http.StatusUnauthorized, "login.html", gin.H{ "WebApiCache": s.cache.getData(), diff --git a/webapi/middleware.go b/webapi/middleware.go index c15ed83d..6f9b9233 100644 --- a/webapi/middleware.go +++ b/webapi/middleware.go @@ -6,6 +6,8 @@ package webapi import ( "bytes" + "crypto/sha256" + "crypto/subtle" "errors" "io" "net/http" @@ -373,3 +375,19 @@ func (s *Server) vspAuth(c *gin.Context) { c.Set(knownTicketKey, ticketFound) c.Set(commitmentAddressKey, commitmentAddress) } + +// authMiddleware checks incoming requests for authentication. +func (s *Server) authMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // User is ignored + _, password, ok := c.Request.BasicAuth() + passwordHash := sha256.Sum256([]byte(password)) + if !ok || subtle.ConstantTimeCompare(s.cfg.AdminAuthHash[:], passwordHash[:]) != 1 { + s.log.Warnf("Failed authentication attempt from %s", c.ClientIP()) + // Credentials doesn't match, we return 401 and abort handlers chain. + c.Header("WWW-Authenticate", `Basic realm="Authorization Required"`) + c.AbortWithStatus(http.StatusUnauthorized) + return + } + } +} diff --git a/webapi/webapi.go b/webapi/webapi.go index 9498313e..9cd7213e 100644 --- a/webapi/webapi.go +++ b/webapi/webapi.go @@ -35,7 +35,7 @@ type Config struct { SupportEmail string VspClosed bool VspClosedMsg string - AdminPass string + AdminAuthHash []byte Debug bool Designation string MaxVoteChangeRecords int @@ -261,9 +261,7 @@ func (s *Server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W // Require Basic HTTP Auth on /admin/status endpoint. basic := router.Group("/admin").Use( - s.withDcrdClient(dcrd), s.withWalletClients(wallets), gin.BasicAuth(gin.Accounts{ - "admin": s.cfg.AdminPass, - }), + s.withDcrdClient(dcrd), s.withWalletClients(wallets), s.authMiddleware(), ) basic.GET("/status", s.statusJSON)