Skip to content

Commit 28d8b1e

Browse files
authored
Add systemd-restart annotation for firewall resource. (#88)
1 parent 6b0e6f5 commit 28d8b1e

4 files changed

Lines changed: 94 additions & 0 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ A user can initiate rolling the latest firewall set by annotating a monitor in t
4141
kubectl annotate fwmon <firewall-name> firewall.metal-stack.io/roll-set=true
4242
```
4343

44+
## Restarting a systemd-service on the Firewall through Annotation
45+
46+
A user can initiate the restart of a systemd service through annotating the `FirewallMonitor`:
47+
48+
```bash
49+
kubectl annotate fwmon <firewall-name> firewall.metal-stack.io/restart-systemd-services=tailscale
50+
```
51+
52+
The firewall-controller imeplements a whitelist of allowed services to restart.
53+
54+
In addition, operators can overwrite this whitelist and also initiate a service restart by annotating the `Firewall` resource instead of the `FirewallMonitor`:
55+
56+
```bash
57+
kubectl annotate fw <firewall-name> firewall.metal-stack.io/restart-systemd-services-whitelist=tailscale.service,frr.service
58+
kubectl annotate fw <firewall-name> firewall.metal-stack.io/restart-systemd-services=frr
59+
```
60+
4461
## Development
4562

4663
Most of the functionality is developed with the help of the [integration](integration) test suite.

api/v2/types_annotations.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ const (
3636
// Defaults to 0 if no annotation is present. Negative values are allowed.
3737
FirewallWeightAnnotation = "firewall.metal-stack.io/weight"
3838

39+
// FirewallRestartSystemdServicesAnnotation can be used to restart a whitelisted set of systemd services running on the firewall.
40+
// Services must be passed comma-separated.
41+
FirewallRestartSystemdServicesAnnotation = "firewall.metal-stack.io/restart-systemd-services"
42+
// FirewallSystemdServicesWhitelistAnnotation can be used to overwrite the default systemd service whitelisted. This allows operators
43+
// to temporarily allow restarts of services like FRR, which might not be desired to be allowed permanently for platform users.
44+
FirewallRestartSystemdServicesWhitelistAnnotation = "firewall.metal-stack.io/restart-systemd-services-whitelist"
45+
3946
// FirewallControllerSetAnnotation is a tag added to the firewall entity indicating to which set a firewall belongs to.
4047
FirewallControllerSetAnnotation = "firewall.metal.stack.io/set"
4148
)

controllers/firewall/reconcile.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,42 @@ import (
1212
"github.com/metal-stack/metal-go/api/client/machine"
1313
"github.com/metal-stack/metal-go/api/models"
1414
"github.com/metal-stack/metal-lib/pkg/pointer"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
1516

1617
corev1 "k8s.io/api/core/v1"
18+
apierrors "k8s.io/apimachinery/pkg/api/errors"
1719
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1820
"k8s.io/apimachinery/pkg/util/sets"
1921
)
2022

2123
// Reconciler must always return either an error or requeue to ensure that it detects if a firewall get lost etc.
2224
func (c *controller) Reconcile(r *controllers.Ctx[*v2.Firewall]) error {
25+
if services, ok := r.Target.GetAnnotations()[v2.FirewallRestartSystemdServicesAnnotation]; ok {
26+
mon := &v2.FirewallMonitor{
27+
ObjectMeta: metav1.ObjectMeta{
28+
Name: r.Target.Name,
29+
Namespace: c.c.GetShootNamespace(),
30+
},
31+
}
32+
33+
err := c.c.GetShootClient().Get(r.Ctx, client.ObjectKeyFromObject(mon), mon)
34+
if err != nil && !apierrors.IsNotFound(err) {
35+
return fmt.Errorf("unable to get firewall monitor: %w", err)
36+
}
37+
38+
if err == nil {
39+
if err := v2.AddAnnotation(r.Ctx, c.c.GetShootClient(), mon, v2.FirewallRestartSystemdServicesAnnotation, services); err != nil {
40+
return fmt.Errorf("unable to pass systemd service restart annotation to the firewall monitor: %w", err)
41+
}
42+
}
43+
44+
if err := v2.RemoveAnnotation(r.Ctx, c.c.GetSeedClient(), r.Target, v2.FirewallRestartSystemdServicesAnnotation); err != nil {
45+
return fmt.Errorf("unable to remove systemd service restart annotation from firewall: %w", err)
46+
}
47+
48+
return controllers.RequeueAfter(0*time.Second, "removed systemd service restart annotation, requeue for regular reconcile")
49+
}
50+
2351
var f *models.V1FirewallResponse
2452
defer func() {
2553
if err := c.setStatus(r, f); err != nil {

integration/integration_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,48 @@ var _ = Context("integration test", Ordered, func() {
441441
})
442442
})
443443

444+
When("the firewall gets annotated with a systemd service restart annotation", Ordered, func() {
445+
var (
446+
fw *v2.Firewall
447+
mon *v2.FirewallMonitor
448+
)
449+
450+
BeforeEach(func() {
451+
fw = testcommon.WaitForResourceAmount(k8sClient, ctx, namespaceName, 1, &v2.FirewallList{}, func(l *v2.FirewallList) []*v2.Firewall {
452+
return l.GetItems()
453+
}, 2*time.Second)
454+
mon = testcommon.WaitForResourceAmount(k8sClient, ctx, namespaceName, 1, &v2.FirewallMonitorList{}, func(l *v2.FirewallMonitorList) []*v2.FirewallMonitor {
455+
return l.GetItems()
456+
}, 2*time.Second)
457+
})
458+
459+
It("setting the annotation works", func() {
460+
fw.Annotations = map[string]string{
461+
v2.FirewallRestartSystemdServicesAnnotation: "droptailer",
462+
}
463+
Expect(k8sClient.Update(ctx, fw)).To(Succeed())
464+
})
465+
466+
It("the annotation gets removed from the firewall", func() {
467+
Eventually(func() map[string]string {
468+
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(fw), fw)).To(Succeed())
469+
return fw.Annotations
470+
}, 5*time.Second, interval).Should(Not(HaveKey(v2.FirewallRestartSystemdServicesAnnotation)), "systemd service restart annotation was not removed")
471+
})
472+
473+
It("the annotation was added to the firewall monitor", func() {
474+
Eventually(func() map[string]string {
475+
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(mon), mon)).To(Succeed())
476+
return mon.Annotations
477+
}, 5*time.Second, interval).Should(HaveKey(v2.FirewallRestartSystemdServicesAnnotation), "systemd service restart annotation was not added to the firewall monitor")
478+
})
479+
480+
It("removing the annotation from the monitor works", func() {
481+
mon.Annotations = nil
482+
Expect(k8sClient.Update(ctx, mon)).To(Succeed())
483+
})
484+
})
485+
444486
When("a significant change occurs", Ordered, func() {
445487
var (
446488
installingFirewall = firewall2("Installing", "is installing")

0 commit comments

Comments
 (0)