Compare commits
1 Commits
develop
...
basicauth-
Author | SHA1 | Date |
---|---|---|
wpetit | c9da46ea98 |
|
@ -0,0 +1,112 @@
|
||||||
|
package basicauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
proxy "forge.cadoles.com/Cadoles/go-proxy"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const LayerType store.LayerType = "basicauth"
|
||||||
|
|
||||||
|
type BasicAuth struct{}
|
||||||
|
|
||||||
|
// LayerType implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) LayerType() store.LayerType {
|
||||||
|
return LayerType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
|
// La méthode doit retourner un "Middleware" qui est un alias
|
||||||
|
// pour les fonctions généralement utilisées
|
||||||
|
// dans les librairies http en Go pour créer
|
||||||
|
// une fonction d'interception/transformation de requête.
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// On récupère les identifiants "basic auth" transmis (ou non)
|
||||||
|
// avec la requête
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
|
||||||
|
// On créait une méthode locale pour gérer le cas d'une erreur d'authentification.
|
||||||
|
unauthorized := func() {
|
||||||
|
// On ajoute cette entête HTTP à la réponse pour déclencher l'affichage
|
||||||
|
// de la popup d'authentification dans le navigateur web de l'utilisateur.
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||||
|
|
||||||
|
// On retoure un code d'erreur HTTP 401 (Unauthorized)
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// L'entête Authorization est absente ou ne correspondant
|
||||||
|
// pas à du Basic Auth, on retourne une erreur HTTP 401 et
|
||||||
|
// on interrompt le traitement de la requête ici
|
||||||
|
unauthorized()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// On extrait les identifiants des options associées
|
||||||
|
// à notre layer
|
||||||
|
expectedUsername, usernameExists := layer.Options["username"].(string)
|
||||||
|
expectedPassword, passwordExists := layer.Options["password"].(string)
|
||||||
|
|
||||||
|
// Si le nom d'utilisateur ou le mot de passe attendu n'existe pas
|
||||||
|
// alors on retourne une erreur HTTP 500 à l'utilisateur.
|
||||||
|
if !usernameExists || !passwordExists {
|
||||||
|
logger.Error(r.Context(), "basicauth layer missing password or username option")
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// On vérifie les identifiants associés à la requête
|
||||||
|
isAuthenticated := authenticate(username, password, expectedUsername, expectedPassword)
|
||||||
|
|
||||||
|
// Si les identifiants sont non reconnus alors
|
||||||
|
// on interrompt le traitement de la requête
|
||||||
|
if !isAuthenticated {
|
||||||
|
unauthorized()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// L'authentification a réussie ! On passe la main au handler HTTP suivant
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(username, password string, expectedUsername, expectedPassword string) bool {
|
||||||
|
// On génère une empreinte au format sha256 pour nos identifiants
|
||||||
|
usernameHash := sha256.Sum256([]byte(username))
|
||||||
|
passwordHash := sha256.Sum256([]byte(password))
|
||||||
|
|
||||||
|
// On effectue de même avec les identifiants attendus.
|
||||||
|
// Pour l'instant, on utilise un couple d'identifiants en "dur".
|
||||||
|
expectedUsernameHash := sha256.Sum256([]byte(expectedUsername))
|
||||||
|
expectedPasswordHash := sha256.Sum256([]byte(expectedPassword))
|
||||||
|
|
||||||
|
// On utilise la méthode subtle.ConstantTimeCompare()
|
||||||
|
// pour faire la comparaison des identifiants en temps constant
|
||||||
|
// et ainsi éviter les attaques par timing.
|
||||||
|
usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1)
|
||||||
|
passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1)
|
||||||
|
|
||||||
|
// L'utilisateur est authentifié si son nom et son mot de passe
|
||||||
|
// correspondent avec ceux attendus.
|
||||||
|
return usernameMatch && passwordMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *BasicAuth {
|
||||||
|
return &BasicAuth{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ director.MiddlewareLayer = &BasicAuth{}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/basicauth-layer-options",
|
||||||
|
"title": "BasicAuth layer options",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package basicauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed layer-options.json
|
||||||
|
var RawLayerOptionsSchema []byte
|
|
@ -0,0 +1,15 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/basicauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterLayer(basicauth.LayerType, setupBasicAuthLayer, basicauth.RawLayerOptionsSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupBasicAuthLayer(conf *config.Config) (director.Layer, error) {
|
||||||
|
return &basicauth.BasicAuth{}, nil
|
||||||
|
}
|
Loading…
Reference in New Issue