@@ -51,6 +51,7 @@ def __init__(self):
5151 self .wasabi_region = "us-east-1"
5252 self .wasabi_enabled = False
5353 self .last_wasabi_status = "Not attempted"
54+ self .verify_ssl = False
5455 self .root = None
5556 self .systray = None
5657 self .backup_lock = threading .Lock ()
@@ -162,7 +163,9 @@ def load_config(self):
162163 'wasabi_secret_key' : '' ,
163164 'wasabi_bucket' : '' ,
164165 'wasabi_region' : 'us-east-1' ,
165- 'wasabi_enabled' : False
166+ 'wasabi_enabled' : False ,
167+ 'verify_ssl' : False ,
168+ 'max_backups' : 5
166169 }
167170 try :
168171 with open (self .config_file , 'r' ) as f :
@@ -185,7 +188,9 @@ def load_config(self):
185188 self .wasabi_bucket = config .get ('wasabi_bucket' , default_config ['wasabi_bucket' ])
186189 self .wasabi_region = config .get ('wasabi_region' , default_config ['wasabi_region' ])
187190 self .wasabi_enabled = config .get ('wasabi_enabled' , default_config ['wasabi_enabled' ])
188- logging .info (f"Loaded config: git_enabled={ self .git_enabled } , wasabi_enabled={ self .wasabi_enabled } " )
191+ self .verify_ssl = config .get ('verify_ssl' , default_config ['verify_ssl' ])
192+ self .max_backups = config .get ('max_backups' , default_config ['max_backups' ])
193+ logging .info (f"Loaded config: git_enabled={ self .git_enabled } , wasabi_enabled={ self .wasabi_enabled } , verify_ssl={ self .verify_ssl } " )
189194 except (FileNotFoundError , json .JSONDecodeError ) as e :
190195 self .csv_file = default_config ['csv_path' ]
191196 self .schedule_frequency = default_config ['schedule_frequency' ]
@@ -205,6 +210,8 @@ def load_config(self):
205210 self .wasabi_bucket = default_config ['wasabi_bucket' ]
206211 self .wasabi_region = default_config ['wasabi_region' ]
207212 self .wasabi_enabled = default_config ['wasabi_enabled' ]
213+ self .verify_ssl = default_config ['verify_ssl' ]
214+ self .max_backups = default_config ['max_backups' ]
208215 if isinstance (e , json .JSONDecodeError ):
209216 logging .warning ("Configuration file is corrupted, using default settings" )
210217 else :
@@ -285,7 +292,9 @@ def save_config(self, switch_name=None, ip=None, config=None):
285292 'wasabi_secret_key' : self ._encrypt (self .wasabi_secret_key ),
286293 'wasabi_bucket' : self .wasabi_bucket ,
287294 'wasabi_region' : self .wasabi_region ,
288- 'wasabi_enabled' : self .wasabi_enabled
295+ 'wasabi_enabled' : self .wasabi_enabled ,
296+ 'verify_ssl' : self .verify_ssl ,
297+ 'max_backups' : self .max_backups
289298 }
290299 try :
291300 with open (self .config_file , 'w' ) as f :
@@ -384,6 +393,35 @@ def setup_gui(self):
384393
385394 ttk .Separator (left_column , orient = "horizontal" ).pack (fill = "x" , pady = 5 )
386395
396+ # Advanced settings frame for configurable options (timeout, max_backups, SSL verify)
397+ adv_frame = ttk .LabelFrame (left_column , text = "Advanced Settings" )
398+ adv_frame .pack (fill = "x" , pady = 10 )
399+ adv_sub = ttk .Frame (adv_frame )
400+ adv_sub .pack (expand = True , padx = 10 , pady = 5 )
401+
402+ self .verify_ssl_var = tk .BooleanVar (value = self .verify_ssl )
403+ verify_toggle = ttk .Checkbutton (adv_sub , text = "Verify SSL certificates (recommended)" , variable = self .verify_ssl_var , command = self .toggle_verify_ssl )
404+ verify_toggle .pack (pady = 2 , anchor = "w" )
405+
406+ timeout_frame = ttk .Frame (adv_sub )
407+ timeout_frame .pack (fill = "x" , pady = 2 )
408+ ttk .Label (timeout_frame , text = "Timeout (s):" ).pack (side = "left" , padx = 3 )
409+ self .timeout_entry = ttk .Entry (timeout_frame , width = 8 )
410+ self .timeout_entry .insert (0 , str (self .timeout ))
411+ self .timeout_entry .pack (side = "left" , padx = 3 )
412+
413+ maxb_frame = ttk .Frame (adv_sub )
414+ maxb_frame .pack (fill = "x" , pady = 2 )
415+ ttk .Label (maxb_frame , text = "Max backups per switch:" ).pack (side = "left" , padx = 3 )
416+ self .max_backups_entry = ttk .Entry (maxb_frame , width = 8 )
417+ self .max_backups_entry .insert (0 , str (self .max_backups ))
418+ self .max_backups_entry .pack (side = "left" , padx = 3 )
419+
420+ adv_save = ttk .Button (adv_sub , text = "Save Settings" , command = self .save_advanced_settings )
421+ adv_save .pack (pady = 5 )
422+
423+ ttk .Separator (left_column , orient = "horizontal" ).pack (fill = "x" , pady = 5 )
424+
387425 control_frame = ttk .LabelFrame (left_column , text = "Manual Backup" )
388426 control_frame .pack (fill = "x" , pady = 10 )
389427 run_backup_button = ttk .Button (control_frame , text = "Run Now" , command = self .manual_backup )
@@ -586,14 +624,44 @@ def toggle_schedule(self):
586624 logging .info ("Schedule disabled" )
587625 self .save_config ()
588626
627+ def toggle_verify_ssl (self ):
628+ self .verify_ssl = self .verify_ssl_var .get ()
629+ self .save_config ()
630+ if not self .verify_ssl :
631+ self ._update_gui (lambda : messagebox .showwarning (
632+ "Security Warning" ,
633+ "SSL verification is DISABLED. This exposes credentials and config data to interception on untrusted networks. "
634+ "Only disable for lab switches with self-signed certs. Enable for production use."
635+ ))
636+ logging .info (f"SSL verification { 'enabled' if self .verify_ssl else 'disabled' } " )
637+
638+ def save_advanced_settings (self ):
639+ try :
640+ self .timeout = int (self .timeout_entry .get ())
641+ if self .timeout < 5 or self .timeout > 120 :
642+ raise ValueError ("Timeout must be 5-120 seconds" )
643+ self .max_backups = int (self .max_backups_entry .get ())
644+ if self .max_backups < 1 or self .max_backups > 20 :
645+ raise ValueError ("Max backups must be 1-20" )
646+ self .verify_ssl = self .verify_ssl_var .get ()
647+ self .save_config ()
648+ messagebox .showinfo ("Success" , "Advanced settings saved" )
649+ logging .info (f"Advanced settings updated: timeout={ self .timeout } , max_backups={ self .max_backups } , verify_ssl={ self .verify_ssl } " )
650+ except ValueError as ve :
651+ messagebox .showerror ("Invalid Input" , str (ve ))
652+ except Exception as e :
653+ logging .error (f"Failed to save advanced settings: { str (e )} " )
654+ messagebox .showerror ("Error" , "Failed to save settings" )
655+
589656 def get_switch_config (self , ip , username , password ):
590657 max_retries = 3
591658 retry_delay = 5
592659 session = requests .Session ()
593660 config_text = None
661+ verify = self .verify_ssl
594662
595663 try :
596- response = requests .get (f"https://{ ip } " , timeout = 5 , verify = False )
664+ response = requests .get (f"https://{ ip } " , timeout = 5 , verify = verify )
597665 logging .info (f"Connectivity test to { ip } : { response .status_code } " )
598666 except requests .exceptions .RequestException as e :
599667 logging .error (f"Connectivity test to { ip } failed: { str (e )} " )
@@ -603,17 +671,26 @@ def get_switch_config(self, ip, username, password):
603671 logged_in = False
604672 try :
605673 login_url = f"https://{ ip } /rest/v10.04/login"
606- login_response = session .post (login_url , data = {"username" : username , "password" : password }, verify = False , timeout = self .timeout )
674+ login_response = session .post (login_url , data = {"username" : username , "password" : password }, verify = verify , timeout = self .timeout )
607675 login_response .raise_for_status ()
608676 logged_in = True
609677 logging .info (f"Login successful for { ip } with API v10.04" )
610678
611679 config_url = f"https://{ ip } /rest/v10.04/configs/running-config"
612- config_response = session .get (config_url , headers = {"Accept" : "text/plain" }, verify = False , timeout = self .timeout )
680+ config_response = session .get (config_url , headers = {"Accept" : "text/plain" }, verify = verify , timeout = self .timeout )
613681 config_response .raise_for_status ()
614682 config_text = config_response .text
615683 logging .info (f"Retrieved config from { ip } with API v10.04" )
616684 break
685+ except requests .exceptions .HTTPError as e :
686+ # More granular error reporting for API responses
687+ status = e .response .status_code if e .response else "unknown"
688+ text = e .response .text [:200 ] if e .response else ""
689+ logging .error (f"HTTP error { status } from { ip } : { text } " )
690+ if attempt < max_retries - 1 :
691+ time .sleep (retry_delay )
692+ continue
693+ return None
617694 except requests .exceptions .RequestException as e :
618695 if attempt < max_retries - 1 :
619696 logging .warning (f"Attempt { attempt + 1 } failed for { ip } : { str (e )} . Retrying..." )
@@ -625,7 +702,7 @@ def get_switch_config(self, ip, username, password):
625702 if logged_in :
626703 try :
627704 logout_url = f"https://{ ip } /rest/v10.04/logout"
628- session .post (logout_url , verify = False , timeout = self .timeout )
705+ session .post (logout_url , verify = verify , timeout = self .timeout )
629706 logging .info (f"Logged out from { ip } " )
630707 except requests .exceptions .RequestException as e :
631708 logging .error (f"Failed to logout from { ip } : { str (e )} " )
0 commit comments