Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(systemd): systemd improvements #133

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions contrib/coraza-spoa.service
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
[Unit]
Description=Coraza WAF SPOA Daemon
Documentation=https://www.coraza.io
After=network.target

[Service]
ExecStart=/usr/bin/coraza-spoa -config=/etc/coraza-spoa/config.yaml
WorkingDirectory=/
ExecReload=/bin/kill -HUP $MAINPID
WorkingDirectory=/etc/coraza-spoa/
Restart=always
Type=exec
Type=notify
# notify-reload is pretty new and isn't available on debian 12
#Type=notify-reload
NotifyAccess=all
User=coraza-spoa
Group=coraza-spoa

Expand Down Expand Up @@ -45,7 +50,7 @@ InaccessiblePaths=-/opt
InaccessiblePaths=-/srv
#InaccessiblePaths=-/bin
InaccessiblePaths=-/bin/bash
inaccessiblepaths=-/bin/find
InaccessiblePaths=-/bin/find
DavidProdinger marked this conversation as resolved.
Show resolved Hide resolved
InaccessiblePaths=-/bin/less
InaccessiblePaths=-/bin/zcat
InaccessiblePaths=-/bin/rm
Expand Down Expand Up @@ -105,7 +110,7 @@ InaccessiblePaths=-/usr/bin/htop
InaccessiblePaths=-/usr/bin/ipcmk
InaccessiblePaths=-/usr/bin/journalctl
InaccessiblePaths=-/usr/bin/keyctl
InaccessiblePaths=-/usr/bin/kill
# InaccessiblePaths=-/usr/bin/kill
InaccessiblePaths=-/usr/bin/killall
InaccessiblePaths=-/usr/bin/ksh
InaccessiblePaths=-/usr/bin/last
Expand Down Expand Up @@ -293,7 +298,7 @@ PrivateTmp=true

RemoveIPC=true

RestrictAddressFamilies=AF_INET AF_INET6
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
#RestrictNamespaces=uts ipc pid user cgroup

SystemCallArchitectures=native
Expand Down
50 changes: 50 additions & 0 deletions daemon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This provides the functionality for notifying the service manager about status changes via sd_notify
// see https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html
package main

import (
"net"
"os"
"strings"
)

const (
// SdNotifyReady Tells the service manager that service startup is finished, or the service finished re-loading its configuration.
SdNotifyReady = "READY=1"

// SdNotifyReloading Tells the service manager that the service is beginning to reload its configuration.
SdNotifyReloading = "RELOADING=1"

// SdNotifyStopping Tells the service manager that the service is beginning its shutdown.
SdNotifyStopping = "STOPPING=1"

// SdNotifyStatus Passes a single-line UTF-8 status string back to the service manager that describes the service state.
SdNotifyStatus = "STATUS="
)

// SdNotify Communicates with the NOTIFY_SOCKET
// Accepts
// Returns nil, if the socket doesn't exist, or the message was sent successfully
// Returns an error, if the message wasn't sent successfully
func SdNotify(messages ...string) error {
socketAddr := &net.UnixAddr{
Name: os.Getenv("NOTIFY_SOCKET"),
Net: "unixgram",
}

if socketAddr.Name == "" {
return nil
}

conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
if err != nil {
return err
}
defer conn.Close()

if _, err = conn.Write([]byte(strings.Join(messages, "\n"))); err != nil {
return err
}

return nil
}
94 changes: 94 additions & 0 deletions daemon_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"net"
"os"
"strings"
"testing"
)

func TestSdNotify(t *testing.T) {
socketAddr := &net.UnixAddr{
Name: "/tmp/coraza-spoa-daemon.sock",
Net: "unixgram",
}
socket, err := net.ListenUnixgram(socketAddr.Net, socketAddr)
if err != nil {
t.Fatal(err)
}

defer os.Remove(socketAddr.Name)

t.Setenv("NOTIFY_SOCKET", socketAddr.Name)

tests := []struct {
name string
messages []string
}{
{
"Ready",
[]string{SdNotifyReady},
},
{
"Reloading",
[]string{SdNotifyReloading},
},
{
"Stopping",
[]string{SdNotifyStopping},
},
{
"Ready with status",
[]string{SdNotifyReady, SdNotifyStatus + "Test Ready"},
},
{
"Reloading with status",
[]string{SdNotifyReloading, SdNotifyStatus + "Test Reloading"},
},
{
"Stopping with status",
[]string{SdNotifyStopping, SdNotifyStatus + "Test Stopping"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := SdNotify(tt.messages...)
if err != nil {
t.Errorf("SdNotify() error = %v", err)
}

// getting the messages
var buf [1024]byte
n, err := socket.Read(buf[:])
if err != nil {
t.Fatal(err)
}

var received = string(buf[:n])
var expected = strings.Join(tt.messages, "\n")
if received != expected {
t.Errorf("SdNotify() = returned:\n---\n%v\n---\nWanted:\n---\n%v\n---", received, expected)
}
})
}
}

func TestSdNotify_NoSocketSet(t *testing.T) {
t.Setenv("NOTIFY_SOCKET", "")

err := SdNotify(SdNotifyReady)
if err != nil {
t.Errorf("SdNotify() error = not nil, %v", err)
}
}

func TestSdNotify_WrongSocketSet(t *testing.T) {
t.Setenv("NOTIFY_SOCKET", "/tmp/coraza-spoa-wrong.sock")

err := SdNotify(SdNotifyReady)
if err != nil && err.Error() == "dial unixgram /tmp/coraza-spoa-wrong.sock: connect: no such file or directory" {
return
}
t.Error("SdNotify() no error")
}
35 changes: 34 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main
import (
"context"
"flag"
"fmt"
"net"
"os"
"os/signal"
Expand All @@ -18,10 +19,19 @@ import (
"github.com/corazawaf/coraza-spoa/internal"
)

type SeverityHook struct{}

func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
if level == zerolog.FatalLevel {
_ = SdNotify(SdNotifyStopping, fmt.Sprintf("%s%s", SdNotifyStatus, msg))
}
}

var configPath string
var cpuProfile string
var memProfile string
var globalLogger = zerolog.New(os.Stderr).With().Timestamp().Logger()
var hook SeverityHook
var globalLogger = zerolog.New(os.Stderr).Hook(hook).With().Timestamp().Logger()

func main() {
flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to `file`")
Expand Down Expand Up @@ -79,6 +89,12 @@ func main() {
defer cancelFunc()

globalLogger.Info().Msg("Starting coraza-spoa")

DavidProdinger marked this conversation as resolved.
Show resolved Hide resolved
err := SdNotify(SdNotifyReady)
if err != nil {
globalLogger.Error().Err(err).Msg("Failed notifying daemon")
}

if err := a.Serve(l); err != nil {
globalLogger.Fatal().Err(err).Msg("listener closed")
}
Expand Down Expand Up @@ -126,11 +142,28 @@ outer:
continue
}

err = SdNotify(SdNotifyReloading)
if err != nil {
globalLogger.Error().Err(err).Msg("Failed notifying daemon")
}

a.ReplaceApplications(apps)
cfg = newCfg

err = SdNotify(SdNotifyReady)
if err != nil {
globalLogger.Error().Err(err).Msg("Failed notifying daemon")
}
}
}

globalLogger.Info().Msg("Stopping coraza-spoa")

err = SdNotify(SdNotifyStopping)
if err != nil {
globalLogger.Error().Err(err).Msg("Failed notifying daemon")
}

if memProfile != "" {
f, err := os.Create(memProfile)
if err != nil {
Expand Down