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{}