CloudWatch に SSL 証明書の有効期限をメトリクスとして入れて有効期限を監視する

クライアント認証のために EC2 インスタンス上で Let's Encrypt の証明書で https しているサーバがあり、CloudWatch Alarm でその証明書の有効期限の監視をしたかったのでそのメモ。

CLI で証明書の有効期限のチェック

openssl s_client-attime でエポック秒を指定すれば現在日時ではなくその日時で有効期限がチェックされます。さらに -verify_return_error を付ければ検証失敗時は終了コードが非0になるので、日時指定の有効期限チェックだけなら下記でできます。

# 有効期限を10日でチェック
openssl s_client -connect localhost:443 \
  -attime $(( $(date +%s) + 24*60*60*10)) \
  -verify_return_error </dev/null

ネットワーク越しではなくローカルの証明書をチェックするなら openssl verify-attime でも同じです。ただし証明書のチェーンも検証されるため、次のように -untrusted で中間証明書を指定する必要があります。certbot などでローカルに保存された証明書をチェックするならこれでも良いかもしれません。

# 中間証明書を -untrusted に指定して検証
openssl verify \
  -untrusted intermediate.crt \
  -attime $(( $(date +%s) + 24*60*60*10)) \
  server.crt

openssl x509-checkend でも有効期限のチェックができます。この場合は現在日時からの相対で指定します。

# ネットワーク越しにチェック
openssl s_client -connect localhost:443 </dev/null | openssl x509 -checkend $((24*60*60*10))

# ローカルの証明書をチェック
openssl x509 -in server.crt -checkend $((24*60*60*10))

CLI で証明書の有効期限の残日数を取得

前述の方法で日付指定の有効期限のチェックはできましたが、残りの有効期限の日数をメトリクスとして CloudWatch に保存したかったので、次のように openssl x509-enddate で終了日時を抜き出して date -d で日時をパースして現在日時からの差分を得るようにしました。

notAfter=$(
  openssl s_client -connect localhost:443 < /dev/null 2> /dev/null |
    openssl x509 -noout -enddate |
    sed -n -e '/^notAfter=/{
      s/^notAfter=//
      p
    }'
)
expire=$(date -d "$notAfter" +%s)
now=$(date +%s)
days=$(bc -l <<< "scale=4; ($expire - $now) / 24 / 60 / 60")

あとは次のように cloudwatch に放り込めば OK です。

aws cloudwatch put-metric-data \
  --region "$region" \
  --namespace 'Certificate' \
  --metric-name 'ServerCertificateExpiration' \
  --value "$days" \
  --dimensions "InstanceId=$instance_id"

さいごに

こんな感じのメトリクスが保存されます。

f:id:ngyuki:20210107205009p:plain

Let's Encrypt の証明書で certbot で自動更新しているのなら有効期限を監視してもあまり意味がない気もする。。。