proxmox class
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| #!/usr/bin/env python3 | ||||
| """ | ||||
| CertPusher - Automated SSL Certificate Distribution Tool | ||||
| Version 1.1 - With unified certificate checking for all host types | ||||
| Version 1.1 - With serial number normalization | ||||
| """ | ||||
|  | ||||
| import configparser | ||||
| @@ -48,6 +48,23 @@ def setup_logging(debug: bool = False): | ||||
| logger = logging.getLogger('CertPusher') | ||||
|  | ||||
|  | ||||
| def normalize_serial(serial: str) -> str: | ||||
|     """ | ||||
|     Normalize certificate serial number | ||||
|     Removes leading zeros, colons, spaces and converts to uppercase | ||||
|      | ||||
|     Examples: | ||||
|         '05BC46A0...' -> '5BC46A0...' | ||||
|         '0005BC...'   -> '5BC...' | ||||
|         '00'          -> '0' | ||||
|     """ | ||||
|     # Remove formatting characters | ||||
|     normalized = serial.upper().replace(':', '').replace(' ', '').replace('-', '') | ||||
|     # Remove leading zeros but keep at least one digit | ||||
|     normalized = normalized.lstrip('0') or '0' | ||||
|     return normalized | ||||
|  | ||||
|  | ||||
| class CertificateManager: | ||||
|     """Manages certificate comparison and validation""" | ||||
|      | ||||
| @@ -91,12 +108,12 @@ class CertificateManager: | ||||
|      | ||||
|     @staticmethod | ||||
|     def compare_certificates(cert1: x509.Certificate, cert2: x509.Certificate) -> bool: | ||||
|         """Compare two certificates by serial number""" | ||||
|         """Compare two certificates by normalized serial number""" | ||||
|         try: | ||||
|             serial1 = format(cert1.serial_number, 'X').upper() | ||||
|             serial2 = format(cert2.serial_number, 'X').upper() | ||||
|             serial1 = normalize_serial(format(cert1.serial_number, 'X').upper()) | ||||
|             serial2 = normalize_serial(format(cert2.serial_number, 'X').upper()) | ||||
|              | ||||
|             logger.debug(f"Comparing serials: {serial1} vs {serial2}") | ||||
|             logger.debug(f"Comparing normalized serials: {serial1} vs {serial2}") | ||||
|             return serial1 == serial2 | ||||
|         except Exception as e: | ||||
|             logger.error(f"Failed to compare certificates: {e}") | ||||
| @@ -110,9 +127,10 @@ class CertificateManager: | ||||
|             valid_to = cert.not_valid_after_utc | ||||
|             now = datetime.now(timezone.utc) | ||||
|             days_left = (valid_to - now).days | ||||
|             serial = normalize_serial(format(cert.serial_number, 'X').upper()) | ||||
|              | ||||
|             return f"""Certificate: {subject} | ||||
|   Serial: {format(cert.serial_number, 'X').upper()} | ||||
|   Serial: {serial} | ||||
|   Expires: {valid_to} | ||||
|   Days left: {days_left}""" | ||||
|         except Exception as e: | ||||
| @@ -222,7 +240,6 @@ class SSHManager: | ||||
|         try: | ||||
|             logger.info("Checking remote certificate via SSH") | ||||
|              | ||||
|             # Try to read and compare certificate via SSH | ||||
|             success, stdout, stderr = self.execute_command( | ||||
|                 f'openssl x509 -in {remote_cert_path} -noout -serial 2>/dev/null || echo "NOTFOUND"', | ||||
|                 ignore_error=True | ||||
| @@ -232,8 +249,12 @@ class SSHManager: | ||||
|                 serial_match = re.search(r'serial=([A-F0-9]+)', stdout, re.IGNORECASE) | ||||
|                  | ||||
|                 if serial_match: | ||||
|                     remote_serial = serial_match.group(1).upper() | ||||
|                     source_serial = format(source_cert.serial_number, 'X').upper() | ||||
|                     remote_serial_raw = serial_match.group(1).upper() | ||||
|                     source_serial_raw = format(source_cert.serial_number, 'X').upper() | ||||
|                      | ||||
|                     # Normalize both serials | ||||
|                     remote_serial = normalize_serial(remote_serial_raw) | ||||
|                     source_serial = normalize_serial(source_serial_raw) | ||||
|                      | ||||
|                     logger.info(f"Source serial:  {source_serial}") | ||||
|                     logger.info(f"Remote serial:  {remote_serial}") | ||||
| @@ -246,7 +267,7 @@ class SSHManager: | ||||
|                         return True | ||||
|              | ||||
|             logger.warning("Could not read remote certificate via SSH") | ||||
|             return True  # Upload if we can't verify | ||||
|             return True | ||||
|              | ||||
|         except Exception as e: | ||||
|             logger.warning(f"Error checking remote certificate: {e}") | ||||
| @@ -316,7 +337,7 @@ class MikroTikManager(SSHManager): | ||||
|         try: | ||||
|             if check_first and source_cert: | ||||
|                 if not self.check_certificate_expiry(source_cert): | ||||
|                     return True, False  # Success but skipped | ||||
|                     return True, False | ||||
|              | ||||
|             logger.info("Deploying MikroTik certificate") | ||||
|              | ||||
| @@ -357,7 +378,7 @@ class MikroTikManager(SSHManager): | ||||
|                 self.execute_command(cmd, ignore_error=True) | ||||
|              | ||||
|             logger.info(f"✓ MikroTik deployment successful") | ||||
|             return True, True  # Success and uploaded | ||||
|             return True, True | ||||
|              | ||||
|         except Exception as e: | ||||
|             logger.error(f"MikroTik deployment failed: {e}") | ||||
| @@ -368,11 +389,18 @@ class ProxmoxManager(SSHManager): | ||||
|     """Specialized manager for Proxmox VE servers""" | ||||
|      | ||||
|     def check_certificate(self, source_cert: x509.Certificate, check_url: str) -> bool: | ||||
|         """Check if certificate on Proxmox needs update""" | ||||
|         """ | ||||
|         Check if certificate on Proxmox needs update | ||||
|         Returns True if upload needed, False if certificates match | ||||
|          | ||||
|         Uses two methods: | ||||
|         1. SSH: Direct file check (preferred) | ||||
|         2. URL: HTTPS check (fallback) | ||||
|         """ | ||||
|         try: | ||||
|             logger.info("Checking Proxmox certificate") | ||||
|              | ||||
|             # Method 1: Check via SSH | ||||
|             # Method 1: Check via SSH - Direct file access | ||||
|             success, stdout, stderr = self.execute_command( | ||||
|                 'openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -serial -dates 2>/dev/null', | ||||
|                 ignore_error=True | ||||
| @@ -382,30 +410,45 @@ class ProxmoxManager(SSHManager): | ||||
|                 serial_match = re.search(r'serial=([A-F0-9]+)', stdout, re.IGNORECASE) | ||||
|                  | ||||
|                 if serial_match: | ||||
|                     proxmox_serial = serial_match.group(1).upper() | ||||
|                     source_serial = format(source_cert.serial_number, 'X').upper() | ||||
|                     proxmox_serial_raw = serial_match.group(1).upper() | ||||
|                     source_serial_raw = format(source_cert.serial_number, 'X').upper() | ||||
|                      | ||||
|                     # Normalize both serials (remove leading zeros) | ||||
|                     proxmox_serial = normalize_serial(proxmox_serial_raw) | ||||
|                     source_serial = normalize_serial(source_serial_raw) | ||||
|                      | ||||
|                     logger.info(f"Source serial:  {source_serial}") | ||||
|                     logger.info(f"Proxmox serial: {proxmox_serial}") | ||||
|                      | ||||
|                     if source_serial == proxmox_serial: | ||||
|                         logger.info("✓ Certificates match. Skipping upload.") | ||||
|                         logger.info("✓ Certificates match (SSH check). Skipping upload.") | ||||
|                         return False | ||||
|                     else: | ||||
|                         logger.info("✗ Certificates differ. Upload needed.") | ||||
|                         logger.info("✗ Certificates differ (SSH check). Upload needed.") | ||||
|                         return True | ||||
|              | ||||
|             # Method 2: Fallback to URL check | ||||
|             if check_url: | ||||
|                 logger.info("Trying URL-based check") | ||||
|                 logger.info("SSH check failed. Trying URL-based check...") | ||||
|                 cert_manager = CertificateManager() | ||||
|                 remote_cert = cert_manager.get_cert_from_url(check_url) | ||||
|                  | ||||
|                 if remote_cert and cert_manager.compare_certificates(source_cert, remote_cert): | ||||
|                     logger.info("✓ Certificates match via URL. Skipping.") | ||||
|                     return False | ||||
|                 if remote_cert: | ||||
|                     # Compare using normalized serials | ||||
|                     remote_serial = normalize_serial(format(remote_cert.serial_number, 'X').upper()) | ||||
|                     source_serial = normalize_serial(format(source_cert.serial_number, 'X').upper()) | ||||
|                      | ||||
|             logger.warning("Could not verify. Proceeding with upload.") | ||||
|                     logger.info(f"Source serial (URL):  {source_serial}") | ||||
|                     logger.info(f"Remote serial (URL):  {remote_serial}") | ||||
|                      | ||||
|                     if remote_serial == source_serial: | ||||
|                         logger.info("✓ Certificates match (URL check). Skipping upload.") | ||||
|                         return False | ||||
|                     else: | ||||
|                         logger.info("✗ Certificates differ (URL check). Upload needed.") | ||||
|                         return True | ||||
|              | ||||
|             logger.warning("Could not verify certificate. Proceeding with upload for safety.") | ||||
|             return True | ||||
|              | ||||
|         except Exception as e: | ||||
| @@ -421,7 +464,7 @@ class ProxmoxManager(SSHManager): | ||||
|         try: | ||||
|             if check_first and source_cert: | ||||
|                 if not self.check_certificate(source_cert, check_url): | ||||
|                     return True, False  # Success but skipped | ||||
|                     return True, False | ||||
|              | ||||
|             logger.info("Deploying Proxmox certificate") | ||||
|              | ||||
| @@ -637,7 +680,7 @@ class CertPusher: | ||||
|                     upload_needed = False | ||||
|                 # Try URL check if SSH check failed | ||||
|                 elif check_url: | ||||
|                     logger.info(f"Checking via URL: {check_url}") | ||||
|                     logger.info(f"SSH check failed. Trying URL: {check_url}") | ||||
|                     remote_cert = self.cert_manager.get_cert_from_url(check_url) | ||||
|                     if remote_cert and self.cert_manager.compare_certificates(source_cert, remote_cert): | ||||
|                         logger.info("✓ Certificate up to date via URL. Skipping.") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Mateusz Gruszczyński
					Mateusz Gruszczyński