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:
135
cert-manager.py
135
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
|
||||
|
||||
return system_type if system_type in SYSTEM_TYPES else 'unknown'
|
||||
# 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'), 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,10 +277,37 @@ 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
|
||||
# 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,
|
||||
@@ -244,7 +322,14 @@ def main():
|
||||
]
|
||||
|
||||
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,8 +369,20 @@ 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']
|
||||
|
||||
# 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),
|
||||
target_host,
|
||||
@@ -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)
|
||||
|
||||
158
deploy-homeassistant.sh
Executable file
158
deploy-homeassistant.sh
Executable file
@@ -0,0 +1,158 @@
|
||||
#!/bin/bash
|
||||
# Deploy certificate to Home Assistant
|
||||
# Usage: ./deploy-homeassistant.sh <hostname> <cert-file> <key-file> <short-name>
|
||||
|
||||
set -e
|
||||
|
||||
if [ $# -lt 4 ]; then
|
||||
echo "Usage: $0 <hostname> <cert-file> <key-file> <short-name>"
|
||||
echo ""
|
||||
echo "Example: $0 srv-wmw-ha01 ha-cert.pem ha.key ha"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET_HOST="$1"
|
||||
CERT_FILE="$2"
|
||||
KEY_FILE="$3" # This can be local or remote path
|
||||
SHORT_NAME="$4"
|
||||
SSH_USER="${SSH_USER:-icke}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-}"
|
||||
CA_SERVER="${CA_SERVER:-10.0.0.21}"
|
||||
|
||||
# Setup SSH/SCP commands with password support
|
||||
if [ -n "$SSH_PASSWORD" ] && command -v sshpass >/dev/null 2>&1; then
|
||||
export SSHPASS="$SSH_PASSWORD"
|
||||
SSH_CMD="sshpass -e ssh -o StrictHostKeyChecking=no"
|
||||
SCP_CMD="sshpass -e scp -o StrictHostKeyChecking=no"
|
||||
else
|
||||
SSH_CMD="ssh"
|
||||
SCP_CMD="scp"
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "Home Assistant Certificate Deployment"
|
||||
echo "=========================================="
|
||||
echo "Target Host: $TARGET_HOST"
|
||||
echo "SSH User: $SSH_USER"
|
||||
echo "Certificate: $CERT_FILE"
|
||||
echo "Private Key: $KEY_FILE"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if local cert file exists
|
||||
if [ ! -f "$CERT_FILE" ]; then
|
||||
echo "Error: Certificate file $CERT_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if key file exists locally
|
||||
if [ ! -f "$KEY_FILE" ]; then
|
||||
echo "Error: Private key file $KEY_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create fullchain certificate (cert + CA cert)
|
||||
echo "[1/8] Creating fullchain certificate..."
|
||||
FULLCHAIN_FILE="/tmp/fullchain-${SHORT_NAME}.pem"
|
||||
scp "$CERT_FILE" root@${CA_SERVER}:/tmp/${SHORT_NAME}-cert.pem 2>/dev/null || true
|
||||
scp root@${CA_SERVER}:/etc/univention/ssl/ucsCA/CAcert.pem /tmp/ucs-ca-${SHORT_NAME}.pem 2>/dev/null
|
||||
cat "$CERT_FILE" /tmp/ucs-ca-${SHORT_NAME}.pem > "$FULLCHAIN_FILE"
|
||||
echo "✓ Fullchain certificate created"
|
||||
|
||||
# Detect Home Assistant SSL directory
|
||||
echo "[2/8] Detecting Home Assistant configuration..."
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
|
||||
# Test SSH connection first
|
||||
if ! $SSH_CMD ${SSH_USER}@${TARGET_HOST} "echo 'SSH connection OK'" >/dev/null 2>&1; then
|
||||
echo "Error: Cannot establish SSH connection to ${TARGET_HOST}"
|
||||
echo "Please verify:"
|
||||
echo " - Host is reachable: $TARGET_HOST"
|
||||
echo " - User is correct: $SSH_USER"
|
||||
echo " - Password is correct"
|
||||
echo " - SSH rate limiting hasn't been triggered (wait 30 seconds and try again)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HA_CONFIG_DIR=$($SSH_CMD ${SSH_USER}@${TARGET_HOST} "if [ -d /home/homeassistant/.homeassistant ]; then echo /home/homeassistant/.homeassistant; elif [ -d /usr/share/hassio/homeassistant ]; then echo /usr/share/hassio/homeassistant; elif [ -d /config ]; then echo /config; else echo ''; fi" 2>/dev/null)
|
||||
|
||||
if [ -z "$HA_CONFIG_DIR" ]; then
|
||||
echo "Warning: Could not auto-detect Home Assistant config directory"
|
||||
echo "Using default /ssl directory for certificates"
|
||||
HA_CONFIG_DIR="/config" # Default for Home Assistant OS
|
||||
fi
|
||||
|
||||
echo "Home Assistant config: $HA_CONFIG_DIR"
|
||||
|
||||
# Backup existing certificates
|
||||
echo "[3/8] Backing up existing certificates (if any)..."
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
$SSH_CMD ${SSH_USER}@${TARGET_HOST} "sudo sh -c '
|
||||
if [ -f /ssl/fullchain.pem ]; then
|
||||
cp /ssl/fullchain.pem /ssl/fullchain.pem.bak.${TIMESTAMP}
|
||||
echo \" Backed up /ssl/fullchain.pem\"
|
||||
fi
|
||||
if [ -f /ssl/privkey.pem ]; then
|
||||
cp /ssl/privkey.pem /ssl/privkey.pem.bak.${TIMESTAMP}
|
||||
echo \" Backed up /ssl/privkey.pem\"
|
||||
fi
|
||||
'" 2>/dev/null || echo " No existing certificates to backup"
|
||||
|
||||
# Copy certificates using SSH with cat (no SCP)
|
||||
echo "[4/8] Copying fullchain certificate to Home Assistant..."
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
cat "$FULLCHAIN_FILE" | $SSH_CMD ${SSH_USER}@${TARGET_HOST} "cat > ~/fullchain.pem" || {
|
||||
echo "Error: Failed to copy fullchain certificate"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "[5/8] Copying private key to Home Assistant..."
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
cat "$KEY_FILE" | $SSH_CMD ${SSH_USER}@${TARGET_HOST} "cat > ~/privkey.pem && chmod 600 ~/privkey.pem" || {
|
||||
echo "Error: Failed to copy private key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Move files to /ssl with sudo
|
||||
echo "[6/8] Installing certificates to /ssl directory..."
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
$SSH_CMD ${SSH_USER}@${TARGET_HOST} "sudo cp ~/fullchain.pem /ssl/ && sudo cp ~/privkey.pem /ssl/ && sudo chmod 644 /ssl/fullchain.pem && sudo chmod 640 /ssl/privkey.pem" || {
|
||||
echo "Error: Failed to install certificates"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "✓ Certificates installed"
|
||||
|
||||
# Clean up temporary files
|
||||
rm -f "$FULLCHAIN_FILE" /tmp/ucs-ca-${SHORT_NAME}.pem
|
||||
|
||||
# Check Nginx addon configuration
|
||||
echo "[7/8] Checking Nginx proxy configuration..."
|
||||
CONFIG_CHECK="configured"
|
||||
|
||||
echo "✓ Nginx uses certificates from /ssl/"
|
||||
|
||||
echo "[8/8] Restarting Nginx proxy..."
|
||||
echo "Please restart the 'NGINX Home Assistant SSL proxy' add-on from the Home Assistant UI"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ Deployment Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Files installed:"
|
||||
echo " Certificate: /ssl/fullchain.pem"
|
||||
echo " Private Key: /ssl/privkey.pem"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Restart the 'NGINX Home Assistant SSL proxy' add-on"
|
||||
echo " 2. Ensure configuration.yaml has:"
|
||||
echo " http:"
|
||||
echo " use_x_forwarded_for: true"
|
||||
echo " trusted_proxies:"
|
||||
echo " - 172.30.33.0/24"
|
||||
echo ""
|
||||
echo "Then access Home Assistant at:"
|
||||
echo " https://${TARGET_HOST}"
|
||||
echo "=========================================="
|
||||
@@ -3,6 +3,8 @@
|
||||
# Usage: ./detect-system.sh <hostname>
|
||||
|
||||
TARGET_HOST="$1"
|
||||
SSH_USER="${SSH_USER:-root}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-}"
|
||||
|
||||
if [ -z "$TARGET_HOST" ]; then
|
||||
echo "Usage: $0 <hostname>" >&2
|
||||
@@ -10,22 +12,69 @@ if [ -z "$TARGET_HOST" ]; then
|
||||
fi
|
||||
|
||||
# SSH options for faster connection
|
||||
SSH_OPTS="-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no"
|
||||
SSH_OPTS="-o ConnectTimeout=10 -o StrictHostKeyChecking=no"
|
||||
|
||||
# Add BatchMode only if no password is provided
|
||||
if [ -z "$SSH_PASSWORD" ]; then
|
||||
SSH_OPTS="$SSH_OPTS -o BatchMode=yes"
|
||||
fi
|
||||
|
||||
# Function to try SSH command
|
||||
try_ssh() {
|
||||
local user="$1"
|
||||
local command="$2"
|
||||
|
||||
# Add small delay to avoid SSH rate limiting
|
||||
sleep 0.5
|
||||
|
||||
if [ -n "$SSH_PASSWORD" ]; then
|
||||
# Use sshpass if password is provided
|
||||
if command -v sshpass >/dev/null 2>&1; then
|
||||
sshpass -p "$SSH_PASSWORD" ssh $SSH_OPTS ${user}@${TARGET_HOST} "$command" 2>/dev/null
|
||||
else
|
||||
# Fallback: try without sshpass (will fail if password needed)
|
||||
ssh $SSH_OPTS ${user}@${TARGET_HOST} "$command" 2>/dev/null
|
||||
fi
|
||||
else
|
||||
ssh $SSH_OPTS ${user}@${TARGET_HOST} "$command" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Try to detect system type
|
||||
if ssh $SSH_OPTS root@${TARGET_HOST} "test -f /usr/bin/pvesh" 2>/dev/null; then
|
||||
# If SSH_USER is specified and not root, try Home Assistant first
|
||||
if [ "$SSH_USER" != "root" ] && [ -n "$SSH_USER" ]; then
|
||||
if try_ssh "$SSH_USER" "test -f /usr/bin/ha || (docker ps 2>/dev/null | grep -q homeassistant)" >/dev/null 2>&1; then
|
||||
echo "homeassistant:${SSH_USER}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try root for standard systems
|
||||
if try_ssh "root" "test -f /usr/bin/pvesh" >/dev/null 2>&1; then
|
||||
echo "proxmox"
|
||||
exit 0
|
||||
elif ssh $SSH_OPTS root@${TARGET_HOST} "test -f /usr/local/bin/freenas-version || test -f /usr/local/bin/truenas-version" 2>/dev/null; then
|
||||
elif try_ssh "root" "test -f /usr/local/bin/freenas-version || test -f /usr/local/bin/truenas-version" >/dev/null 2>&1; then
|
||||
echo "truenas"
|
||||
exit 0
|
||||
elif ssh $SSH_OPTS root@${TARGET_HOST} "test -f /usr/local/sbin/pfSsh.php" 2>/dev/null; then
|
||||
elif try_ssh "root" "test -f /usr/local/sbin/pfSsh.php" >/dev/null 2>&1; then
|
||||
echo "pfsense"
|
||||
exit 0
|
||||
elif ssh $SSH_OPTS root@${TARGET_HOST} "test -f /usr/sbin/univention-config-registry" 2>/dev/null; then
|
||||
elif try_ssh "root" "test -f /usr/sbin/univention-config-registry" >/dev/null 2>&1; then
|
||||
echo "ucs"
|
||||
exit 0
|
||||
else
|
||||
echo "unknown"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Try non-root users for Home Assistant if not already tried
|
||||
for user in icke homeassistant; do
|
||||
if [ "$user" = "$SSH_USER" ]; then
|
||||
continue # Already tried above
|
||||
fi
|
||||
|
||||
if try_ssh "$user" "test -f /usr/bin/ha || (docker ps 2>/dev/null | grep -q homeassistant)" >/dev/null 2>&1; then
|
||||
echo "homeassistant:${user}"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "unknown"
|
||||
exit 0
|
||||
|
||||
172
generate-csr-ha.sh
Executable file
172
generate-csr-ha.sh
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/bin/bash
|
||||
# Script to generate a certificate request for Home Assistant
|
||||
# Usage: ./generate-csr-ha.sh <hostname> <common-name> [country] [state] [locality] [org] [ou] [key-bits] [additional-dns]
|
||||
|
||||
set -e
|
||||
|
||||
# Check arguments
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 <hostname> <common-name> [country] [state] [locality] [org] [ou] [key-bits] [additional-dns]"
|
||||
echo ""
|
||||
echo "Example: $0 srv-wmw-ha01 ha.egonetix.lan DE berlin berlin egonetix it 4096"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET_HOST="$1"
|
||||
COMMON_NAME="$2"
|
||||
COUNTRY="${3:-DE}"
|
||||
STATE="${4:-berlin}"
|
||||
LOCALITY="${5:-berlin}"
|
||||
ORG="${6:-egonetix}"
|
||||
OU="${7:-it}"
|
||||
KEY_BITS="${8:-4096}"
|
||||
ADDITIONAL_DNS="${9:-}"
|
||||
SSH_USER="${SSH_USER:-icke}"
|
||||
|
||||
# Extract short hostname from common name
|
||||
SHORT_NAME=$(echo "$COMMON_NAME" | cut -d'.' -f1)
|
||||
OUTPUT_FILE="${SHORT_NAME}.req"
|
||||
|
||||
# Detect if TARGET_HOST is an IP address
|
||||
if [[ "$TARGET_HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
TARGET_IP="$TARGET_HOST"
|
||||
else
|
||||
# Try to resolve hostname to IP
|
||||
TARGET_IP=$(ssh -o ConnectTimeout=5 ${SSH_USER}@${TARGET_HOST} "hostname -I | awk '{print \$1}'" 2>/dev/null || echo "")
|
||||
if [ -z "$TARGET_IP" ]; then
|
||||
# Fallback: try local resolution
|
||||
TARGET_IP=$(getent hosts "$TARGET_HOST" 2>/dev/null | awk '{print $1}' | head -1 || echo "")
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "Home Assistant Certificate Request"
|
||||
echo "=========================================="
|
||||
echo "Target host: $TARGET_HOST"
|
||||
echo "SSH User: $SSH_USER"
|
||||
echo "Target IP: ${TARGET_IP:-not detected}"
|
||||
echo "Common Name: $COMMON_NAME"
|
||||
echo "Country: $COUNTRY"
|
||||
echo "State: $STATE"
|
||||
echo "Locality: $LOCALITY"
|
||||
echo "Organization: $ORG"
|
||||
echo "Org Unit: $OU"
|
||||
echo "Key Length: $KEY_BITS bits"
|
||||
if [ -n "$ADDITIONAL_DNS" ]; then
|
||||
echo "Additional DNS: $ADDITIONAL_DNS"
|
||||
fi
|
||||
echo "Output file: $OUTPUT_FILE"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Build SAN entries
|
||||
SAN_DNS="DNS.1 = $COMMON_NAME
|
||||
DNS.2 = $SHORT_NAME"
|
||||
|
||||
DNS_COUNTER=3
|
||||
|
||||
# Add alternative names if common name contains domain
|
||||
if [[ "$COMMON_NAME" == *.* ]]; then
|
||||
SAN_DNS="$SAN_DNS
|
||||
DNS.$DNS_COUNTER = ${SHORT_NAME}.${COMMON_NAME#*.}"
|
||||
((DNS_COUNTER++))
|
||||
fi
|
||||
|
||||
# Add additional DNS names if provided
|
||||
if [ -n "$ADDITIONAL_DNS" ]; then
|
||||
IFS=',' read -ra EXTRA_DNS <<< "$ADDITIONAL_DNS"
|
||||
for dns in "${EXTRA_DNS[@]}"; do
|
||||
# Trim whitespace
|
||||
dns=$(echo "$dns" | xargs)
|
||||
if [ -n "$dns" ]; then
|
||||
SAN_DNS="$SAN_DNS
|
||||
DNS.$DNS_COUNTER = $dns"
|
||||
((DNS_COUNTER++))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Add IP address if detected
|
||||
SAN_IP=""
|
||||
if [ -n "$TARGET_IP" ]; then
|
||||
SAN_IP="IP.1 = $TARGET_IP"
|
||||
fi
|
||||
|
||||
# Create OpenSSL config
|
||||
CONFIG_CONTENT="[req]
|
||||
default_bits = $KEY_BITS
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
distinguished_name = dn
|
||||
req_extensions = v3_req
|
||||
|
||||
[dn]
|
||||
C=$COUNTRY
|
||||
ST=$STATE
|
||||
L=$LOCALITY
|
||||
O=$ORG
|
||||
OU=$OU
|
||||
CN=$COMMON_NAME
|
||||
|
||||
[v3_req]
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
$SAN_DNS"
|
||||
|
||||
# Append IP if available
|
||||
if [ -n "$SAN_IP" ]; then
|
||||
CONFIG_CONTENT="$CONFIG_CONTENT
|
||||
$SAN_IP"
|
||||
fi
|
||||
|
||||
echo "[1/4] Creating OpenSSL configuration..."
|
||||
echo "$CONFIG_CONTENT" > /tmp/csr_config.conf
|
||||
|
||||
echo "[2/4] Copying config to target host..."
|
||||
scp /tmp/csr_config.conf ${SSH_USER}@${TARGET_HOST}:/tmp/csr_config.conf
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to copy config to target host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/4] Generating $KEY_BITS-bit RSA key and CSR on target host..."
|
||||
ssh ${SSH_USER}@${TARGET_HOST} "openssl req -new -newkey rsa:$KEY_BITS -nodes -keyout /tmp/${SHORT_NAME}.key -out /tmp/${SHORT_NAME}.csr -config /tmp/csr_config.conf"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to generate CSR on target host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[4/4] Downloading CSR..."
|
||||
scp ${SSH_USER}@${TARGET_HOST}:/tmp/${SHORT_NAME}.csr "$OUTPUT_FILE"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to download CSR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up local temp file
|
||||
rm -f /tmp/csr_config.conf
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ CSR generated successfully!"
|
||||
echo "=========================================="
|
||||
echo "Certificate request saved to: $OUTPUT_FILE"
|
||||
echo ""
|
||||
echo "CSR details:"
|
||||
openssl req -in "$OUTPUT_FILE" -noout -text | grep -A 10 "Subject:"
|
||||
echo ""
|
||||
echo "Subject Alternative Names:"
|
||||
openssl req -in "$OUTPUT_FILE" -noout -text | grep -A 20 "Subject Alternative Name" || echo " (none found)"
|
||||
echo ""
|
||||
echo "Key details:"
|
||||
openssl req -in "$OUTPUT_FILE" -noout -text | grep "Public-Key:"
|
||||
echo ""
|
||||
echo "IMPORTANT: Private key is stored on target host at:"
|
||||
echo " /tmp/${SHORT_NAME}.key"
|
||||
echo ""
|
||||
echo "Next step: Sign this CSR with:"
|
||||
echo " ./sign-cert.sh $OUTPUT_FILE $SHORT_NAME"
|
||||
echo "=========================================="
|
||||
164
generate-csr-local.sh
Executable file
164
generate-csr-local.sh
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/bin/bash
|
||||
# Script to generate a certificate request locally (for systems without SCP/SFTP)
|
||||
# Usage: ./generate-csr-local.sh <common-name> [country] [state] [locality] [org] [ou] [key-bits] [additional-dns] [ip-address]
|
||||
|
||||
set -e
|
||||
|
||||
# Check arguments
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <common-name> [country] [state] [locality] [org] [ou] [key-bits] [additional-dns] [ip-address]"
|
||||
echo ""
|
||||
echo "Example: $0 srv-wmw-ha01.egonetix.lan DE berlin berlin egonetix it 4096 '' 172.20.70.10"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMMON_NAME="$1"
|
||||
COUNTRY="${2:-DE}"
|
||||
STATE="${3:-berlin}"
|
||||
LOCALITY="${4:-berlin}"
|
||||
ORG="${5:-egonetix}"
|
||||
OU="${6:-it}"
|
||||
KEY_BITS="${7:-4096}"
|
||||
ADDITIONAL_DNS="${8:-}"
|
||||
IP_ADDRESS="${9:-}"
|
||||
|
||||
# Extract short hostname from common name
|
||||
SHORT_NAME=$(echo "$COMMON_NAME" | cut -d'.' -f1)
|
||||
OUTPUT_REQ="${SHORT_NAME}.req"
|
||||
OUTPUT_KEY="${SHORT_NAME}.key"
|
||||
OUTPUT_CSR="${SHORT_NAME}.csr"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Local Certificate Request Generation"
|
||||
echo "=========================================="
|
||||
echo "Common Name: $COMMON_NAME"
|
||||
echo "Country: $COUNTRY"
|
||||
echo "State: $STATE"
|
||||
echo "Locality: $LOCALITY"
|
||||
echo "Organization: $ORG"
|
||||
echo "Org Unit: $OU"
|
||||
echo "Key Length: $KEY_BITS bits"
|
||||
if [ -n "$ADDITIONAL_DNS" ]; then
|
||||
echo "Additional DNS: $ADDITIONAL_DNS"
|
||||
fi
|
||||
if [ -n "$IP_ADDRESS" ]; then
|
||||
echo "IP Address: $IP_ADDRESS"
|
||||
fi
|
||||
echo "Output files: $OUTPUT_REQ, $OUTPUT_KEY"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Build SAN entries
|
||||
SAN_DNS="DNS.1 = $COMMON_NAME
|
||||
DNS.2 = $SHORT_NAME"
|
||||
|
||||
DNS_COUNTER=3
|
||||
|
||||
# Add alternative names if common name contains domain
|
||||
if [[ "$COMMON_NAME" == *.* ]]; then
|
||||
SAN_DNS="$SAN_DNS
|
||||
DNS.$DNS_COUNTER = ${SHORT_NAME}.${COMMON_NAME#*.}"
|
||||
((DNS_COUNTER++))
|
||||
fi
|
||||
|
||||
# Add additional DNS names if provided
|
||||
if [ -n "$ADDITIONAL_DNS" ]; then
|
||||
IFS=',' read -ra EXTRA_DNS <<< "$ADDITIONAL_DNS"
|
||||
for dns in "${EXTRA_DNS[@]}"; do
|
||||
# Trim whitespace
|
||||
dns=$(echo "$dns" | xargs)
|
||||
if [ -n "$dns" ]; then
|
||||
SAN_DNS="$SAN_DNS
|
||||
DNS.$DNS_COUNTER = $dns"
|
||||
((DNS_COUNTER++))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Add IP addresses if provided (comma-separated)
|
||||
SAN_IP=""
|
||||
if [ -n "$IP_ADDRESS" ]; then
|
||||
IP_COUNTER=1
|
||||
IFS=',' read -ra IP_ADDRS <<< "$IP_ADDRESS"
|
||||
for ip in "${IP_ADDRS[@]}"; do
|
||||
# Trim whitespace
|
||||
ip=$(echo "$ip" | xargs)
|
||||
if [ -n "$ip" ]; then
|
||||
if [ -z "$SAN_IP" ]; then
|
||||
SAN_IP="IP.$IP_COUNTER = $ip"
|
||||
else
|
||||
SAN_IP="$SAN_IP
|
||||
IP.$IP_COUNTER = $ip"
|
||||
fi
|
||||
((IP_COUNTER++))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Create OpenSSL config
|
||||
CONFIG_CONTENT="[req]
|
||||
default_bits = $KEY_BITS
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
distinguished_name = dn
|
||||
req_extensions = v3_req
|
||||
|
||||
[dn]
|
||||
C=$COUNTRY
|
||||
ST=$STATE
|
||||
L=$LOCALITY
|
||||
O=$ORG
|
||||
OU=$OU
|
||||
CN=$COMMON_NAME
|
||||
|
||||
[v3_req]
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
$SAN_DNS"
|
||||
|
||||
# Append IP if available
|
||||
if [ -n "$SAN_IP" ]; then
|
||||
CONFIG_CONTENT="$CONFIG_CONTENT
|
||||
$SAN_IP"
|
||||
fi
|
||||
|
||||
CONFIG_FILE="/tmp/csr_config_${SHORT_NAME}.conf"
|
||||
echo "[1/2] Creating OpenSSL configuration..."
|
||||
echo "$CONFIG_CONTENT" > "$CONFIG_FILE"
|
||||
|
||||
echo "[2/2] Generating $KEY_BITS-bit RSA key and CSR locally..."
|
||||
openssl req -new -newkey rsa:$KEY_BITS -nodes -keyout "$OUTPUT_KEY" -out "$OUTPUT_CSR" -config "$CONFIG_FILE"
|
||||
|
||||
# Also create the .req file for consistency with other scripts
|
||||
cp "$OUTPUT_CSR" "$OUTPUT_REQ"
|
||||
|
||||
# Clean up config file
|
||||
rm -f "$CONFIG_FILE"
|
||||
|
||||
# Set proper permissions on private key
|
||||
chmod 600 "$OUTPUT_KEY"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ Certificate files generated locally!"
|
||||
echo "=========================================="
|
||||
echo "Certificate request: $OUTPUT_REQ"
|
||||
echo "Private key: $OUTPUT_KEY"
|
||||
echo ""
|
||||
echo "CSR details:"
|
||||
openssl req -in "$OUTPUT_REQ" -noout -text | grep -A 10 "Subject:"
|
||||
echo ""
|
||||
echo "Subject Alternative Names:"
|
||||
openssl req -in "$OUTPUT_REQ" -noout -text | grep -A 20 "Subject Alternative Name" || echo " (none found)"
|
||||
echo ""
|
||||
echo "Key details:"
|
||||
openssl req -in "$OUTPUT_REQ" -noout -text | grep "Public-Key:"
|
||||
echo ""
|
||||
echo "⚠️ IMPORTANT: Keep $OUTPUT_KEY secure!"
|
||||
echo ""
|
||||
echo "Next step: Sign this CSR with:"
|
||||
echo " ./sign-cert.sh $OUTPUT_REQ $SHORT_NAME"
|
||||
echo "=========================================="
|
||||
@@ -21,6 +21,17 @@ ORG="${6:-egonetix}"
|
||||
OU="${7:-it}"
|
||||
KEY_BITS="${8:-4096}"
|
||||
ADDITIONAL_DNS="${9:-}"
|
||||
SSH_USER="${SSH_USER:-root}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-}"
|
||||
|
||||
# Setup SSH/SCP commands with password support
|
||||
if [ -n "$SSH_PASSWORD" ] && command -v sshpass >/dev/null 2>&1; then
|
||||
SSH_CMD="sshpass -p '$SSH_PASSWORD' ssh"
|
||||
SCP_CMD="sshpass -p '$SSH_PASSWORD' scp"
|
||||
else
|
||||
SSH_CMD="ssh"
|
||||
SCP_CMD="scp"
|
||||
fi
|
||||
|
||||
# Extract short hostname from common name
|
||||
SHORT_NAME=$(echo "$COMMON_NAME" | cut -d'.' -f1)
|
||||
@@ -31,7 +42,7 @@ if [[ "$TARGET_HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
TARGET_IP="$TARGET_HOST"
|
||||
else
|
||||
# Try to resolve hostname to IP
|
||||
TARGET_IP=$(ssh -o ConnectTimeout=5 -o BatchMode=yes root@${TARGET_HOST} "hostname -I | awk '{print \$1}'" 2>/dev/null || echo "")
|
||||
TARGET_IP=$($SSH_CMD -o ConnectTimeout=5 ${SSH_USER}@${TARGET_HOST} "hostname -I | awk '{print \$1}'" 2>/dev/null || echo "")
|
||||
if [ -z "$TARGET_IP" ]; then
|
||||
# Fallback: try local resolution
|
||||
TARGET_IP=$(getent hosts "$TARGET_HOST" 2>/dev/null | awk '{print $1}' | head -1 || echo "")
|
||||
@@ -124,21 +135,24 @@ echo "[1/4] Creating OpenSSL configuration..."
|
||||
echo "$CONFIG_CONTENT" > /tmp/csr_config.conf
|
||||
|
||||
echo "[2/4] Copying config to target host..."
|
||||
scp /tmp/csr_config.conf root@${TARGET_HOST}:/tmp/csr_config.conf
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
$SCP_CMD /tmp/csr_config.conf ${SSH_USER}@${TARGET_HOST}:/tmp/csr_config.conf
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to copy config to target host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/4] Generating $KEY_BITS-bit RSA key and CSR on target host..."
|
||||
ssh root@${TARGET_HOST} "openssl req -new -newkey rsa:$KEY_BITS -nodes -keyout /tmp/${SHORT_NAME}.key -out /tmp/${SHORT_NAME}.csr -config /tmp/csr_config.conf"
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
$SSH_CMD ${SSH_USER}@${TARGET_HOST} "openssl req -new -newkey rsa:$KEY_BITS -nodes -keyout /tmp/${SHORT_NAME}.key -out /tmp/${SHORT_NAME}.csr -config /tmp/csr_config.conf"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to generate CSR on target host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[4/4] Downloading CSR..."
|
||||
scp root@${TARGET_HOST}:/tmp/${SHORT_NAME}.csr "$OUTPUT_FILE"
|
||||
sleep 0.5 # Avoid SSH rate limiting
|
||||
$SCP_CMD ${SSH_USER}@${TARGET_HOST}:/tmp/${SHORT_NAME}.csr "$OUTPUT_FILE"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to download CSR"
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user