#!/usr/bin/env python3 """ Enroll and verify the unified Ed25519 automation SSH key across all target hosts. Uses base64-encoded payload for robust execution across bash/sh/dropbear. """ import sys import os import base64 import paramiko sys.stdout.reconfigure(encoding="utf-8") PUB_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOsRiwuL7nfm1Ruew+Xk+3tbzD9ONyKAO0q2QwkppEPZ snarfnet-automation-master@snarfnet.net" PRIV_KEY_PATH = os.path.expanduser("~/.ssh/id_ed25519_snarfnet") TARGETS = [ # Aerodrome (Debian 12 / K3s) ("aerodrome-ctl01", "140.44.4.11", "root", "Katzir476!"), ("aerodrome-ctl02", "140.44.4.12", "root", "Katzir476!"), ("aerodrome-ctl03", "140.44.4.13", "root", "Katzir476!"), ("aerodrome-wrk01", "140.44.4.21", "root", "Katzir476!"), ("aerodrome-wrk02", "140.44.4.22", "root", "Katzir476!"), ("aerodrome-wrk03", "140.44.4.23", "root", "Katzir476!"), ("aerodrome-wrk04", "140.44.4.24", "root", "Katzir476!"), ("aerodrome-wrk05", "140.44.4.25", "root", "Katzir476!"), ("aerodrome-wrk06", "140.44.4.26", "root", "Katzir476!"), ("aerodrome-wrk07", "140.44.4.27", "root", "Katzir476!"), # Spaceport (DietPi / Debian 12 / K3s) ("spaceport-ctl01", "140.44.4.41", "root", "dietpi"), ("spaceport-ctl02", "140.44.4.42", "root", "dietpi"), ("spaceport-ctl03", "140.44.4.43", "root", "dietpi"), ("spaceport-ctl04", "140.44.4.44", "root", "dietpi"), ("spaceport-wrk01", "140.44.4.51", "root", "dietpi"), ("spaceport-wrk02", "140.44.4.52", "root", "dietpi"), ("spaceport-wrk03", "140.44.4.53", "root", "dietpi"), ("spaceport-wrk04", "140.44.4.54", "root", "dietpi"), ("spaceport-wrk05", "140.44.4.55", "root", "Katzir476!"), ("spaceport-wrk06", "140.44.4.56", "root", "dietpi"), # Shipyard (Raspberry Pi OS / Docker Swarm) ("shipyard-swm01", "140.44.4.71", "snarf", "Katzir476!"), ("shipyard-swm02", "140.44.4.72", "snarf", "Katzir476!"), ("shipyard-swm03", "140.44.4.73", "snarf", "Katzir476!"), ("shipyard-swm04", "140.44.4.74", "snarf", "Katzir476!"), ("shipyard-swm05", "140.44.4.75", "snarf", "Katzir476!"), # AI & Monitoring hosts ("ai-ops01", "140.44.4.7", "snarf", "Katzir476!"), ("ai-web01", "140.44.4.8", "snarf", "Katzir476!"), ("nutserver", "14.4.4.44", "snarf", "Katzir476!"), ] SETUP_SCRIPT = f""" set -e PUB='{PUB_KEY}' # 1. Install for root mkdir -p /root/.ssh chmod 700 /root/.ssh touch /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys if ! grep -Fxq "$PUB" /root/.ssh/authorized_keys; then echo "$PUB" >> /root/.ssh/authorized_keys echo " [ROOT] Key appended to /root/.ssh/authorized_keys" else echo " [ROOT] Key already present in /root/.ssh/authorized_keys" fi # 2. Install for snarf if snarf user exists if id -u snarf >/dev/null 2>&1; then SNARF_HOME=$(eval echo ~snarf) mkdir -p "$SNARF_HOME/.ssh" chmod 700 "$SNARF_HOME/.ssh" touch "$SNARF_HOME/.ssh/authorized_keys" chmod 600 "$SNARF_HOME/.ssh/authorized_keys" chown -R snarf:snarf "$SNARF_HOME/.ssh" 2>/dev/null || chown -R snarf "$SNARF_HOME/.ssh" if ! grep -Fxq "$PUB" "$SNARF_HOME/.ssh/authorized_keys"; then echo "$PUB" >> "$SNARF_HOME/.ssh/authorized_keys" echo " [SNARF] Key appended to $SNARF_HOME/.ssh/authorized_keys" else echo " [SNARF] Key already present in $SNARF_HOME/.ssh/authorized_keys" fi # 3. Ensure passwordless sudo for snarf mkdir -p /etc/sudoers.d echo "snarf ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/90-snarfnet-automation.tmp chmod 440 /etc/sudoers.d/90-snarfnet-automation.tmp if visudo -c -f /etc/sudoers.d/90-snarfnet-automation.tmp >/dev/null 2>&1; then mv /etc/sudoers.d/90-snarfnet-automation.tmp /etc/sudoers.d/90-snarfnet-automation chmod 440 /etc/sudoers.d/90-snarfnet-automation echo " [SUDO] Passwordless sudo configured for snarf" else rm -f /etc/sudoers.d/90-snarfnet-automation.tmp echo " [SUDO] visudo check failed!" fi fi """ B64_PAYLOAD = base64.b64encode(SETUP_SCRIPT.encode('utf-8')).decode('ascii') def enroll_host(name, ip, user, pw): print(f"\n>>> Enrolling {name} ({ip}) via {user}...") try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(ip, username=user, password=pw, timeout=10, banner_timeout=15, auth_timeout=15, look_for_keys=False, allow_agent=False) if user == "root": cmd = f"echo '{B64_PAYLOAD}' | base64 -d | bash" else: cmd = f"echo '{pw}' | sudo -S -p '' bash -c \"echo '{B64_PAYLOAD}' | base64 -d | bash\"" stdin, stdout, stderr = ssh.exec_command(cmd, timeout=20) out_str = stdout.read().decode('utf-8', errors='ignore').strip() err_str = stderr.read().decode('utf-8', errors='ignore').strip() if out_str: print(out_str) if err_str: print(f" [STDERR] {err_str}") ssh.close() return True except Exception as e: print(f" [ENROLL ERROR] {e}") return False def verify_host(name, ip, user): k = paramiko.Ed25519Key.from_private_key_file(PRIV_KEY_PATH) try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(ip, username=user, pkey=k, timeout=8, banner_timeout=15, auth_timeout=15, look_for_keys=False, allow_agent=False) cmd = "whoami; hostname; uptime -p" _, out, _ = ssh.exec_command(cmd, timeout=10) resp = out.read().decode('utf-8', errors='ignore').strip().replace('\n', ' | ') print(f" [KEY VERIFIED] {user}@{ip} -> {resp}") # Test passwordless sudo if user != "root": _, out_sudo, _ = ssh.exec_command("sudo whoami", timeout=10) sudo_resp = out_sudo.read().decode('utf-8', errors='ignore').strip() print(f" [SUDO VERIFIED] sudo whoami -> {sudo_resp}") ssh.close() return True except Exception as e: print(f" [VERIFY FAILED] {user}@{ip}: {e}") return False def main(): print(f"==================================================") print(f"Snarfnet Unified SSH Key Enrollment & Verification") print(f"Key: {PUB_KEY}") print(f"==================================================") enrolled = 0 verified = 0 for name, ip, user, pw in TARGETS: if enroll_host(name, ip, user, pw): enrolled += 1 if verify_host(name, ip, user): verified += 1 print("\n==================================================") print(f"Enrollment Summary: {enrolled}/{len(TARGETS)} enrolled, {verified}/{len(TARGETS)} verified with Ed25519 key!") print("==================================================") if __name__ == "__main__": main()