diff --git a/ca/ucs-ca-cert.der b/ca/ucs-ca-cert.der new file mode 100644 index 0000000..bbd58ae Binary files /dev/null and b/ca/ucs-ca-cert.der differ diff --git a/docs/DNS_INTEGRATION.md b/docs/DNS_INTEGRATION.md new file mode 100644 index 0000000..7cb7eee --- /dev/null +++ b/docs/DNS_INTEGRATION.md @@ -0,0 +1,77 @@ +# DNS Integration Feature + +## Overview +The certificate manager now automatically checks if hostnames in certificates are resolvable in DNS and can create missing DNS records on the UCS DNS server. + +## How It Works + +### 1. Certificate Analysis +After signing a certificate, the tool extracts all DNS names from: +- Common Name (CN) in the certificate Subject +- Subject Alternative Names (SANs) + +### 2. DNS Resolution Check +For each hostname found, the tool checks if it resolves using standard DNS lookup. + +### 3. Missing Record Detection +If a hostname doesn't resolve, it's flagged as missing. + +### 4. Automatic DNS Record Creation +The tool offers to create missing DNS records on the UCS DNS server using: +```bash +univention-directory-manager dns/host_record create +``` + +## Example Output + +``` +============================================================ +Step 4: Checking DNS Records +============================================================ + +Checking 4 hostname(s) from certificate... + ✓ vscode.egonetix.lan - resolves + ✓ vscode - resolves + ✓ srvdocker02.egonetix.lan - resolves + ✗ newhost.egonetix.lan - NOT found in DNS + +⚠ Found 1 hostname(s) not in DNS: + - newhost.egonetix.lan + +Do you want to create missing DNS records on UCS? [Y/n]: y + +Creating DNS records on 10.0.0.21... + ✓ Created DNS record: newhost.egonetix.lan → 10.0.0.48 + +✓ Successfully created 1 DNS record(s) + +Note: DNS changes may take a few seconds to propagate. +``` + +## Benefits + +✅ **Prevents Configuration Errors** - Ensures all certificate hostnames are resolvable +✅ **Saves Time** - No need to manually create DNS records +✅ **Automatic Workflow** - Integrated into the certificate generation process +✅ **Safe** - Always asks for confirmation before creating records +✅ **Idempotent** - Detects existing records and skips them + +## Requirements + +- SSH access to UCS DNS server (default: 10.0.0.21) +- Root access or UDM permissions on UCS server +- Target system must have an IP address for the A record + +## Configuration + +The DNS server is automatically set to the same server as the CA (configured in cert-manager.py): +```python +config['ca_server'] = '10.0.0.21' # Default UCS server +``` + +## Limitations + +- Only creates A records (IPv4) +- Requires the hostname to be part of an existing DNS zone on UCS +- Short hostnames (without domain) are skipped +- AAAA records (IPv6) not yet supported diff --git a/EXAMPLES.md b/docs/EXAMPLES.md similarity index 100% rename from EXAMPLES.md rename to docs/EXAMPLES.md diff --git a/README.md b/docs/README.md similarity index 89% rename from README.md rename to docs/README.md index 9508f71..dbb77f7 100644 --- a/README.md +++ b/docs/README.md @@ -4,8 +4,9 @@ Automated certificate generation and signing tools for UCS CA with intelligent s ## Features -- 🔍 **Automatic System Detection** - Detects target system type (Proxmox, pfSense, TrueNAS, UCS) +- 🔍 **Automatic System Detection** - Detects target system type (Proxmox, Home Assistant, pfSense, TrueNAS, UCS) - 🤖 **Automated Deployment** - Fully automated certificate installation for supported systems +- 🌐 **DNS Integration** - Automatically checks and creates DNS records for certificate hostnames - 💾 **Configuration Persistence** - Remembers your settings between runs - 🔐 **Proper Certificate Extensions** - Generates certificates with correct serverAuth extensions - 🎯 **Interactive & Scriptable** - Works both interactively and in automation scripts @@ -46,34 +47,37 @@ The main interactive tool that handles the entire certificate lifecycle with sys **Usage:** ```bash -./cert-manager.py +./scripts/cert-manager.py ``` **Workflow:** 1. Detects target system type automatically 2. Prompts for certificate details with smart defaults 3. Generates CSR on remote host with proper extensions -4. Signs certificate with UCS CA -5. Deploys automatically (Proxmox) or copies to target (others) +4. Signs certificate with UCS CA (outputs to `certs//` directory) +5. **Checks DNS records and offers to create missing ones** 🌐 +6. Deploys automatically (Proxmox/Home Assistant) or copies to target (others) **Features:** - Interactive prompts with default values from previous runs - Automatic system type detection +- **Automatic DNS record creation on UCS DNS server** - Intelligent deployment based on system capabilities - Configurable key length (default: 4096 bits) - Remembers last used values for quick reuse +- Organized certificate storage by system type ### 2. generate-csr.sh (Standalone) Generates a certificate signing request on a remote host. **Usage:** ```bash -./generate-csr.sh [country] [state] [locality] [org] [ou] [key-bits] +./scripts/generate-csr.sh [country] [state] [locality] [org] [ou] [key-bits] ``` **Example:** ```bash -./generate-csr.sh 192.168.1.100 server.example.com DE berlin berlin egonetix it 4096 +./scripts/generate-csr.sh 192.168.1.100 server.example.com DE berlin berlin egonetix it 4096 ``` **Features:** @@ -87,20 +91,22 @@ Signs a certificate request with the UCS CA. **Usage:** ```bash -./sign-cert.sh [days] +./scripts/sign-cert.sh [days] ``` **Example:** ```bash -./sign-cert.sh server.req server 3650 +./scripts/sign-cert.sh certs/proxmox/server.csr server 3650 ``` +**Output:** Certificate is saved to the same directory as the CSR file. + ### 4. detect-system.sh (Utility) Detects the type of system on a remote host. **Usage:** ```bash -./detect-system.sh +./scripts/detect-system.sh ``` **Returns:** `proxmox`, `pfsense`, `truenas`, `ucs`, or `unknown` diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md new file mode 100644 index 0000000..babc12a --- /dev/null +++ b/docs/STRUCTURE.md @@ -0,0 +1,57 @@ +# Folder Structure + +This directory is organized for efficient certificate management: + +## 📁 Structure Overview + +``` +zertifizierung/ +├── ca/ # Certificate Authority files +│ └── ucs-ca-cert.* # UCS CA certificates (crt, der, pem) +│ +├── certs/ # Generated certificates organized by system +│ ├── fritzbox/ # Fritz!Box router certificates +│ ├── vscode/ # VS Code server certificates +│ ├── proxmox/ # Proxmox host certificates +│ ├── homeassistant/ # Home Assistant certificates +│ ├── gateway/ # Network gateway certificates +│ └── ilo/ # iLO interface certificates +│ +├── scripts/ # Certificate management tools +│ ├── cert-manager.py # Main interactive tool +│ ├── sign-cert.sh # Sign certificates with UCS CA +│ ├── generate-csr*.sh # CSR generation scripts +│ ├── deploy-*.sh # Automated deployment scripts +│ ├── install-ca-cert.sh # CA certificate installation +│ └── detect-system.sh # System type detection +│ +└── docs/ # Documentation + ├── README.md # Main documentation + ├── EXAMPLES.md # Usage examples + ├── DNS_INTEGRATION.md # DNS automation feature + └── STRUCTURE.md # This file +``` + +## 🎯 Usage + +All scripts should be run from the workspace root or scripts directory: + +```bash +# Run interactive certificate manager +./scripts/cert-manager.py + +# Sign a certificate +./scripts/sign-cert.sh certs/fritzbox/fritzbox.csr fritzbox 3650 + +# Deploy to Proxmox +./scripts/deploy-proxmox.sh certs/proxmox/srv-wmw-host01 +``` + +## 📝 Certificate Files + +Each certificate directory (e.g., `certs/fritzbox/`) typically contains: +- `*.key` - Private key +- `*.csr` - Certificate signing request +- `*.pem` - Signed certificate +- `*-cert.pem` - Certificate only +- `*-fullchain.pem` - Certificate + CA chain diff --git a/cert-manager.py b/scripts/cert-manager.py similarity index 70% rename from cert-manager.py rename to scripts/cert-manager.py index 18d12e6..9314fc3 100755 --- a/cert-manager.py +++ b/scripts/cert-manager.py @@ -9,8 +9,14 @@ 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 = { @@ -32,7 +38,8 @@ SYSTEM_TYPES = { 'deploy_script': 'deploy-proxmox.sh', 'key_location': '/tmp/{short_name}.key', 'default_port': '8006', - 'ssh_user': 'root' + 'ssh_user': 'root', + 'cert_dir': 'proxmox' }, 'homeassistant': { 'name': 'Home Assistant', @@ -40,35 +47,40 @@ SYSTEM_TYPES = { 'key_location': '/tmp/{short_name}.key', 'default_port': '8123', 'ssh_user': 'icke', - 'uses_local_csr': True + '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' + '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' + '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' + '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' + 'ssh_user': 'root', + 'cert_dir': 'other' } } @@ -158,6 +170,145 @@ def detect_system_type(target_host, script_dir, ssh_user=None, ssh_password=None 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") @@ -271,6 +422,15 @@ def main(): 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 @@ -280,6 +440,9 @@ def main(): 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 @@ -359,12 +522,16 @@ def main(): print(f"\nError: sign-cert.sh not found in {script_dir}") sys.exit(1) - print("\n" + "=" * 60) - print("Step 3: Deploying certificate to target host") - print("=" * 60) - + # 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): diff --git a/deploy-homeassistant.sh b/scripts/deploy-homeassistant.sh similarity index 100% rename from deploy-homeassistant.sh rename to scripts/deploy-homeassistant.sh diff --git a/deploy-proxmox.sh b/scripts/deploy-proxmox.sh similarity index 100% rename from deploy-proxmox.sh rename to scripts/deploy-proxmox.sh diff --git a/detect-system.sh b/scripts/detect-system.sh similarity index 100% rename from detect-system.sh rename to scripts/detect-system.sh diff --git a/generate-csr-ha.sh b/scripts/generate-csr-ha.sh similarity index 100% rename from generate-csr-ha.sh rename to scripts/generate-csr-ha.sh diff --git a/generate-csr-local.sh b/scripts/generate-csr-local.sh similarity index 100% rename from generate-csr-local.sh rename to scripts/generate-csr-local.sh diff --git a/generate-csr.sh b/scripts/generate-csr.sh similarity index 100% rename from generate-csr.sh rename to scripts/generate-csr.sh diff --git a/install-ca-cert.sh b/scripts/install-ca-cert.sh similarity index 100% rename from install-ca-cert.sh rename to scripts/install-ca-cert.sh diff --git a/sign-cert.sh b/scripts/sign-cert.sh similarity index 96% rename from sign-cert.sh rename to scripts/sign-cert.sh index f7ef9a5..77b0ef1 100755 --- a/sign-cert.sh +++ b/scripts/sign-cert.sh @@ -34,7 +34,9 @@ fi # Get absolute path of req file REQ_FILE=$(realpath "$REQ_FILE") -OUTPUT_FILE="${HOSTNAME}-cert.pem" +# 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"