feat: add infrastructure automation scripts and master improvement plan
This commit is contained in:
169
scripts/enroll_unified_ssh_key.py
Normal file
169
scripts/enroll_unified_ssh_key.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user