diff --git a/api/v1beta1/firewallgroup_types.go b/api/v1beta1/firewallgroup_types.go index c662578..d6b93e9 100644 --- a/api/v1beta1/firewallgroup_types.go +++ b/api/v1beta1/firewallgroup_types.go @@ -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 diff --git a/config/crd/bases/unifi.engen.priv.no_firewallgroups.yaml b/config/crd/bases/unifi.engen.priv.no_firewallgroups.yaml index f54583f..b9cf679 100644 --- a/config/crd/bases/unifi.engen.priv.no_firewallgroups.yaml +++ b/config/crd/bases/unifi.engen.priv.no_firewallgroups.yaml @@ -99,6 +99,10 @@ spec: items: type: string type: array + manualPorts: + items: + type: string + type: array matchServicesInAllNamespaces: type: boolean name: diff --git a/internal/controller/firewallgroup_controller.go b/internal/controller/firewallgroup_controller.go index fdea25f..b17e603 100644 --- a/internal/controller/firewallgroup_controller.go +++ b/internal/controller/firewallgroup_controller.go @@ -21,7 +21,10 @@ import ( "fmt" "net" "reflect" + "slices" + "strconv" "strings" + "regexp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -68,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) @@ -98,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 { @@ -123,6 +146,22 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R } } } + 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))) + } + } + } + } } } nwObj.Status.ResolvedAddresses = ipv4 @@ -142,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 { @@ -215,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 @@ -237,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)) @@ -264,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 }