feat(security): Implement Bcrypt for basic auth and deprecate SHA512

I've re-written the code for this several times before but always ended up not going through with it because a hashed Bcrypt string has dollar signs in it, which caused issues with the config due to environment variable support.
I finally decided to go through with it by forcing users to base64 encode the bcrypt hash
This commit is contained in:
TwiN
2022-01-08 19:26:37 -05:00
parent fc016bd682
commit c712133df0
11 changed files with 835 additions and 14 deletions

View File

@ -1,15 +1,26 @@
package security
import "log"
// BasicConfig is the configuration for Basic authentication
type BasicConfig struct {
// Username is the name which will need to be used for a successful authentication
Username string `yaml:"username"`
// PasswordSha512Hash is the SHA512 hash of the password which will need to be used for a successful authentication
// XXX: Remove this on v4.0.0
// Deprecated: Use PasswordBcryptHashBase64Encoded instead
PasswordSha512Hash string `yaml:"password-sha512"`
// PasswordBcryptHashBase64Encoded is the base64 encoded string of the Bcrypt hash of the password to use to
// authenticate using basic auth.
PasswordBcryptHashBase64Encoded string `yaml:"password-bcrypt-base64"`
}
// isValid returns whether the basic security configuration is valid or not
func (c *BasicConfig) isValid() bool {
return len(c.Username) > 0 && len(c.PasswordSha512Hash) == 128
if len(c.PasswordSha512Hash) > 0 {
log.Println("WARNING: security.basic.password-sha512 has been deprecated in favor of security.basic.password-bcrypt-base64")
}
return len(c.Username) > 0 && (len(c.PasswordSha512Hash) == 128 || len(c.PasswordBcryptHashBase64Encoded) > 0)
}

View File

@ -1,11 +1,13 @@
package security
import (
"encoding/base64"
"net/http"
"strings"
"github.com/TwiN/g8"
"github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
)
const (
@ -41,7 +43,7 @@ func (c *Config) RegisterHandlers(router *mux.Router) error {
// ApplySecurityMiddleware applies an authentication middleware to the router passed.
// The router passed should be a subrouter in charge of handlers that require authentication.
func (c *Config) ApplySecurityMiddleware(api *mux.Router) {
func (c *Config) ApplySecurityMiddleware(api *mux.Router) error {
if c.OIDC != nil {
// We're going to use g8 for session handling
clientProvider := g8.NewClientProvider(func(token string) *g8.Client {
@ -62,19 +64,37 @@ func (c *Config) ApplySecurityMiddleware(api *mux.Router) {
c.gate = g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)
api.Use(c.gate.Protect)
} else if c.Basic != nil {
var decodedBcryptHash []byte
if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 {
var err error
decodedBcryptHash, err = base64.URLEncoding.DecodeString(c.Basic.PasswordBcryptHashBase64Encoded)
if err != nil {
return err
}
}
api.Use(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
usernameEntered, passwordEntered, ok := r.BasicAuth()
if !ok || usernameEntered != c.Basic.Username || Sha512(passwordEntered) != strings.ToLower(c.Basic.PasswordSha512Hash) {
w.Header().Set("WWW-Authenticate", "Basic")
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte("Unauthorized"))
return
if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 {
if !ok || usernameEntered != c.Basic.Username || bcrypt.CompareHashAndPassword(decodedBcryptHash, []byte(passwordEntered)) != nil {
w.Header().Set("WWW-Authenticate", "Basic")
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte("Unauthorized"))
return
}
} else if len(c.Basic.PasswordSha512Hash) > 0 {
if !ok || usernameEntered != c.Basic.Username || Sha512(passwordEntered) != strings.ToLower(c.Basic.PasswordSha512Hash) {
w.Header().Set("WWW-Authenticate", "Basic")
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte("Unauthorized"))
return
}
}
handler.ServeHTTP(w, r)
})
})
}
return nil
}
// IsAuthenticated checks whether the user is authenticated

View File

@ -6,6 +6,7 @@ import (
)
// Sha512 hashes a provided string using SHA512 and returns the resulting hash as a string
// Deprecated: Use bcrypt instead
func Sha512(s string) string {
hash := sha512.New()
hash.Write([]byte(s))