Files
linux-scripts/cert-check.sh
T

429 lines
13 KiB
Bash
Executable File

#!/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