Sau Heartbleed và tạo ra nhiều chứng chỉ mới khác nhau, tôi đã tìm kiếm một công cụ gửi cảnh báo cho tôi khi chứng chỉ sắp hết hạn. Về cơ bản, tôi cần tự động kiểm tra ngày hết hạn của chứng chỉ. yêu cầu của tôi là
- kiểm tra hàng ngày;
- thông báo qua email;
- kiểm tra các chứng chỉ trên mạng nội bộ và bên ngoài;
- kiểm tra chứng chỉ trên dịch vụ không phải web [imap, pop, …]
Có một số công cụ đáp ứng một phần yêu cầu của tôi nhưng không có công cụ nào làm được mọi thứ tôi cần. Vì vậy, tôi đã tự làm nó
Kiểm tra ngày hết hạn của chứng chỉ SSL
nhượng lại. py là một tập lệnh python đọc tệp dưới dạng đầu vào [ceds. kiểm tra] và kiểm tra SSL trên mọi máy chủ được liệt kê trong tệp. Tập lệnh có một vài tham số cấu hình nội tuyến
Đây là tập lệnh Python để kiểm tra ngày hết hạn của chứng chỉ TLS/SSL của trang web, được sử dụng để tạo kết nối HTTPS an toàn
Để sử dụng tập lệnh, chỉ cần chạy tập lệnh từ dòng lệnh, cùng với danh sách tên miền bạn muốn kiểm tra. Ví dụ
> python check-certificates.py codebox.net www.codebox.net api.codebox.net oldtime.radio c0debox.net
Checking 5 endpoints...
codebox.net OK expires in 48 days
www.codebox.net OK expires in 48 days
api.codebox.net OK expires in 48 days
oldtime.radio WARN expires in 6 days 21 hours 13 minutes
c0debox.net ERROR [Errno 8] nodename nor servname provided, or not known
Tập lệnh sẽ liệt kê trạng thái của chứng chỉ của từng miền, hiển thị 'OK
' nếu chứng chỉ đã được truy xuất và sắp hết hạn, 'WARN
' nếu ngày hết hạn của chứng chỉ sắp đến hoặc 'ERROR
' nếu chứng chỉ đã hết hạn hoặc
Theo mặc định, 'WARN
' sẽ được hiển thị nếu còn ít hơn 7 ngày cho đến khi chứng chỉ hết hạn, nhưng khoảng thời gian này có thể được thay đổi bằng cách thay đổi giá trị của biến
Nếu bất kỳ miền nào đang sử dụng cổng không chuẩn cho HTTPS thì cổng này phải được chỉ định bằng cách sử dụng ký hiệu thông thường là host:port
, chẳng hạn
> python check-certificates.py test.codebox.net:8443
Tập lệnh trả về mã thoát cho biết kiểm tra có đạt hay không, giúp dễ dàng thực hiện hành động thích hợp trong tập lệnh shell [ví dụ: gửi email nếu kiểm tra không thành công]
Mã thoát điều kiệnMọi thứ đều ổn, không có chứng chỉ nào sắp hết hạn0Ít nhất một chứng chỉ sắp hết hạn1Ít nhất một chứng chỉ đã hết hạn, không hợp lệ hoặc không thể truy xuất được2Cả hai điều kiện trước đó đều xảy ra3Không có danh sách miền nào được cung cấp khi chạy tập lệnh9Kiểm tra chứng chỉ được thực hiện song song, giúp quá trình kiểm tra nhiều miền nhanh hơn nhiều. Số lần kiểm tra đồng thời sẽ được thực hiện được xác định bởi giá trị của biến
Nhận thông tin SSL đã được xác minh bằng Python [3. x] rất dễ dàng. Các ví dụ mã cho nó cũng nằm rải rác khắp nơi trên internet. Đây là một trong những ví dụ chỉ cần vài dòng với các thư viện tích hợp sẵn [socket
và ssl
]
import datetime import socket import ssl def get_num_days_before_expired[hostname: str, port: str = '443'] -> int: """ Get number of days before an TLS/SSL of a domain expired """ context = ssl.create_default_context[] with socket.create_connection[[hostname, port]] as sock: with context.wrap_socket[sock, server_hostname=hostname] as ssock: ssl_info = ssock.getpeercert[] expiry_date = datetime.datetime.strptime[ssl_info['notAfter'], '%b %d %H:%M:%S %Y %Z'] delta = expiry_date - datetime.datetime.utcnow[] print[f'{hostname} expires in {delta.days} day[s]'] return delta.days if __name__ == '__main__': get_num_days_before_expired['google.com']
Hàm getpeercert[]
trả về một bản đồ chứa nhiều thông tin từ chứng chỉ SSL, bao gồm cả notAfter
đại diện cho ngày hết hạn. Đối với hầu hết các trường hợp, mã này sẽ là đủ
Nhưng trong trường hợp của tôi, tôi cần lấy thông tin từ một miền sử dụng chứng chỉ SSL mà tôi đã tạo thủ công bằng Let's Encrypt certbot và tải nó lên một máy chủ lưu trữ được chia sẻ. Điều này là do gói lưu trữ chia sẻ không bao gồm tính năng tự động tạo chứng chỉ Let's Encrypt và gia hạn chúng. Vì một số lý do, đoạn mã trên sẽ đưa ra ngoại lệ CERTIFICATE_VERIFY_FAILED
khi tôi chạy với tên miền đó
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
Từ các giải pháp khác nhau do internet cung cấp, việc sử dụng gói
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
0 [ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
1] ban đầu có vẻ phù hợp với tôi vì nó không đưa ra bất kỳ lỗi nào và trả về thông tin notAfter
. Đây là một trong những mã tôi nhận được từ Stackoverflowimport datetime import socket import ssl import OpenSSL def get_num_days_before_expired[hostname: str, port: str = '443'] -> int: """ Get number of days before an TLS/SSL of a domain expired """ cert = ssl.get_server_certificate[[hostname, int[port]]] x509 = OpenSSL.crypto.load_certificate[OpenSSL.crypto.FILETYPE_PEM, cert] expiry_date = datetime.datetime.strptime[x509.get_notAfter[].decode['utf-8'], '%Y%m%d%H%M%S%z'] delta = expiry_date - datetime.datetime.now[datetime.timezone.utc] print[f'{hostname} expires in {delta.days} day[s]'] return delta.days
Nhưng vì lý do nào đó, mã này không trả về cùng thông tin notafter với mã mà chúng tôi thấy bằng công cụ Chrome cho miền của tôi. Rõ ràng, đó là do hành vi của
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
3 như được mô tả trong câu trả lời Stackoverflow nàyMặc dù không được ghi lại như vậy
4 chỉ coissl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
5 đã cho là mục tiêu để kết nối nhưng không đặtssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
6 làssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
7 trong tiện ích mở rộng TLS SNI giống như các trình duyệt sẽ làmhttps. // stackoverflow. com/a/52867893/1862500ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate [_ssl.c:1131]
Vì vậy, cuối cùng, tôi quyết định chỉ kết hợp hai giải pháp trên 😅
from urllib.request import ssl, socket from datetime import datetime, timezone import OpenSSL def get_num_days_before_expired[hostname: str, port: str = '443'] -> int: """ Get number of days before an TLS/SSL of a domain expired """ context = ssl.SSLContext[] with socket.create_connection[[hostname, port]] as sock: with context.wrap_socket[sock, server_hostname = hostname] as ssock: certificate = ssock.getpeercert[True] cert = ssl.DER_cert_to_PEM_cert[certificate] x509 = OpenSSL.crypto.load_certificate[OpenSSL.crypto.FILETYPE_PEM, cert] cert_expires = datetime.strptime[x509.get_notAfter[].decode['utf-8'], '%Y%m%d%H%M%S%z'] num_days = [cert_expires - datetime.now[timezone.utc]].days print[f'{hostname} expires in {num_days} day[s]'] return num_days if __name__ == '__main__': get_num_days_before_expired['ssis.sionministry.org']
Sử dụng mã này, tôi có thể truy xuất thông tin hết hạn chính xác từ chứng chỉ SSL chưa được xác minh của mình. Vì vậy, tôi có thể tạo một tập lệnh được lên lịch để thông báo cho tôi khi gần đến ngày hết hạn 🎉
Tôi không thể tìm thấy tài liệu tốt cho đối tượng x509, may mắn thay, chúng tôi vẫn có thể kiểm tra để biết cách tìm nạp thông tin khác. Vì vậy, chúng tôi có thể mở rộng tập lệnh để tìm nạp thông tin về chứng chỉ và tên miền