From af4c723cd317ab83b82376b84c3e8bd1b0891dfd Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 27 May 2026 11:26:57 +0200 Subject: [PATCH 1/6] Add possibility to restart services on Firewall through annotations --- api/v1/clusterwidenetworkpolicy_types.go | 2 + controllers/firewall_annotation_controller.go | 99 ++++++++++++++++ .../firewall_monitor_annotation_controller.go | 112 ++++++++++++++++++ main.go | 22 ++++ pkg/updater/common.go | 2 +- pkg/updater/nftables-exporter.go | 2 +- 6 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 controllers/firewall_annotation_controller.go create mode 100644 controllers/firewall_monitor_annotation_controller.go diff --git a/api/v1/clusterwidenetworkpolicy_types.go b/api/v1/clusterwidenetworkpolicy_types.go index e8b9d3fa..dc7bd204 100644 --- a/api/v1/clusterwidenetworkpolicy_types.go +++ b/api/v1/clusterwidenetworkpolicy_types.go @@ -21,6 +21,8 @@ const ( allowedDNSCharsREGroup = "[-a-zA-Z0-9_.]" IPv4 IPVersion = "ip" IPv6 IPVersion = "ip6" + + AnnotationRestartSystemdServices = "firewall.metal-stack.io/restart-systemd-services" ) // ClusterwideNetworkPolicy contains the desired state for a cluster wide network policy to be applied. diff --git a/controllers/firewall_annotation_controller.go b/controllers/firewall_annotation_controller.go new file mode 100644 index 00000000..6c679f7e --- /dev/null +++ b/controllers/firewall_annotation_controller.go @@ -0,0 +1,99 @@ +package controllers + +import ( + "context" + "fmt" + "strings" + + "github.com/go-logr/logr" + firewallv2 "github.com/metal-stack/firewall-controller-manager/api/v2" + firewallv1 "github.com/metal-stack/firewall-controller/v2/api/v1" + "github.com/metal-stack/firewall-controller/v2/pkg/updater" + apierrors "k8s.io/apimachinery/pkg/api/errors" + 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" +) + +type FirewallAnnotationController struct { + SeedClient client.Client + FirewallName string + Namespace string + Log logr.Logger +} + +func (r *FirewallAnnotationController) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&firewallv2.Firewall{}, + 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() == r.Namespace && object.GetName() == r.FirewallName + })). + Complete(r) +} + +func (r *FirewallAnnotationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + fw := &firewallv2.Firewall{} + + if err := r.SeedClient.Get(ctx, req.NamespacedName, 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 := fw.Annotations[firewallv1.AnnotationRestartSystemdServices] + if !ok { + return reconcile.Result{}, nil + } + + var ( + restartFirewallController bool + ) + + for serviceName := range strings.SplitSeq(services, ",") { + if !strings.HasSuffix(serviceName, ".service") { + serviceName = serviceName + ".service" + } + // 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 == "firewall-controller.service" { + restartFirewallController = true + continue + } + + r.Log.Info("restart service", "service-name", serviceName) + if err := updater.Restart(ctx, serviceName); err != nil { + r.Log.Error(err, "error restarting service", "service-name", serviceName) + } + } + + r.Log.Info("Removing annotation from firewall", "annotation", firewallv1.AnnotationRestartSystemdServices) + patch := client.MergeFrom(fw.DeepCopy()) + delete(fw.Annotations, firewallv1.AnnotationRestartSystemdServices) + if err := r.SeedClient.Patch(ctx, fw, patch); err != nil { + return reconcile.Result{}, err + } + + if restartFirewallController { + r.Log.Info("restart firewall-controller") + if err := updater.Restart(ctx, "firewall-controller.service"); err != nil { + r.Log.Error(err, "error restarting firewall-controller") + } + } + + return ctrl.Result{}, nil +} diff --git a/controllers/firewall_monitor_annotation_controller.go b/controllers/firewall_monitor_annotation_controller.go new file mode 100644 index 00000000..dd66a11e --- /dev/null +++ b/controllers/firewall_monitor_annotation_controller.go @@ -0,0 +1,112 @@ +package controllers + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/go-logr/logr" + firewallv2 "github.com/metal-stack/firewall-controller-manager/api/v2" + firewallv1 "github.com/metal-stack/firewall-controller/v2/api/v1" + "github.com/metal-stack/firewall-controller/v2/pkg/updater" + apierrors "k8s.io/apimachinery/pkg/api/errors" + 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" +) + +type FirewallMonitorAnnotationController struct { + ShootClient client.Client + FirewallName string + Namespace string + Log logr.Logger +} + +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() == r.Namespace && object.GetName() == r.FirewallName + })). + Complete(r) +} + +func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + 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) + } + + services, ok := fwmon.Annotations[firewallv1.AnnotationRestartSystemdServices] + if !ok { + return reconcile.Result{}, nil + } + + var ( + restartFirewallController bool + ) + + for serviceName := range strings.SplitSeq(services, ",") { + if !strings.HasSuffix(serviceName, ".service") { + serviceName = serviceName + ".service" + } + + if !slices.Contains([]string{ + "droptailer.service", + "firewall-controller.service", + "nftables-exporter.service", + "node-exporter.service", + "tailscaled.service", + }, 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 == "firewall-controller.service" { + restartFirewallController = true + continue + } + + r.Log.Info("restart service", "service-name", serviceName) + if err := updater.Restart(ctx, serviceName); err != nil { + r.Log.Error(err, "error restarting service", "service-name", serviceName) + } + } + + r.Log.Info("Removing annotation from firewall monitor", "annotation", firewallv1.AnnotationRestartSystemdServices) + patch := client.MergeFrom(fwmon.DeepCopy()) + delete(fwmon.Annotations, firewallv1.AnnotationRestartSystemdServices) + 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, "firewall-controller.service"); err != nil { + r.Log.Error(err, "error restarting firewall-controller") + } + } + + return ctrl.Result{}, nil +} diff --git a/main.go b/main.go index 6f359da8..9aec8e1b 100644 --- a/main.go +++ b/main.go @@ -292,6 +292,28 @@ func main() { panic(err) } + // FirewallAnnotationReconciler + if err = (&controllers.FirewallAnnotationController{ + SeedClient: seedMgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("FirewallAnnotation"), + FirewallName: firewallName, + Namespace: seedNamespace, + }).SetupWithManager(seedMgr); err != nil { + l.Error("unable to create firewall annotation controller", "error", err) + panic(err) + } + + // FirewallMonitorAnnotationReconciler + if err = (&controllers.FirewallMonitorAnnotationController{ + ShootClient: shootMgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("FirewallMonitorAnnotation"), + FirewallName: firewallName, + Namespace: firewallv2.FirewallShootNamespace, + }).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()) diff --git a/pkg/updater/common.go b/pkg/updater/common.go index bf8763cd..90c732c8 100644 --- a/pkg/updater/common.go +++ b/pkg/updater/common.go @@ -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) diff --git a/pkg/updater/nftables-exporter.go b/pkg/updater/nftables-exporter.go index 75076233..e71c4ff2 100644 --- a/pkg/updater/nftables-exporter.go +++ b/pkg/updater/nftables-exporter.go @@ -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 From 5c978b6a55c8de256609add02254b2a68730915b Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 27 May 2026 12:38:03 +0200 Subject: [PATCH 2/6] Name the controllers --- controllers/firewall_annotation_controller.go | 1 + controllers/firewall_monitor_annotation_controller.go | 1 + 2 files changed, 2 insertions(+) diff --git a/controllers/firewall_annotation_controller.go b/controllers/firewall_annotation_controller.go index 6c679f7e..16388b91 100644 --- a/controllers/firewall_annotation_controller.go +++ b/controllers/firewall_annotation_controller.go @@ -40,6 +40,7 @@ func (r *FirewallAnnotationController) SetupWithManager(mgr ctrl.Manager) error WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool { return object.GetNamespace() == r.Namespace && object.GetName() == r.FirewallName })). + Named("FirewallAnnotationController"). Complete(r) } diff --git a/controllers/firewall_monitor_annotation_controller.go b/controllers/firewall_monitor_annotation_controller.go index dd66a11e..a87669e3 100644 --- a/controllers/firewall_monitor_annotation_controller.go +++ b/controllers/firewall_monitor_annotation_controller.go @@ -41,6 +41,7 @@ func (r *FirewallMonitorAnnotationController) SetupWithManager(mgr ctrl.Manager) WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool { return object.GetNamespace() == r.Namespace && object.GetName() == r.FirewallName })). + Named("FirewallMonitorAnnotationController"). Complete(r) } From a9657f31898588ad0012ef827e7628d430785ad4 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 28 May 2026 08:39:10 +0200 Subject: [PATCH 3/6] Const --- controllers/firewall_annotation_controller.go | 6 ++++-- controllers/firewall_monitor_annotation_controller.go | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/controllers/firewall_annotation_controller.go b/controllers/firewall_annotation_controller.go index 16388b91..27a777d6 100644 --- a/controllers/firewall_annotation_controller.go +++ b/controllers/firewall_annotation_controller.go @@ -18,6 +18,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +const firewallControllerService = "firewall-controller.service" + type FirewallAnnotationController struct { SeedClient client.Client FirewallName string @@ -71,7 +73,7 @@ func (r *FirewallAnnotationController) Reconcile(ctx context.Context, req ctrl.R } // 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 == "firewall-controller.service" { + if serviceName == firewallControllerService { restartFirewallController = true continue } @@ -91,7 +93,7 @@ func (r *FirewallAnnotationController) Reconcile(ctx context.Context, req ctrl.R if restartFirewallController { r.Log.Info("restart firewall-controller") - if err := updater.Restart(ctx, "firewall-controller.service"); err != nil { + if err := updater.Restart(ctx, firewallControllerService); err != nil { r.Log.Error(err, "error restarting firewall-controller") } } diff --git a/controllers/firewall_monitor_annotation_controller.go b/controllers/firewall_monitor_annotation_controller.go index a87669e3..463c183d 100644 --- a/controllers/firewall_monitor_annotation_controller.go +++ b/controllers/firewall_monitor_annotation_controller.go @@ -84,7 +84,7 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req // 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 == "firewall-controller.service" { + if serviceName == firewallControllerService { restartFirewallController = true continue } @@ -104,7 +104,7 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req if restartFirewallController { r.Log.Info("restart firewall-controller") - if err := updater.Restart(ctx, "firewall-controller.service"); err != nil { + if err := updater.Restart(ctx, firewallControllerService); err != nil { r.Log.Error(err, "error restarting firewall-controller") } } From 21cc22b05bd841d9b5ac9a075acd74e6e0c06481 Mon Sep 17 00:00:00 2001 From: Gerrit Date: Fri, 29 May 2026 14:31:07 +0200 Subject: [PATCH 4/6] Adaptions. --- api/v1/clusterwidenetworkpolicy_types.go | 2 - controllers/firewall_annotation_controller.go | 102 ------------------ .../firewall_monitor_annotation_controller.go | 65 ++++++++--- go.mod | 2 +- go.sum | 4 +- main.go | 20 +--- 6 files changed, 56 insertions(+), 139 deletions(-) delete mode 100644 controllers/firewall_annotation_controller.go diff --git a/api/v1/clusterwidenetworkpolicy_types.go b/api/v1/clusterwidenetworkpolicy_types.go index dc7bd204..e8b9d3fa 100644 --- a/api/v1/clusterwidenetworkpolicy_types.go +++ b/api/v1/clusterwidenetworkpolicy_types.go @@ -21,8 +21,6 @@ const ( allowedDNSCharsREGroup = "[-a-zA-Z0-9_.]" IPv4 IPVersion = "ip" IPv6 IPVersion = "ip6" - - AnnotationRestartSystemdServices = "firewall.metal-stack.io/restart-systemd-services" ) // ClusterwideNetworkPolicy contains the desired state for a cluster wide network policy to be applied. diff --git a/controllers/firewall_annotation_controller.go b/controllers/firewall_annotation_controller.go deleted file mode 100644 index 27a777d6..00000000 --- a/controllers/firewall_annotation_controller.go +++ /dev/null @@ -1,102 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - "strings" - - "github.com/go-logr/logr" - firewallv2 "github.com/metal-stack/firewall-controller-manager/api/v2" - firewallv1 "github.com/metal-stack/firewall-controller/v2/api/v1" - "github.com/metal-stack/firewall-controller/v2/pkg/updater" - apierrors "k8s.io/apimachinery/pkg/api/errors" - 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" - -type FirewallAnnotationController struct { - SeedClient client.Client - FirewallName string - Namespace string - Log logr.Logger -} - -func (r *FirewallAnnotationController) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&firewallv2.Firewall{}, - 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() == r.Namespace && object.GetName() == r.FirewallName - })). - Named("FirewallAnnotationController"). - Complete(r) -} - -func (r *FirewallAnnotationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - fw := &firewallv2.Firewall{} - - if err := r.SeedClient.Get(ctx, req.NamespacedName, 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 := fw.Annotations[firewallv1.AnnotationRestartSystemdServices] - if !ok { - return reconcile.Result{}, nil - } - - var ( - restartFirewallController bool - ) - - for serviceName := range strings.SplitSeq(services, ",") { - if !strings.HasSuffix(serviceName, ".service") { - serviceName = serviceName + ".service" - } - // 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.Log.Error(err, "error restarting service", "service-name", serviceName) - } - } - - r.Log.Info("Removing annotation from firewall", "annotation", firewallv1.AnnotationRestartSystemdServices) - patch := client.MergeFrom(fw.DeepCopy()) - delete(fw.Annotations, firewallv1.AnnotationRestartSystemdServices) - if err := r.SeedClient.Patch(ctx, fw, patch); err != nil { - return reconcile.Result{}, err - } - - if restartFirewallController { - r.Log.Info("restart firewall-controller") - if err := updater.Restart(ctx, firewallControllerService); err != nil { - r.Log.Error(err, "error restarting firewall-controller") - } - } - - return ctrl.Result{}, nil -} diff --git a/controllers/firewall_monitor_annotation_controller.go b/controllers/firewall_monitor_annotation_controller.go index 463c183d..d4578216 100644 --- a/controllers/firewall_monitor_annotation_controller.go +++ b/controllers/firewall_monitor_annotation_controller.go @@ -8,9 +8,10 @@ import ( "github.com/go-logr/logr" firewallv2 "github.com/metal-stack/firewall-controller-manager/api/v2" - firewallv1 "github.com/metal-stack/firewall-controller/v2/api/v1" + v2 "github.com/metal-stack/firewall-controller-manager/api/v2" "github.com/metal-stack/firewall-controller/v2/pkg/updater" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -19,11 +20,24 @@ import ( "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 - FirewallName string - Namespace string - Log logr.Logger + ShootClient client.Client + SeedClient client.Client + FirewallName string + SeedNamespace string + Log logr.Logger } func (r *FirewallMonitorAnnotationController) SetupWithManager(mgr ctrl.Manager) error { @@ -39,14 +53,22 @@ func (r *FirewallMonitorAnnotationController) SetupWithManager(mgr ctrl.Manager) }, }). WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool { - return object.GetNamespace() == r.Namespace && object.GetName() == r.FirewallName + return object.GetNamespace() == v2.FirewallShootNamespace && object.GetName() == r.FirewallName })). Named("FirewallMonitorAnnotationController"). Complete(r) } func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - fwmon := &firewallv2.FirewallMonitor{} + 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) { @@ -57,27 +79,35 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req return reconcile.Result{}, fmt.Errorf("error retrieving object: %w", err) } - services, ok := fwmon.Annotations[firewallv1.AnnotationRestartSystemdServices] + 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()[v2.FirewallRestartSystemdServicesWhitelistAnnotation]; ok { + whitelist = strings.Split(overwrite, ",") + } + for serviceName := range strings.SplitSeq(services, ",") { if !strings.HasSuffix(serviceName, ".service") { serviceName = serviceName + ".service" } - if !slices.Contains([]string{ - "droptailer.service", - "firewall-controller.service", - "nftables-exporter.service", - "node-exporter.service", - "tailscaled.service", - }, serviceName) { + if !slices.Contains(whitelist, serviceName) { r.Log.Info("skipping service restart because not in whitelist", "service-name", serviceName) continue } @@ -95,9 +125,10 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req } } - r.Log.Info("Removing annotation from firewall monitor", "annotation", firewallv1.AnnotationRestartSystemdServices) + r.Log.Info("Removing annotation from firewall monitor", "annotation", firewallv2.FirewallRestartSystemdServicesAnnotation) + patch := client.MergeFrom(fwmon.DeepCopy()) - delete(fwmon.Annotations, firewallv1.AnnotationRestartSystemdServices) + delete(fwmon.Annotations, firewallv2.FirewallRestartSystemdServicesAnnotation) if err := r.ShootClient.Patch(ctx, fwmon, patch); err != nil { return reconcile.Result{}, err } diff --git a/go.mod b/go.mod index ba31d4a7..a22b08d2 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index f2623ca2..e0fb4589 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/main.go b/main.go index 9aec8e1b..3e8fa0f0 100644 --- a/main.go +++ b/main.go @@ -292,23 +292,13 @@ func main() { panic(err) } - // FirewallAnnotationReconciler - if err = (&controllers.FirewallAnnotationController{ - SeedClient: seedMgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("FirewallAnnotation"), - FirewallName: firewallName, - Namespace: seedNamespace, - }).SetupWithManager(seedMgr); err != nil { - l.Error("unable to create firewall annotation controller", "error", err) - panic(err) - } - // FirewallMonitorAnnotationReconciler if err = (&controllers.FirewallMonitorAnnotationController{ - ShootClient: shootMgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("FirewallMonitorAnnotation"), - FirewallName: firewallName, - Namespace: firewallv2.FirewallShootNamespace, + ShootClient: shootMgr.GetClient(), + SeedClient: seedMgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("FirewallMonitorAnnotation"), + FirewallName: firewallName, + SeedNamespace: seedNamespace, }).SetupWithManager(shootMgr); err != nil { l.Error("unable to create firewall monitor annotation controller", "error", err) panic(err) From c60b51f253f84f1792406456e63cdbc1da00f9ae Mon Sep 17 00:00:00 2001 From: Gerrit Date: Fri, 29 May 2026 14:35:51 +0200 Subject: [PATCH 5/6] =?UTF-8?q?Linter=20is=20happy=20again.=20=F0=9F=A5=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controllers/firewall_monitor_annotation_controller.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/firewall_monitor_annotation_controller.go b/controllers/firewall_monitor_annotation_controller.go index d4578216..0bb30d3a 100644 --- a/controllers/firewall_monitor_annotation_controller.go +++ b/controllers/firewall_monitor_annotation_controller.go @@ -8,7 +8,6 @@ import ( "github.com/go-logr/logr" firewallv2 "github.com/metal-stack/firewall-controller-manager/api/v2" - v2 "github.com/metal-stack/firewall-controller-manager/api/v2" "github.com/metal-stack/firewall-controller/v2/pkg/updater" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,7 +52,7 @@ func (r *FirewallMonitorAnnotationController) SetupWithManager(mgr ctrl.Manager) }, }). WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool { - return object.GetNamespace() == v2.FirewallShootNamespace && object.GetName() == r.FirewallName + return object.GetNamespace() == firewallv2.FirewallShootNamespace && object.GetName() == r.FirewallName })). Named("FirewallMonitorAnnotationController"). Complete(r) @@ -98,7 +97,7 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req whitelist = systemdServiceRestartWhitelist ) - if overwrite, ok := fw.GetAnnotations()[v2.FirewallRestartSystemdServicesWhitelistAnnotation]; ok { + if overwrite, ok := fw.GetAnnotations()[firewallv2.FirewallRestartSystemdServicesWhitelistAnnotation]; ok { whitelist = strings.Split(overwrite, ",") } From ef00b56e139bf5c8676168bd71865c1f26a9f3df Mon Sep 17 00:00:00 2001 From: Gerrit Date: Fri, 29 May 2026 14:45:42 +0200 Subject: [PATCH 6/6] Record events. --- .../firewall_monitor_annotation_controller.go | 33 +++++++++++++++++++ main.go | 1 + 2 files changed, 34 insertions(+) diff --git a/controllers/firewall_monitor_annotation_controller.go b/controllers/firewall_monitor_annotation_controller.go index 0bb30d3a..905c7004 100644 --- a/controllers/firewall_monitor_annotation_controller.go +++ b/controllers/firewall_monitor_annotation_controller.go @@ -9,8 +9,10 @@ import ( "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" @@ -37,6 +39,7 @@ type FirewallMonitorAnnotationController struct { FirewallName string SeedNamespace string Log logr.Logger + Recorder record.EventRecorder } func (r *FirewallMonitorAnnotationController) SetupWithManager(mgr ctrl.Manager) error { @@ -119,8 +122,23 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req } 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), + ) } } @@ -134,8 +152,23 @@ func (r *FirewallMonitorAnnotationController) Reconcile(ctx context.Context, req 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), + ) } } diff --git a/main.go b/main.go index 3e8fa0f0..5af3e4be 100644 --- a/main.go +++ b/main.go @@ -299,6 +299,7 @@ func main() { 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)