feat: add authn-basic layer
This commit is contained in:
75
internal/proxy/director/layer/authn/basic/authenticator.go
Normal file
75
internal/proxy/director/layer/authn/basic/authenticator.go
Normal file
@ -0,0 +1,75 @@
|
||||
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{}
|
||||
)
|
Reference in New Issue
Block a user