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:
161
cert-manager.py
161
cert-manager.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user