このスクリプトの改善案をGoogle Geminiに言ったところ、更に完成度が上がりました。
概要
Web運用において、「サーバのSSL証明書更新」をチェックすることは大切です。
適切に更新されていない/更新が遅れたまま放置すると
- ブラウザで「危険なサイト」と認識される
- それによるレピュテーションリスク
- HSTSを厳密に設定した場合はサイトそのものへのアクセス不可
など、リスクは甚大です。
そこで、
- OpenSSLコマンドでドメインに設定されている証明書の期限
- 並びに残り何日か
を表示するRubyスクリプトを準備しました。
スクリプト内容
- ssl_checker.rb
#!/usr/bin/env ruby
require 'openssl'
require 'socket'
require 'date'
require 'uri'
require 'timeout'
# 色付け用の定数
COLOR_RED = "\e[31m"
COLOR_YELLOW = "\e[33m"
COLOR_GREEN = "\e[32m"
COLOR_RESET = "\e[0m"
# URLの最終的な到達先を取得するメソッド
def get_effective_url(url)
# curlを使ってリダイレクトを追いかけ、最終的なURLを取得する
# -s: サイレント, -L: リダイレクト追従, -I: ヘッダのみ, -o /dev/null: ボディ破棄, -w '%{url_effective}': 最終URLを出力
effective_url = `curl -sLI -o /dev/null -w '%{url_effective}' "#{url}"`
effective_url.empty? ? nil : effective_url
end
# 証明書の有効期限を取得するメソッド
def get_certificate_expiry_date(url)
uri = URI.parse(url)
hostname = uri.host
port = uri.port || 443 # ポートがなければ443を使う
ssl_socket = nil
tcp_client = nil
begin
Timeout.timeout(5) do
tcp_client = TCPSocket.new(hostname, port)
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
ssl_socket.hostname = hostname
ssl_socket.connect
cert = ssl_socket.peer_cert
expiration_date = DateTime.parse(cert.not_after.to_s)
days_remaining = (expiration_date - DateTime.now).to_i
return expiration_date, days_remaining
end
rescue Timeout::Error
return nil, "サーバーへの接続がタイムアウトしました。"
rescue => e
return nil, e.to_s
ensure
ssl_socket&.close
tcp_client&.close
end
end
# 結果を表示するメソッド
def print_result(url, expiration_date, days_remaining)
if expiration_date
formatted_date = expiration_date.strftime("%Y/%m/%d")
# 残り日数に応じて色を選択
color = if days_remaining < 14
COLOR_RED
elsif days_remaining < 30
COLOR_YELLOW
else
COLOR_GREEN
end
puts "サイト #{url} の有効期限は #{formatted_date} です。#{color}残り #{days_remaining} 日です。#{COLOR_RESET}"
else
puts "#{COLOR_RED}サイト #{url} の証明書取得に失敗しました: #{days_remaining}#{COLOR_RESET}"
end
end
# メイン処理
def main
# コマンドライン引数があるかどうかで処理を分岐
domains_to_check = if ARGV.empty?
# 引数がない場合は、対話式で入力を受け付ける
print "チェックしたいサイトのドメインを入力してください(例: example.com): "
[gets.chomp]
else
# 引数がある場合は、それらを全てチェック対象とする
ARGV
end
# 各ドメインをチェック
domains_to_check.each do |domain|
# 対話モードで空エンターされた場合などをスキップ
next if domain.nil? || domain.strip.empty?
# http/httpsから始まらない場合はhttpsを付与
initial_url = domain.start_with?('http') ? domain : "https://#{domain}"
puts "Checking: #{initial_url} ..."
final_url = get_effective_url(initial_url)
if final_url.nil?
puts "#{COLOR_RED}サイト #{initial_url} にアクセスできませんでした。#{COLOR_RESET}"
next
end
expiration_date, days_remaining = get_certificate_expiry_date(final_url)
print_result(final_url, expiration_date, days_remaining)
end
end
# メイン処理を呼び出し
main
スクリプト実行例
ruby ssl_checker.rb
※Rubyで動かすためスクリプトに実行権は持たせる必要はありません
- 対話式でドメインを入力する
- 証明書の有効期限と残り期日を表示する
ruby script.rb www.hoge.com
など、ドメインを引数にしても同様の効果が得られる- 更新が近づくと
- 30日以内:黄色
- 15日以内:赤
とアラートも出してくれるようにしています。
- 対話式
ruby ssl_checker.rb
チェックしたいサイトのドメインを入力してください(例: example.com): www.yahoo.co.jp
Checking: https://www.yahoo.co.jp ...
サイト https://www.yahoo.co.jp/ の有効期限は 2026/05/14 です。残り 310 日です。
- 引数にした場合
ruby ssl_checker.rb yahoo.co.jp www.msn.com
Checking: https://yahoo.co.jp ...
サイト https://www.yahoo.co.jp:443/ の有効期限は 2026/05/14 です。残り 310 日です。
Checking: https://www.msn.com ...
サイト https://www.msn.com/ の有効期限は 2025/10/05 です。残り 89 日です。
スクリプト使用例
筆者は/etc/update-motd
配下に
ruby /path/to/script/ruby/ssl_checker.rb ryza.jp
と記入することで、ログインのたびに
Checking: https://atelier.reisalin.com ...
サイト https://atelier.reisalin.com/ の有効期限は 2025/08/15 です。残り 37 日です。
と次の更新のタイミングを読みやすくしています。