#!/bin/bash set -euo pipefail # Proxmox Certificate Verification Script # Verifies ACME certificate installation and validity on Proxmox nodes # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration NODES=( "ml110-01.sankofa.nexus:192.168.11.10" "r630-01.sankofa.nexus:192.168.11.11" ) PORT=8006 WARN_DAYS=30 # Warn if certificate expires in less than 30 days log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $*" >&2 } info() { echo -e "${GREEN}✓${NC} $*" } warn() { echo -e "${YELLOW}⚠${NC} $*" >&2 } error() { echo -e "${RED}✗${NC} $*" >&2 } # Check if openssl is available check_openssl() { if ! command -v openssl &> /dev/null; then error "openssl is not installed. Please install openssl to use this script." exit 1 fi } # Get certificate expiration date get_cert_expiry() { local hostname=$1 local ip=$2 local port=$3 # Connect and extract expiration date local expiry=$(echo | openssl s_client -connect "${ip}:${port}" -servername "${hostname}" 2>/dev/null | \ openssl x509 -noout -enddate 2>/dev/null | \ cut -d= -f2) if [ -z "${expiry}" ]; then return 1 fi echo "${expiry}" } # Calculate days until expiration days_until_expiry() { local expiry_date=$1 # Convert expiry date to epoch timestamp # Try Linux date format first, then macOS format local expiry_epoch if expiry_epoch=$(date -d "${expiry_date}" +%s 2>/dev/null); then : elif expiry_epoch=$(date -j -f "%b %d %H:%M:%S %Y %Z" "${expiry_date}" +%s 2>/dev/null); then : else return 1 fi local now_epoch=$(date +%s) local diff=$((expiry_epoch - now_epoch)) local days=$((diff / 86400)) echo "${days}" } # Get certificate issuer get_cert_issuer() { local hostname=$1 local ip=$2 local port=$3 local issuer=$(echo | openssl s_client -connect "${ip}:${port}" -servername "${hostname}" 2>/dev/null | \ openssl x509 -noout -issuer 2>/dev/null | \ sed 's/issuer=//') echo "${issuer}" } # Get certificate subject get_cert_subject() { local hostname=$1 local ip=$2 local port=$3 local subject=$(echo | openssl s_client -connect "${ip}:${port}" -servername "${hostname}" 2>/dev/null | \ openssl x509 -noout -subject 2>/dev/null | \ sed 's/subject=//') echo "${subject}" } # Get certificate SANs (Subject Alternative Names) get_cert_sans() { local hostname=$1 local ip=$2 local port=$3 local sans=$(echo | openssl s_client -connect "${ip}:${port}" -servername "${hostname}" 2>/dev/null | \ openssl x509 -noout -text 2>/dev/null | \ grep -A1 "Subject Alternative Name" | \ sed 's/.*DNS://g' | \ tr ',' '\n' | \ sed 's/^ *//' | \ grep -v "^$") echo "${sans}" } # Test SSL connection test_ssl_connection() { local hostname=$1 local ip=$2 local port=$3 # Test connection and get certificate local output=$(echo | openssl s_client -connect "${ip}:${port}" -servername "${hostname}" 2>&1) if echo "${output}" | grep -q "Verify return code: 0"; then return 0 else return 1 fi } # Verify certificate for a node verify_node_cert() { local node_info=$1 local hostname=$(echo "${node_info}" | cut -d: -f1) local ip=$(echo "${node_info}" | cut -d: -f2) log "Checking certificate for ${hostname} (${ip}:${PORT})..." # Test SSL connection if ! test_ssl_connection "${hostname}" "${ip}" "${PORT}"; then error "SSL connection failed for ${hostname}" return 1 fi info "SSL connection successful" # Get certificate details local issuer=$(get_cert_issuer "${hostname}" "${ip}" "${PORT}") local subject=$(get_cert_subject "${hostname}" "${ip}" "${PORT}") local expiry=$(get_cert_expiry "${hostname}" "${ip}" "${PORT}") if [ -z "${expiry}" ]; then error "Could not retrieve certificate expiration for ${hostname}" return 1 fi # Check if certificate is from Let's Encrypt if echo "${issuer}" | grep -qi "lets encrypt\|let's encrypt\|letsencrypt"; then info "Certificate issuer: Let's Encrypt" else warn "Certificate issuer: ${issuer}" warn "Certificate may not be from Let's Encrypt" fi # Display certificate subject info "Certificate subject: ${subject}" # Check expiration local days=$(days_until_expiry "${expiry}") if [ "${days}" -lt 0 ]; then error "Certificate expired ${days#-} days ago!" error "Expiration date: ${expiry}" return 1 elif [ "${days}" -lt "${WARN_DAYS}" ]; then warn "Certificate expires in ${days} days (${expiry})" warn "Renewal should occur automatically, but please monitor" else info "Certificate expires in ${days} days (${expiry})" fi # Get and display SANs local sans=$(get_cert_sans "${hostname}" "${ip}" "${PORT}") if [ -n "${sans}" ]; then info "Certificate SANs:" echo "${sans}" | while read -r san; do echo " - ${san}" done fi # Verify hostname matches if echo "${subject}" | grep -qi "${hostname}" || echo "${sans}" | grep -qi "${hostname}"; then info "Hostname ${hostname} matches certificate" else warn "Hostname ${hostname} may not match certificate" warn "Subject: ${subject}" fi echo "" return 0 } # Main verification function main() { log "Proxmox Certificate Verification Script" log "========================================" echo "" check_openssl local total_nodes=${#NODES[@]} local passed=0 local failed=0 for node_info in "${NODES[@]}"; do if verify_node_cert "${node_info}"; then ((passed++)) else ((failed++)) fi done echo "" log "========================================" log "Verification Summary" log "========================================" info "Total nodes checked: ${total_nodes}" info "Passed: ${passed}" if [ "${failed}" -gt 0 ]; then error "Failed: ${failed}" exit 1 else info "All certificates are valid" exit 0 fi } # Run main function main "$@"