shell脚本监控ssl证书到期时间
一、需求
说明:
(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=$1
local message=$2
local 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=$1
local 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/null
else
# 如果没有timeout命令,使用其他方法实现超时
local pid
# local result
# 在后台执行openssl命令
(echo | openssl s_client -connect "$domain:$port" -servername "$domain" 2>/dev/null) &
pid=$!
# 等待进程结束,最多等待CONNECT_TIMEOUT秒
local count=0
while [ $count -lt $CONNECT_TIMEOUT ]; do
if ! kill -0 $pid 2>/dev/null; then
# 进程已经结束
break
fi
sleep 1
count=$((count + 1))
done
# 如果进程还在运行,杀死它
if kill -0 $pid 2>/dev/null; then
kill $pid 2>/dev/null
wait $pid 2>/dev/null
return 124 # 超时返回码
else
# 获取命令执行结果
wait $pid
return $?
fi
fi
}
## 第8步
# 获取SSL证书剩余天数
get_ssl_days_remaining() {
local domain=$1
local port=${2:-443}
# 获取证书到期时间(带超时)
local not_after
not_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 ]; then
echo "TIMEOUT"
return 1
elif [ $ssl_result -ne 0 ] || [ -z "$not_after" ]; then
echo "ERROR"
return 1
fi
# 转换为时间戳
local expiry_timestamp
expiry_timestamp=$(date -d "$not_after" +%s 2>/dev/null)
if [ $? -ne 0 ]; then
echo "ERROR"
return 1
fi
local current_timestamp=$(date +%s)
local days_remaining=$(( (expiry_timestamp - current_timestamp) / 86400 ))
echo "$days_remaining"
}
## 第7步
# 快速检查模式(只检查剩余天数)
quick_check_domain() {
local domain=$1
local port=${2:-443}
local days_remaining=$(get_ssl_days_remaining "$domain" "$port")
if [ "$days_remaining" == "TIMEOUT" ]; then
echo -e "${RED}⏰ $domain: 连接超时 (${CONNECT_TIMEOUT}秒)${NC}"
ALERT_DOMAINS+=("${RED}⏰ $domain: 连接超时${NC}")
ALL_CERTIFICATES+=("${RED}⏰ $domain: 连接超时${NC}")
elif [ "$days_remaining" == "ERROR" ]; then
echo -e "${RED}❌ $domain: 检查失败${NC}"
ALERT_DOMAINS+=("${RED}❌ $domain: 检查失败${NC}")
ALL_CERTIFICATES+=("${RED}❌ $domain: 检查失败${NC}")
elif [ "$days_remaining" -lt 0 ]; then
echo -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" ]; then
echo -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" ]; then
echo -e "${YELLOW}🟡 $domain: 剩余 $days_remaining 天${NC}"
ALL_CERTIFICATES+=("${YELLOW}🟡 $domain: 剩余 $days_remaining 天${NC}")
else
echo -e "${GREEN}✅ $domain: 剩余 $days_remaining 天${NC}"
ALL_CERTIFICATES+=("${GREEN}✅ $domain: 剩余 $days_remaining 天${NC}")
fi
}
## 第6步
# 从文件读取域名列表
read_domains_from_file() {
local file_path=$1
local domains=()
if [ ! -f "$file_path" ]; then
echo -e "${RED}错误: 域名列表文件 $file_path 不存在${NC}" >&2
return 1
fi
while IFS= read -r line || [ -n "$line" ]; do
# 跳过空行和注释行
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
domains+=("$line")
done < "$file_path"
printf '%s\n' "${domains[@]}"
}
## 第5步
# 批量检查域名
check_multiple_domains() {
local mode=$1
local file_path=$2
local domains=()
# 从文件读取域名
if [ -n "$file_path" ] && [ -f "$file_path" ]; then
mapfile -t domains < <(read_domains_from_file "$file_path")
if [ $? -ne 0 ]; then
echo -e "${RED}错误: 无法读取域名列表文件${NC}"
return 1
fi
else
echo -e "${RED}错误: 域名列表文件不存在${NC}"
return 1
fi
echo -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" == *":"* ]]; then
domain="${domain_entry%:*}"
port="${domain_entry#*:}"
else
domain="$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"
;;
esac
done
}
## 第10步
# 显示告警汇总
show_alerts() {
if [ ${#ALERT_DOMAINS[@]} -eq 0 ]; then
echo -e "\n${GREEN}🎉 所有证书状态正常,无需处理!${NC}"
return
fi
echo -e "\n${RED}🚨 证书告警汇总 (剩余 ≤ ${ALERT_DAYS} 天/连接问题):${NC}"
echo "=========================================="
local has_critical_alerts=0
for alert in "${ALERT_DOMAINS[@]}"; do
if [[ "$alert" == *"剩余 $ALERT_DAYS"* ]] ||
[[ "$alert" == *"已过期"* ]] ||
[[ "$alert" == *"检查失败"* ]] ||
[[ "$alert" == *"连接超时"* ]]; then
echo -e "$alert"
has_critical_alerts=1
fi
done
if [ $has_critical_alerts -eq 0 ]; then
echo -e "${GREEN}暂无紧急告警${NC}"
fi
echo "=========================================="
}
## 第11步
# 检查是否有需要告警的证书
has_alert_certificates() {
for alert in "${ALERT_DOMAINS[@]}"; do
return 0
done
return 1 # 没有需要告警的证书
}
## 第13步
# 生成邮件内容
generate_email_content() {
local email_content=""
email_content+="SSL证书检查报告\n检查时间: $(date)\n\n"
# 显示所有证书状态
if [ ${#ALL_CERTIFICATES[@]} -gt 0 ]; then
email_content+="📋 所有证书检查结果:\n"
email_content+="==========================================\n"
for cert in "${ALL_CERTIFICATES[@]}"; do
email_content+="$cert\n"
done
email_content+="==========================================\n\n"
fi
# 显示告警证书
email_content+="🚨 证书告警汇总 (剩余 ≤ ${ALERT_DAYS} 天/连接问题)"
if [ ${#ALERT_DOMAINS[@]} -eq 0 ]; then
email_content+="\n${GREEN}🎉 所有证书状态正常,无需处理!${NC}"
elif [ ${#ALERT_DOMAINS[@]} -gt 0 ]; then
email_content+="\n==========================================\n"
local has_critical_alerts=0
for alert in "${ALERT_DOMAINS[@]}"; do
email_content+="$alert\n"
has_critical_alerts=1
done
fi
echo -e "$email_content"
}
## 第12步
# 发送邮件
send_email() {
local subject="${EMAIL_SUBJECT_PREFIX} - $(date '+%Y-%m-%d %H:%M')"
local content
content=$(generate_email_content)
#echo "$content"
echo "--------------------------------------------------1111---------------------------------------------------- "
echo "$content"
echo "--------------------------------------------------1111---------------------------------------------------- "
# 检查是否启用邮件发送
if [ "$EMAIL_ENABLED" != "true" ]; then
echo -e "${YELLOW}邮件发送未启用${NC}"
return 0
fi
# 检查邮件命令是否存在
if ! command -v "$EMAIL_COMMAND" >/dev/null 2>&1; then
echo -e "${RED}错误: 邮件命令 '$EMAIL_COMMAND' 不存在${NC}"
return 1
fi
# 发送邮件
echo -e "$content" | sed 's/\x1b\[[0-9;]*m//g' | $EMAIL_COMMAND -s "$subject" "$EMAIL_TO"
local mail_result=$?
if [ $mail_result -eq 0 ]; then
echo -e "${GREEN}邮件发送成功${NC}"
return 0
else
echo -e "${RED}邮件发送失败 (返回码: $mail_result)${NC}"
return 1
fi
}
## 第3步
# 主函数
main() {
local mode="quick"
local file_path=""
# 处理命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-f|--file)
if [ -n "$2" ]; then
file_path="$2"
shift 2
else
echo -e "${RED}错误: --file 需要参数${NC}"
show_usage
exit 1
fi
;;
-q|--quick)
mode="quick"
shift
;;
-t|--timeout)
if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; then
CONNECT_TIMEOUT="$2"
shift 2
else
echo -e "${RED}错误: --timeout 需要数字参数${NC}"
show_usage
exit 1
fi
;;
-m|--mail)
EMAIL_ENABLED=true
shift
;;
-h|--help)
show_usage
exit 0
;;
-*)
echo -e "${RED}错误: 未知选项 $1${NC}"
show_usage
exit 1
;;
*)
echo -e "${RED}错误: 不支持的参数 $1${NC}"
show_usage
exit 1
;;
esac
done
# 如果没有指定文件,使用默认文件
if [ -z "$file_path" ]; then
if [ -f "$DOMAIN_LIST_FILE" ]; then
file_path="$DOMAIN_LIST_FILE"
else
echo -e "${RED}错误: 未指定域名文件且默认文件 $DOMAIN_LIST_FILE 不存在${NC}"
show_usage
exit 1
fi
fi
# 执行检查
check_multiple_domains "$mode" "$file_path"
# 显示告警汇总
show_alerts
# 发送邮件(如果启用且有需要告警的证书)
if [ "$EMAIL_ENABLED" == "true" ]; then
if has_alert_certificates; then
echo -e "\n${BLUE}检测到需要告警的证书,正在发送邮件...${NC}"
send_email
else
echo -e "\n${GREEN}无需要告警的证书,无需发送邮件${NC}"
fi
fi
}
## 第2步
# 执行主函数
main "$@"
然后执行命令
./ssl_alert.sh -f domains.txt -m
在crontab中添加定时任务,进行定期执行

浙公网安备 33010602011771号