Proxy set-up for CUBE
This commit is contained in:
@@ -5,6 +5,10 @@ from dotenv import load_dotenv
|
|||||||
import io # Make sure io is imported at the top of your script
|
import io # Make sure io is imported at the top of your script
|
||||||
import os
|
import os
|
||||||
from cube_activate_ssh import activate_ssh
|
from cube_activate_ssh import activate_ssh
|
||||||
|
from ruamel.yaml import YAML
|
||||||
|
from ruamel.yaml.scalarstring import DoubleQuotedScalarString
|
||||||
|
import shlex
|
||||||
|
import base64
|
||||||
|
|
||||||
def execute_command(c, command):
|
def execute_command(c, command):
|
||||||
"""Executes a simple command on the remote device."""
|
"""Executes a simple command on the remote device."""
|
||||||
@@ -45,7 +49,7 @@ def read_remote_config_sudo(c, remote_path, sudo_pass):
|
|||||||
print(f"Error reading remote file with sudo: {e}")
|
print(f"Error reading remote file with sudo: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
import shlex # Make sure to import shlex at the top of your script
|
# Make sure to import shlex at the top of your script
|
||||||
|
|
||||||
def write_remote_config_sudo(c, remote_path, content, sudo_pass, user_owner, group_owner, permissions):
|
def write_remote_config_sudo(c, remote_path, content, sudo_pass, user_owner, group_owner, permissions):
|
||||||
"""
|
"""
|
||||||
@@ -261,11 +265,187 @@ def parse_connection_string(connection_string):
|
|||||||
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
|
def find_yaml_value(yaml_content, key_path):
|
||||||
|
"""
|
||||||
|
Finds a value in a YAML string using a dot-separated path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
yaml_content (str): The string content of the YAML file.
|
||||||
|
key_path (str): A dot-separated path to the key (e.g., "cubeProcess.cyber_check").
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value if found, otherwise None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
yaml = YAML()
|
||||||
|
data = yaml.load(yaml_content)
|
||||||
|
|
||||||
|
# Traverse the path
|
||||||
|
keys = key_path.split('.')
|
||||||
|
current_level = data
|
||||||
|
for key in keys:
|
||||||
|
current_level = current_level[key]
|
||||||
|
|
||||||
|
return current_level
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
# KeyError if a key is not found, TypeError if trying to index a non-dict
|
||||||
|
# print(f"Warning: Key path '{key_path}' not found in YAML content.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_yaml_value(yaml_content, key_path, new_value):
|
||||||
|
"""
|
||||||
|
Sets a value in a YAML string using a dot-separated path.
|
||||||
|
Preserves comments, formatting, and quotes thanks to ruamel.yaml.
|
||||||
|
This version correctly traverses nested keys.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
yaml_content (str): The string content of the YAML file.
|
||||||
|
key_path (str): A dot-separated path to the key (e.g., "cubeProcess.cyber_check").
|
||||||
|
new_value: The new value to set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The modified YAML content as a string, or the original content on error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# --- FIX 1: Configure the YAML object to preserve quotes ---
|
||||||
|
yaml = YAML()
|
||||||
|
yaml.preserve_quotes = True
|
||||||
|
yaml.indent(mapping=2, sequence=4, offset=2) # Optional: ensures consistent indentation
|
||||||
|
|
||||||
|
data = yaml.load(yaml_content)
|
||||||
|
|
||||||
|
# --- FIX 2: Correct traversal logic ---
|
||||||
|
keys = key_path.split('.')
|
||||||
|
current_level = data
|
||||||
|
|
||||||
|
# Traverse down to the final key's parent dictionary
|
||||||
|
for key in keys[:-1]:
|
||||||
|
current_level = current_level[key]
|
||||||
|
|
||||||
|
final_key = keys[-1]
|
||||||
|
|
||||||
|
# Check if the key exists before setting it
|
||||||
|
if final_key not in current_level:
|
||||||
|
print(f"❌ Error: Final key '{final_key}' not found in the structure. Aborting.")
|
||||||
|
return yaml_content # Return original content
|
||||||
|
|
||||||
|
# Set the new value
|
||||||
|
current_level[final_key] = new_value
|
||||||
|
|
||||||
|
# Dump the modified data back to a string
|
||||||
|
string_stream = io.StringIO()
|
||||||
|
yaml.dump(data, string_stream)
|
||||||
|
return string_stream.getvalue()
|
||||||
|
|
||||||
|
except (KeyError, TypeError) as e:
|
||||||
|
print(f"❌ Error: Key path '{key_path}' is invalid or part of the path does not exist. Error: {e}")
|
||||||
|
return yaml_content # Return original content on failure
|
||||||
|
def ensure_iptables_port_rule(config_content, target_port, template_port):
|
||||||
|
"""
|
||||||
|
Ensures that iptables rules for a target port exist in the configuration.
|
||||||
|
|
||||||
|
If rules for the target port are not found, it finds rules for a
|
||||||
|
template port and replaces the port number.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_content (str): The multi-line string of the iptables rules file.
|
||||||
|
target_port (int or str): The port number that should exist (e.g., 8080).
|
||||||
|
template_port (int or str): The port number to use as a template (e.g., 443).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The modified (or original) configuration content.
|
||||||
|
"""
|
||||||
|
target_port_str = str(target_port)
|
||||||
|
template_port_str = str(template_port)
|
||||||
|
lines = config_content.splitlines()
|
||||||
|
|
||||||
|
target_port_found = False
|
||||||
|
|
||||||
|
# --- PASS 1: Check if the target port rule already exists ---
|
||||||
|
for line in lines:
|
||||||
|
# Check for the target port in an active rule line
|
||||||
|
# The spaces around the port string prevent accidentally matching '8080' in '18080'
|
||||||
|
if line.strip().startswith('-A') and (f"--dport {target_port_str}" in line or f"--sport {target_port_str}" in line):
|
||||||
|
print(f"✅ Info: Rule for target port {target_port_str} already exists. No changes needed.")
|
||||||
|
target_port_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# If the rule was found, return the original content without any changes.
|
||||||
|
if target_port_found:
|
||||||
|
return config_content
|
||||||
|
|
||||||
|
# --- PASS 2: If we get here, the rule was not found. We must replace the template. ---
|
||||||
|
print(f"Info: Rule for target port {target_port_str} not found. Searching for template port {template_port_str} to replace.")
|
||||||
|
|
||||||
|
new_lines = []
|
||||||
|
changes_made = False
|
||||||
|
for line in lines:
|
||||||
|
# Check for the template port in an active rule line
|
||||||
|
if line.strip().startswith('-A') and (f"--dport {template_port_str}" in line or f"--sport {template_port_str}" in line):
|
||||||
|
# This is a line we need to modify
|
||||||
|
modified_line = line.replace(template_port_str, target_port_str)
|
||||||
|
new_lines.append(modified_line)
|
||||||
|
print(f" - Replacing: '{line}'")
|
||||||
|
print(f" + With: '{modified_line}'")
|
||||||
|
changes_made = True
|
||||||
|
else:
|
||||||
|
# This line doesn't need changing, add it as is.
|
||||||
|
new_lines.append(line)
|
||||||
|
|
||||||
|
if not changes_made:
|
||||||
|
print(f"❌ Warning: Target port {target_port_str} was not found, AND template port {template_port_str} was also not found. No changes made.")
|
||||||
|
return config_content # Return original if template wasn't found either
|
||||||
|
|
||||||
|
return "\n".join(new_lines)
|
||||||
|
|
||||||
|
def write_remote_config_base64_sudo(c, remote_path, content, sudo_pass, user_owner, group_owner, permissions):
|
||||||
|
"""
|
||||||
|
Writes content directly to a remote file by passing it as a Base64 string.
|
||||||
|
|
||||||
|
This is the most robust method for no-SFTP environments, as it completely
|
||||||
|
avoids all shell quoting and parsing issues for complex, multi-line content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
c (fabric.Connection): The active connection object.
|
||||||
|
remote_path (str): The absolute path to the file on the remote host.
|
||||||
|
content (str): The string content to be written to the file.
|
||||||
|
sudo_pass (str): The sudo password for the write operation.
|
||||||
|
"""
|
||||||
|
print(f"\n--- [{c.host}] Writing content via Base64 to: {remote_path} ---")
|
||||||
|
try:
|
||||||
|
# Step 1: Encode the string content into Base64.
|
||||||
|
# base64.b64encode requires bytes, so we encode the string to utf-8.
|
||||||
|
# The result is bytes, so we decode it back to a simple ascii string to use in our command.
|
||||||
|
base64_content = base64.b64encode(content.encode('utf-8')).decode('ascii')
|
||||||
|
|
||||||
|
# Step 2: Construct the command.
|
||||||
|
# 'echo ... | base64 --decode > file': This pipeline decodes the content
|
||||||
|
# and redirects the output to the destination file.
|
||||||
|
# We wrap the entire pipeline in 'sudo sh -c "..."' so that the
|
||||||
|
# redirection ('>') is performed by a shell running as root.
|
||||||
|
command = f"sh -c \"echo '{base64_content}' | base64 --decode > {remote_path}\""
|
||||||
|
|
||||||
|
print("Step 1: Writing Base64 content via root shell...")
|
||||||
|
c.sudo(command, password=sudo_pass, hide=True)
|
||||||
|
|
||||||
|
# Step 3: Set ownership and permissions.
|
||||||
|
print("Step 2: Setting ownership and permissions...")
|
||||||
|
c.sudo(f"chown {user_owner}:{group_owner} {remote_path}", password=sudo_pass)
|
||||||
|
c.sudo(f"chmod {permissions} {remote_path}", password=sudo_pass)
|
||||||
|
|
||||||
|
print(f"✅ Successfully wrote content to {remote_path}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error writing Base64 content to remote file: {e}")
|
||||||
|
# Re-raise the exception for the main loop.
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main function to parse arguments and orchestrate tasks."""
|
"""Main function to parse arguments and orchestrate tasks."""
|
||||||
ip_address_prefix = "10.81.35." # Carling subnet
|
ip_address_prefix = "10.81.35." # Carling subnet
|
||||||
ip_address_range = [] # list(range(65, 75)) # From 65 to 74
|
ip_address_range = list(range(68, 75)) # Fronm 65 to 74
|
||||||
ip_address_range.append(66) #(85) # Add 85 after 74.
|
ip_address_range.append(85) # Add 85 after 74.
|
||||||
hosts = [f"{ip_address_prefix}{suffix}" for suffix in ip_address_range]
|
hosts = [f"{ip_address_prefix}{suffix}" for suffix in ip_address_range]
|
||||||
|
|
||||||
ssh_port = 11022
|
ssh_port = 11022
|
||||||
@@ -305,7 +485,7 @@ def main():
|
|||||||
print(f"Checking Cloud configuration:", end=" ", flush=True)
|
print(f"Checking Cloud configuration:", end=" ", flush=True)
|
||||||
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
|
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
|
||||||
print(f"✅", end="\n", flush=True)
|
print(f"✅", end="\n", flush=True)
|
||||||
except:
|
except Exception as e:
|
||||||
print(f"❌", end="\n", flush=True)
|
print(f"❌", end="\n", flush=True)
|
||||||
print(f"[Cloud configuration check] Exception: {e}")
|
print(f"[Cloud configuration check] Exception: {e}")
|
||||||
continue
|
continue
|
||||||
@@ -318,7 +498,7 @@ def main():
|
|||||||
result = result_proxy_host_port
|
result = result_proxy_host_port
|
||||||
cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080")
|
cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080")
|
||||||
|
|
||||||
response = input(f"Apply the change on {hostname.strip()}? (y)es to apply, anything else to cancel - ").lower()
|
response = input(f"Apply the change on {hostname.strip()}? (y)es or (n)o, anything else to cancel - ").lower()
|
||||||
if response in ['y']:
|
if response in ['y']:
|
||||||
print(f"Applying changes:", end=" ", flush=True)
|
print(f"Applying changes:", end=" ", flush=True)
|
||||||
try:
|
try:
|
||||||
@@ -332,15 +512,141 @@ def main():
|
|||||||
try:
|
try:
|
||||||
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
|
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
|
||||||
print(f"✅", end="\n", flush=True)
|
print(f"✅", end="\n", flush=True)
|
||||||
except:
|
except Exception as e:
|
||||||
print(f"❌", end="\n", flush=True)
|
print(f"❌", end="\n", flush=True)
|
||||||
print(f"[Proxy verification] Exception: {e}")
|
print(f"[Proxy verification] Exception: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080")
|
cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080")
|
||||||
|
elif response in ['n']:
|
||||||
|
print(f"Not applying configuration...")
|
||||||
else:
|
else:
|
||||||
print(f"Not applying configuration...")
|
print(f"Not applying configuration...")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
print(f"Disabling Cyber Check:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
execute_sudo_command(c, "systemctl stop cube-monit.service", ssh_password)
|
||||||
|
execute_sudo_command(c, "mount -o remount,rw /", ssh_password)
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[Disabling Cyber Check] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Reading Cyber Check configuration:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
result = read_remote_config_sudo(c, "/etc/cube-default/configfile_monit.yaml", ssh_password)
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[Cyber Check configuration] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Checking cyber_check:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
status = find_yaml_value(result, "cubeProcess.cyber_check")
|
||||||
|
if status == False:
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
else:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[cyber_check value] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Modifying cyber_check:", end=" ", flush=True)
|
||||||
|
modified_result = ""
|
||||||
|
try:
|
||||||
|
modified_result = set_yaml_value(result, "cubeProcess.cyber_check", False)
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[cyber_check modification] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Checking modified cyber_check:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
status = find_yaml_value(modified_result, "cubeProcess.cyber_check")
|
||||||
|
if status == False:
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
else:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[Modified cyber_check value] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
response = input(f"Apply the change on {hostname.strip()}? (y)es or (n)o, anything else to cancel - ").lower()
|
||||||
|
if response in ['y']:
|
||||||
|
print(f"Applying changes:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
write_remote_config_base64_sudo(c, "/etc/cube-default/configfile_monit.yaml", modified_result, ssh_password, "root", "root", "644")
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[cyber_check configuration] Exception: {e}")
|
||||||
|
continue
|
||||||
|
print(f"Checking cyber_check configuration:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
result = read_remote_config_sudo(c, "/etc/cube-default/configfile_monit.yaml", ssh_password)
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[cyber_check configuration] Exception: {e}")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
status = find_yaml_value(result, "cubeProcess.cyber_check")
|
||||||
|
if status == False:
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
else:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[Modified cyber_check configuration verification] Exception: {e}")
|
||||||
|
continue
|
||||||
|
elif response in ['n']:
|
||||||
|
print(f"Not applying configuration...")
|
||||||
|
else:
|
||||||
|
print(f"Not applying configuration...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
print(f"Firewall check:", end="\n", flush=True)
|
||||||
|
modified_result = ""
|
||||||
|
try:
|
||||||
|
result = read_remote_config_sudo(c, "/etc/iptables/iptables-cube.rules", ssh_password)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Firewall reading] Exception: {e}")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
modified_result = ensure_iptables_port_rule(result, 8080, 443)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Firewall changes] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
response = input(f"Apply the change on {hostname.strip()}? (y)es or (n)o, anything else to cancel - ").lower()
|
||||||
|
if response in ['y']:
|
||||||
|
try:
|
||||||
|
write_remote_config_base64_sudo(c, "/etc/iptables/iptables-cube.rules", modified_result, ssh_password, "root", "root", 600)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Firewall configuration] Exception: {e}")
|
||||||
|
continue
|
||||||
|
elif response in ['n']:
|
||||||
|
print(f"Not applying configuration...")
|
||||||
|
else:
|
||||||
|
print(f"Not applying configuration...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Restarting Cyber Check:", end=" ", flush=True)
|
||||||
|
try:
|
||||||
|
execute_sudo_command(c, "mount -o remount,ro /", ssh_password)
|
||||||
|
execute_sudo_command(c, "systemctl start cube-monit.service", ssh_password)
|
||||||
|
print(f"✅", end="\n", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌", end="\n", flush=True)
|
||||||
|
print(f"[Restarting Cyber Check] Exception: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user