Initial commit of AthonetTools

This commit is contained in:
2025-08-21 12:59:43 +00:00
commit cd932b8fcb
2483 changed files with 433999 additions and 0 deletions

535
core_functions.py Normal file
View File

@@ -0,0 +1,535 @@
import requests
import json
import paramiko
import subprocess
import time
import re
import os
import auth_utils
import hashlib
from requests.exceptions import HTTPError
from datetime import datetime
requests.packages.urllib3.disable_warnings()
VPN_CONFIG_NAMES = ["Triton", "Star", "Bluebonnet", "Lonestar", "Production", "US-Support", "EU-Support"]
SERIAL_PASSWORDS = {
"3M1D2211Z3": "EP5G!f15878b4af20", "3M1D10146B": "EP5G!076689528baf",
"3M1D10146G": "EP5G!c3b0072cabf5", "3M1D2211Z1": "EP5G!65b22ae8617a",
"3M1D19125H": "EP5G!da3c04fde559", "3M1D19125G": "EP5G!b73f98633108",
"3M1D19125F": "EP5G!e61201fb9234", "3M1D1R16M4": "EP5G!ca439b544329"
}
def list_home_network_keys(host_ip, token):
url = f"https://{host_ip}/core/udm/api/1/provisioning/home_network_keys"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json().get("data", [])
def get_home_network_key(host_ip, token, key_id):
url = f"https://{host_ip}/core/udm/api/1/provisioning/home_network_keys/{key_id}"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json()
def create_home_network_key(host_ip, token, key_id, home_network_identifier, private_key, profile, description=None):
url = f"https://{host_ip}/core/udm/api/1/provisioning/home_network_keys"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"key_id": key_id,
"home_network_identifier": home_network_identifier,
"private_key": private_key,
"profile": profile
}
if description:
payload["description"] = description
response = requests.post(url, headers=headers, json=payload, verify=False)
response.raise_for_status()
return {"status": "success", "message": f"Home Network Key with ID {key_id} created successfully."}
def delete_home_network_key(host_ip, token, key_id):
"""Deletes a Home Network Key and returns a success message."""
url = f"https://{host_ip}/core/udm/api/1/provisioning/home_network_keys/{key_id}"
headers = {"Authorization": f"Bearer {token}"}
response = requests.delete(url, headers=headers, verify=False)
response.raise_for_status()
return {"status": "success", "message": f"Home Network Key with ID {key_id} deleted successfully."}
def list_m2000_vpns(base_url, token, session):
"""Lists all network devices from the Aruba dashboard."""
network_url = f"{base_url}/portal/api/1/network"
auth_headers = session.headers.copy()
auth_headers["horus-token"] = token
response = session.get(network_url, headers=auth_headers, verify=False)
response.raise_for_status()
processed_items = []
items = response.json().get("items", [])
for item in items:
hw_list = item.get("info", {}).get("hardware", [])
for hw in hw_list:
processed_items.append({
"id": item.get("id"),
"name": item.get("name"),
"status": item.get("status"),
"serial": hw.get("serial"),
"subnet": hw.get("subnet_delegation")
})
return processed_items
def restart_m2000_vpn(serial, subnet):
import ipaddress
try:
password = SERIAL_PASSWORDS.get(serial)
if not password:
raise ValueError(f"No password found for serial {serial}")
subnet_obj = ipaddress.ip_network(subnet, strict=False)
router_ip = str(list(subnet_obj.hosts())[0])
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(router_ip, username='root', password=password, timeout=10)
ssh.exec_command('systemctl restart openvpn@openvpn.service')
ssh.close()
return {"status": "success", "message": f"Restart command sent to {serial}. Please refresh the list in a moment to see the updated status."}
except Exception as e:
raise e
def get_vpn_config_and_details(host_ip):
"""
Connects to a host via SSH, gets VPN config, and fetches system details.
"""
# --- 1. Get VPN Config and Current Endpoint via SSH ---
key_path = os.path.expanduser("~/.ssh/5G-SSH-Key.pem")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host_ip, username='root', key_filename=key_path, timeout=10)
stdin, stdout, stderr = ssh.exec_command('head -n 5 /etc/openvpn/client/athonet.conf')
vpn_config_output = stdout.read().decode().strip()
# Also get the current endpoint IP from the config
stdin, stdout, stderr = ssh.exec_command("grep '^remote' /etc/openvpn/client/athonet.conf")
config_line = stdout.read().decode().strip()
ssh.close()
if not config_line:
raise Exception("Could not read VPN configuration from host.")
current_ip = config_line.split()[1]
current_region = "Unknown"
for region, ip in VPN_ENDPOINTS.items():
if ip == current_ip:
current_region = region
break
# --- 2. Get Host Details via API ---
host_details = get_host_details(host_ip)
# --- 3. Combine all the results ---
return {
"vpn_config": vpn_config_output,
"vpn_endpoint": {"region": current_region, "ip": current_ip},
"details": host_details
}
VPN_ENDPOINTS = {
"US": "128.136.82.165",
"EU": "156.54.30.27"
}
def get_current_vpn_endpoint(host_ip):
"""Connects to a host and reads the current VPN endpoint from the config file."""
key_path = os.path.expanduser("~/.ssh/5G-SSH-Key.pem")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host_ip, username='root', key_filename=key_path, timeout=10)
# Read the 'remote' line from the config file
stdin, stdout, stderr = ssh.exec_command("grep '^remote' /etc/openvpn/client/athonet.conf")
config_line = stdout.read().decode().strip()
ssh.close()
if not config_line:
raise Exception("Could not read VPN configuration from host.")
current_ip = config_line.split()[1]
# Determine if it's US or EU
for region, ip in VPN_ENDPOINTS.items():
if ip == current_ip:
return {"region": region, "ip": ip}
return {"region": "Unknown", "ip": current_ip}
def set_vpn_endpoint(host_ip, region):
"""Connects to a host, updates the VPN endpoint, and restarts the service."""
if region not in VPN_ENDPOINTS:
raise ValueError("Invalid region specified.")
new_ip = VPN_ENDPOINTS[region]
key_path = os.path.expanduser("~/.ssh/5G-SSH-Key.pem")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host_ip, username='root', key_filename=key_path, timeout=10)
# Use sed to replace the IP in the config file and then restart the service
command = (
f"sed -i 's/^remote .*/remote {new_ip}/' /etc/openvpn/client/athonet.conf && "
"systemctl restart openvpn-client@athonet.service"
)
stdin, stdout, stderr = ssh.exec_command(command)
# It's good practice to check for errors
error = stderr.read().decode().strip()
ssh.close()
if error:
raise Exception(f"Failed to switch VPN: {error}")
return {"status": "success", "message": f"VPN endpoint switched to {region} ({new_ip})."}
def get_active_vpn():
for name in VPN_CONFIG_NAMES:
for pattern in ["openvpn-client@{name}.service", "openvpn@{name}.service"]:
try:
service_name = pattern.format(name=name)
cmd = ["/usr/bin/sudo", "/usr/bin/systemctl", "is-active", service_name]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.stdout.strip() == "active":
return name
except FileNotFoundError:
continue
return None
def toggle_vpn_connection(vpn_name, turn_on):
active_vpn = get_active_vpn()
if active_vpn:
subprocess.run(["/usr/bin/sudo", "/usr/bin/systemctl", "stop", f"openvpn-client@{active_vpn}.service"])
subprocess.run(["/usr/bin/sudo", "/usr/bin/systemctl", "stop", f"openvpn@{active_vpn}.service"])
if turn_on:
try:
service_name = f"openvpn-client@{vpn_name}.service"
cmd = ["/usr/bin/sudo", "/usr/bin/systemctl", "start", service_name]
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError:
service_name = f"openvpn@{vpn_name}.service"
cmd = ["/usr/bin/sudo", "/usr/bin/systemctl", "start", service_name]
subprocess.run(cmd, check=True)
time.sleep(4)
if get_active_vpn() != vpn_name:
raise Exception(f"Connection failed for {vpn_name}. Check OpenVPN logs for details.")
return get_active_vpn()
def get_full_network_config(base_url, token, session):
network_url = f"{base_url}/portal/api/1/network"
auth_headers = session.headers.copy()
auth_headers["horus-token"] = token
response = session.get(network_url, headers=auth_headers, verify=False)
response.raise_for_status()
return response.json()
def list_tenants(base_url, token, session):
url = f"{base_url}/portal/api/tenants/"
headers = session.headers.copy()
headers["horus-token"] = token
response = session.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json().get("tenants", [])
def list_plmns(base_url, token, session, tenant_id):
url = f"{base_url}/portal/api/tenants/{tenant_id}/plmns"
headers = session.headers.copy()
headers["horus-token"] = token
response = session.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json().get("items", [])
def list_plmn_hnks(base_url, token, session, tenant_id, plmn_id):
url = f"{base_url}/portal/api/tenants/{tenant_id}/plmns/{plmn_id}/home-network-keys"
headers = session.headers.copy()
headers["horus-token"] = token
response = session.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json().get("items", [])
def update_radio_count(base_url, token, session, network_id, new_count, operation):
url = f"{base_url}/portal/api/1/network/{network_id}"
headers = {
"horus-token": token,
"Content-Type": "application/json-patch+json"
}
payload = {"ops": []}
if operation == 'replace':
op_details = {
"op": "replace",
"path": "/info/radio_pool/0/num_of_radios",
"value": str(new_count)
}
else:
op_details = {
"op": "add",
"path": "/info/radio_pool",
"value": [{"num_of_radios": str(new_count)}]
}
payload["ops"].append(op_details)
response = session.patch(url, headers=headers, json=payload, verify=False)
response.raise_for_status()
return response.json()
def get_system_browser_data():
customers = {}
try:
with open('customers.txt', 'r') as f:
for line in f:
line = line.strip()
if line:
parts = line.split(',', 1)
if len(parts) == 2:
customers[parts[0]] = parts[1]
except FileNotFoundError:
raise Exception("Error: customers.txt file not found on the server.")
vpn_clients = {}
routing_table = {}
vpn_status_urls = [
"http://100.127.0.1/_vpn_status/t2-status.txt",
"http://100.127.0.6/_vpn_status/t2-status.txt"
]
for url in vpn_status_urls:
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
lines = response.text.split('\n')
is_parsing_routing_table = False
for line in lines:
if "ROUTING TABLE" in line or "ROUTING_TABLE" in line:
is_parsing_routing_table = True
continue
if not line.strip() or line.startswith(("TITLE", "TIME", "HEADER", "GLOBAL", "OpenVPN", "Updated", "END")):
continue
parts = line.split(',')
common_name = ""
real_address = ""
virtual_ip = "N/A"
connected_since = ""
if is_parsing_routing_table:
if len(parts) >= 2:
routing_table[parts[1]] = parts[0]
elif line.startswith("CLIENT_LIST"):
if len(parts) > 7:
common_name, real_address, virtual_ip, connected_since = parts[1], parts[2], parts[3] if parts[3] else "N/A", parts[7]
elif len(parts) >= 5:
common_name, real_address, connected_since = parts[0], parts[1], parts[4]
if common_name and common_name not in vpn_clients:
customer_id_match = re.search(r'(\d{3})z', common_name)
customer_id = customer_id_match.group(1) if customer_id_match else "N/A"
customer_name = customers.get(customer_id, "Unknown")
# Clean the public IP to remove the port
public_ip = real_address.split(':')[0]
vpn_clients[common_name] = {
"customer_id": customer_id, "customer_name": customer_name,
"common_name": common_name, "virtual_ip": virtual_ip,
"public_ip": public_ip, "connected_since": connected_since
}
except requests.exceptions.RequestException as e:
print(f"Warning: Could not fetch VPN status from {url}. Error: {e}")
continue
for name, client_data in vpn_clients.items():
if client_data["virtual_ip"] == "N/A" and name in routing_table:
client_data["virtual_ip"] = routing_table[name]
if not vpn_clients:
raise Exception("Connection failed. Please ensure you are connected to the correct VPN.")
return list(vpn_clients.values())
# ------- System ID Data Begin ---------
def _make_host_api_get_request(host_ip, token, endpoint):
url = f"https://{host_ip}/{endpoint}"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json()
def get_system_info(host_ip, token):
return _make_host_api_get_request(host_ip, token, "core/pls/api/1/system/info")
def get_site_info(host_ip, token):
return _make_host_api_get_request(host_ip, token, "core/pls/api/1/site/info")
def get_frontend_config(host_ip, token):
return _make_host_api_get_request(host_ip, token, "frontend/config")
def get_licensed_host_info(host_ip, token):
"""Retrieves host info from the /mgt/host endpoint."""
url = f"https://{host_ip}/core/licensed/api/1/mgt/host"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json()
def get_licenses_info(host_ip, token):
url = f"https://{host_ip}/core/licensed/api/1/mgt/licenses"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json()
def get_host_details(host_ip):
token = auth_utils.authenticate(host_ip)
system_info = get_system_info(host_ip, token)
site_info = get_site_info(host_ip, token)
frontend_config = get_frontend_config(host_ip, token)
licensed_host_info = get_licensed_host_info(host_ip, token)
licenses_info = get_licenses_info(host_ip, token)
license_map = {}
if isinstance(licenses_info, list):
for lic in licenses_info:
app_content = lic.get('license', {}).get('app_content', {})
app_type = app_content.get('app_type')
if app_type:
license_map[app_type] = lic
services_with_licenses = []
for service in frontend_config.get("services", []):
matching_license = license_map.get(service.get('name'))
if matching_license:
params = matching_license.get('license', {}).get('license_params', {})
start_epoch = params.get('start_date')
expire_epoch = params.get('expire_date')
if isinstance(start_epoch, (int, float)):
params['start_date_str'] = datetime.fromtimestamp(start_epoch).strftime('%Y-%m-%d')
if isinstance(expire_epoch, (int, float)):
params['expire_date_str'] = datetime.fromtimestamp(expire_epoch).strftime('%Y-%m-%d')
service['license'] = matching_license
services_with_licenses.append(service)
main_license = licenses_info[0] if licenses_info else None
if main_license:
params = main_license.get('license', {}).get('license_params', {})
start_epoch = params.get('start_date')
expire_epoch = params.get('expire_date')
if isinstance(start_epoch, (int, float)):
params['start_date_str'] = datetime.fromtimestamp(start_epoch).strftime('%Y-%m-%d')
if isinstance(expire_epoch, (int, float)):
params['expire_date_str'] = datetime.fromtimestamp(expire_epoch).strftime('%Y-%m-%d')
combined_data = {
"system": system_info,
"site": site_info,
"services": services_with_licenses,
"licensed_host": licensed_host_info,
"license": main_license
}
return combined_data
# ------- System ID Data End ---------
def create_backup(host_ip, token):
url = f"https://{host_ip}/core/pls/api/1/backup/create"
headers = {"Authorization": f"Bearer {token}"}
payload = {
"services": [
"amf", "ausf", "bmsc", "chf", "dra", "dsm", "eir", "mme", "smsf",
"licensed", "nrf", "pcf", "aaa", "sgwc", "smf", "udm", "udr",
"upf", "ncm", "pls"
]
}
response = requests.post(url, headers=headers, json=payload, verify=False, stream=True)
response.raise_for_status()
return response
def list_users(base_url, token, session):
url = f"{base_url}/portal/api/tenants/users"
headers = session.headers.copy()
headers["horus-token"] = token
response = session.get(url, headers=headers, verify=False)
response.raise_for_status()
return response.json().get("users", [])
def generate_m2000_password(serial, seed="ANWEP5G", prefix="EP5G"):
seed_serial = seed + serial
sha_dig = hashlib.sha256(seed_serial.encode('utf-8')).hexdigest()
pointer = int(sha_dig[0], 16)
twelve = sha_dig[pointer:pointer+12]
password = f"{prefix}!{twelve}"
return password
def reset_m2000_configuration(base_ipv6):
"""
Resets specified services on two hosts derived from a base IPv6 address.
"""
# List of services to be reset
services_to_reset = ["amf", "upf", "smf", "sgwc", "mme", "pcf"]
# Derive the two host IPs
# This assumes the base address ends with something like ':0' or '::'
base_parts = base_ipv6.rsplit(':', 1)
if len(base_parts) < 2:
raise ValueError("Invalid IPv6 address format for deriving hosts.")
base_prefix = base_parts[0]
host_a_ip = f"{base_prefix}:a"
host_b_ip = f"{base_prefix}:b"
hosts = [host_a_ip, host_b_ip]
results = []
for host in hosts:
try:
# Authenticate with the current host
token = auth_utils.authenticate(host)
for service in services_to_reset:
try:
# Construct the specific reset URL for each service
url = f"https://[{host}]/core/{service}/api/1/mgmt/config/factory_reset"
headers = {"Authorization": f"Bearer {token}"}
# Make the POST request to trigger the reset
response = requests.post(url, headers=headers, json={}, verify=False)
response.raise_for_status()
results.append({"host": host, "service": service, "status": "Success", "message": "Reset command sent successfully."})
except HTTPError as http_err:
results.append({"host": host, "service": service, "status": "Failed", "message": str(http_err)})
except Exception as e:
results.append({"host": host, "service": "N/A", "status": "Connection Failed", "message": str(e)})
return results