#!/bin/bash ################################################ #### AD Certificate checker and renewal #### #### for Amazon, Ubuntu and RedHat servers #### #### #### #### Author: Phil Connor #### #### License: MIT #### #### Contact: contact@mylinux.work #### #### Version: 3.00-081425 #### ################################################ set -o pipefail SCRIPT_NAME=$(basename "$0") readonly SCRIPT_NAME # Default configuration readonly DEFAULT_PEM_PATH="/etc/pki/ca-trust/source/anchors/ad-cert.pem" readonly DEFAULT_DAYS_THRESHOLD=30 readonly DEFAULT_DOMAIN="example" readonly DEFAULT_NODE_DIR="/var/lib/node_exporter" # Configuration variables (can be overridden by environment) PEM_PATH=${PEM_PATH:-$DEFAULT_PEM_PATH} DAYS_THRESHOLD=${DAYS_THRESHOLD:-$DEFAULT_DAYS_THRESHOLD} DOMAIN=${DOMAIN:-$DEFAULT_DOMAIN} NODE_DIR=${NODE_DIR:-$DEFAULT_NODE_DIR} SERVER_TYPE=${SERVER_TYPE:-} DEBUG=${DEBUG:-} # Runtime flags MONITOR_ONLY=false RENEW_ONLY=false handle_error() { local exit_code=$1 local line_number=$2 echo "Error: $SCRIPT_NAME failed at line $line_number with exit code $exit_code" >&2 exit "$exit_code" } trap 'handle_error $? $LINENO' ERR debug_echo() { if [[ -n "$DEBUG" ]]; then echo "[DEBUG] $*" >&2 fi } show_help() { cat << EOF Usage: $SCRIPT_NAME [OPTIONS] SSL certificate checker and renewal script for Prometheus monitoring. OPTIONS: --monitor Only generate Prometheus metrics (no renewal) --renew Only handle certificate renewal (no monitoring) --all Run both monitoring and renewal (default) --help, -h Show this help message ENVIRONMENT VARIABLES: PEM_PATH Path to certificate file (default: $DEFAULT_PEM_PATH) DAYS_THRESHOLD Days before expiry to trigger renewal (default: $DEFAULT_DAYS_THRESHOLD) DOMAIN Domain name (default: $DEFAULT_DOMAIN) NODE_DIR Node exporter directory (default: $DEFAULT_NODE_DIR) SERVER_TYPE Server type (artifactory, bitbucket, cloudaccess, jira) DEBUG Enable debug output EXAMPLES: $SCRIPT_NAME --monitor SERVER_TYPE=bitbucket $SCRIPT_NAME --renew DEBUG=1 $SCRIPT_NAME --all EOF } validate_certificate_file() { local cert_file="$1" if [[ ! -f "$cert_file" ]]; then debug_echo "Certificate file not found: $cert_file" return 1 fi if ! openssl x509 -noout -text -in "$cert_file" >/dev/null 2>&1; then echo "Error: Invalid certificate file: $cert_file" >&2 return 1 fi return 0 } download_certificate() { local domain="$1" local output_file="$2" local server_url="us.${domain}.net:636" debug_echo "Downloading certificate from $server_url" if ! timeout 30 openssl s_client -connect "$server_url" -servername "us.${domain}.net" < /dev/null 2>/dev/null | \ sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > "$output_file"; then echo "Error: Failed to download certificate from $server_url" >&2 return 1 fi return 0 } calculate_certificate_dates() { local cert_file="$1" local -n days_left_ref=$2 local -n days_gone_ref=$3 local beg_date end_date beg_sec end_sec now_sec beg_date=$(openssl x509 -noout -startdate -in "$cert_file") end_date=$(openssl x509 -noout -enddate -in "$cert_file") beg_sec=$(date --date="${beg_date##*=}" +%s) end_sec=$(date --date="${end_date##*=}" +%s) now_sec=$(date +%s) days_gone_ref=$(( (now_sec - beg_sec) / 86400 )) days_left_ref=$(( (end_sec - now_sec) / 86400 )) debug_echo "Certificate valid from $(date -d @"$beg_sec") to $(date -d @"$end_sec")" debug_echo "Days gone: $days_gone_ref, Days left: $days_left_ref" } generate_prometheus_metrics() { local days_left="$1" local days_gone="$2" local output_file="$NODE_DIR/adcert_check.prom" debug_echo "Generating Prometheus metrics to $output_file" mkdir -p "$NODE_DIR" { echo '# HELP linux_ad_cert_expire AD Certificate expiration days' echo '# TYPE linux_ad_cert_expire gauge' if [[ $days_left -lt 0 ]]; then echo "linux_ad_cert_expire{status=\"expired\",days_gone=\"$days_gone\"} 0" else echo "linux_ad_cert_expire{status=\"valid\"} $days_left" fi } > "$output_file" } get_keystore_password() { local password_url="$1" local storepass="" # Try Vault HTTP API first if URL provided if [[ -n "$password_url" ]]; then debug_echo "Retrieving keystore password from $password_url" storepass=$(curl -sf -X GET "$password_url" 2>/dev/null | jq -r '.data.password // empty' 2>/dev/null || true) fi # Fall back to Vault CLI if [[ -z "$storepass" ]]; then debug_echo "Falling back to Vault CLI for keystore password" storepass=$(vault kv get -field=password secret/keystore 2>/dev/null || true) fi # Fall back to default if [[ -z "$storepass" ]]; then debug_echo "Using default keystore password" storepass="changeit" fi echo "$storepass" } execute_keytool_command() { local java_bin="$1" local keystore="$2" local action="$3" local cert_file="$4" local password_url="$5" local storepass storepass=$(get_keystore_password "$password_url") case "$action" in "delete") "$java_bin/keytool" -delete -alias ad -keystore "$keystore" -storepass "$storepass" 2>/dev/null || true ;; "import") "$java_bin/keytool" -import -noprompt -alias ad -keystore "$keystore" -file "$cert_file" -storepass "$storepass" ;; esac } handle_artifactory_renewal() { local java_bin keystore local vault_url="http://vault.${DOMAIN}.net/v1/secret/secret/artifactory/keytool" # Check app-specific paths first, then fall back to auto-detection java_bin="/opt/jfrog/artifactory/app/third-party/java/bin" keystore="/opt/jfrog/artifactory/app/third-party/java/lib/security/cacerts" if [[ ! -x "$java_bin/keytool" || ! -f "$keystore" ]]; then debug_echo "Artifactory default paths not found, searching for Java" if ! find_java_keystore java_bin keystore; then echo "Error: Could not find Java keytool or keystore for Artifactory" >&2 return 1 fi fi execute_keytool_command "$java_bin" "$keystore" "delete" "$PEM_PATH" "$vault_url" execute_keytool_command "$java_bin" "$keystore" "import" "$PEM_PATH" "$vault_url" systemctl restart artifactory } handle_bitbucket_renewal() { local java_bin keystore local vault_url="http://vault.${DOMAIN}.net/v1/secret/secret/bitbucket/keytool" # Check app-specific paths first, then fall back to auto-detection java_bin="/mnt/ebs/bitbucket/8.19.3/jre/bin" keystore="/mnt/ebs/bitbucket/8.19.3/jre/lib/security/cacerts" if [[ ! -x "$java_bin/keytool" || ! -f "$keystore" ]]; then debug_echo "Bitbucket default paths not found, searching for Java" if ! find_java_keystore java_bin keystore; then echo "Error: Could not find Java keytool or keystore for Bitbucket" >&2 return 1 fi fi if [[ -n "$DEBUG" ]]; then debug_echo "Would execute: $java_bin/keytool -delete -alias ad -keystore $keystore" debug_echo "Would execute: curl -X GET $vault_url" debug_echo "Would execute: $java_bin/keytool -import -alias ad -keystore $keystore -file $PEM_PATH" debug_echo "Would execute: systemctl restart atlbitbucket" else execute_keytool_command "$java_bin" "$keystore" "delete" "$PEM_PATH" "$vault_url" execute_keytool_command "$java_bin" "$keystore" "import" "$PEM_PATH" "$vault_url" systemctl restart atlbitbucket fi } handle_cloudaccess_renewal() { docker restart cloudaccess_server_ } handle_jira_renewal() { local java_bin keystore local vault_url="http://vault.${DOMAIN}.net/v1/secret/secret/jira/keytool" # Check app-specific paths first, then fall back to auto-detection java_bin="/mnt/ebs/jira/jre/bin" keystore="/mnt/ebs/jira/jre/lib/security/cacerts" if [[ ! -x "$java_bin/keytool" || ! -f "$keystore" ]]; then debug_echo "Jira default paths not found, searching for Java" if ! find_java_keystore java_bin keystore; then echo "Error: Could not find Java keytool or keystore for Jira" >&2 return 1 fi fi execute_keytool_command "$java_bin" "$keystore" "delete" "$PEM_PATH" "$vault_url" execute_keytool_command "$java_bin" "$keystore" "import" "$PEM_PATH" "$vault_url" systemctl restart jira } find_java_keystore() { local -n java_bin_ref=$1 local -n keystore_ref=$2 # Common Java installation paths local java_paths=( "/opt/jfrog/artifactory/app/third-party/java" "/mnt/ebs/bitbucket/*/jre" "/mnt/ebs/jira/jre" "/usr/lib/jvm/java-*-openjdk" "/usr/lib/jvm/default-java" "/opt/java" "/usr/java/latest" ) # Check JAVA_HOME first if [[ -n "$JAVA_HOME" && -x "$JAVA_HOME/bin/keytool" ]]; then java_bin_ref="$JAVA_HOME/bin" keystore_ref="$JAVA_HOME/lib/security/cacerts" if [[ -f "$keystore_ref" ]]; then debug_echo "Found Java via JAVA_HOME: $java_bin_ref" return 0 fi fi # Search common paths with glob expansion for path_pattern in "${java_paths[@]}"; do for java_dir in $path_pattern; do if [[ -d "$java_dir" ]]; then local bin_dir="$java_dir/bin" local cacerts="$java_dir/lib/security/cacerts" if [[ -x "$bin_dir/keytool" && -f "$cacerts" ]]; then java_bin_ref="$bin_dir" keystore_ref="$cacerts" debug_echo "Found Java at: $java_dir" return 0 fi fi done done # Fallback: try system keytool if command -v keytool >/dev/null 2>&1; then java_bin_ref="$(dirname "$(command -v keytool)")" # Try common system keystore locations local system_keystores=( "/etc/ssl/certs/java/cacerts" "/usr/lib/jvm/default-java/lib/security/cacerts" "/etc/pki/ca-trust/extracted/java/cacerts" ) for keystore in "${system_keystores[@]}"; do if [[ -f "$keystore" ]]; then keystore_ref="$keystore" debug_echo "Found system Java at: $java_bin_ref" return 0 fi done fi return 1 } handle_server_renewal() { if [[ -z "$SERVER_TYPE" ]]; then echo "Error: SERVER_TYPE environment variable must be set for renewal" >&2 echo "Valid values: artifactory, bitbucket, cloudaccess, jira" >&2 return 1 fi debug_echo "Handling renewal for server type: $SERVER_TYPE" case "$SERVER_TYPE" in "artifactory") handle_artifactory_renewal ;; "bitbucket") handle_bitbucket_renewal ;; "cloudaccess") handle_cloudaccess_renewal ;; "jira") handle_jira_renewal ;; *) echo "Error: Unknown server type: $SERVER_TYPE" >&2 echo "Valid values: artifactory, bitbucket, cloudaccess, jira" >&2 return 1 ;; esac } parse_arguments() { while [[ $# -gt 0 ]]; do case $1 in --monitor) MONITOR_ONLY=true shift ;; --renew) RENEW_ONLY=true shift ;; --all) MONITOR_ONLY=false RENEW_ONLY=false shift ;; --help|-h) show_help exit 0 ;; *) echo "Error: Unknown option: $1" >&2 show_help >&2 exit 1 ;; esac done } main() { parse_arguments "$@" # Check if certificate file exists, if not exit silently if [[ ! -f "$PEM_PATH" ]]; then debug_echo "Certificate file not found: $PEM_PATH" exit 0 fi # Download fresh certificate if ! download_certificate "$DOMAIN" "$PEM_PATH"; then exit 1 fi # Validate the downloaded certificate if ! validate_certificate_file "$PEM_PATH"; then exit 1 fi # Calculate certificate expiration dates local days_left days_gone calculate_certificate_dates "$PEM_PATH" days_left days_gone # Handle monitoring (unless renew-only mode) if [[ "$RENEW_ONLY" != true ]]; then generate_prometheus_metrics "$days_left" "$days_gone" debug_echo "Generated Prometheus metrics" fi # Handle renewal (unless monitor-only mode) if [[ "$MONITOR_ONLY" != true && $days_left -le $DAYS_THRESHOLD ]]; then debug_echo "Certificate expires in $days_left days (threshold: $DAYS_THRESHOLD)" if ! handle_server_renewal; then exit 1 fi debug_echo "Certificate renewal completed" fi debug_echo "Script completed successfully" } # Execute main function if script is run directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi