Reorganize workspace structure with system-specific cert directories and DNS automation

This commit is contained in:
root
2025-12-17 09:39:46 +01:00
parent 296948f07e
commit 99fcd122ea
14 changed files with 329 additions and 20 deletions

604
scripts/cert-manager.py Executable file
View File

@@ -0,0 +1,604 @@
#!/usr/bin/env python3
"""
Interactive Certificate Manager
Generates CSR on remote host and signs it with UCS CA
"""
import os
import sys
import json
import subprocess
import getpass
import socket
import re
from pathlib import Path
# Get script directory and workspace root
SCRIPT_DIR = Path(__file__).parent.resolve()
WORKSPACE_ROOT = SCRIPT_DIR.parent
# Configuration
CONFIG_FILE = Path.home() / '.cert-manager-config.json'
DEFAULT_CONFIG = {
'country': 'DE',
'state': 'berlin',
'locality': 'berlin',
'organization': 'egonetix',
'organizational_unit': 'it',
'ca_server': '10.0.0.21',
'validity_days': '3650',
'key_bits': '4096',
'last_target_host': '',
'last_common_name': ''
}
SYSTEM_TYPES = {
'proxmox': {
'name': 'Proxmox VE',
'deploy_script': 'deploy-proxmox.sh',
'key_location': '/tmp/{short_name}.key',
'default_port': '8006',
'ssh_user': 'root',
'cert_dir': 'proxmox'
},
'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,
'cert_dir': 'homeassistant'
},
'pfsense': {
'name': 'pfSense',
'deploy_script': None, # Manual deployment via web interface
'key_location': '/tmp/{short_name}.key',
'default_port': '443',
'ssh_user': 'root',
'cert_dir': 'pfsense'
},
'truenas': {
'name': 'TrueNAS',
'deploy_script': None, # Manual deployment via web interface
'key_location': '/tmp/{short_name}.key',
'default_port': '443',
'ssh_user': 'root',
'cert_dir': 'truenas'
},
'ucs': {
'name': 'Univention Corporate Server',
'deploy_script': None,
'key_location': '/tmp/{short_name}.key',
'default_port': '443',
'ssh_user': 'root',
'cert_dir': 'ucs'
},
'unknown': {
'name': 'Unknown System',
'deploy_script': None,
'key_location': '/tmp/{short_name}.key',
'default_port': '443',
'ssh_user': 'root',
'cert_dir': 'other'
}
}
def load_config():
"""Load configuration from file or return defaults"""
if CONFIG_FILE.exists():
try:
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
return {**DEFAULT_CONFIG, **config}
except Exception as e:
print(f"Warning: Could not load config: {e}")
return DEFAULT_CONFIG.copy()
def save_config(config):
"""Save configuration to file"""
try:
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2)
except Exception as e:
print(f"Warning: Could not save config: {e}")
def prompt_with_default(prompt, default):
"""Prompt user with a default value"""
if default:
user_input = input(f"{prompt} [{default}]: ").strip()
return user_input if user_input else default
else:
return input(f"{prompt}: ").strip()
def yes_no_prompt(prompt, default=True):
"""Ask a yes/no question"""
default_str = "Y/n" if default else "y/N"
while True:
response = input(f"{prompt} [{default_str}]: ").strip().lower()
if not response:
return default
if response in ['y', 'yes']:
return True
if response in ['n', 'no']:
return False
print("Please answer 'y' or 'n'")
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', 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,
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', None
system_type = result.stdout.strip()
if not system_type:
print("Warning: Detection script returned empty output")
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'), detected_user
except subprocess.TimeoutExpired:
print(f"Warning: System detection timed out for {target_host}")
return 'unknown', None
except Exception as e:
print(f"Warning: Could not detect system type: {e}")
return 'unknown', None
def check_dns_resolution(hostname):
"""Check if a hostname resolves in DNS"""
try:
socket.gethostbyname(hostname)
return True
except socket.gaierror:
return False
def extract_hostnames_from_cert(cert_file):
"""Extract all DNS names from a certificate"""
try:
result = subprocess.run(
['openssl', 'x509', '-in', cert_file, '-text', '-noout'],
capture_output=True,
text=True,
check=True
)
hostnames = []
# Extract CN from Subject (not from Issuer!)
subject_match = re.search(r'Subject:.*?CN\s*=\s*([^,\n]+)', result.stdout, re.DOTALL)
if subject_match:
cn = subject_match.group(1).strip()
# Only add if it looks like a hostname (contains letters and possibly dots/dashes)
if re.match(r'^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$', cn):
hostnames.append(cn)
# Extract SANs
san_match = re.search(r'X509v3 Subject Alternative Name:.*?DNS:([^\n]+)', result.stdout, re.DOTALL)
if san_match:
san_text = san_match.group(1)
# Extract all DNS entries
dns_entries = re.findall(r'DNS:([^,\s]+)', san_text)
hostnames.extend(dns_entries)
# Remove duplicates and filter out IP addresses
unique_hostnames = []
for h in hostnames:
h = h.strip()
# Skip if it looks like an IP address or contains special chars from CA names
if not re.match(r'^\d+\.\d+\.\d+\.\d+$', h) and '(' not in h and ')' not in h:
if h not in unique_hostnames and len(h) > 0:
unique_hostnames.append(h)
return unique_hostnames
except Exception as e:
print(f"Warning: Could not extract hostnames from certificate: {e}")
return []
def create_dns_record(hostname, target_ip, dns_server='10.0.0.21'):
"""Create a DNS record on UCS server"""
try:
# Split hostname into name and domain
parts = hostname.split('.', 1)
if len(parts) == 1:
# Short hostname without domain - skip it
return False
name = parts[0]
domain = parts[1]
# Construct the zone DN
zone_parts = domain.split('.')
zone_dn = f"zoneName={domain},cn=dns,dc={',dc='.join(zone_parts)}"
# Create DNS record via SSH to UCS server
cmd = [
'ssh', f'root@{dns_server}',
'univention-directory-manager', 'dns/host_record', 'create',
'--superordinate', zone_dn,
'--set', f'name={name}',
'--set', f'a={target_ip}'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print(f" ✓ Created DNS record: {hostname}{target_ip}")
return True
else:
if 'already exists' in result.stderr.lower():
print(f" DNS record already exists: {hostname}")
return True
else:
print(f" ✗ Failed to create DNS record for {hostname}: {result.stderr.strip()}")
return False
except Exception as e:
print(f" ✗ Error creating DNS record for {hostname}: {e}")
return False
def check_and_create_dns_records(cert_file, target_ip, dns_server='10.0.0.21'):
"""Check DNS records for certificate hostnames and offer to create missing ones"""
print("\n" + "=" * 60)
print("Step 4: Checking DNS Records")
print("=" * 60)
hostnames = extract_hostnames_from_cert(cert_file)
if not hostnames:
print("No hostnames found in certificate to check.")
return
print(f"\nChecking {len(hostnames)} hostname(s) from certificate...")
missing_hostnames = []
for hostname in hostnames:
if check_dns_resolution(hostname):
print(f"{hostname} - resolves")
else:
print(f"{hostname} - NOT found in DNS")
missing_hostnames.append(hostname)
if not missing_hostnames:
print("\n✓ All hostnames are resolvable in DNS!")
return
print(f"\n⚠ Found {len(missing_hostnames)} hostname(s) not in DNS:")
for hostname in missing_hostnames:
print(f" - {hostname}")
if yes_no_prompt("\nDo you want to create missing DNS records on UCS?", True):
print(f"\nCreating DNS records on {dns_server}...")
success_count = 0
for hostname in missing_hostnames:
if create_dns_record(hostname, target_ip, dns_server):
success_count += 1
if success_count > 0:
print(f"\n✓ Successfully created {success_count} DNS record(s)")
print("\nNote: DNS changes may take a few seconds to propagate.")
else:
print("\n⚠ No DNS records were created")
else:
print("\nSkipping DNS record creation.")
print("You may need to create these records manually for the certificate to work properly.")
def main():
print("=" * 60)
print("Interactive Certificate Manager")
print("=" * 60)
print()
# Load config
config = load_config()
# Ask if user wants to modify defaults
if CONFIG_FILE.exists():
if yes_no_prompt("Do you want to modify default values?", False):
print("\n--- Default Values Configuration ---")
config['country'] = prompt_with_default("Country (C)", config['country'])
config['state'] = prompt_with_default("State/Province (ST)", config['state'])
config['locality'] = prompt_with_default("Locality (L)", config['locality'])
config['organization'] = prompt_with_default("Organization (O)", config['organization'])
config['organizational_unit'] = prompt_with_default("Organizational Unit (OU)", config['organizational_unit'])
config['ca_server'] = prompt_with_default("CA Server", config['ca_server'])
config['validity_days'] = prompt_with_default("Validity (days)", config['validity_days'])
config['key_bits'] = prompt_with_default("Key Length (bits)", config['key_bits'])
print()
# Get certificate details
print("--- Certificate Details ---")
target_host = prompt_with_default("Target Host (IP or hostname)", config['last_target_host'])
if not target_host:
print("Error: Target host is required!")
sys.exit(1)
# Get script directory
script_dir = Path(__file__).parent.absolute()
# Detect system type (try key-based auth first)
print(f"\nDetecting system type on {target_host}...")
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'])
# Ask for additional DNS names
print("\nAdditional DNS names (optional, comma-separated):")
print(" Example: firewall.domain.com,vpn.domain.com")
additional_dns = input("Additional DNS names [none]: ").strip()
if not common_name:
print("Error: Common name is required!")
sys.exit(1)
# Extract short name for filenames
short_name = common_name.split('.')[0]
# Ask for custom values for this certificate
print("\n--- Certificate Subject (press Enter to use defaults) ---")
country = prompt_with_default("Country (C)", config['country'])
state = prompt_with_default("State/Province (ST)", config['state'])
locality = prompt_with_default("Locality (L)", config['locality'])
organization = prompt_with_default("Organization (O)", config['organization'])
org_unit = prompt_with_default("Organizational Unit (OU)", config['organizational_unit'])
validity_days = prompt_with_default("Validity (days)", config['validity_days'])
key_bits = prompt_with_default("Key Length (bits)", config['key_bits'])
print("\n" + "=" * 60)
print("Summary:")
print("=" * 60)
print(f"System Type: {system_info['name']}")
print(f"Target Host: {target_host}")
print(f"Common Name: {common_name}")
if additional_dns:
print(f"Additional DNS: {additional_dns}")
print(f"Country: {country}")
print(f"State: {state}")
print(f"Locality: {locality}")
print(f"Organization: {organization}")
print(f"Org Unit: {org_unit}")
print(f"Key Length: {key_bits} bits")
print(f"Validity: {validity_days} days")
print(f"CA Server: {config['ca_server']}")
print(f"Output files: {short_name}.req, {short_name}-cert.pem")
print("=" * 60)
print()
if not yes_no_prompt("Proceed with certificate generation?", True):
print("Cancelled.")
sys.exit(0)
# Create output directory for this system type
cert_dir = WORKSPACE_ROOT / 'certs' / system_info.get('cert_dir', 'other')
cert_dir.mkdir(parents=True, exist_ok=True)
# Change to cert output directory
original_dir = Path.cwd()
os.chdir(cert_dir)
print(f"\nOutput directory: {cert_dir}")
# Save config for next run
config['last_target_host'] = target_host
config['last_common_name'] = common_name
save_config(config)
print("\n" + "=" * 60)
print("Step 1: Generating CSR")
print("=" * 60)
# Get script directory
script_dir = SCRIPT_DIR
# 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:
# 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)
except FileNotFoundError:
print(f"\nError: generate-csr.sh not found in {script_dir}")
sys.exit(1)
print("\n" + "=" * 60)
print("Step 2: Signing certificate with CA")
print("=" * 60)
# Run sign-cert.sh
req_file = f"{short_name}.req"
sign_cmd = [
str(script_dir / 'sign-cert.sh'),
req_file,
short_name,
validity_days
]
try:
result = subprocess.run(sign_cmd, check=True)
except subprocess.CalledProcessError as e:
print(f"\nError: Certificate signing failed with exit code {e.returncode}")
sys.exit(1)
except FileNotFoundError:
print(f"\nError: sign-cert.sh not found in {script_dir}")
sys.exit(1)
# Certificate file path
cert_file = f"{short_name}-cert.pem"
# Check and create DNS records if needed
check_and_create_dns_records(cert_file, target_host, config['ca_server'])
print("\n" + "=" * 60)
print("Step 5: Deploying certificate to target host")
print("=" * 60)
# Use system-specific deployment if available
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,
cert_file,
key_file,
short_name
]
try:
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)
except FileNotFoundError:
print(f"\nError: {system_info['deploy_script']} not found")
sys.exit(1)
else:
# Generic deployment - just copy files
if yes_no_prompt("Copy certificate back to the target host?", True):
try:
subprocess.run(['scp', cert_file, f'root@{target_host}:/tmp/{short_name}.crt'], check=True)
print(f"\n✓ Certificate copied to target host at /tmp/{short_name}.crt")
print(f" Private key is at /tmp/{short_name}.key")
print(f"\n⚠ Manual installation required for {system_info['name']}")
print(f" Please install the certificate through the web interface.")
except subprocess.CalledProcessError:
print("\nWarning: Failed to copy certificate to target host")
print("\n" + "=" * 60)
print("✓ Certificate Management Complete!")
print("=" * 60)
print(f"\nFiles created:")
print(f" - {req_file} (Certificate Request)")
print(f" - {cert_file} (Signed Certificate)")
print(f"\nOn target host ({target_host}):")
print(f" - /tmp/{short_name}.key (Private Key - {key_bits} bits)")
print(f" - /tmp/{short_name}.crt (Certificate)")
if system_type == 'proxmox':
print(f"\n✓ Access Proxmox at: https://{target_host}:{system_info['default_port']}")
print("\n")
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print("\n\nCancelled by user.")
sys.exit(1)
except Exception as e:
print(f"\nError: {e}")
sys.exit(1)

158
scripts/deploy-homeassistant.sh Executable file
View 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 "=========================================="

68
scripts/deploy-proxmox.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Deploy certificate to Proxmox host
# Usage: ./deploy-proxmox.sh <hostname> <cert-file> <key-file> <short-name>
set -e
TARGET_HOST="$1"
CERT_FILE="$2"
KEY_FILE="$3"
SHORT_NAME="$4"
if [ $# -lt 4 ]; then
echo "Usage: $0 <hostname> <cert-file> <key-file> <short-name>"
exit 1
fi
echo "=========================================="
echo "Proxmox Certificate Deployment"
echo "=========================================="
echo "Target: $TARGET_HOST"
echo "Certificate: $CERT_FILE"
echo "Key file: $KEY_FILE"
echo "=========================================="
echo ""
# Backup existing certificates
echo "[1/5] Backing up existing Proxmox certificates..."
ssh root@${TARGET_HOST} "cp /etc/pve/local/pveproxy-ssl.pem /etc/pve/local/pveproxy-ssl.pem.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true"
ssh root@${TARGET_HOST} "cp /etc/pve/local/pveproxy-ssl.key /etc/pve/local/pveproxy-ssl.key.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true"
# Copy certificate to target
echo "[2/5] Copying certificate to Proxmox..."
scp "$CERT_FILE" root@${TARGET_HOST}:/tmp/${SHORT_NAME}.crt
# Copy or retrieve key
echo "[3/5] Copying private key to Proxmox..."
if [ -f "$KEY_FILE" ]; then
scp "$KEY_FILE" root@${TARGET_HOST}:/tmp/${SHORT_NAME}.key
else
echo "Note: Key file should already be on target at /tmp/${SHORT_NAME}.key"
fi
# Install certificate
echo "[4/5] Installing certificate for Proxmox web interface..."
ssh root@${TARGET_HOST} "cat /tmp/${SHORT_NAME}.crt > /etc/pve/local/pveproxy-ssl.pem && \
cat /tmp/${SHORT_NAME}.key > /etc/pve/local/pveproxy-ssl.key && \
chmod 640 /etc/pve/local/pveproxy-ssl.pem && \
chmod 640 /etc/pve/local/pveproxy-ssl.key"
# Restart Proxmox web service
echo "[5/5] Restarting Proxmox web interface..."
ssh root@${TARGET_HOST} "systemctl restart pveproxy.service"
echo ""
echo "=========================================="
echo "✓ Proxmox certificate deployed!"
echo "=========================================="
echo ""
echo "Certificate installed at:"
echo " /etc/pve/local/pveproxy-ssl.pem"
echo " /etc/pve/local/pveproxy-ssl.key"
echo ""
echo "Backup created at:"
echo " /etc/pve/local/pveproxy-ssl.pem.bak.*"
echo " /etc/pve/local/pveproxy-ssl.key.bak.*"
echo ""
echo "Access Proxmox at: https://${TARGET_HOST}:8006"
echo "=========================================="

80
scripts/detect-system.sh Executable file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
# Detect system type on remote host
# 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
exit 1
fi
# SSH options for faster connection
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_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 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 try_ssh "root" "test -f /usr/local/sbin/pfSsh.php" >/dev/null 2>&1; then
echo "pfsense"
exit 0
elif try_ssh "root" "test -f /usr/sbin/univention-config-registry" >/dev/null 2>&1; then
echo "ucs"
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
scripts/generate-csr-ha.sh Executable file
View 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
scripts/generate-csr-local.sh Executable file
View 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 "=========================================="

184
scripts/generate-csr.sh Executable file
View File

@@ -0,0 +1,184 @@
#!/bin/bash
# Script to generate a certificate request on a remote host
# Usage: ./generate-csr.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 192.168.1.100 myserver.domain.com DE berlin berlin egonetix it 4096 'firewall.domain.com,vpn.domain.com'"
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:-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)
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_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 "")
fi
fi
echo "=========================================="
echo "Certificate Request Generation"
echo "=========================================="
echo "Target host: $TARGET_HOST"
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..."
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..."
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..."
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
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 "=========================================="

181
scripts/install-ca-cert.sh Executable file
View File

@@ -0,0 +1,181 @@
#!/bin/bash
# Script to install UCS CA certificate into system and browsers
# Usage: ./install-ca-cert.sh [ca-server-ip]
set -e
# Configuration
UCS_SERVER="${1:-10.0.0.21}"
CA_CERT_FILE="/usr/local/share/ca-certificates/ucs-root-ca.crt"
TEMP_CERT="/tmp/ucs-root-ca.crt"
echo "============================================================"
echo "UCS CA Certificate Installation"
echo "============================================================"
echo "CA Server: $UCS_SERVER"
echo "Install to: System + All Browsers"
echo "============================================================"
echo ""
# Check if running as root for system installation
if [ "$EUID" -eq 0 ]; then
SUDO=""
RUNNING_AS_ROOT=true
else
SUDO="sudo"
RUNNING_AS_ROOT=false
fi
# Check for certutil
if ! command -v certutil &> /dev/null; then
echo "⚠ certutil not found, installing libnss3-tools..."
if [ "$RUNNING_AS_ROOT" = true ]; then
apt-get update && apt-get install -y libnss3-tools
else
$SUDO apt-get update && $SUDO apt-get install -y libnss3-tools
fi
echo ""
fi
# Step 1: Download CA certificate from UCS server
echo "[1/5] Downloading CA certificate from UCS server..."
scp root@${UCS_SERVER}:/etc/univention/ssl/ucsCA/CAcert.pem "$TEMP_CERT"
if [ $? -ne 0 ]; then
echo "Error: Failed to download CA certificate"
exit 1
fi
echo "✓ Downloaded CA certificate"
echo ""
# Step 2: Install to system CA certificates
echo "[2/5] Installing to system CA certificates..."
if [ "$RUNNING_AS_ROOT" = true ]; then
cp "$TEMP_CERT" "$CA_CERT_FILE"
update-ca-certificates
else
$SUDO cp "$TEMP_CERT" "$CA_CERT_FILE"
$SUDO update-ca-certificates
fi
if [ $? -eq 0 ]; then
echo "✓ Installed to system CA certificates"
else
echo "⚠ Warning: Failed to install system CA certificate"
fi
echo ""
# Step 3: Install to NSS database (Chrome, Chromium, Brave)
echo "[3/5] Installing to NSS database (Chrome/Chromium/Brave)..."
NSS_DB="$HOME/.pki/nssdb"
if [ -d "$NSS_DB" ]; then
# Remove old certificate if exists
certutil -D -d sql:$NSS_DB -n "UCS Root CA" 2>/dev/null || true
# Add certificate
certutil -A -d sql:$NSS_DB -t "CT,C,C" -n "UCS Root CA" -i "$TEMP_CERT"
if [ $? -eq 0 ]; then
echo "✓ Installed to NSS database"
else
echo "⚠ Warning: Failed to install to NSS database"
fi
else
echo "⚠ NSS database not found at $NSS_DB"
echo " (Chrome/Chromium/Brave may not be installed)"
fi
echo ""
# Step 4: Install to Firefox profiles
echo "[4/5] Installing to Firefox profiles..."
FIREFOX_DIR="$HOME/.mozilla/firefox"
FIREFOX_INSTALLED=false
if [ -d "$FIREFOX_DIR" ]; then
for profile in "$FIREFOX_DIR"/*.default*; do
if [ -d "$profile" ]; then
PROFILE_NAME=$(basename "$profile")
# Check if cert9.db exists
if [ -f "$profile/cert9.db" ]; then
# Remove old certificate if exists
certutil -D -d sql:$profile -n "UCS Root CA" 2>/dev/null || true
# Add certificate
certutil -A -d sql:$profile -t "CT,C,C" -n "UCS Root CA" -i "$TEMP_CERT"
if [ $? -eq 0 ]; then
echo " ✓ Installed to Firefox profile: $PROFILE_NAME"
FIREFOX_INSTALLED=true
else
echo " ⚠ Failed to install to profile: $PROFILE_NAME"
fi
fi
fi
done
if [ "$FIREFOX_INSTALLED" = false ]; then
echo "⚠ No Firefox profiles found with cert9.db"
fi
else
echo "⚠ Firefox directory not found"
echo " (Firefox may not be installed)"
fi
echo ""
# Step 5: Verify installation
echo "[5/5] Verifying installation..."
echo ""
# Check system CA
if [ -f "$CA_CERT_FILE" ]; then
echo "✓ System CA: Installed"
else
echo "✗ System CA: Not found"
fi
# Check NSS database
if [ -d "$NSS_DB" ]; then
if certutil -L -d sql:$NSS_DB | grep -q "UCS Root CA"; then
echo "✓ NSS Database: Installed (Chrome/Chromium/Brave)"
else
echo "✗ NSS Database: Not installed"
fi
fi
# Check Firefox
if [ -d "$FIREFOX_DIR" ]; then
FIREFOX_OK=false
for profile in "$FIREFOX_DIR"/*.default*; do
if [ -f "$profile/cert9.db" ]; then
if certutil -L -d sql:$profile | grep -q "UCS Root CA" 2>/dev/null; then
FIREFOX_OK=true
break
fi
fi
done
if [ "$FIREFOX_OK" = true ]; then
echo "✓ Firefox: Installed"
else
echo "✗ Firefox: Not installed"
fi
fi
# Clean up
rm -f "$TEMP_CERT"
echo ""
echo "============================================================"
echo "✓ CA Certificate Installation Complete!"
echo "============================================================"
echo ""
echo "Certificate Details:"
openssl x509 -in "$CA_CERT_FILE" -noout -subject -issuer -dates
echo ""
echo "IMPORTANT: Restart your browsers for changes to take effect!"
echo ""
echo "To verify, visit any UCS-signed HTTPS site:"
echo " https://$UCS_SERVER"
echo "============================================================"

128
scripts/sign-cert.sh Executable file
View File

@@ -0,0 +1,128 @@
#!/bin/bash
# Script to sign a certificate request with UCS CA
# Usage: ./sign-cert.sh <req-file> <hostname> [days]
set -e
# Configuration
UCS_SERVER="10.0.0.21"
UCS_USER="root"
DEFAULT_DAYS=3650
# Check arguments
if [ $# -lt 2 ]; then
echo "Usage: $0 <req-file> <hostname> [days]"
echo ""
echo "Example: $0 webui.req myserver 3650"
echo ""
echo "The script will:"
echo " 1. Copy the CSR to UCS server"
echo " 2. Sign it with the UCS CA (preserving extensions)"
echo " 3. Download the signed certificate to current directory"
exit 1
fi
REQ_FILE="$1"
HOSTNAME="$2"
DAYS="${3:-$DEFAULT_DAYS}"
# Validate req file exists
if [ ! -f "$REQ_FILE" ]; then
echo "Error: Certificate request file '$REQ_FILE' not found!"
exit 1
fi
# Get absolute path of req file
REQ_FILE=$(realpath "$REQ_FILE")
# Output to same directory as input CSR
REQ_DIR=$(dirname "$REQ_FILE")
OUTPUT_FILE="${REQ_DIR}/${HOSTNAME}-cert.pem"
echo "=========================================="
echo "UCS Certificate Signing Script"
echo "=========================================="
echo "Request file: $REQ_FILE"
echo "Hostname: $HOSTNAME"
echo "Valid days: $DAYS"
echo "Output file: $OUTPUT_FILE"
echo "=========================================="
echo ""
# Extract SAN from CSR and fix format
echo "[1/4] Extracting Subject Alternative Names from CSR..."
SAN_ENTRIES=$(openssl req -in "$REQ_FILE" -noout -text | grep -A 1 "Subject Alternative Name" | tail -1 | sed 's/^ *//' | sed 's/IP Address:/IP:/g' || echo "")
if [ -z "$SAN_ENTRIES" ]; then
echo "Warning: No Subject Alternative Names found in CSR"
SAN_CONFIG=""
else
echo "Found SANs: $SAN_ENTRIES"
SAN_CONFIG="subjectAltName = $SAN_ENTRIES"
fi
# Step 2: Copy CSR to UCS server
echo "[2/4] Copying CSR to UCS server..."
scp "$REQ_FILE" ${UCS_USER}@${UCS_SERVER}:/tmp/${HOSTNAME}.csr
if [ $? -ne 0 ]; then
echo "Error: Failed to copy CSR to UCS server"
exit 1
fi
# Step 3: Sign using x509 command with CA password
echo "[3/4] Signing certificate on UCS server..."
# Create extension config and sign on UCS server
ssh ${UCS_USER}@${UCS_SERVER} "
set -e
# Create extensions config
cat > /tmp/${HOSTNAME}-ext.cnf << 'EXTCONF'
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
${SAN_CONFIG}
EXTCONF
# Sign the certificate using CA password file
openssl x509 -req \
-in /tmp/${HOSTNAME}.csr \
-CA /etc/univention/ssl/ucsCA/CAcert.pem \
-CAkey /etc/univention/ssl/ucsCA/private/CAkey.pem \
-CAcreateserial \
-out /tmp/${HOSTNAME}-cert.pem \
-days ${DAYS} \
-sha256 \
-extfile /tmp/${HOSTNAME}-ext.cnf \
-passin file:/etc/univention/ssl/password
"
if [ $? -ne 0 ]; then
echo "Error: Failed to sign certificate"
exit 1
fi
# Step 4: Download signed certificate
echo "[4/4] Downloading signed certificate..."
scp ${UCS_USER}@${UCS_SERVER}:/tmp/${HOSTNAME}-cert.pem "$OUTPUT_FILE"
if [ $? -ne 0 ]; then
echo "Error: Failed to download signed certificate"
exit 1
fi
# Clean up on UCS server
ssh ${UCS_USER}@${UCS_SERVER} "rm -f /tmp/${HOSTNAME}.csr /tmp/${HOSTNAME}-cert.pem /tmp/${HOSTNAME}-ext.cnf"
echo ""
echo "=========================================="
echo "✓ Certificate signed successfully!"
echo "=========================================="
echo "Certificate saved to: $OUTPUT_FILE"
echo ""
echo "Certificate details:"
openssl x509 -in "$OUTPUT_FILE" -noout -subject -issuer -dates
echo ""
echo "Subject Alternative Names:"
openssl x509 -in "$OUTPUT_FILE" -noout -text | grep -A 2 "Subject Alternative Name" | tail -2 || echo " (none)"
echo ""
echo "Extended Key Usage:"
openssl x509 -in "$OUTPUT_FILE" -noout -text | grep -A 1 "Extended Key Usage" | tail -1 || echo " (none)"
echo "=========================================="