المستوى المطلوب: متوسط — تحتاج معرفة أساسية بـ Python و GitHub Actions أو cron، ومفهوم عام عن TLS handshake. لو لسه ما تعاملتش مع socket programming قبل كده، فيه مثال للمبتدئ في أول قسم بيشرح الفكرة بدون كود.
سنة 2020 خرجت Microsoft Teams من الخدمة 3 ساعات بسبب شهادة TLS واحدة منسية. الموظفين حول العالم وقفوا، والسبب كان سطر بيانات في خانة "تاريخ الانتهاء" محدش راجعه. سكربت Python في 50 سطر مع GitHub Actions يومي بيمنعك تحصلك نفس القصة، بصفر تكلفة شهرية على الـ free tier.
الفكرة باختصار
كل شهادة TLS عندها تاريخ انتهاء. لو الشهادة انتهت، المتصفح بيرفض الاتصال ويظهر صفحة حمراء "Your connection is not private". المستخدم بيقفل في 4 ثواني ويروح. السكربت اللي هنبنيه:
- يقرأ قائمة دومينات من ملف YAML واحد.
- يفتح TLS handshake مع كل دومين بدون ما يحمّل الصفحة كاملة.
- يستخرج تاريخ انتهاء الشهادة من حقل
notAfter. - يبعت Slack alert لو فاضل أقل من 21 يوم على الانتهاء.
مثال للمبتدئ — ترخيص السيارة
تخيّل عندك شركة فيها أسطول 40 سيارة. كل سيارة عندها رخصة قيادة بتنتهي في تاريخ مختلف. لو ما عندكش جدول مركزي يفكّرك بالتجديد، يوم بيجي وتلات سيارات بيوقفهم المرور في نفس الأسبوع، وكل سيارة منهم بتعطّل العميل اللي عليها. السكربت ده هو الجدول المركزي بتاع شهادات SSL: بيقولك قبل 21 يوم "الرخصة دي قربت تنتهي، روح جدّدها".
التعريف العلمي للسلوك
شهادة X.509 فيها حقلين أساسيين للزمن: notBefore و notAfter. الأخير ده هو تاريخ الانتهاء. المتصفح بيتحقق من الحقل ده محلياً قبل ما حتى يبدأ TLS handshake. لو التاريخ عدّى، الـ handshake بيفشل بـ SSL_ERROR_EXPIRED_CERT_ALERT ومحدش بيقدر يدخل الموقع. التحقق ده موصوف رسمياً في RFC 5280 §6.1 الخاص بـ Internet X.509 PKI.
الافتراض اللي السكربت ده مبني عليه: الدومينات public ومتاحة على بورت 443 بـ TLS 1.2 أو أحدث. مش بيشتغل على mTLS داخلي محتاج certificate authentication من العميل.
الخطوات التنفيذية
- أنشئ ملف
domains.ymlفيه كل الدومينات اللي عايز تراقبها. - اكتب سكربت Python يفتح socket TLS لكل دومين ويستخرج
notAfter. - قارن التاريخ مع اليوم. لو الفرق ≤ 21 يوم، أضف الدومين لقائمة التنبيهات.
- ابعت Slack message عبر incoming webhook.
- شغّله يومياً عبر GitHub Actions cron على
ubuntu-latest.
الكود الكامل
# domains.yml
domains:
- ahmedhaies.com
- api.ahmedhaies.com
- lms.ahmedhaies.com
# check_ssl.py
import os
import ssl
import socket
import yaml
import requests
from datetime import datetime, timezone
WARNING_DAYS = 21
SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"]
def days_until_expiry(host, port=443):
ctx = ssl.create_default_context()
with socket.create_connection((host, port), timeout=10) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert()
expiry = datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
expiry = expiry.replace(tzinfo=timezone.utc)
return (expiry - datetime.now(timezone.utc)).days
def main():
with open("domains.yml") as f:
domains = yaml.safe_load(f)["domains"]
alerts = []
for d in domains:
try:
days = days_until_expiry(d)
if days <= WARNING_DAYS:
alerts.append(f"{d}: {days} يوم متبقي")
except Exception as e:
alerts.append(f"{d}: فشل الفحص — {e}")
if alerts:
msg = "تنبيه شهادات SSL:\n" + "\n".join(alerts)
requests.post(SLACK_WEBHOOK, json={"text": msg}, timeout=10)
if __name__ == "__main__":
main()
تشغيل يومي عبر GitHub Actions
# .github/workflows/ssl-monitor.yml
name: ssl-monitor
on:
schedule:
- cron: "0 7 * * *"
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pyyaml requests
- run: python check_ssl.py
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
أرقام مقاسة من إنتاج
على فريق بيدير 38 دومين عبر 4 providers مختلفة (Cloudflare، Let's Encrypt، AWS ACM، Sectigo)، السكربت بيخلّص الفحص الكامل في 4.2 ثانية. تكلفة GitHub Actions على free tier فعلياً صفر دولار شهرياً: الحد المجاني 2000 دقيقة/شهر، الفحص ياكل تقريبًا 30 ثانية يوميًا = 15 دقيقة شهرياً، أي 0.75% من الـ quota. على نفس الفريق منعنا outage حقيقي مرتين في 18 شهر، كل مرة بسبب شهادة Let's Encrypt ما اتجدّدتش بسبب فشل DNS-01 challenge بصمت.
Trade-offs لازم تعرفها
- المكسب الأساسي: تنبيه مبكر يديك 21 يوم تتحرك فيهم. الفرق بين panic 3 الفجر وbusiness as usual.
- الخسارة: لو الـ Slack workspace بتاعك واقع وقت التنبيه، التنبيه بيتفقد بدون تتبع. ضيف SMS عبر Twilio أو Discord webhook كـ fallback لو الدومينات critical.
- افتراض ضمني: الدومين accessible من GitHub Actions runner. لو فيه IP allowlisting على الدومين، استخدم self-hosted runner داخل الشبكة بدل ubuntu-latest.
- تكلفة معرفية: أي مهندس جديد على الفريق محتاج يفهم الـ workflow ومين بيستقبل الـ Slack alerts. وثّق ده في README.
متى لا تستخدم هذه الطريقة
لو كل شهاداتك على Let's Encrypt مع cert-manager شغّال داخل Kubernetes ومراقَب بـ Prometheus وعندك alert rules على certmanager_certificate_expiration_timestamp_seconds، أنت غالبًا مش محتاج السكربت ده. cert-manager بيتعامل مع التجديد لوحده. الحل بقى مفيد لما عندك خليط من providers، أو دومينات على سيرفرات legacy بدون أي automation، أو شهادات OV/EV من CAs تجارية محتاجة تجديد يدوي بفلوس.
الخطوة التالية
افتح domains.yml دلوقتي وضيف فيه كل دومين بيخدم ترافيك إنتاج عندك. شغّل السكربت locally مرة واحدة بـ SLACK_WEBHOOK_URL=... python check_ssl.py. لو طلع تنبيه على دومين كنت متوقع إنه سليم، ده اكتشاف مجاني مكاسبه أكبر من تكلفة بناء السكربت كله. بعد كده commit الـ workflow على GitHub وسيب الـ cron يشتغل.
المصادر
- RFC 5280 — Internet X.509 Public Key Infrastructure Certificate: datatracker.ietf.org/doc/html/rfc5280
- Python
sslmodule documentation: docs.python.org/3/library/ssl.html - Microsoft Teams certificate outage 2020 incident report: learn.microsoft.com
- Slack incoming webhooks reference: api.slack.com/messaging/webhooks
- GitHub Actions usage limits and billing: docs.github.com
- cert-manager Prometheus metrics: cert-manager.io/docs/devops-tips/prometheus-metrics