背景
Let's Encryptで証明書の自動更新をしているドメインで、マニュアルどおりにやっていれば、デフォルト設定で30日を切ったタイミングでSSL証明書の更新が行われるはずですが、更新が実施されているかチェックする必要性があったためスクリプトで実装しました。
実際には下記をチェック対象ドメインリストのコンフィグファイルを食わせるなどして運用しますが、実装に利用したM2Cryptoライブラリの説明があまりなかったので、実際に操作した記録を含めて書き起こしておきます。
本当は使い慣れてるurllibとかで実装できればよかったんですが、SSL証明書の検証ができる方法がよくわからなかったのでM2Cryptoライブラリを利用しました。
環境
- Amazon Linux
- Python3
利用するライブラリ
- datetime
- ssl
- M2Crypto
Python実行環境
こんな感じでPyenvを利用してpython3環境を作成済み。
# pwd
/root/python3
# pyenv versions
system
* 3.5.6 (set by /root/python3/.python-version)
# python -V
Python 3.5.6
SSL証明書の期限確認
対話型のインタラクティブモードを利用してデータ状態などを確認しながら必要なものを揃えていきます。
import ssl
import M2Crypto
import datetime
port = 443
hostname = 'www.qiita.com' # ドメインは適当にご自身のものを。
cert = ssl.get_server_certificate((hostname, port))
x509 = M2Crypto.X509.load_cert_string(cert)
x509.get_subject().as_text() # 'CN=qiita.com'
この辺から、あんまりドキュメントが見当たらなくてよくわからないので、使えるメソッドを掘り出しながら進めます。
dir(x509)
# ['__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
# '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_ptr', '_pyfree', 'add_ext', 'as_der', 'as_pem',
# 'as_text', 'check_ca', 'check_purpose', 'get_ext', 'get_ext_at', 'get_ext_count', 'get_fingerprint', 'get_issuer', 'get_not_after',
# 'get_not_before', 'get_pubkey', 'get_serial_number', 'get_subject', 'get_version', 'm2_x509_free', 'save', 'save_pem', 'set_issuer',
# 'set_issuer_name', 'set_not_after', 'set_not_before', 'set_pubkey', 'set_serial_number', 'set_subject', 'set_subject_name',
# 'set_version', 'sign', 'verify', 'x509']
使えそうなget_not_afterを発見。
type(x509.get_not_after()) # <M2Crypto.ASN1.ASN1_TIME object at 0x7f26999f1940>
dir(x509.get_not_after())
# ['__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
# '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_ptr', '_pyfree', '_ssl_months', 'asn1_time',
# 'get_datetime', 'm2_asn1_time_free', 'set_datetime', 'set_string', 'set_time']
datetimeで出力できそうな get_datetime なるものを発見。
type(x509.get_not_after().get_datetime()) # <class 'datetime.datetime'>
x509.get_not_after().get_datetime() # datetime.datetime(2020, 4, 30, 12, 0, tzinfo=<Timezone: UTC>)
datetimeでデータが取得できたので、後はdatetimeライブラリを利用してtimedeltaを求めてあげればOK
exp_date = x509.get_not_after().get_datetime()
now = datetime.datetime.now() # datetime.datetime(2019, 11, 29, 10, 52, 24, 89337)
exp_date - now
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't subtract offset-naive and offset-aware datetimes
datetime同士の引き算はタイムゾーンがくっついてるとできないそうです。
参考 : Pythonのタイムゾーンの扱い
ということで、タイムゾーン情報を削って比較。
remaining_time = exp_date.replace(tzinfo=None) - now # datetime.timedelta(53, 15629, 910663)
remaining_time.days
152
無事SSL証明書の残り日数の取得ができました。
最終的なコードは以下。
割とコンパクト。
import ssl
import M2Crypto
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import datetime
port = 443
hostname = 'www.qiita.com' # 適宜書き換えるか引数を読み込んで下さい
cert = ssl.get_server_certificate((hostname, port))
x509 = M2Crypto.X509.load_cert_string(cert)
exp_date = x509.get_not_after().get_datetime()
now = datetime.datetime.now()
remaining_time = exp_date.replace(tzinfo=None) - now
print(remaining_time.days)
152