mirror of https://github.com/poanetwork/gecko.git
use hash of password rather than plaintext password
This commit is contained in:
parent
e144b1087e
commit
fe3e1a319e
|
@ -1,6 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -10,6 +11,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/gecko/utils/hashing"
|
||||
|
||||
"github.com/ava-labs/gecko/utils/timer"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
@ -34,11 +37,11 @@ var (
|
|||
|
||||
// Auth handles HTTP API authorization for this node
|
||||
type Auth struct {
|
||||
lock sync.RWMutex // Prevent race condition when accessing password
|
||||
Enabled bool // True iff API calls need auth token
|
||||
clock timer.Clock // Tells the time. Can be faked for testing
|
||||
Password string // The password. Can be changed via API call.
|
||||
revoked []string // List of tokens that have been revoked
|
||||
lock sync.RWMutex // Prevent race condition when accessing password
|
||||
Enabled bool // True iff API calls need auth token
|
||||
clock timer.Clock // Tells the time. Can be faked for testing
|
||||
HashedPassword []byte // Hash of the password. Can be changed via API call.
|
||||
revoked []string // List of tokens that have been revoked
|
||||
}
|
||||
|
||||
// Custom claim type used for API access token
|
||||
|
@ -70,7 +73,7 @@ func getToken(r *http.Request) (string, error) {
|
|||
func (auth *Auth) newToken(password string, endpoints []string) (string, error) {
|
||||
auth.lock.RLock()
|
||||
defer auth.lock.RUnlock()
|
||||
if password != auth.Password {
|
||||
if !bytes.Equal(hashing.ComputeHash256([]byte(password)), auth.HashedPassword) {
|
||||
return "", errWrongPassword
|
||||
}
|
||||
canAccessAll := false
|
||||
|
@ -91,7 +94,7 @@ func (auth *Auth) newToken(password string, endpoints []string) (string, error)
|
|||
claims.Endpoints = endpoints
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(auth.Password)) // Sign the token and return its string repr.
|
||||
return token.SignedString(auth.HashedPassword) // Sign the token and return its string repr.
|
||||
|
||||
}
|
||||
|
||||
|
@ -104,12 +107,12 @@ func (auth *Auth) newToken(password string, endpoints []string) (string, error)
|
|||
func (auth *Auth) revokeToken(tokenStr string, password string) error {
|
||||
auth.lock.Lock()
|
||||
defer auth.lock.Unlock()
|
||||
if auth.Password != password {
|
||||
if !bytes.Equal(auth.HashedPassword, hashing.ComputeHash256([]byte(password))) {
|
||||
return errWrongPassword
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenStr, func(*jwt.Token) (interface{}, error) { // See if token is well-formed and signature is right
|
||||
return []byte(auth.Password), nil
|
||||
return auth.HashedPassword, nil
|
||||
})
|
||||
if err == nil && token.Valid { // Only need to revoke if the token is valid
|
||||
auth.revoked = append(auth.revoked, tokenStr)
|
||||
|
@ -124,14 +127,14 @@ func (auth *Auth) revokeToken(tokenStr string, password string) error {
|
|||
func (auth *Auth) changePassword(oldPassword, newPassword string) error {
|
||||
auth.lock.Lock()
|
||||
defer auth.lock.Unlock()
|
||||
if auth.Password != oldPassword {
|
||||
if !bytes.Equal(auth.HashedPassword, hashing.ComputeHash256([]byte(oldPassword))) {
|
||||
return errWrongPassword
|
||||
} else if len(newPassword) == 0 || len(newPassword) > maxPasswordLen {
|
||||
return fmt.Errorf("new password length exceeds maximum length, %d", maxPasswordLen)
|
||||
} else if oldPassword == newPassword {
|
||||
return errors.New("new password can't be same as old password")
|
||||
}
|
||||
auth.Password = newPassword
|
||||
auth.HashedPassword = hashing.ComputeHash256([]byte(newPassword))
|
||||
auth.revoked = []string{} // All the revoked tokens are now invalid; no need to mark specifically as revoked
|
||||
return nil
|
||||
}
|
||||
|
@ -162,7 +165,7 @@ func (auth *Auth) WrapHandler(h http.Handler) http.Handler {
|
|||
token, err := jwt.ParseWithClaims(tokenStr, &endpointClaims{}, func(*jwt.Token) (interface{}, error) { // See if token is well-formed and signature is right
|
||||
auth.lock.RLock()
|
||||
defer auth.lock.RUnlock()
|
||||
return []byte(auth.Password), nil
|
||||
return auth.HashedPassword, nil
|
||||
})
|
||||
if err != nil { // Probably because signature wrong
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -9,13 +10,16 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/gecko/utils/hashing"
|
||||
|
||||
"github.com/ava-labs/gecko/utils/timer"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
const (
|
||||
password = "password"
|
||||
var (
|
||||
password = "password"
|
||||
hashedPassword = hashing.ComputeHash256([]byte(password))
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -25,8 +29,8 @@ var (
|
|||
|
||||
func TestNewTokenWrongPassword(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
if _, err := auth.newToken("", []string{"endpoint1, endpoint2"}); err == nil {
|
||||
t.Fatal("should have failed because password is wrong")
|
||||
|
@ -37,8 +41,8 @@ func TestNewTokenWrongPassword(t *testing.T) {
|
|||
|
||||
func TestNewTokenHappyPath(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
now := time.Now()
|
||||
auth.clock.Set(now)
|
||||
|
@ -54,7 +58,7 @@ func TestNewTokenHappyPath(t *testing.T) {
|
|||
token, err := jwt.ParseWithClaims(tokenStr, &endpointClaims{}, func(*jwt.Token) (interface{}, error) {
|
||||
auth.lock.RLock()
|
||||
defer auth.lock.RUnlock()
|
||||
return []byte(auth.Password), nil
|
||||
return auth.HashedPassword, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't parse new token: %s", err)
|
||||
|
@ -74,8 +78,8 @@ func TestNewTokenHappyPath(t *testing.T) {
|
|||
|
||||
func TestTokenHasWrongSig(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token
|
||||
|
@ -106,8 +110,8 @@ func TestTokenHasWrongSig(t *testing.T) {
|
|||
|
||||
func TestChangePassword(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
password2 := "password2"
|
||||
|
@ -121,7 +125,7 @@ func TestChangePassword(t *testing.T) {
|
|||
t.Fatal("should have succeeded")
|
||||
}
|
||||
|
||||
if auth.Password != password2 {
|
||||
if !bytes.Equal(auth.HashedPassword, hashing.ComputeHash256([]byte(password2))) {
|
||||
t.Fatal("password should have been changed")
|
||||
}
|
||||
|
||||
|
@ -161,8 +165,8 @@ func TestGetToken(t *testing.T) {
|
|||
|
||||
func TestRevokeToken(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token
|
||||
|
@ -181,8 +185,8 @@ func TestRevokeToken(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerHappyPath(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token
|
||||
|
@ -207,8 +211,8 @@ func TestWrapHandlerHappyPath(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerRevokedToken(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token
|
||||
|
@ -236,9 +240,9 @@ func TestWrapHandlerRevokedToken(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerExpiredToken(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
clock: timer.Clock{},
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
clock: timer.Clock{},
|
||||
}
|
||||
auth.clock.Set(time.Now().Add(-2 * TokenLifespan))
|
||||
|
||||
|
@ -264,8 +268,8 @@ func TestWrapHandlerExpiredToken(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerNoAuthToken(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
endpoints := []string{"/ext/info", "/ext/bc/X", "/ext/metrics"}
|
||||
|
@ -282,8 +286,8 @@ func TestWrapHandlerNoAuthToken(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerUnauthorizedEndpoint(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token
|
||||
|
@ -309,8 +313,8 @@ func TestWrapHandlerUnauthorizedEndpoint(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerAuthEndpoint(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token
|
||||
|
@ -332,8 +336,8 @@ func TestWrapHandlerAuthEndpoint(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerAccessAll(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: true,
|
||||
Password: password,
|
||||
Enabled: true,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
// Make a token that allows access to all endpoints
|
||||
|
@ -357,8 +361,8 @@ func TestWrapHandlerAccessAll(t *testing.T) {
|
|||
|
||||
func TestWrapHandlerAuthDisabled(t *testing.T) {
|
||||
auth := Auth{
|
||||
Enabled: false,
|
||||
Password: password,
|
||||
Enabled: false,
|
||||
HashedPassword: hashedPassword,
|
||||
}
|
||||
|
||||
endpoints := []string{"/ext/info", "/ext/bc/X", "/ext/metrics", "", "/foo", "/ext/foo/info", "/ext/auth"}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/ava-labs/gecko/api/auth"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/snow/engine/common"
|
||||
"github.com/ava-labs/gecko/utils/hashing"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
)
|
||||
|
||||
|
@ -52,8 +53,8 @@ func (s *Server) Initialize(log logging.Logger, factory logging.Factory, host st
|
|||
s.listenAddress = fmt.Sprintf("%s:%d", host, port)
|
||||
s.router = newRouter()
|
||||
s.auth = &auth.Auth{
|
||||
Enabled: authEnabled,
|
||||
Password: authPassword,
|
||||
Enabled: authEnabled,
|
||||
HashedPassword: hashing.ComputeHash256([]byte(authPassword)),
|
||||
}
|
||||
if authEnabled { // only create auth service if token authorization is required
|
||||
s.log.Info("API authorization is enabled. Auth token must be passed in header of API requests (except requests to auth service.)")
|
||||
|
|
Loading…
Reference in New Issue