From 5b0b555557d5b5d93cedaab212364336e2c63409 Mon Sep 17 00:00:00 2001 From: Vegard Engen Date: Thu, 10 Apr 2025 10:46:41 +0200 Subject: [PATCH 1/3] Create logic to handle sessions in Unifi API --- internal/unifi/unifi.go | 45 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/internal/unifi/unifi.go b/internal/unifi/unifi.go index 819dbfa..cfebb8e 100644 --- a/internal/unifi/unifi.go +++ b/internal/unifi/unifi.go @@ -15,8 +15,12 @@ import ( ) type UnifiClient struct { - Client *unifi.Client - SiteID string + Client *unifi.Client + SiteID string + mutex sync.Mutex + controller string + username string + password string } func CreateUnifiClient() (*UnifiClient, error) { @@ -64,9 +68,42 @@ func CreateUnifiClient() (*UnifiClient, error) { } unifiClient := &UnifiClient{ - Client: client, - SiteID: siteID, + Client: client, + SiteID: siteID, + controller: unifiURL, + username: username, + password: password, } return unifiClient, nil } + +func (s *Session) WithSession(action func(c *unifi.Client) error) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + err := action(s.client) + if err == nil { + return nil + } + + if IsSessionExpired(err) { + if loginErr := s.Client.Login(context.Background(), s.username, s.password); loginErr != nil { + return fmt.Errorf("re-login to Unifi failed: %w", loginErr) + } + + return action(s.Client) + } +} + +func isSessionExpired(err error) bool { + if err == nil { + return false + } + + msg := strings.ToLower(err.Error()) + return strings.Contains(msg, "unauthorized") || + strings.Contains(msg, "authentication") || + strings.Contains(msg, "login required") || + strings.Contains(msg, "token") +} From 1306939d58cb6ea27ad808c29eff80c111cf36bd Mon Sep 17 00:00:00 2001 From: Vegard Engen Date: Thu, 10 Apr 2025 12:06:24 +0200 Subject: [PATCH 2/3] fix compile errors --- internal/unifi/unifi.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/unifi/unifi.go b/internal/unifi/unifi.go index cfebb8e..6a76077 100644 --- a/internal/unifi/unifi.go +++ b/internal/unifi/unifi.go @@ -7,6 +7,8 @@ import ( "crypto/tls" "errors" "fmt" + "sync" + "strings" "net/http" "net/http/cookiejar" "os" @@ -78,11 +80,11 @@ func CreateUnifiClient() (*UnifiClient, error) { return unifiClient, nil } -func (s *Session) WithSession(action func(c *unifi.Client) error) error { +func (s *UnifiClient) WithSession(action func(c *unifi.Client) error) error { s.mutex.Lock() defer s.mutex.Unlock() - err := action(s.client) + err := action(s.Client) if err == nil { return nil } @@ -94,9 +96,10 @@ func (s *Session) WithSession(action func(c *unifi.Client) error) error { return action(s.Client) } + return err } -func isSessionExpired(err error) bool { +func IsSessionExpired(err error) bool { if err == nil { return false } From a1ed82258c4a0053d4c0ec544522d3acff7a8ac5 Mon Sep 17 00:00:00 2001 From: Vegard Engen Date: Thu, 10 Apr 2025 13:45:51 +0200 Subject: [PATCH 3/3] Reauthenticate method plus workaround for delete --- .../samples/unifi_v1beta1_firewallgroup.yaml | 5 -- .../controller/firewallgroup_controller.go | 61 +++++++++++++++++-- internal/unifi/unifi.go | 18 +++++- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/config/samples/unifi_v1beta1_firewallgroup.yaml b/config/samples/unifi_v1beta1_firewallgroup.yaml index 7a99241..d673c5e 100644 --- a/config/samples/unifi_v1beta1_firewallgroup.yaml +++ b/config/samples/unifi_v1beta1_firewallgroup.yaml @@ -9,9 +9,4 @@ spec: name: Test manualAddresses: - 192.168.1.153 - - 192.168.1.154 - - 192.168.1.155 - - 2a01::3 - - 2a01:0::5 - - 2a01:2a01::/32 # TODO(user): Add fields here diff --git a/internal/controller/firewallgroup_controller.go b/internal/controller/firewallgroup_controller.go index b194c06..d16069f 100644 --- a/internal/controller/firewallgroup_controller.go +++ b/internal/controller/firewallgroup_controller.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "reflect" + "strings" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -90,6 +91,10 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques } } } + err := r.UnifiClient.Reauthenticate() + if err != nil { + return ctrl.Result{}, err + } firewall_groups, err := r.UnifiClient.Client.ListFirewallGroup(context.Background(), r.UnifiClient.SiteID) if err != nil { log.Error(err, "Could not list network objects") @@ -105,8 +110,21 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques log.Info(fmt.Sprintf("Delete %s", ipv4_name)) err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID) if err != nil { - log.Error(err, "Could not delete firewall group") - return ctrl.Result{}, err + 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 ctrl.Result{}, updateerr + } + } else { + log.Error(err, "Could not delete firewall group") + return ctrl.Result{}, err + } } ipv4_done = true } else { @@ -127,8 +145,21 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques log.Info(fmt.Sprintf("Delete %s", ipv6_name)) err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID) if err != nil { - log.Error(err, "Could not delete firewall group") - return ctrl.Result{}, err + 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{"::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 ctrl.Result{}, updateerr + } + } else { + log.Error(err, "Could not delete firewall group") + return ctrl.Result{}, err + } } ipv6_done = true } else { @@ -144,6 +175,28 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques ipv6_done = true } } + if firewall_group.Name == ipv4_name+"-deleted" && len(ipv4) > 0 { + firewall_group.Name = ipv4_name + firewall_group.GroupMembers = ipv4 + log.Info(fmt.Sprintf("Creating %s (from previously deleted)", ipv4_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 ctrl.Result{}, err + } + ipv4_done = true + } + if firewall_group.Name == ipv6_name+"-deleted" && len(ipv6) > 0 { + firewall_group.Name = ipv6_name + firewall_group.GroupMembers = ipv6 + log.Info(fmt.Sprintf("Creating %s (from previously deleted)", ipv6_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 ctrl.Result{}, err + } + ipv6_done = true + } } if len(ipv4) > 0 && !ipv4_done { log.Info(fmt.Sprintf("Creating %s", ipv4_name)) diff --git a/internal/unifi/unifi.go b/internal/unifi/unifi.go index 6a76077..7aa812e 100644 --- a/internal/unifi/unifi.go +++ b/internal/unifi/unifi.go @@ -7,11 +7,11 @@ import ( "crypto/tls" "errors" "fmt" - "sync" - "strings" "net/http" "net/http/cookiejar" "os" + "strings" + "sync" "github.com/vegardengen/go-unifi/unifi" ) @@ -99,6 +99,20 @@ func (s *UnifiClient) WithSession(action func(c *unifi.Client) error) error { return err } +func (uClient *UnifiClient) Reauthenticate() error { + _, err := uClient.Client.ListSites(context.Background()) + if err == nil { + return nil + } + + if IsSessionExpired(err) { + if loginErr := uClient.Client.Login(context.Background(), uClient.username, uClient.password); loginErr != nil { + return fmt.Errorf("re-login to Unifi failed: %w", loginErr) + } + } + return nil +} + func IsSessionExpired(err error) bool { if err == nil { return false