Watchdog: Verify Server TLS Certificate
This commit is contained in:

committed by
Sebastian Serth

parent
b48c7fe8b6
commit
221a6ff1b2
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -27,6 +27,7 @@ var (
|
|||||||
Token: "",
|
Token: "",
|
||||||
TLS: TLS{
|
TLS: TLS{
|
||||||
Active: false,
|
Active: false,
|
||||||
|
CAFile: "",
|
||||||
CertFile: "",
|
CertFile: "",
|
||||||
KeyFile: "",
|
KeyFile: "",
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user