Handle ports in specification and in load balancer services

This commit is contained in:
2025-04-12 19:52:33 +02:00
parent 4af8b3f78c
commit 2aeb2dd25a
3 changed files with 212 additions and 44 deletions

View File

@@ -36,6 +36,7 @@ type FirewallGroupSpec struct {
// ManualAddresses is a list of manual IPs or CIDRs (IPv4 or IPv6)
// +optional
ManualAddresses []string `json:"manualAddresses,omitempty"`
ManualPorts []string `json:"manualPorts,omitempty"`
// AutoIncludeSelector defines which services to extract addresses from
// +optional

View File

@@ -99,6 +99,10 @@ spec:
items:
type: string
type: array
manualPorts:
items:
type: string
type: array
matchServicesInAllNamespaces:
type: boolean
name:

View File

@@ -21,19 +21,21 @@ import (
"fmt"
"net"
"reflect"
"slices"
"strconv"
"strings"
"regexp"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/handler"
// "sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
// "sigs.k8s.io/controller-runtime/pkg/source"
goUnifi "github.com/vegardengen/go-unifi/unifi"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
@@ -69,7 +71,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
return reconcile.Result{}, client.IgnoreNotFound(err)
}
log.Info(nwObj.Spec.Name)
var ipv4, ipv6 []string
var ipv4, ipv6, tcpports, udpports []string
for _, addressEntry := range nwObj.Spec.ManualAddresses {
ip := net.ParseIP(addressEntry)
@@ -99,6 +101,26 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
}
}
}
for _, portEntry := range nwObj.Spec.ManualPorts {
port_type := "tcp"
port := portEntry
if match, _ := regexp.MatchString("(?:tcp|udp)\\/?)\\d+", string(portEntry)); match {
fields := strings.Split("/",portEntry)
port_type = fields[0]
port = fields[1]
}
if(port_type == "tcp") {
if !slices.Contains(tcpports, port) {
tcpports = append(tcpports, port)
}
}
if(port_type == "udp") {
if !slices.Contains(udpports, port) {
tcpports = append(udpports, port)
}
}
}
var services corev1.ServiceList
if nwObj.Spec.MatchServicesInAllNamespaces {
if err := r.List(ctx, &services); err != nil {
@@ -120,7 +142,23 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
if isIPv6(ip) {
ipv6 = append(ipv6, ip)
} else {
ipv4= append(ipv4, ip)
ipv4 = append(ipv4, ip)
}
}
}
if service.Spec.Ports != nil {
for _, portSpec := range service.Spec.Ports {
log.Info(fmt.Sprintf("portSpec: %+v", portSpec))
log.Info(fmt.Sprintf("Port: %s %d", strconv.Itoa(int(portSpec.Port)), portSpec.Port))
if(portSpec.Protocol == "TCP") {
if !slices.Contains(tcpports, strconv.Itoa(int(portSpec.Port))) {
tcpports = append(tcpports, strconv.Itoa(int(portSpec.Port)))
}
}
if(portSpec.Protocol == "UDP") {
if !slices.Contains(udpports, strconv.Itoa(int(portSpec.Port))) {
udpports = append(udpports, strconv.Itoa(int(portSpec.Port)))
}
}
}
}
@@ -129,7 +167,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
nwObj.Status.ResolvedAddresses = ipv4
nwObj.Status.ResolvedAddresses = append(nwObj.Status.ResolvedAddresses, ipv6...)
currentTime := metav1.Now()
nwObj.Status.LastSyncTime = &currentTime;
nwObj.Status.LastSyncTime = &currentTime
nwObj.Status.SyncedWithUnifi = true
err := r.UnifiClient.Reauthenticate()
@@ -143,8 +181,12 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
}
ipv4_name := "k8s-" + nwObj.Spec.Name + "-ipv4"
ipv6_name := "k8s-" + nwObj.Spec.Name + "-ipv6"
tcpports_name := "k8s-" + nwObj.Spec.Name + "-tcpports"
udpports_name := "k8s-" + nwObj.Spec.Name + "-udpports"
ipv4_done := false
ipv6_done := false
tcpports_done := false
udpports_done := false
for _, firewall_group := range firewall_groups {
if firewall_group.Name == ipv4_name {
if len(ipv4) == 0 {
@@ -216,6 +258,76 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
ipv6_done = true
}
}
if firewall_group.Name == tcpports_name {
if len(tcpports) == 0 {
log.Info(fmt.Sprintf("Delete %s", tcpports_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
tcpports_done = true
} else {
if !reflect.DeepEqual(firewall_group.GroupMembers, tcpports) {
firewall_group.GroupMembers = tcpports
log.Info(fmt.Sprintf("Updating %s", tcpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
}
tcpports_done = true
}
}
if firewall_group.Name == udpports_name {
if len(udpports) == 0 {
log.Info(fmt.Sprintf("Delete %s", udpports_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
udpports_done = true
} else {
if !reflect.DeepEqual(firewall_group.GroupMembers, udpports) {
firewall_group.GroupMembers = udpports
log.Info(fmt.Sprintf("Updating %s", udpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
}
udpports_done = true
}
}
if firewall_group.Name == ipv4_name+"-deleted" && len(ipv4) > 0 {
firewall_group.Name = ipv4_name
firewall_group.GroupMembers = ipv4
@@ -238,6 +350,28 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
}
ipv6_done = true
}
if firewall_group.Name == tcpports_name+"-deleted" && len(tcpports) > 0 {
firewall_group.Name = tcpports_name
firewall_group.GroupMembers = tcpports
log.Info(fmt.Sprintf("Creating %s (from previously deleted)", tcpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
tcpports_done = true
}
if firewall_group.Name == udpports_name+"-deleted" && len(udpports) > 0 {
firewall_group.Name = udpports_name
firewall_group.GroupMembers = udpports
log.Info(fmt.Sprintf("Creating %s (from previously deleted)", udpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
udpports_done = true
}
}
if len(ipv4) > 0 && !ipv4_done {
log.Info(fmt.Sprintf("Creating %s", ipv4_name))
@@ -265,12 +399,40 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
return reconcile.Result{}, err
}
}
if len(tcpports) > 0 && !tcpports_done {
log.Info(fmt.Sprintf("Creating %s", tcpports_name))
var firewall_group goUnifi.FirewallGroup
firewall_group.Name = tcpports_name
firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = tcpports
firewall_group.GroupType = "port-group"
log.Info(fmt.Sprintf("Trying to apply: %+v", firewall_group))
_, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err
}
}
if len(udpports) > 0 && !udpports_done {
log.Info(fmt.Sprintf("Creating %s", udpports_name))
var firewall_group goUnifi.FirewallGroup
firewall_group.Name = udpports_name
firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = udpports
firewall_group.GroupType = "port-group"
log.Info(fmt.Sprintf("Trying to apply: %+v", firewall_group))
_, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err
}
}
if err := r.Status().Update(ctx, &nwObj); err != nil {
log.Error(err, "unable to update FirewallGroup status")
return reconcile.Result{}, err
}
log.Info("Successfully updated FirewallGroup status with collected IP addresses")
log.Info("Successfully updated FirewallGroup status with collected IP addresses and ports")
return reconcile.Result{}, nil
}
@@ -307,6 +469,7 @@ func (r *FirewallGroupReconciler) mapServiceToFirewallGroups(ctx context.Context
return requests
}
// SetupWithManager sets up the controller with the Manager.
func (r *FirewallGroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).