基于shell脚本来检测SSL证书过期并发送通知到钉钉

bash 复制代码
#!/bin/bash

# SSL证书过期检测脚本
# 支持多域名检测,发送通知到钉钉

# 配置部分
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN"
DAYS_THRESHOLD=30  # 过期前多少天开始警告
RETRY_COUNT=3  # 重试次数
TIMEOUT=10  # 连接超时时间

# 需要检测的域名列表,格式:域名:端口
DOMAINS=(
    "example.com:443"
    "www.example.com:443"
    "api.example.com:443"
    "mail.example.com:993"
)

# 钉钉机器人安全设置(可选)
# 如果需要加签,请设置SECRET
DINGTALK_SECRET=""
# 如果需要关键词,请设置KEYWORD
DINGTALK_KEYWORD="证书告警"

# 日志文件
LOG_FILE="/var/log/ssl-checker.log"

# 颜色定义
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 初始化日志
log() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

# 计算日期差异
get_days_diff() {
    local expiry_date=$1
    local current_date=$(date +%s)
    local expiry_seconds=$(date -d "$expiry_date" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$expiry_date" +%s 2>/dev/null)
    
    if [ -z "$expiry_seconds" ]; then
        echo "0"
        return
    fi
    
    local diff_seconds=$((expiry_seconds - current_date))
    local diff_days=$((diff_seconds / 86400))
    echo $diff_days
}

# 生成钉钉签名(如果需要)
generate_sign() {
    if [ -n "$DINGTALK_SECRET" ]; then
        local timestamp=$(date +%s%3N)
        local string_to_sign="${timestamp}\n${DINGTALK_SECRET}"
        local sign=$(echo -en "$string_to_sign" | openssl dgst -sha256 -hmac "$DINGTALK_SECRET" -binary | base64)
        echo "&timestamp=${timestamp}&sign=${sign}"
    else
        echo ""
    fi
}

# 发送钉钉通知
send_dingtalk_message() {
    local domain=$1
    local days_left=$2
    local expiry_date=$3
    local status=$4
    
    # 根据状态设置消息内容
    if [ "$status" = "expired" ]; then
        local title="🔴 SSL证书已过期"
        local color="FF0000"
    elif [ "$days_left" -le 7 ]; then
        local title="🟠 SSL证书即将过期(${days_left}天后)"
        local color="FF9900"
    elif [ "$days_left" -le "$DAYS_THRESHOLD" ]; then
        local title="🟡 SSL证书即将过期(${days_left}天后)"
        local color="FFCC00"
    else
        local title="🟢 SSL证书正常"
        local color="00CC00"
    fi
    
    # 构造消息
    local message="
## ${title}

**域名:** ${domain}
**过期时间:** ${expiry_date}
**剩余天数:** ${days_left}天
**检测时间:** $(date '+%Y-%m-%d %H:%M:%S')

> 请及时处理证书更新事宜"
    
    # 添加关键词(如果需要)
    if [ -n "$DINGTALK_KEYWORD" ]; then
        message="${DINGTICK_KEYWORD}\n${message}"
    fi
    
    # 构造JSON数据
    local data=$(cat <<EOF
{
    "msgtype": "markdown",
    "markdown": {
        "title": "SSL证书检测通知",
        "text": "$message"
    },
    "at": {
        "isAtAll": false
    }
}
EOF
)
    
    # 生成签名
    local sign_params=$(generate_sign)
    local webhook_url="${DINGTALK_WEBHOOK}${sign_params}"
    
    # 发送请求
    for i in $(seq 1 $RETRY_COUNT); do
        local response=$(curl -s -w "%{http_code}" -X POST \
            -H "Content-Type: application/json" \
            -d "$data" \
            "$webhook_url" -o /dev/null)
        
        if [ "$response" = "200" ]; then
            log "INFO" "钉钉通知发送成功:${domain}"
            return 0
        else
            log "WARNING" "钉钉通知发送失败,重试第${i}次:${domain}"
            sleep 2
        fi
    done
    
    log "ERROR" "钉钉通知发送失败:${domain}"
    return 1
}

# 检测单个域名的证书
check_domain_ssl() {
    local domain_info=$1
    local domain=$(echo "$domain_info" | cut -d: -f1)
    local port=$(echo "$domain_info" | cut -d: -f2)
    
    if [ -z "$port" ]; then
        port=443
    fi
    
    log "INFO" "开始检测域名:${domain}:${port}"
    
    # 获取证书信息
    local cert_info
    cert_info=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
    
    if [ $? -ne 0 ] || [ -z "$cert_info" ]; then
        log "ERROR" "无法获取证书信息:${domain}:${port}"
        return 1
    fi
    
    # 提取过期时间
    local expiry_date=$(echo "$cert_info" | grep -i "notAfter" | cut -d= -f2-)
    
    if [ -z "$expiry_date" ]; then
        log "ERROR" "无法解析证书过期时间:${domain}:${port}"
        return 1
    fi
    
    # 计算剩余天数
    local days_left=$(get_days_diff "$expiry_date")
    
    # 获取证书其他信息
    local issuer=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -issuer 2>/dev/null | cut -d= -f2-)
    local subject=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -subject 2>/dev/null | cut -d= -f2-)
    
    # 输出结果
    echo -e "${BLUE}========================================${NC}"
    echo -e "${GREEN}域名:${NC}${domain}:${port}"
    echo -e "${GREEN}主题:${NC}${subject}"
    echo -e "${GREEN}颁发者:${NC}${issuer}"
    echo -e "${GREEN}过期时间:${NC}${expiry_date}"
    
    if [ "$days_left" -le 0 ]; then
        echo -e "${RED}状态:证书已过期${NC}"
        send_dingtalk_message "${domain}:${port}" 0 "$expiry_date" "expired"
        return 2
    elif [ "$days_left" -le "$DAYS_THRESHOLD" ]; then
        echo -e "${YELLOW}状态:剩余 ${days_left} 天过期${NC}"
        send_dingtalk_message "${domain}:${port}" "$days_left" "$expiry_date" "warning"
        return 1
    else
        echo -e "${GREEN}状态:剩余 ${days_left} 天过期${NC}"
        send_dingtalk_message "${domain}:${port}" "$days_left" "$expiry_date" "normal"
        return 0
    fi
}

# 主函数
main() {
    log "INFO" "========== SSL证书检测开始 =========="
    
    local expired_count=0
    local warning_count=0
    local total_count=0
    
    for domain_info in "${DOMAINS[@]}"; do
        total_count=$((total_count + 1))
        if check_domain_ssl "$domain_info"; then
            case $? in
                2)
                    expired_count=$((expired_count + 1))
                    ;;
                1)
                    warning_count=$((warning_count + 1))
                    ;;
            esac
        fi
        sleep 1  # 避免请求过于频繁
    done
    
    log "INFO" "检测完成:总共${total_count}个域名,${expired_count}个已过期,${warning_count}个即将过期"
    log "INFO" "========== SSL证书检测结束 =========="
    
    # 如果有证书过期,返回非0退出码
    if [ "$expired_count" -gt 0 ]; then
        exit 2
    elif [ "$warning_count" -gt 0 ]; then
        exit 1
    else
        exit 0
    fi
}

# 安装openssl检查
check_dependencies() {
    if ! command -v openssl &> /dev/null; then
        echo -e "${RED}错误:openssl 未安装${NC}"
        echo "请使用以下命令安装:"
        echo "  Ubuntu/Debian: sudo apt-get install openssl"
        echo "  CentOS/RHEL: sudo yum install openssl"
        exit 1
    fi
    
    if ! command -v curl &> /dev/null; then
        echo -e "${RED}错误:curl 未安装${NC}"
        echo "请使用以下命令安装:"
        echo "  Ubuntu/Debian: sudo apt-get install curl"
        echo "  CentOS/RHEL: sudo yum install curl"
        exit 1
    fi
}

# 显示帮助信息
show_help() {
    echo "SSL证书过期检测脚本"
    echo ""
    echo "用法: $0 [选项]"
    echo ""
    echo "选项:"
    echo "  -h, --help     显示此帮助信息"
    echo "  -d, --domain   检测单个域名(格式:domain:port)"
    echo "  -f, --file     从文件读取域名列表"
    echo "  -t, --days     设置警告阈值(默认:30天)"
    echo "  --test         测试钉钉通知"
    echo ""
    echo "示例:"
    echo "  $0 -d example.com:443"
    echo "  $0 -f domains.txt"
    echo "  $0 -t 15 -d example.com:443"
}

# 测试钉钉通知
test_dingtalk() {
    log "INFO" "发送测试通知..."
    send_dingtalk_message "test.example.com:443" 365 "Dec 31 23:59:59 2025 GMT" "normal"
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}测试通知发送成功!${NC}"
    else
        echo -e "${RED}测试通知发送失败!${NC}"
    fi
}

# 参数解析
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                show_help
                exit 0
                ;;
            -d|--domain)
                DOMAINS=("$2")
                shift 2
                ;;
            -f|--file)
                if [ -f "$2" ]; then
                    mapfile -t DOMAINS < "$2"
                else
                    echo -e "${RED}错误:文件 $2 不存在${NC}"
                    exit 1
                fi
                shift 2
                ;;
            -t|--days)
                DAYS_THRESHOLD="$2"
                shift 2
                ;;
            --test)
                test_dingtalk
                exit 0
                ;;
            *)
                echo -e "${RED}未知选项:$1${NC}"
                show_help
                exit 1
                ;;
        esac
    done
}

# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    # 检查依赖
    check_dependencies
    
    # 解析参数
    parse_args "$@"
    
    # 运行主函数
    main
fi
  1. 配置脚本
    编辑脚本,修改以下配置:
bash 复制代码
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN"
DAYS_THRESHOLD=30  # 过期前多少天开始警告
DOMAINS=(
    "example.com:443"
    "www.example.com:443"
)
  1. 获取钉钉Webhook
    在钉钉群 -> 群设置 -> 智能群助手 -> 添加机器人 -> 自定义
    设置机器人名称和安全设置
    复制Webhook地址

3. 运行脚本

bash 复制代码
# 给予执行权限
chmod +x ssl_checker.sh

# 检测所有配置的域名
./ssl_checker.sh

# 检测单个域名
./ssl_checker.sh -d example.com:443

# 从文件读取域名列表
./ssl_checker.sh -f domains.txt

# 设置警告阈值为15天
./ssl_checker.sh -t 15

# 测试钉钉通知
./ssl_checker.sh --test

# 显示帮助
./ssl_checker.sh -h
  1. 创建域名列表文件
    创建 domains.txt:
bash 复制代码
example.com:443
www.example.com:443
api.example.com:443
mail.example.com:993
  1. 设置定时任务
bash 复制代码
# 编辑crontab
crontab -e
# 每天凌晨2点运行
0 2 * * * /path/to/ssl_checker.sh >> /var/log/ssl-checker.log 2>&1
# 每6小时运行一次
0 */6 * * * /path/to/ssl_checker.sh >> /var/log/ssl-checker.log 2>&1

功能特性

多域名检测:支持批量检测多个域名

智能告警:过期/即将过期(可配置阈值)时发送通知

钉钉通知:支持Markdown格式,颜色区分状态

重试机制:网络失败时自动重试

日志记录:详细的操作日志

错误处理:完善的错误检测和处理

参数支持:支持命令行参数

颜色输出:终端彩色输出,便于查看

钉钉通知效果

🔴 红色:证书已过期

🟠 橙色:7天内过期

🟡 黄色:30天内过期

🟢 绿色:证书正常

安全注意事项

将脚本放在安全目录,避免泄露Webhook

定期检查钉钉机器人的安全设置

建议使用加签方式增强安全性

设置适当的文件权限:chmod 700 ssl_checker.sh

相关推荐
CCPC不拿奖不改名2 小时前
基于FastAPI的API开发(爬虫的工作原理):从设计到部署详解+面试习题
爬虫·python·网络协议·tcp/ip·http·postman·fastapi
掘根2 小时前
【仿Muduo库项目】HTTP模块4——HttpServer子模块
网络协议·http·php
Htojk3 小时前
openssl签发自签名证书的流程
网络协议·https·ssl
北京耐用通信4 小时前
如何用耐达讯自动化Profibus总线光纤中继器解决变频器长距离通信干扰问题?
人工智能·物联网·网络协议·自动化·信息与通信
傣味洋芋4 小时前
WebSocket
网络·vue.js·websocket·网络协议
Hi202402175 小时前
HTTPS流量分析-网关抓包与解密全解析
网络协议·http·https
小李独爱秋5 小时前
计算机网络经典问题透视:可以通过哪些方案改造互联网,使互联网能够适合于传送音频/视频数据?
运维·服务器·网络协议·计算机网络·音视频
不知疲倦的仄仄5 小时前
HTTP解析/版本变化/TSL
网络协议·tcp/ip·macos
上海云盾第一敬业销售5 小时前
高防IP架构解析与实践分享
网络协议·tcp/ip·架构