巡检-域名-https证书过期-邮件通知
mail_https_check.py
通过脚本巡检即将到期的https域名证书,并发送邮件到相关人
只需要修改***就可以添加发送人和接收人
巡检内容地址为:/data/https_list,格式为域名+空格备注(liwork.cn 博客网站)
效果示意:
执行命令:
python3 mail_https_check.py
代码内容
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
import smtplib
import ssl
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# ------------------ 配置参数 ------------------
SMTP_HOST = "***"
SMTP_PORT = ***
SMTP_USER = "***" # 发件人邮箱
SMTP_PASSWORD = "***" # 发件人密码
RECIPIENTS = [ # 收件人列表(重要修改点)
"***", # 主收件人
"***" # 备用收件人
]
HTTPS_LIST_FILE = "/data/https_list"
OUTPUT_FILE = "/data/https_lists"
# ---------------------------------------------
def get_cert_expiry(domain):
"""获取证书过期时间"""
try:
cmd = f"echo | timeout 30 openssl s_client -servername {domain} -connect {domain}:443 2>/dev/null | openssl x509 -noout -enddate"
result = subprocess.run(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
)
if result.returncode != 0:
raise Exception(f"OpenSSL错误: {result.stderr.strip()}")
end_time_str = result.stdout.split('=')[1].strip()
return datetime.strptime(end_time_str, '%b %d %H:%M:%S %Y GMT')
except Exception as e:
print(f"[ERROR] {domain} 检测失败: {str(e)}")
return None
def check_certificates():
"""主检查函数"""
if not os.path.exists(HTTPS_LIST_FILE):
print(f"[FATAL] 配置文件 {HTTPS_LIST_FILE} 不存在")
sys.exit(1)
warning_domains = []
with open(HTTPS_LIST_FILE, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(maxsplit=1)
domain = parts[0]
note = parts[1] if len(parts) > 1 else ''
print(f"\n{'='*80}")
print(f"正在检测: {domain}")
expiry_time = get_cert_expiry(domain)
if not expiry_time:
print("连接失败,跳过该域名")
continue
now = datetime.utcnow()
days_left = (expiry_time - now).days
print(f"剩余天数: {days_left}")
if days_left < 10:
print("[WARNING] 证书即将过期")
with open(OUTPUT_FILE, 'a', encoding='utf-8') as f:
f.write(f"{domain}\n")
warning_domains.append({
'domain': domain,
'expire': expiry_time.strftime('%Y-%m-%d %H:%M:%S'),
'note': note
})
return warning_domains
def send_email(warnings):
"""发送专业告警邮件"""
if not warnings:
print("无证书过期预警")
return
current_date = datetime.now().strftime("%Y-%m-%d")
msg = MIMEMultipart('alternative')
msg['Subject'] = f'[{current_date}] SSL证书过期预警 - 需人工处理'
msg['From'] = f"Liwork自动巡检 <{SMTP_USER}>"
msg['To'] = ", ".join(RECIPIENTS) # 多个收件人用逗号分隔
# 生成表格内容
table_rows = []
for item in warnings:
table_rows.append(f"""
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 12px 0; color: #2c3e50; width: 40%;">{item['domain']}</td>
<td style="padding: 12px 0; color: #e74c3c; width: 30%;">{item['expire']}</td>
<td style="padding: 12px 0; color: #95a5a6; width: 30%;">{item['note']}</td>
</tr>
""")
html_content = f"""
<html>
<head>
<meta charset="UTF-8">
<style>
body {{
font-family: Helvetica, Arial, sans-serif;
margin: 0;
padding: 30px;
background: #fafafa;
}}
.header {{
background: #2c3e50;
color: white;
padding: 25px 30px;
border-radius: 8px;
margin-bottom: 25px;
}}
.alert-section {{
background: #ffe5e5;
color: #c0392b;
padding: 15px;
border-radius: 6px;
margin-bottom: 25px;
border-left: 4px solid #c0392b;
}}
.report-table {{
width: 100%;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
border-collapse: collapse;
margin-bottom: 30px;
}}
.report-table th {{
background: #f8f9fb;
padding: 16px 0;
color: #7f8c8d;
font-weight: 500;
text-align: left;
border-bottom: 2px solid #eee;
}}
.report-table td {{
padding: 14px 0;
}}
.footer {{
color: #95a5a6;
font-size: 0.9em;
padding-left: 10px;
line-height: 1.5;
}}
</style>
</head>
<body>
<div class="header">
<h1 style="margin:0; font-weight:300;">SSL证书巡检报告</h1>
<h3 style="margin:10px 0 0; font-weight:400;">检测日期:{current_date}</h3>
</div>
<div class="alert-section">
发现 {len(warnings)} 个证书即将过期(剩余天数 < 10)
</div>
<table class="report-table">
<thead>
<tr>
<th style="padding-left: 30px;">域名</th>
<th>到期时间</th>
<th style="padding-right: 30px;">备注信息</th>
</tr>
</thead>
<tbody>
{''.join(table_rows)}
</tbody>
</table>
<div class="footer">
<p>技术支持:zhouli@liwork.cn</p>
<p style="margin-top:8px;">此邮件由系统自动生成,请勿直接回复</p>
</div>
</body>
</html>
"""
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
try:
context = ssl.create_default_context()
with smtplib.SMTP_SSL(
host=SMTP_HOST,
port=SMTP_PORT,
context=context,
timeout=30
) as server:
server.login(SMTP_USER, SMTP_PASSWORD)
# 发送给所有收件人(重要修改点)
server.sendmail(SMTP_USER, RECIPIENTS, msg.as_string())
print("告警邮件已发送至:", RECIPIENTS)
except Exception as e:
print(f"[ERROR] 邮件发送失败: {str(e)}")
if __name__ == "__main__":
# 初始化输出文件
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
f.write("")
print("=== SSL证书巡检开始 ===")
warnings = check_certificates()
if warnings:
print(f"\n发现 {len(warnings)} 个证书即将过期")
send_email(warnings)
else:
print("\n所有证书状态正常")
print("=== 检查完成 ===")
评论
其他文章