Lambda を使って SSL サーバ証明書の有効期限をチェックする

Posted on

SSL サーバ証明書の更新は何かと忘れがちで、大手サイトでもたまに有効期限切れで事故ったりしています。常時 SSL/TLS が当たり前となった現在では、機会損失だけでなく信用も失いかねません。

ドメイン認証 (DV) 証明書であれば更新作業のいらない AWS Certificate Manager が使えるのですが、EV SSL 証明書だとそういうわけにもいきません。

更新を忘れないために Zabbix などの監視ソフトウェアでチェックしている人が多いと思いますが、サーバを立てずにもっと手軽に導入できる方法として今回は Lambda を使ってみました。監視ソフトウェアと比べてサーバの運用が不要で、かつ 0 円で構築できるというメリットがあります。

openssl コマンドで証明書の有効期限を取得する

証明書の有効期限を取得するには openssl コマンドを使うのが手っ取り早いです。

$ openssl s_client -connect www.google.co.jp:443 -servername www.google.co.jp < /dev/null 2> /dev/null | openssl x509 -text | grep 'Not After'
            Not After : Sep 28 08:03:00 2016 GMT

OpenSSL 0.9.8 系だと SNI の実装に問題があるようで -servername オプションをつけないと "sslv3 alert handshake failure" というエラーになります。

Python で監視スクリプトを書く

子プロセスでさっきのコマンドが叩ければいいので Node.js や Java でもいいのですが、日時計算が簡単な Python で書いてみます。

有効期限が 60 日を切ったら SNS へ通知して、そこからメールを送信しています。

from datetime import datetime
import boto3
import re
import subprocess

threshold_days = 60
topic_arn = "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:cert_expire_alert"
domains = [
    "www.google.co.jp",
    "..."
]

def get_datetime(string):
    date = re.search(r"Not After : (.+)", string)
    return datetime.strptime(date.group(1), '%b %d %H:%M:%S %Y %Z')

def get_cert_expire_date(domain):
    command = "openssl s_client -connect %s:443 -servername %s < /dev/null " \
              "2> /dev/null | openssl x509 -text | grep 'Not After'" % (domain, domain)
    output = subprocess.check_output(command, shell=True)
    expire_date = get_datetime(output)
    return expire_date

def publish_sns(topic_arn, subject, message):
    client = boto3.client("sns")
    response = client.publish(
        TopicArn = topic_arn,
        Subject = subject,
        Message = message
    )
    return response

def lambda_handler(event, context):
    for domain in domains:
        expire_date = get_cert_expire_date(domain)
        delta_date = expire_date - datetime.now()
        print "%s: %s days" % (domain, delta_date.days)

        if delta_date.days < threshold_days:
            subject = "Cert Expire Alert: %s" % domain
            message = "Warning: The SSL server certificate is disabled after %s days." % delta_date.days
            publish_sns(topic_arn, subject, message)

SNS へ通知する必要があるので、

  • Lambda に紐付けるロールに SNS の権限を追加
  • No VPC かインターネットに出れる VPC で実行

という点に気をつける必要があります。あとは Triggers の設定から CloudWatch Events のスケジュール機能を追加して 1 日おきに実行します。

CloudWatch Events - Schedule

まとめ

有効期限までの残日数で処理を分岐すれば、例えば 1 週間を切ると Twilio 経由で電話をかけることも簡単に実装できそうです。

サーバレスという文脈で語られることが多い Lambda ですが、ちょっとした運用ツールでも Lambda は活用できそうです。