一、需求
说明:
(1)读取域名列表文件。
(2)获取域名到期时间,进行告警后邮件提醒。
#!/bin/bash
## 第1步 配置文件
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 告警阈值(天)
WARNING_DAYS=20
CRITICAL_DAYS=10
ALERT_DAYS=10
# 超时设置(秒)
CONNECT_TIMEOUT=5
# 邮件配置
EMAIL_ENABLED=false
EMAIL_TO="xxxxxxx@qq.com" # 目标邮箱地址
EMAIL_COMMAND="mailx" # 邮件发送命令
EMAIL_SUBJECT_PREFIX="SSL证书检查报告" # 邮件主题前缀
# 全局变量
ALERT_DOMAINS=() # 存储需要告警的域名
ALL_CERTIFICATES=() # 存储所有证书检查结果
DOMAIN_LIST_FILE="domains.txt"# 日志函数
log() {local level=$1local message=$2local timestamp=$(date '+%Y-%m-%d %H:%M:%S')echo -e "[$timestamp] [$level] $message"
}
## 第4步
# 显示使用帮助
show_usage() {echo -e "${GREEN}SSL证书到期监控脚本${NC}"echo "使用方法: $0 [选项]"echo ""echo "选项:"echo " -f, --file <文件> 从文件读取域名列表 (默认: $DOMAIN_LIST_FILE)"echo " -d, --detailed 显示详细证书信息"echo " -q, --quick 快速检查模式(默认)"echo " -t, --timeout <秒> 设置连接超时时间(默认: ${CONNECT_TIMEOUT}秒)"echo " -m, --mail 启用邮件发送功能"echo " -h, --help 显示此帮助信息"echo ""echo "示例:"echo " $0 -f domains.txt # 从文件检查"echo " $0 -f domains.txt -d # 详细模式检查"echo " $0 -f domains.txt -m # 检查并发送邮件"echo ""echo "域名列表文件格式:"echo " # 注释行"echo " example.com"echo " google.com"echo " mysite.com:8443"
}
## 第9步
# 带超时的SSL连接检查
ssl_connect_with_timeout() {local domain=$1local port=$2# 使用timeout命令设置超时if command -v timeout >/dev/null 2>&1; then# 如果有timeout命令timeout $CONNECT_TIMEOUT bash -c "echo | openssl s_client -connect \"$domain:$port\" -servername \"$domain\" 2>/dev/null" 2>/dev/nullelse# 如果没有timeout命令,使用其他方法实现超时local pid# local result# 在后台执行openssl命令(echo | openssl s_client -connect "$domain:$port" -servername "$domain" 2>/dev/null) &pid=$!# 等待进程结束,最多等待CONNECT_TIMEOUT秒local count=0while [ $count -lt $CONNECT_TIMEOUT ]; doif ! kill -0 $pid 2>/dev/null; then# 进程已经结束breakfisleep 1count=$((count + 1))done# 如果进程还在运行,杀死它if kill -0 $pid 2>/dev/null; thenkill $pid 2>/dev/nullwait $pid 2>/dev/nullreturn 124 # 超时返回码else# 获取命令执行结果wait $pidreturn $?fifi
}## 第8步
# 获取SSL证书剩余天数
get_ssl_days_remaining() {local domain=$1local port=${2:-443}# 获取证书到期时间(带超时)local not_afternot_after=$(ssl_connect_with_timeout "$domain" "$port" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)local ssl_result=$?if [ $ssl_result -eq 124 ]; thenecho "TIMEOUT"return 1elif [ $ssl_result -ne 0 ] || [ -z "$not_after" ]; thenecho "ERROR"return 1fi# 转换为时间戳local expiry_timestampexpiry_timestamp=$(date -d "$not_after" +%s 2>/dev/null)if [ $? -ne 0 ]; thenecho "ERROR"return 1filocal current_timestamp=$(date +%s)local days_remaining=$(( (expiry_timestamp - current_timestamp) / 86400 ))echo "$days_remaining"
}## 第7步
# 快速检查模式(只检查剩余天数)
quick_check_domain() {local domain=$1local port=${2:-443}local days_remaining=$(get_ssl_days_remaining "$domain" "$port")if [ "$days_remaining" == "TIMEOUT" ]; thenecho -e "${RED}⏰ $domain: 连接超时 (${CONNECT_TIMEOUT}秒)${NC}"ALERT_DOMAINS+=("${RED}⏰ $domain: 连接超时${NC}")ALL_CERTIFICATES+=("${RED}⏰ $domain: 连接超时${NC}")elif [ "$days_remaining" == "ERROR" ]; thenecho -e "${RED}❌ $domain: 检查失败${NC}"ALERT_DOMAINS+=("${RED}❌ $domain: 检查失败${NC}")ALL_CERTIFICATES+=("${RED}❌ $domain: 检查失败${NC}")elif [ "$days_remaining" -lt 0 ]; thenecho -e "${RED}🔴 $domain: 已过期 $(( -days_remaining )) 天!${NC}"ALERT_DOMAINS+=("${RED}🔴 $domain: 已过期 $(( -days_remaining )) 天${NC}")ALL_CERTIFICATES+=("${RED}🔴 $domain: 已过期 $(( -days_remaining )) 天${NC}")elif [ "$days_remaining" -le "$ALERT_DAYS" ]; thenecho -e "${RED}🔴 $domain: 剩余 $days_remaining 天 (需处理)${NC}"ALERT_DOMAINS+=("${RED}🔴 $domain: 剩余 $days_remaining 天${NC}")ALL_CERTIFICATES+=("${RED}🔴 $domain: 剩余 $days_remaining 天${NC}")elif [ "$days_remaining" -le "$WARNING_DAYS" ]; thenecho -e "${YELLOW}🟡 $domain: 剩余 $days_remaining 天${NC}"ALL_CERTIFICATES+=("${YELLOW}🟡 $domain: 剩余 $days_remaining 天${NC}")elseecho -e "${GREEN}✅ $domain: 剩余 $days_remaining 天${NC}"ALL_CERTIFICATES+=("${GREEN}✅ $domain: 剩余 $days_remaining 天${NC}")fi
}## 第6步
# 从文件读取域名列表
read_domains_from_file() {local file_path=$1local domains=()if [ ! -f "$file_path" ]; thenecho -e "${RED}错误: 域名列表文件 $file_path 不存在${NC}" >&2return 1fiwhile IFS= read -r line || [ -n "$line" ]; do# 跳过空行和注释行line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continuedomains+=("$line")done < "$file_path"printf '%s\n' "${domains[@]}"
}## 第5步
# 批量检查域名
check_multiple_domains() {local mode=$1local file_path=$2local domains=()# 从文件读取域名if [ -n "$file_path" ] && [ -f "$file_path" ]; thenmapfile -t domains < <(read_domains_from_file "$file_path")if [ $? -ne 0 ]; thenecho -e "${RED}错误: 无法读取域名列表文件${NC}"return 1fielseecho -e "${RED}错误: 域名列表文件不存在${NC}"return 1fiecho -e "${BLUE}开始检查 ${#domains[@]} 个域名的SSL证书...${NC}"echo "告警阈值: ${ALERT_DAYS}天"echo "超时设置: ${CONNECT_TIMEOUT}秒"echo "模式: $mode"echo "=========================================="for domain_entry in "${domains[@]}"; do# 支持 domain:port 格式if [[ "$domain_entry" == *":"* ]]; thendomain="${domain_entry%:*}"port="${domain_entry#*:}"elsedomain="$domain_entry"port="443"fi# 清理域名domain=$(echo "$domain" | sed -e 's|^https://||' -e 's|^http://||' -e 's|/.*$||')case "$mode" in"detailed")get_ssl_cert_info "$domain" "$port" "$days_remaining";;"quick"|*)quick_check_domain "$domain" "$port";;esacdone
}## 第10步
# 显示告警汇总
show_alerts() {if [ ${#ALERT_DOMAINS[@]} -eq 0 ]; thenecho -e "\n${GREEN}🎉 所有证书状态正常,无需处理!${NC}"returnfiecho -e "\n${RED}🚨 证书告警汇总 (剩余 ≤ ${ALERT_DAYS} 天/连接问题):${NC}"echo "=========================================="local has_critical_alerts=0for alert in "${ALERT_DOMAINS[@]}"; doif [[ "$alert" == *"剩余 $ALERT_DAYS"* ]] || [[ "$alert" == *"已过期"* ]] || [[ "$alert" == *"检查失败"* ]] || [[ "$alert" == *"连接超时"* ]]; thenecho -e "$alert"has_critical_alerts=1fidoneif [ $has_critical_alerts -eq 0 ]; thenecho -e "${GREEN}暂无紧急告警${NC}"fiecho "=========================================="
}## 第11步
# 检查是否有需要告警的证书
has_alert_certificates() {for alert in "${ALERT_DOMAINS[@]}"; doreturn 0donereturn 1 # 没有需要告警的证书
}## 第13步
# 生成邮件内容
generate_email_content() {local email_content=""email_content+="SSL证书检查报告\n检查时间: $(date)\n\n"# 显示所有证书状态if [ ${#ALL_CERTIFICATES[@]} -gt 0 ]; thenemail_content+="📋 所有证书检查结果:\n"email_content+="==========================================\n"for cert in "${ALL_CERTIFICATES[@]}"; doemail_content+="$cert\n"doneemail_content+="==========================================\n\n"fi# 显示告警证书email_content+="🚨 证书告警汇总 (剩余 ≤ ${ALERT_DAYS} 天/连接问题)" if [ ${#ALERT_DOMAINS[@]} -eq 0 ]; thenemail_content+="\n${GREEN}🎉 所有证书状态正常,无需处理!${NC}"elif [ ${#ALERT_DOMAINS[@]} -gt 0 ]; thenemail_content+="\n==========================================\n"local has_critical_alerts=0for alert in "${ALERT_DOMAINS[@]}"; doemail_content+="$alert\n" has_critical_alerts=1donefi echo -e "$email_content"
}## 第12步
# 发送邮件
send_email() {local subject="${EMAIL_SUBJECT_PREFIX} - $(date '+%Y-%m-%d %H:%M')"local contentcontent=$(generate_email_content)#echo "$content"echo "--------------------------------------------------1111---------------------------------------------------- "echo "$content"echo "--------------------------------------------------1111---------------------------------------------------- "# 检查是否启用邮件发送if [ "$EMAIL_ENABLED" != "true" ]; thenecho -e "${YELLOW}邮件发送未启用${NC}"return 0fi# 检查邮件命令是否存在if ! command -v "$EMAIL_COMMAND" >/dev/null 2>&1; thenecho -e "${RED}错误: 邮件命令 '$EMAIL_COMMAND' 不存在${NC}"return 1fi# 发送邮件echo -e "$content" | sed 's/\x1b\[[0-9;]*m//g' | $EMAIL_COMMAND -s "$subject" "$EMAIL_TO"local mail_result=$?if [ $mail_result -eq 0 ]; thenecho -e "${GREEN}邮件发送成功${NC}"return 0elseecho -e "${RED}邮件发送失败 (返回码: $mail_result)${NC}"return 1fi
}## 第3步
# 主函数
main() {local mode="quick"local file_path=""# 处理命令行参数while [[ $# -gt 0 ]]; docase $1 in-f|--file)if [ -n "$2" ]; thenfile_path="$2"shift 2elseecho -e "${RED}错误: --file 需要参数${NC}"show_usageexit 1fi;;-q|--quick)mode="quick"shift;;-t|--timeout)if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; thenCONNECT_TIMEOUT="$2"shift 2elseecho -e "${RED}错误: --timeout 需要数字参数${NC}"show_usageexit 1fi;;-m|--mail)EMAIL_ENABLED=trueshift;;-h|--help)show_usageexit 0;;-*)echo -e "${RED}错误: 未知选项 $1${NC}"show_usageexit 1;;*)echo -e "${RED}错误: 不支持的参数 $1${NC}"show_usageexit 1;;esacdone# 如果没有指定文件,使用默认文件if [ -z "$file_path" ]; thenif [ -f "$DOMAIN_LIST_FILE" ]; thenfile_path="$DOMAIN_LIST_FILE"elseecho -e "${RED}错误: 未指定域名文件且默认文件 $DOMAIN_LIST_FILE 不存在${NC}"show_usageexit 1fifi# 执行检查check_multiple_domains "$mode" "$file_path"# 显示告警汇总show_alerts# 发送邮件(如果启用且有需要告警的证书)if [ "$EMAIL_ENABLED" == "true" ]; thenif has_alert_certificates; thenecho -e "\n${BLUE}检测到需要告警的证书,正在发送邮件...${NC}"send_emailelseecho -e "\n${GREEN}无需要告警的证书,无需发送邮件${NC}"fifi}## 第2步
# 执行主函数
main "$@"
然后执行命令
./ssl_alert.sh -f domains.txt -m
在crontab中添加定时任务,进行定期执行