From e3e3902891ccc370b6c05ceb5e59a161a558fd75 Mon Sep 17 00:00:00 2001 From: Quentin WEPHRE Date: Tue, 14 Oct 2025 15:12:12 +0200 Subject: [PATCH] port-dependant (nat) batch for cube inventory --- Python/cube_activate_ssh_port.py | 118 +++++++++++++++++++++ Python/cube_ssh_batch_passive_port.py | 141 ++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 Python/cube_activate_ssh_port.py create mode 100644 Python/cube_ssh_batch_passive_port.py diff --git a/Python/cube_activate_ssh_port.py b/Python/cube_activate_ssh_port.py new file mode 100644 index 0000000..859c08c --- /dev/null +++ b/Python/cube_activate_ssh_port.py @@ -0,0 +1,118 @@ +import requests +import json +import os +import urllib3 +from dotenv import load_dotenv +import time +import sys + +def resource_path(relative_path): + """ Get absolute path to resource, works for dev and for PyInstaller """ + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) + +dotenv_path = resource_path('.env') +load_dotenv(dotenv_path=dotenv_path) + +def authenticate(base_url): + """ + Authenticate with the CUBE API using username, password and certificate. + Returns the JWT token if successful. + """ + auth_url = f"{base_url}/api/auth" + + ENV_WEB = { + "DEFAULT_CUBE_WEB_ADMIN_USER": os.getenv("DEFAULT_CUBE_WEB_ADMIN_USER"), + "DEFAULT_CUBE_WEB_ADMIN_PASSWORD": os.getenv("DEFAULT_CUBE_WEB_ADMIN_PASSWORD"), + "DEFAULT_CERTIFICATE": os.getenv("DEFAULT_CERTIFICATE") + } + + username = ENV_WEB["DEFAULT_CUBE_WEB_ADMIN_USER"] + password = ENV_WEB["DEFAULT_CUBE_WEB_ADMIN_PASSWORD"] + certificate = ENV_WEB["DEFAULT_CERTIFICATE"].encode("utf-8") + + auth_params = { + "login": username, + "password": password + } + files = { + "params": (None, json.dumps(auth_params), "application/json"), + "certificate": ("certificate.pem", certificate, "application/octet-stream") + } + try: + response = requests.post(auth_url, files=files, verify=False, timeout=10) + response.raise_for_status() # Raise exception for 4XX/5XX responses + + # Extract token from response + auth_data = response.json() + token = auth_data.get("token") + + if not token: + raise requests.exceptions.RequestException + + print("HTTPS ✅", end = " ", flush=True) + return token + + except requests.exceptions.RequestException as e: + print(f"HTTPS ❌", flush=True) + if hasattr(e, 'response') and e.response: + raise Exception(e.response) + else: + raise + +def set_ssh_status(base_url, token): + """ + Set SSH status (enable) using the provided JWT token. + """ + ssh_url = f"{base_url}/api/ssh" + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token}" + } + + # Set new SSH status + payload = { "currentStatus": True } + + try: + response = requests.post(ssh_url, headers=headers, json=payload, verify=False, timeout=10) + response.raise_for_status() + + print(f"SSH ✅", end = " ", flush=True) + + return True + + except requests.exceptions.RequestException as e: + print("SSH ❌", flush=True) + if hasattr(e, 'response') and e.response: + raise Exception(e.response) + else: + raise + +def activate_ssh(ip_address, port): + + # Ensure the URL uses HTTPS + url = ip_address + if not url.startswith("https://"): + # Convert http:// to https:// or add https:// if no protocol specified + if url.startswith("http://"): + url = "https://" + url[7:] + else: + url = "https://" + url + if not url.endswith(f":{port}"): + url = url + f":{port}" + + verify_ssl = False + if not verify_ssl: + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + token = authenticate(url) + if not token: + return + time.sleep(3) + set_ssh_status(url, token) \ No newline at end of file diff --git a/Python/cube_ssh_batch_passive_port.py b/Python/cube_ssh_batch_passive_port.py new file mode 100644 index 0000000..7c0e2f3 --- /dev/null +++ b/Python/cube_ssh_batch_passive_port.py @@ -0,0 +1,141 @@ +import csv +import paramiko +import time +from cube_activate_ssh_port import activate_ssh +from dotenv import load_dotenv +import os +import re +from azure.iot.hub import IoTHubRegistryManager +from azure.iot.hub.models import Twin, TwinProperties + +load_dotenv(override=True) + +ssh_username = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_USER") +ssh_password = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_PASSWORD") +CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD")) + +def execute_ssh_command(ip, port, command): + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + client.connect(ip, port=port, username=ssh_username, password=ssh_password, allow_agent=False, look_for_keys=False) + stdin, stdout, stderr = client.exec_command(command) + result = stdout.read().decode().lower().strip() + return result + except Exception as e: + print(f"SSH Error: {str(e)}") + raise + finally: + client.close() + +def update_cloud_config(ip, new_content): + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + client.connect(ip, port=11022, username=ssh_username, password=ssh_password, allow_agent=False, look_for_keys=False) + stdin, stdout, stderr = client.exec_command(f'sudo -S bash -c \'cat > /etc/cube/config-azure.properties << EOF\n{new_content}\nEOF\'\n') + stdin.write(ssh_password + "\n") + stdin.flush() + stdoutput = [line for line in stdout] + stderroutput = [line for line in stderr] + for output in stdoutput: + print(output.strip()) + except Exception as e: + print(f"SSH Error: {str(e)}") + raise + finally: + client.close() + +def main(): + + SITE_NAME = "LIBERTY" + + ip_address = "192.168.15.158" + + https_start_port = 8080 + https_end_port = 8096 + https_port_range = list(range(https_start_port, https_end_port + 1)) + print(f"{https_port_range}") + + ssh_start_port = 8180 + ssh_start_end = 8196 + ssh_port_range = list(range(ssh_start_port, ssh_start_end + 1)) + print(f"{ssh_port_range}") + + + print(f"Site: {SITE_NAME}") + print(f"From {ip_address}:{str(https_start_port)} to {ip_address}:{str(https_end_port)}") + + file_numbering = 0 + + while os.path.exists(SITE_NAME + "_" + str(file_numbering) + ".csv"): + file_numbering = file_numbering + 1 + + csv_filename = SITE_NAME + "_" + str(file_numbering) + ".csv" + + print(f"Logging results to {csv_filename}") + + with open(csv_filename, mode="w", newline="") as file: + writer = csv.writer(file) + writer.writerow(["Number", "IP address", "Cube ID", "Environment", "Correct configuration"]) + + registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING) + + results = [] + + for i in [0]: # range(0, len(https_port_range)): + ip_address = f"{ip_address}" + print(f"Activating SSH for {ip_address}:{https_port_range[i]} ", end=" ") + + try: + activate_ssh(ip_address, https_port_range[i]) + except Exception as e: + writer.writerow([i, f"{ip_address}:{https_port_range[i]}", "UNREACHABLE", "NA", "NA"]) + file.flush() + continue + + ssh_command = "hostname" + print(f"Executing {ssh_command} for {ip_address}:{ssh_port_range[i]}:", end=" ") + try: + cube_id = execute_ssh_command(ip_address, ssh_port_range[i], ssh_command) + except Exception as e: + print("Failed!") + writer.writerow([i, ip_address, "UNREACHABLE", "NA", "NA"]) + file.flush() + continue + print(cube_id) + + ssh_command = "grep \"connection-string\" /etc/cube/config-azure.properties" + print(f"Getting configured Connection String for {ip_address}:", end=" ") + try: + connection_string = execute_ssh_command(ip_address, ssh_port_range[i], ssh_command) + if connection_string == "": + raise Exception("No Connection String was extracted!") + + iothub_match = re.search(r"hostname\\=(.*?);", connection_string, re.IGNORECASE) + iothub = iothub_match.group(1) if iothub_match else None + if iothub.lower() == "IotHub-CUBE-PROD.azure-devices.net".lower(): + migration = "SAFT" + elif iothub.lower() == "iot-ingest-ess-prod.azure-devices.net".lower(): + migration = "INOX" + else: + migration = "NONE" + + device_id_match = re.search(r"deviceid\\=(.*?);", connection_string, re.IGNORECASE) + cloud_cube_id = device_id_match.group(1) if device_id_match else None + if cloud_cube_id.lower() == cube_id.lower(): + status = "CORRECT" + else: + status = "INCORRECT" + except Exception as e: + print(e) + migration = "NONE" + status = "INCORRECT" + finally: + print(f"{migration} {status}") + + writer.writerow([i, ip_address, cube_id, migration, status]) + file.flush() + +if __name__ == "__main__": + main()