Add interactive credential prompting and fix Home Assistant SSH password authentication

- Added interactive username/password prompts to cert-manager.py
- Removed requirement for SSH_USER environment variable prefix
- Fixed password authentication in deploy-homeassistant.sh using SSHPASS environment variable
- Added SSH rate limiting delays throughout deployment script
- Improved error handling with SSH connection testing
- Prioritized SSH_USER in detect-system.sh to avoid unnecessary root attempts
- Added StrictHostKeyChecking=no for automated deployments

Tool now works fully interactively - just run ./cert-manager.py and answer prompts
This commit is contained in:
root
2025-12-12 15:38:41 +01:00
parent 823c6a9056
commit 296948f07e
6 changed files with 698 additions and 44 deletions

View File

@@ -8,6 +8,7 @@ import os
import sys
import json
import subprocess
import getpass
from pathlib import Path
# Configuration
@@ -30,31 +31,44 @@ SYSTEM_TYPES = {
'name': 'Proxmox VE',
'deploy_script': 'deploy-proxmox.sh',
'key_location': '/tmp/{short_name}.key',
'default_port': '8006'
'default_port': '8006',
'ssh_user': 'root'
},
'homeassistant': {
'name': 'Home Assistant',
'deploy_script': 'deploy-homeassistant.sh',
'key_location': '/tmp/{short_name}.key',
'default_port': '8123',
'ssh_user': 'icke',
'uses_local_csr': True
},
'pfsense': {
'name': 'pfSense',
'deploy_script': None, # Manual deployment via web interface
'key_location': '/tmp/{short_name}.key',
'default_port': '443'
'default_port': '443',
'ssh_user': 'root'
},
'truenas': {
'name': 'TrueNAS',
'deploy_script': None, # Manual deployment via web interface
'key_location': '/tmp/{short_name}.key',
'default_port': '443'
'default_port': '443',
'ssh_user': 'root'
},
'ucs': {
'name': 'Univention Corporate Server',
'deploy_script': None,
'key_location': '/tmp/{short_name}.key',
'default_port': '443'
'default_port': '443',
'ssh_user': 'root'
},
'unknown': {
'name': 'Unknown System',
'deploy_script': None,
'key_location': '/tmp/{short_name}.key',
'default_port': '443'
'default_port': '443',
'ssh_user': 'root'
}
}
@@ -98,39 +112,51 @@ def yes_no_prompt(prompt, default=True):
return False
print("Please answer 'y' or 'n'")
def detect_system_type(target_host, script_dir):
def detect_system_type(target_host, script_dir, ssh_user=None, ssh_password=None):
"""Detect the type of system on the target host"""
try:
detect_script = script_dir / 'detect-system.sh'
if not detect_script.exists():
print(f"Warning: detect-system.sh not found at {detect_script}")
return 'unknown'
return 'unknown', None
env = os.environ.copy()
if ssh_user:
env['SSH_USER'] = ssh_user
if ssh_password:
env['SSH_PASSWORD'] = ssh_password
result = subprocess.run(
[str(detect_script), target_host],
capture_output=True,
text=True,
timeout=15
timeout=15,
env=env
)
if result.returncode != 0:
print(f"Warning: Detection script returned error code {result.returncode}")
if result.stderr:
print(f"Error output: {result.stderr}")
return 'unknown'
return 'unknown', None
system_type = result.stdout.strip()
if not system_type:
print("Warning: Detection script returned empty output")
return 'unknown'
return 'unknown', None
# Extract SSH user if detected
detected_user = None
if ':' in system_type:
system_type, detected_user = system_type.split(':', 1)
return system_type if system_type in SYSTEM_TYPES else 'unknown'
return (system_type if system_type in SYSTEM_TYPES else 'unknown'), detected_user
except subprocess.TimeoutExpired:
print(f"Warning: System detection timed out for {target_host}")
return 'unknown'
return 'unknown', None
except Exception as e:
print(f"Warning: Could not detect system type: {e}")
return 'unknown'
return 'unknown', None
def main():
print("=" * 60)
@@ -166,11 +192,36 @@ def main():
# Get script directory
script_dir = Path(__file__).parent.absolute()
# Detect system type
# Detect system type (try key-based auth first)
print(f"\nDetecting system type on {target_host}...")
system_type = detect_system_type(target_host, script_dir)
print("Trying SSH key-based authentication...")
system_type, detected_user = detect_system_type(target_host, script_dir)
# If detection failed, ask for credentials
ssh_user = None
ssh_password = None
if system_type == 'unknown':
print("\nKey-based authentication failed or system not detected.")
if yes_no_prompt("Do you want to try with username/password?", True):
ssh_user = input("SSH Username: ").strip()
import getpass
ssh_password = getpass.getpass("SSH Password: ")
print(f"\nRetrying detection with credentials...")
system_type, detected_user = detect_system_type(target_host, script_dir, ssh_user, ssh_password)
# Use detected user or ask for one
if detected_user:
ssh_user = detected_user
elif not ssh_user and system_type != 'unknown':
# Ask for SSH user if not detected
default_user = SYSTEM_TYPES[system_type].get('ssh_user', 'root')
ssh_user = prompt_with_default("SSH Username", default_user)
system_info = SYSTEM_TYPES[system_type]
print(f"✓ Detected: {system_info['name']}")
if ssh_user:
print(f" SSH User: {ssh_user}")
common_name = prompt_with_default("Common Name (FQDN)", config['last_common_name'])
@@ -226,25 +277,59 @@ def main():
save_config(config)
print("\n" + "=" * 60)
print("Step 1: Generating CSR on target host")
print("Step 1: Generating CSR")
print("=" * 60)
# Run generate-csr.sh
generate_cmd = [
str(script_dir / 'generate-csr.sh'),
target_host,
common_name,
country,
state,
locality,
organization,
org_unit,
key_bits,
additional_dns
]
# Check if system requires local CSR generation
if system_info.get('uses_local_csr', False):
# Generate CSR locally for Home Assistant and similar systems
# Get target IP addresses
print()
target_ip = prompt_with_default("Target IP address(es) (comma-separated)", "172.20.70.10,172.20.20.10")
generate_cmd = [
str(script_dir / 'generate-csr-local.sh'),
common_name,
country,
state,
locality,
organization,
org_unit,
key_bits,
additional_dns,
target_ip
]
else:
# Set environment for remote SSH commands
env = os.environ.copy()
if ssh_user:
env['SSH_USER'] = ssh_user
if ssh_password:
env['SSH_PASSWORD'] = ssh_password
# Run generate-csr.sh for remote CSR generation
generate_cmd = [
str(script_dir / 'generate-csr.sh'),
target_host,
common_name,
country,
state,
locality,
organization,
org_unit,
key_bits,
additional_dns
]
try:
result = subprocess.run(generate_cmd, check=True)
# Set environment variables for all subprocess calls
env = os.environ.copy()
if ssh_user:
env['SSH_USER'] = ssh_user
if ssh_password:
env['SSH_PASSWORD'] = ssh_password
result = subprocess.run(generate_cmd, check=True, env=env)
except subprocess.CalledProcessError as e:
print(f"\nError: CSR generation failed with exit code {e.returncode}")
sys.exit(1)
@@ -284,7 +369,19 @@ def main():
if system_info['deploy_script']:
if yes_no_prompt(f"Deploy certificate to {system_info['name']} automatically?", True):
deploy_script = script_dir / system_info['deploy_script']
key_file = f"/tmp/{short_name}.key" # Key is on remote host
# For local CSR generation, key file is local
if system_info.get('uses_local_csr', False):
key_file = f"{short_name}.key" # Key is local
else:
key_file = f"/tmp/{short_name}.key" # Key is on remote host
# Set SSH_USER and SSH_PASSWORD environment variables if needed
env = os.environ.copy()
if ssh_user:
env['SSH_USER'] = ssh_user
if ssh_password:
env['SSH_PASSWORD'] = ssh_password
deploy_cmd = [
str(deploy_script),
@@ -295,7 +392,7 @@ def main():
]
try:
subprocess.run(deploy_cmd, check=True)
subprocess.run(deploy_cmd, check=True, env=env)
except subprocess.CalledProcessError as e:
print(f"\nError: Deployment failed with exit code {e.returncode}")
sys.exit(1)