Reorganize workspace structure with system-specific cert directories and DNS automation
This commit is contained in:
604
scripts/cert-manager.py
Executable file
604
scripts/cert-manager.py
Executable 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
158
scripts/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 "=========================================="
|
||||
68
scripts/deploy-proxmox.sh
Executable file
68
scripts/deploy-proxmox.sh
Executable 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
80
scripts/detect-system.sh
Executable 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
172
scripts/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
scripts/generate-csr-local.sh
Executable file
164
scripts/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 "=========================================="
|
||||
184
scripts/generate-csr.sh
Executable file
184
scripts/generate-csr.sh
Executable 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
181
scripts/install-ca-cert.sh
Executable 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
128
scripts/sign-cert.sh
Executable 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 "=========================================="
|
||||
Reference in New Issue
Block a user