Initial commit: Certificate management tools
- cert-manager.py: Interactive certificate lifecycle management - generate-csr.sh: Generate CSR on remote host - sign-cert.sh: Sign certificate with UCS CA - README.md: Complete documentation - .gitignore: Ignore certificate and config files Features: - Interactive prompts with default values - Config persistence between runs - Remote CSR generation with proper server extensions - Automated CA signing - Optional certificate deployment
This commit is contained in:
225
cert-manager.py
Executable file
225
cert-manager.py
Executable file
@@ -0,0 +1,225 @@
|
||||
#!/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
|
||||
from pathlib import Path
|
||||
|
||||
# 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',
|
||||
'last_target_host': '',
|
||||
'last_common_name': ''
|
||||
}
|
||||
|
||||
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)
|
||||
# Merge with defaults to add any new fields
|
||||
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 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'])
|
||||
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)
|
||||
|
||||
common_name = prompt_with_default("Common Name (FQDN)", config['last_common_name'])
|
||||
|
||||
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'])
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Summary:")
|
||||
print("=" * 60)
|
||||
print(f"Target Host: {target_host}")
|
||||
print(f"Common Name: {common_name}")
|
||||
print(f"Country: {country}")
|
||||
print(f"State: {state}")
|
||||
print(f"Locality: {locality}")
|
||||
print(f"Organization: {organization}")
|
||||
print(f"Org Unit: {org_unit}")
|
||||
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)
|
||||
|
||||
# Save config for next run
|
||||
config['last_target_host'] = target_host
|
||||
config['last_common_name'] = common_name
|
||||
save_config(config)
|
||||
|
||||
# Get script directory
|
||||
script_dir = Path(__file__).parent.absolute()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Step 1: Generating CSR on target host")
|
||||
print("=" * 60)
|
||||
|
||||
# Run generate-csr.sh
|
||||
generate_cmd = [
|
||||
str(script_dir / 'generate-csr.sh'),
|
||||
target_host,
|
||||
common_name,
|
||||
country,
|
||||
state,
|
||||
locality,
|
||||
organization,
|
||||
org_unit
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(generate_cmd, check=True)
|
||||
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)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Step 3: Deploying certificate to target host")
|
||||
print("=" * 60)
|
||||
|
||||
cert_file = f"{short_name}-cert.pem"
|
||||
|
||||
if yes_no_prompt("Do you want to copy the certificate back to the target host?", True):
|
||||
try:
|
||||
# Copy certificate to target
|
||||
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")
|
||||
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)")
|
||||
print(f" - /tmp/{short_name}.crt (Certificate)")
|
||||
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)
|
||||
Reference in New Issue
Block a user