mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-14 11:58:04 +00:00
c712133df0
I've re-written the code for this several times before but always ended up not going through with it because a hashed Bcrypt string has dollar signs in it, which caused issues with the config due to environment variable support. I finally decided to go through with it by forcing users to base64 encode the bcrypt hash
109 lines
3.5 KiB
Go
109 lines
3.5 KiB
Go
package security
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/TwiN/g8"
|
|
"github.com/gorilla/mux"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
cookieNameState = "gatus_state"
|
|
cookieNameNonce = "gatus_nonce"
|
|
cookieNameSession = "gatus_session"
|
|
)
|
|
|
|
// Config is the security configuration for Gatus
|
|
type Config struct {
|
|
Basic *BasicConfig `yaml:"basic,omitempty"`
|
|
OIDC *OIDCConfig `yaml:"oidc,omitempty"`
|
|
|
|
gate *g8.Gate
|
|
}
|
|
|
|
// IsValid returns whether the security configuration is valid or not
|
|
func (c *Config) IsValid() bool {
|
|
return (c.Basic != nil && c.Basic.isValid()) || (c.OIDC != nil && c.OIDC.isValid())
|
|
}
|
|
|
|
// RegisterHandlers registers all handlers required based on the security configuration
|
|
func (c *Config) RegisterHandlers(router *mux.Router) error {
|
|
if c.OIDC != nil {
|
|
if err := c.OIDC.initialize(); err != nil {
|
|
return err
|
|
}
|
|
router.HandleFunc("/oidc/login", c.OIDC.loginHandler)
|
|
router.HandleFunc("/authorization-code/callback", c.OIDC.callbackHandler)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ApplySecurityMiddleware applies an authentication middleware to the router passed.
|
|
// The router passed should be a subrouter in charge of handlers that require authentication.
|
|
func (c *Config) ApplySecurityMiddleware(api *mux.Router) error {
|
|
if c.OIDC != nil {
|
|
// We're going to use g8 for session handling
|
|
clientProvider := g8.NewClientProvider(func(token string) *g8.Client {
|
|
if _, exists := sessions.Get(token); exists {
|
|
return g8.NewClient(token)
|
|
}
|
|
return nil
|
|
})
|
|
customTokenExtractorFunc := func(request *http.Request) string {
|
|
sessionCookie, err := request.Cookie(cookieNameSession)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return sessionCookie.Value
|
|
}
|
|
// TODO: g8: Add a way to update cookie after? would need the writer
|
|
authorizationService := g8.NewAuthorizationService().WithClientProvider(clientProvider)
|
|
c.gate = g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)
|
|
api.Use(c.gate.Protect)
|
|
} else if c.Basic != nil {
|
|
var decodedBcryptHash []byte
|
|
if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 {
|
|
var err error
|
|
decodedBcryptHash, err = base64.URLEncoding.DecodeString(c.Basic.PasswordBcryptHashBase64Encoded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
api.Use(func(handler http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
usernameEntered, passwordEntered, ok := r.BasicAuth()
|
|
if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 {
|
|
if !ok || usernameEntered != c.Basic.Username || bcrypt.CompareHashAndPassword(decodedBcryptHash, []byte(passwordEntered)) != nil {
|
|
w.Header().Set("WWW-Authenticate", "Basic")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
_, _ = w.Write([]byte("Unauthorized"))
|
|
return
|
|
}
|
|
} else if len(c.Basic.PasswordSha512Hash) > 0 {
|
|
if !ok || usernameEntered != c.Basic.Username || Sha512(passwordEntered) != strings.ToLower(c.Basic.PasswordSha512Hash) {
|
|
w.Header().Set("WWW-Authenticate", "Basic")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
_, _ = w.Write([]byte("Unauthorized"))
|
|
return
|
|
}
|
|
}
|
|
handler.ServeHTTP(w, r)
|
|
})
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsAuthenticated checks whether the user is authenticated
|
|
// If the Config does not warrant authentication, it will always return true.
|
|
func (c *Config) IsAuthenticated(r *http.Request) bool {
|
|
if c.gate != nil {
|
|
token := c.gate.ExtractTokenFromRequest(r)
|
|
_, hasSession := sessions.Get(token)
|
|
return hasSession
|
|
}
|
|
return false
|
|
}
|