2020-10-15 01:22:58 +02:00
|
|
|
package security
|
|
|
|
|
2021-12-15 05:20:43 +01:00
|
|
|
import (
|
2022-01-09 01:26:37 +01:00
|
|
|
"encoding/base64"
|
2023-07-09 02:37:41 +02:00
|
|
|
"log"
|
2021-12-31 06:10:54 +01:00
|
|
|
"net/http"
|
|
|
|
|
2023-04-22 18:55:30 +02:00
|
|
|
g8 "github.com/TwiN/g8/v2"
|
2023-07-09 02:37:41 +02:00
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
"github.com/gofiber/fiber/v2/middleware/adaptor"
|
|
|
|
"github.com/gofiber/fiber/v2/middleware/basicauth"
|
2022-01-09 01:26:37 +01:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2021-12-15 05:20:43 +01:00
|
|
|
)
|
|
|
|
|
2021-12-31 06:10:54 +01:00
|
|
|
const (
|
|
|
|
cookieNameState = "gatus_state"
|
|
|
|
cookieNameNonce = "gatus_nonce"
|
|
|
|
cookieNameSession = "gatus_session"
|
|
|
|
)
|
|
|
|
|
2020-10-23 21:58:59 +02:00
|
|
|
// Config is the security configuration for Gatus
|
2020-10-15 01:22:58 +02:00
|
|
|
type Config struct {
|
2021-12-15 05:20:43 +01:00
|
|
|
Basic *BasicConfig `yaml:"basic,omitempty"`
|
|
|
|
OIDC *OIDCConfig `yaml:"oidc,omitempty"`
|
2022-01-03 00:29:34 +01:00
|
|
|
|
|
|
|
gate *g8.Gate
|
2020-10-15 01:22:58 +02:00
|
|
|
}
|
|
|
|
|
2020-10-23 21:58:59 +02:00
|
|
|
// IsValid returns whether the security configuration is valid or not
|
2020-10-15 01:22:58 +02:00
|
|
|
func (c *Config) IsValid() bool {
|
2021-12-15 05:20:43 +01:00
|
|
|
return (c.Basic != nil && c.Basic.isValid()) || (c.OIDC != nil && c.OIDC.isValid())
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterHandlers registers all handlers required based on the security configuration
|
2023-07-09 02:37:41 +02:00
|
|
|
func (c *Config) RegisterHandlers(router fiber.Router) error {
|
2021-12-15 05:20:43 +01:00
|
|
|
if c.OIDC != nil {
|
|
|
|
if err := c.OIDC.initialize(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
router.All("/oidc/login", c.OIDC.loginHandler)
|
|
|
|
router.All("/authorization-code/callback", adaptor.HTTPHandlerFunc(c.OIDC.callbackHandler))
|
2021-12-15 05:20:43 +01:00
|
|
|
}
|
|
|
|
return nil
|
2020-10-15 01:22:58 +02:00
|
|
|
}
|
|
|
|
|
2022-01-03 00:29:34 +01:00
|
|
|
// ApplySecurityMiddleware applies an authentication middleware to the router passed.
|
2023-07-09 02:37:41 +02:00
|
|
|
// The router passed should be a sub-router in charge of handlers that require authentication.
|
|
|
|
func (c *Config) ApplySecurityMiddleware(router fiber.Router) error {
|
2021-12-31 06:10:54 +01:00
|
|
|
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)
|
2022-01-03 00:29:34 +01:00
|
|
|
c.gate = g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)
|
2023-07-09 02:37:41 +02:00
|
|
|
router.Use(adaptor.HTTPMiddleware(c.gate.Protect))
|
2021-12-31 06:10:54 +01:00
|
|
|
} else if c.Basic != nil {
|
2022-01-09 01:26:37 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
router.Use(basicauth.New(basicauth.Config{
|
|
|
|
Authorizer: func(username, password string) bool {
|
2022-01-09 01:26:37 +01:00
|
|
|
if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 {
|
2023-07-09 02:37:41 +02:00
|
|
|
if username != c.Basic.Username || bcrypt.CompareHashAndPassword(decodedBcryptHash, []byte(password)) != nil {
|
|
|
|
return false
|
2022-01-09 01:26:37 +01:00
|
|
|
}
|
2021-12-31 06:10:54 +01:00
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
return true
|
|
|
|
},
|
|
|
|
Unauthorized: func(ctx *fiber.Ctx) error {
|
|
|
|
ctx.Set("WWW-Authenticate", "Basic")
|
|
|
|
return ctx.Status(401).SendString("Unauthorized")
|
|
|
|
},
|
|
|
|
}))
|
2021-12-31 06:10:54 +01:00
|
|
|
}
|
2022-01-09 01:26:37 +01:00
|
|
|
return nil
|
2020-10-15 01:22:58 +02:00
|
|
|
}
|
2022-01-03 00:29:34 +01:00
|
|
|
|
|
|
|
// IsAuthenticated checks whether the user is authenticated
|
|
|
|
// If the Config does not warrant authentication, it will always return true.
|
2023-07-09 02:37:41 +02:00
|
|
|
func (c *Config) IsAuthenticated(ctx *fiber.Ctx) bool {
|
2022-01-03 00:29:34 +01:00
|
|
|
if c.gate != nil {
|
2023-07-09 02:37:41 +02:00
|
|
|
// TODO: Update g8 to support fasthttp natively? (see g8's fasthttp branch)
|
|
|
|
request, err := adaptor.ConvertRequest(ctx, false)
|
|
|
|
if err != nil {
|
2024-04-02 03:47:14 +02:00
|
|
|
log.Printf("[security.IsAuthenticated] Unexpected error converting request: %v", err)
|
2023-07-09 02:37:41 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
token := c.gate.ExtractTokenFromRequest(request)
|
2022-01-03 00:29:34 +01:00
|
|
|
_, hasSession := sessions.Get(token)
|
|
|
|
return hasSession
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|