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

BIN
ca/ucs-ca-cert.der Normal file

Binary file not shown.

77
docs/DNS_INTEGRATION.md Normal file
View File

@@ -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

View File

@@ -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/<system-type>/` 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 <hostname> <common-name> [country] [state] [locality] [org] [ou] [key-bits]
./scripts/generate-csr.sh <hostname> <common-name> [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 <req-file> <hostname> [days]
./scripts/sign-cert.sh <req-file> <hostname> [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 <hostname>
./scripts/detect-system.sh <hostname>
```
**Returns:** `proxmox`, `pfsense`, `truenas`, `ucs`, or `unknown`

57
docs/STRUCTURE.md Normal file
View File

@@ -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

View File

@@ -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):

View File

@@ -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"