diff --git a/cert-manager.py b/cert-manager.py index 27b4273..18d12e6 100755 --- a/cert-manager.py +++ b/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) diff --git a/deploy-homeassistant.sh b/deploy-homeassistant.sh new file mode 100755 index 0000000..e1af71e --- /dev/null +++ b/deploy-homeassistant.sh @@ -0,0 +1,158 @@ +#!/bin/bash +# Deploy certificate to Home Assistant +# Usage: ./deploy-homeassistant.sh + +set -e + +if [ $# -lt 4 ]; then + echo "Usage: $0 " + 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 "==========================================" diff --git a/detect-system.sh b/detect-system.sh index d4c6101..734ca7a 100755 --- a/detect-system.sh +++ b/detect-system.sh @@ -3,6 +3,8 @@ # Usage: ./detect-system.sh TARGET_HOST="$1" +SSH_USER="${SSH_USER:-root}" +SSH_PASSWORD="${SSH_PASSWORD:-}" if [ -z "$TARGET_HOST" ]; then echo "Usage: $0 " >&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 diff --git a/generate-csr-ha.sh b/generate-csr-ha.sh new file mode 100755 index 0000000..f665ce7 --- /dev/null +++ b/generate-csr-ha.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# Script to generate a certificate request for Home Assistant +# Usage: ./generate-csr-ha.sh [country] [state] [locality] [org] [ou] [key-bits] [additional-dns] + +set -e + +# Check arguments +if [ $# -lt 2 ]; then + echo "Usage: $0 [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 "==========================================" diff --git a/generate-csr-local.sh b/generate-csr-local.sh new file mode 100755 index 0000000..2a88c91 --- /dev/null +++ b/generate-csr-local.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# Script to generate a certificate request locally (for systems without SCP/SFTP) +# Usage: ./generate-csr-local.sh [country] [state] [locality] [org] [ou] [key-bits] [additional-dns] [ip-address] + +set -e + +# Check arguments +if [ $# -lt 1 ]; then + echo "Usage: $0 [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 "==========================================" diff --git a/generate-csr.sh b/generate-csr.sh index 3d7d9bd..aaecfac 100755 --- a/generate-csr.sh +++ b/generate-csr.sh @@ -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