Watchdog: Verify Server TLS Certificate

This commit is contained in:
Maximilian Paß
2024-01-16 15:03:52 +01:00
committed by Sebastian Serth
parent b48c7fe8b6
commit 221a6ff1b2
5 changed files with 52 additions and 28 deletions

View File

@ -9,6 +9,7 @@ Requires=poseidon.socket
WorkingDirectory=${GITHUB_WORKSPACE} WorkingDirectory=${GITHUB_WORKSPACE}
ExecStart=${GITHUB_WORKSPACE}/poseidon ExecStart=${GITHUB_WORKSPACE}/poseidon
Environment="POSEIDON_SERVER_SYSTEMDSOCKETACTIVATION=TRUE" Environment="POSEIDON_SERVER_SYSTEMDSOCKETACTIVATION=TRUE"
Environment="GOCOVERDIR=${GITHUB_WORKSPACE}/${GOCOVERDIR}"
Restart=always Restart=always
StartLimitBurst=0 StartLimitBurst=0

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"github.com/coreos/go-systemd/v22/activation" "github.com/coreos/go-systemd/v22/activation"
@ -26,6 +27,7 @@ import (
"runtime/debug" "runtime/debug"
"runtime/pprof" "runtime/pprof"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
) )
@ -234,18 +236,30 @@ func notifySystemd(router *mux.Router) {
log.WithError(err).Error("Systemd Watchdog not supported") log.WithError(err).Error("Systemd Watchdog not supported")
return return
} }
go notifyWatchdog(context.Background(), router, interval) go systemdWatchdogLoop(context.Background(), router, interval)
} }
func notifyWatchdog(ctx context.Context, router *mux.Router, interval time.Duration) { func systemdWatchdogLoop(ctx context.Context, router *mux.Router, interval time.Duration) {
healthRoute, err := router.Get(api.HealthPath).URL() healthRoute, err := router.Get(api.HealthPath).URL()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to parse Health route") log.WithError(err).Error("Failed to parse Health route")
return return
} }
// We do not verify the certificate as we (intend to) perform only requests to the local server. healthURL := config.Config.Server.URL().String() + healthRoute.String()
tlsConfig := &tls.Config{InsecureSkipVerify: true} // #nosec G402 The default min tls version is secure. healthURL = strings.ReplaceAll(healthURL, "0.0.0.0", "localhost") // Workaround for certificate subject names
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
client := &http.Client{}
if config.Config.Server.TLS.Active {
tlsConfig := &tls.Config{RootCAs: x509.NewCertPool()} // #nosec G402 The default MinTLSVersion is secure.
caCertBytes, err := os.ReadFile(config.Config.Server.TLS.CAFile)
if err != nil {
log.WithError(err).Warn("Cannot read tls ca file")
} else {
ok := tlsConfig.RootCAs.AppendCertsFromPEM(caCertBytes)
log.WithField("success", ok).Trace("Loaded CA certificate")
}
client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
}
// notificationIntervalFactor defines how many more notifications we send than required. // notificationIntervalFactor defines how many more notifications we send than required.
const notificationIntervalFactor = 2 const notificationIntervalFactor = 2
@ -254,31 +268,36 @@ func notifyWatchdog(ctx context.Context, router *mux.Router, interval time.Durat
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.After(interval / notificationIntervalFactor): case <-time.After(interval / notificationIntervalFactor):
req, err := http.NewRequestWithContext(ctx, http.MethodGet, config.Config.Server.URL().String()+healthRoute.String(), http.NoBody) notifySystemdWatchdog(ctx, healthURL, client)
if err != nil {
continue
}
resp, err := client.Do(req)
if err != nil {
// We do not check for resp.StatusCode == 503 as Poseidon's error recovery will try to handle such errors
// by itself. The Watchdog should just check that Poseidon handles http requests at all.
continue
}
_ = resp.Body.Close()
notify, err := daemon.SdNotify(false, daemon.SdNotifyWatchdog)
switch {
case err == nil && !notify:
log.Debug("Systemd Watchdog Notification not supported")
case err != nil:
log.WithError(err).WithField("notify", notify).Warn("Failed notifying Systemd Watchdog")
default:
log.Trace("Notified Systemd Watchdog")
}
} }
} }
} }
func notifySystemdWatchdog(ctx context.Context, healthURL string, client *http.Client) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, healthURL, http.NoBody)
if err != nil {
return
}
resp, err := client.Do(req)
if err != nil {
log.WithError(err).Debug("Failed watchdog health check")
// We do not check for resp.StatusCode == 503 as Poseidon's error recovery will try to handle such errors
// by itself. The Watchdog should just check that Poseidon handles http requests at all.
return
}
_ = resp.Body.Close()
notify, err := daemon.SdNotify(false, daemon.SdNotifyWatchdog)
switch {
case err == nil && !notify:
log.Debug("Systemd Watchdog Notification not supported")
case err != nil:
log.WithError(err).WithField("notify", notify).Warn("Failed notifying Systemd Watchdog")
default:
log.Trace("Notified Systemd Watchdog")
}
}
type managerCreator func(ctx context.Context) ( type managerCreator func(ctx context.Context) (
runnerManager runner.Manager, environmentManager environment.ManagerHandler) runnerManager runner.Manager, environmentManager environment.ManagerHandler)
@ -375,7 +394,7 @@ func initServer(router *mux.Router) *http.Server {
func shutdownOnOSSignal(server *http.Server, ctx context.Context, stopProfiling func()) { func shutdownOnOSSignal(server *http.Server, ctx context.Context, stopProfiling func()) {
// wait for SIGINT // wait for SIGINT
shutdownSignals := make(chan os.Signal, 1) shutdownSignals := make(chan os.Signal, 1)
signal.Notify(shutdownSignals, unix.SIGINT, unix.SIGTERM) signal.Notify(shutdownSignals, unix.SIGINT, unix.SIGTERM, unix.SIGABRT)
// wait for SIGUSR1 // wait for SIGUSR1
writeProfileSignal := make(chan os.Signal, 1) writeProfileSignal := make(chan os.Signal, 1)

View File

@ -15,6 +15,8 @@ server:
tls: tls:
# If set, the API uses TLS for all incoming connections. # If set, the API uses TLS for all incoming connections.
active: false active: false
# The path to the certificate of the CA authority used for TLS
# cafile: ./ca.crt
# The path to the certificate file used for TLS # The path to the certificate file used for TLS
# certfile: ./poseidon.crt # certfile: ./poseidon.crt
# The path to the key file used for TLS # The path to the key file used for TLS

View File

@ -27,6 +27,7 @@ var (
Token: "", Token: "",
TLS: TLS{ TLS: TLS{
Active: false, Active: false,
CAFile: "",
CertFile: "", CertFile: "",
KeyFile: "", KeyFile: "",
}, },

View File

@ -7,6 +7,7 @@ import (
"github.com/openHPI/poseidon/tests/e2e" "github.com/openHPI/poseidon/tests/e2e"
"github.com/openHPI/poseidon/tests/helpers" "github.com/openHPI/poseidon/tests/helpers"
"github.com/shirou/gopsutil/v3/process" "github.com/shirou/gopsutil/v3/process"
"golang.org/x/sys/unix"
"net/http" "net/http"
"time" "time"
) )
@ -59,7 +60,7 @@ func killPoseidon() {
continue continue
} }
if n == "poseidon" { if n == "poseidon" {
err = p.Kill() err = p.SendSignal(unix.SIGTERM)
if err != nil { if err != nil {
log.WithError(err).Error("Error killing Poseidon") log.WithError(err).Error("Error killing Poseidon")
} else { } else {