package basic import ( "crypto/sha256" "crypto/subtle" "fmt" "net/http" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn" "forge.cadoles.com/cadoles/bouncer/internal/store" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "golang.org/x/crypto/bcrypt" ) type Authenticator struct { } // Authenticate implements authn.Authenticator. func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, layer *store.Layer) (*authn.User, error) { options, err := fromStoreOptions(layer.Options) if err != nil { return nil, errors.WithStack(err) } username, password, ok := r.BasicAuth() unauthorized := func() { w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s", charset="UTF-8"`, stripNonASCII(options.Realm))) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) } if !ok { unauthorized() return nil, errors.WithStack(authn.ErrSkipRequest) } for _, userInfo := range options.Users { if matches := a.matchUser(userInfo, username, password); !matches { continue } metricAuthorizedTotal.With(prometheus.Labels{ metricLabelLayer: string(layer.Name), metricLabelProxy: string(layer.Proxy), }).Add(1) user := authn.NewUser(userInfo.Username, userInfo.Attributes) return user, nil } metricForbiddenTotal.With(prometheus.Labels{ metricLabelLayer: string(layer.Name), metricLabelProxy: string(layer.Proxy), }).Add(1) unauthorized() return nil, errors.WithStack(authn.ErrSkipRequest) } func (a *Authenticator) matchUser(user User, username, password string) bool { usernameHash := sha256.Sum256([]byte(username)) expectedUsernameHash := sha256.Sum256([]byte(user.Username)) usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1) passwordMatch := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)) == nil return usernameMatch && passwordMatch } var ( _ authn.Authenticator = &Authenticator{} )