76 lines
2.0 KiB
Go
76 lines
2.0 KiB
Go
|
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{}
|
||
|
)
|