Skip to content
Snippets Groups Projects
Unverified Commit 2835bfa2 authored by Gabriel Zachmann's avatar Gabriel Zachmann Committed by GitHub
Browse files

Add healthcheck endpoint (#428)

parents ee4fadfa 6ce24c4c
No related branches found
No related tags found
No related merge requests found
Pipeline #467358 passed
......@@ -25,6 +25,7 @@
- Add "Enforceable Restrictions"
- Depending on a user attribute different restriction templates can be
enforced
- Add possibility to have an healthcheck endpoint
### Enhancements
......
......@@ -20,6 +20,7 @@ import (
"github.com/oidc-mytoken/server/internal/oidc/oidcfed"
provider2 "github.com/oidc-mytoken/server/internal/oidc/provider"
"github.com/oidc-mytoken/server/internal/server"
"github.com/oidc-mytoken/server/internal/server/healthcheck"
"github.com/oidc-mytoken/server/internal/server/routes"
"github.com/oidc-mytoken/server/internal/utils/cache"
"github.com/oidc-mytoken/server/internal/utils/cookies"
......@@ -44,6 +45,7 @@ func main() {
settings.InitSettings()
cookies.Init()
notifier.Init()
healthcheck.Start()
server.Start()
}
......
......@@ -46,6 +46,9 @@ server:
# hostnames including wildcards.
always_allow:
- "127.0.0.1"
healthcheck:
enabled: true
port: 9876
# The database file for ip geolocation. Will be installed by setup to this location.
geo_ip_db_file: "/IP2LOCATION-LITE-DB1.IPV6.BIN"
......
......@@ -414,9 +414,15 @@ type serverConf struct {
TLS tlsConf `yaml:"tls"`
Secure bool `yaml:"-"` // Secure indicates if the connection to the mytoken server is secure. This is
// independent of TLS, e.g. a Proxy can be used.
ProxyHeader string `yaml:"proxy_header"`
Limiter limiterConf `yaml:"request_limits"`
DistributedServers bool `yaml:"distributed_servers"`
ProxyHeader string `yaml:"proxy_header"`
Limiter limiterConf `yaml:"request_limits"`
DistributedServers bool `yaml:"distributed_servers"`
Healthcheck healtcheckConfig `yaml:"healthcheck"`
}
type healtcheckConfig struct {
Enabled bool `yaml:"enabled"`
Port int `yaml:"port"`
}
type limiterConf struct {
......
package healthcheck
import (
"fmt"
"sync"
"time"
"github.com/gofiber/fiber/v2"
"github.com/oidc-mytoken/utils/httpclient"
"github.com/oidc-mytoken/utils/utils"
log "github.com/sirupsen/logrus"
"github.com/zachmann/go-oidfed/pkg"
"github.com/zachmann/go-oidfed/pkg/cache"
"github.com/oidc-mytoken/server/internal/config"
"github.com/oidc-mytoken/server/internal/db/dbrepo/versionrepo"
"github.com/oidc-mytoken/server/internal/model/version"
"github.com/oidc-mytoken/server/internal/server/routes"
)
// Start starts the healthcheck endpoint on the configured port if enabled
func Start() {
if !config.Get().Server.Healthcheck.Enabled {
return
}
httpServer := fiber.New()
httpServer.Get(
"", handleHealthCheck,
)
addr := fmt.Sprintf(":%d", config.Get().Server.Healthcheck.Port)
log.Infof("Healthcheck endpoint started on %s", addr)
go func() {
log.WithError(httpServer.Listen(addr)).Fatal()
}()
}
type status struct {
Healthy bool `json:"healthy"`
Operational bool `json:"operational"`
Components componentsStatus `json:"components"`
Version string `json:"version"`
Timestamp pkg.Unixtime `json:"timestamp"`
}
type componentsStatus struct {
ServerUp bool `json:"server_up"`
ServerReachable bool `json:"server_reachable"`
Database bool `json:"database_up"`
Cache bool `json:"cache_up"`
}
func (c componentsStatus) healthy() bool {
return c.ServerUp && c.ServerReachable && c.Database && c.Cache
}
func (c componentsStatus) operational() bool {
return c.ServerUp && c.ServerReachable && c.Database
}
func handleHealthCheck(ctx *fiber.Ctx) error {
state := healthcheck()
if !state.Operational {
ctx.Status(fiber.StatusServiceUnavailable)
}
return ctx.JSON(state)
}
func healthcheck() status {
components := componentsStatus{
ServerUp: true,
ServerReachable: checkServer(),
Database: checkDB(),
Cache: checkCache(),
}
return status{
Healthy: components.healthy(),
Operational: components.operational(),
Components: components,
Version: version.VERSION,
Timestamp: pkg.Unixtime{Time: time.Now()},
}
}
func checkServer() bool {
_, err := httpclient.Do().R().Get(routes.ConfigEndpoint)
if err != nil {
log.WithError(err).WithField("healthcheck", "server_reachable").Error("error server healthcheck")
return false
}
return true
}
func checkDB() bool {
_, err := versionrepo.GetVersionState(log.StandardLogger(), nil)
if err != nil {
log.WithError(err).WithField("healthcheck", "db").Error("error db healthcheck")
return false
}
return true
}
var cacheMutex sync.Mutex
func checkCache() bool {
cacheMutex.Lock()
defer cacheMutex.Unlock()
k := "healthcheck"
v := utils.RandASCIIString(64)
if err := cache.Set(k, v, time.Second); err != nil {
log.WithError(err).WithField("healthcheck", "cache").Error("error caching healthcheck")
return false
}
var cached string
set, err := cache.Get(k, &cached)
if err != nil {
log.WithError(err).WithField("healthcheck", "cache").
Error("error obtaining cached healthcheck")
return false
}
if !set {
log.WithField("healthcheck", "cache").Error("cached healthcheck not found")
return false
}
if cached != v {
log.WithField("healthcheck", "cache").
WithField("cached", v).
WithField("obtained", cached).
Error("cached value does not match")
return false
}
return true
}
......@@ -18,6 +18,7 @@ var (
CalendarDownloadEndpoint string
ActionsEndpoint string
NotificationManagementEndpoint string
ConfigEndpoint string
)
// Init initializes the authcode component
......@@ -31,6 +32,7 @@ func Init() {
config.Get().IssuerURL,
generalPaths.NotificationManagementEndpoint,
)
ConfigEndpoint = utils.CombineURLPath(config.Get().IssuerURL, generalPaths.ConfigurationEndpoint)
}
// ActionsURL builds an action url from a pkg.ActionInfo
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment