Skip to content
Draft
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
176 changes: 176 additions & 0 deletions controllers/firewall_monitor_annotation_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package controllers

import (
"context"
"fmt"
"slices"
"strings"

"github.com/go-logr/logr"
firewallv2 "github.com/metal-stack/firewall-controller-manager/api/v2"
"github.com/metal-stack/firewall-controller/v2/pkg/updater"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const firewallControllerService = "firewall-controller.service"

var (
systemdServiceRestartWhitelist = []string{
"droptailer.service",
"firewall-controller.service",
"nftables-exporter.service",
"node-exporter.service",
"tailscaled.service",
}
)

type FirewallMonitorAnnotationController struct {
ShootClient client.Client
SeedClient client.Client
FirewallName string
SeedNamespace string
Log logr.Logger
Recorder record.EventRecorder
}

func (r *FirewallMonitorAnnotationController) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&firewallv2.FirewallMonitor{},
builder.WithPredicates(
predicate.AnnotationChangedPredicate{},
),
).
WithEventFilter(predicate.Funcs{
DeleteFunc: func(de event.DeleteEvent) bool {
return false
},
}).
WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetNamespace() == firewallv2.FirewallShootNamespace && object.GetName() == r.FirewallName
})).
Named("FirewallMonitorAnnotationController").
Complete(r)
}

func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var (
fw = &firewallv2.Firewall{
ObjectMeta: metav1.ObjectMeta{
Name: r.FirewallName,
Namespace: r.SeedNamespace,
},
}
fwmon = &firewallv2.FirewallMonitor{}
)

if err := r.ShootClient.Get(ctx, req.NamespacedName, fwmon); err != nil {
if apierrors.IsNotFound(err) {
r.Log.V(1).Info("object is gone, stop reconciling")
return reconcile.Result{}, nil
}

return reconcile.Result{}, fmt.Errorf("error retrieving object: %w", err)
}

if err := r.SeedClient.Get(ctx, client.ObjectKeyFromObject(fw), fw); err != nil {
if apierrors.IsNotFound(err) {
r.Log.V(1).Info("object is gone, stop reconciling")
return reconcile.Result{}, nil
}

return reconcile.Result{}, fmt.Errorf("error retrieving object: %w", err)
}

services, ok := fwmon.Annotations[firewallv2.FirewallRestartSystemdServicesAnnotation]
if !ok {
return reconcile.Result{}, nil
}

var (
restartFirewallController bool
whitelist = systemdServiceRestartWhitelist
)

if overwrite, ok := fw.GetAnnotations()[firewallv2.FirewallRestartSystemdServicesWhitelistAnnotation]; ok {
whitelist = strings.Split(overwrite, ",")
}

for serviceName := range strings.SplitSeq(services, ",") {
if !strings.HasSuffix(serviceName, ".service") {
serviceName = serviceName + ".service"
}

if !slices.Contains(whitelist, serviceName) {
r.Log.Info("skipping service restart because not in whitelist", "service-name", serviceName)
continue
}

// If the firewall-controller itself should be restarted, we have to first remove the annotation from the node.
// Otherwise, the annotation is never removed and it restarts itself indefinitely.
if serviceName == firewallControllerService {
restartFirewallController = true
continue
}

r.Log.Info("restart service", "service-name", serviceName)

if err := updater.Restart(ctx, serviceName); err != nil {
r.Recorder.Event(
fwmon,
corev1.EventTypeWarning,
"ServiceRestarted",
fmt.Sprintf("systemd service restart of service %q failed: %s", serviceName, err),
)

r.Log.Error(err, "error restarting service", "service-name", serviceName)
} else {
r.Recorder.Event(
fwmon,
corev1.EventTypeNormal,
"ServiceRestarted",
fmt.Sprintf("systemd service %q was restarted through monitor annotation", serviceName),
)
}
}

r.Log.Info("Removing annotation from firewall monitor", "annotation", firewallv2.FirewallRestartSystemdServicesAnnotation)

patch := client.MergeFrom(fwmon.DeepCopy())
delete(fwmon.Annotations, firewallv2.FirewallRestartSystemdServicesAnnotation)
if err := r.ShootClient.Patch(ctx, fwmon, patch); err != nil {
return reconcile.Result{}, err
}

if restartFirewallController {
r.Log.Info("restart firewall-controller")

if err := updater.Restart(ctx, firewallControllerService); err != nil {
r.Recorder.Event(
fwmon,
corev1.EventTypeWarning,
"ServiceRestarted",
fmt.Sprintf("systemd service restart of service %q failed: %s", firewallControllerService, err),
)

r.Log.Error(err, "error restarting firewall-controller")
} else {
r.Recorder.Event(
fwmon,
corev1.EventTypeNormal,
"ServiceRestarted",
fmt.Sprintf("systemd service %q was restarted through monitor annotation", firewallControllerService),
)
}
}

return ctrl.Result{}, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/nftables v0.3.0
github.com/ks2211/go-suricata v0.0.0-20200823200910-986ce1470707
github.com/metal-stack/firewall-controller-manager v0.6.0
github.com/metal-stack/firewall-controller-manager v0.6.1-0.20260529122307-ec72cac16dfe
github.com/metal-stack/metal-go v0.43.0
github.com/metal-stack/metal-lib v0.24.0
github.com/metal-stack/metal-networker v0.46.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKc
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/metal-stack/firewall-controller-manager v0.6.0 h1:+/VV/VXJa4NRFBRHBw5NxkT2Ap1vXjkFdfBRO5t4MfA=
github.com/metal-stack/firewall-controller-manager v0.6.0/go.mod h1:bQjb3pVL3R6XPUqWA/WX8ktlzcgVYWDbsFANKcrW3FA=
github.com/metal-stack/firewall-controller-manager v0.6.1-0.20260529122307-ec72cac16dfe h1:WdRxxR1iDtnI7WjQBjxr1C5ik2KLLWzPX61CqKoidSg=
github.com/metal-stack/firewall-controller-manager v0.6.1-0.20260529122307-ec72cac16dfe/go.mod h1:bQjb3pVL3R6XPUqWA/WX8ktlzcgVYWDbsFANKcrW3FA=
github.com/metal-stack/metal-go v0.43.0 h1:uODD0YCwnAYzyvFxWNakZrymBoMz1FAvP5hkhsR83VQ=
github.com/metal-stack/metal-go v0.43.0/go.mod h1:GSfXrAj55LGsUSMHWGDsmq5n056NG0yb1JM8bgfvKOw=
github.com/metal-stack/metal-hammer v0.13.17 h1:W2IrWmnz6IXpL7Y35RfVgSVO66EVdqeF+/WeopgycMI=
Expand Down
13 changes: 13 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,19 @@ func main() {
panic(err)
}

// FirewallMonitorAnnotationReconciler
if err = (&controllers.FirewallMonitorAnnotationController{
ShootClient: shootMgr.GetClient(),
SeedClient: seedMgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("FirewallMonitorAnnotation"),
FirewallName: firewallName,
SeedNamespace: seedNamespace,
Recorder: shootMgr.GetEventRecorderFor("FirewallMonitorAnnotation"),
}).SetupWithManager(shootMgr); err != nil {
l.Error("unable to create firewall monitor annotation controller", "error", err)
panic(err)
}

// +kubebuilder:scaffold:builder

setupLog.Info("starting firewall-controller", "version", v.V.String())
Expand Down
2 changes: 1 addition & 1 deletion pkg/updater/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func slurpFile(url string) (string, error) {

const done = "done"

func restart(ctx context.Context, unitName string) error {
func Restart(ctx context.Context, unitName string) error {
dbc, err := dbus.NewWithContext(ctx)
if err != nil {
return fmt.Errorf("unable to connect to dbus: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/updater/nftables-exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (u *Updater) updateNFTablesExporter(ctx context.Context, f *firewallv2.Fire
return err
}

err = restart(ctx, "nftables-exporter.service")
err = Restart(ctx, "nftables-exporter.service")
if err != nil {
u.log.Error(err, "error restarting nftables-exporter")
return err
Expand Down
Loading