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 os
|
||||
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):
|
||||
"""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}")
|
||||
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):
|
||||
"""
|
||||
@@ -261,11 +265,187 @@ def parse_connection_string(connection_string):
|
||||
|
||||
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():
|
||||
"""Main function to parse arguments and orchestrate tasks."""
|
||||
ip_address_prefix = "10.81.35." # Carling subnet
|
||||
ip_address_range = [] # list(range(65, 75)) # From 65 to 74
|
||||
ip_address_range.append(66) #(85) # Add 85 after 74.
|
||||
ip_address_range = list(range(68, 75)) # Fronm 65 to 74
|
||||
ip_address_range.append(85) # Add 85 after 74.
|
||||
hosts = [f"{ip_address_prefix}{suffix}" for suffix in ip_address_range]
|
||||
|
||||
ssh_port = 11022
|
||||
@@ -305,7 +485,7 @@ def main():
|
||||
print(f"Checking Cloud configuration:", end=" ", flush=True)
|
||||
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
|
||||
print(f"✅", end="\n", flush=True)
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"❌", end="\n", flush=True)
|
||||
print(f"[Cloud configuration check] Exception: {e}")
|
||||
continue
|
||||
@@ -318,7 +498,7 @@ def main():
|
||||
result = result_proxy_host_port
|
||||
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']:
|
||||
print(f"Applying changes:", end=" ", flush=True)
|
||||
try:
|
||||
@@ -332,15 +512,141 @@ def main():
|
||||
try:
|
||||
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
|
||||
print(f"✅", end="\n", flush=True)
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"❌", end="\n", flush=True)
|
||||
print(f"[Proxy verification] Exception: {e}")
|
||||
continue
|
||||
|
||||
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:
|
||||
print(f"Not applying configuration...")
|
||||
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__":
|
||||
main()
|
||||
Reference in New Issue
Block a user