サイトの命綱であるSSL証明書を簡単にチェックするRubyスクリプトを更に改良しました。
スクリプト内容
このRubyスクリプトの動きを解説してください
#!/usr/bin/env ruby
require 'openssl'
require 'socket'
require 'date'
require 'uri'
require 'timeout'
require 'net/http'
# ANSIカラーコード
COLORS = {
red: "\e[31m",
yellow: "\e[33m",
green: "\e[32m",
reset: "\e[0m"
}.freeze
# 1. 引数のURL/ドメインを適切にパースするメソッド
def parse_to_uri(input)
clean_input = input.strip
url = clean_input.match?(%r{\Ahttps?://}) ? clean_input : "https://#{clean_input}"
URI.parse(url)
rescue URI::InvalidURIError
nil
end
# 2. リダイレクトを追跡して最終的なURIを返すメソッド
def fetch_effective_uri(uri, limit = 5)
return nil if limit == 0
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.open_timeout = 5
http.read_timeout = 5
http.head(uri.path.empty? ? '/' : uri.path)
end
case response
when Net::HTTPRedirection
redirect_uri = URI.join(uri, response['location'])
fetch_effective_uri(redirect_uri, limit - 1)
else
uri
end
rescue => e
nil
end
# 3. 証明書の有効期限を取得するメソッド(エラー箇所を修正)
def fetch_certificate_expiry(uri)
Timeout.timeout(5) do
# ポートが明示されていない場合は、スキーマがhttpsなら443、それ以外なら80にする
port = uri.port || (uri.scheme == 'https' ? 443 : 80)
# TCPSocket.open はブロックを渡すと自動クローズしてくれます
TCPSocket.open(uri.host, port) do |tcp_socket|
ctx = OpenSSL::SSL::SSLContext.new
# .open ではなく .new を使用し、ブロックの代わりに ensure でクローズする形に修正
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ctx)
ssl_socket.hostname = uri.host
begin
ssl_socket.connect
cert = ssl_socket.peer_cert
expiration_date = cert.not_after.to_date
days_remaining = (expiration_date - Date.today).to_i
[expiration_date, days_remaining]
ensure
ssl_socket.close rescue nil
end
end
end
rescue Timeout::Error
[nil, "サーバーへの接続がタイムアウトしました。"]
rescue => e
[nil, e.message]
end
# 4. 結果を表示するメソッド
def print_result(uri, expiration_date, days_remaining)
if expiration_date
formatted_date = expiration_date.strftime("%Y/%m/%d")
color = case days_remaining
when ...14 then COLORS[:red]
when ...30 then COLORS[:yellow]
else COLORS[:green]
end
puts "サイト #{uri} の有効期限は #{formatted_date} です。#{color}残り #{days_remaining} 日です。#{COLORS[:reset]}"
else
puts "#{COLORS[:red]}サイト #{uri} の証明書取得に失敗しました: #{days_remaining}#{COLORS[:reset]}"
end
end
# メイン処理
def main
inputs = ARGV.empty? ? [print("チェックしたいサイト(ドメイン/URL)を入力してください: "), gets].last.to_s : ARGV
inputs.each do |input|
next if input.strip.empty?
uri = parse_to_uri(input)
if uri.nil?
puts "#{COLORS[:red]}無効な入力です: #{input}#{COLORS[:reset]}"
next
end
puts "Checking: #{uri} ..."
final_uri = fetch_effective_uri(uri)
if final_uri.nil?
puts "#{COLORS[:red]}サイト #{uri} にアクセスできませんでした。#{COLORS[:reset]}"
next
end
unless final_uri.scheme == 'https'
puts "#{COLORS[:yellow]}最終遷移先がHTTPSではないため、スキップします: #{final_uri}#{COLORS[:reset]}"
next
end
expiration_date, days_remaining = fetch_certificate_expiry(final_uri)
print_result(final_uri, expiration_date, days_remaining)
end
end
main if __FILE__ == $PROGRAM_NAME
スクリプトの使い方
このスクリプトを実行するには、Rubyがインストールされた環境(MacのターミナルやLinux、Windowsのコマンドプロンプトなど)が必要です。
ステップ1: ファイルに保存する
上記のコードをコピーし、任意の場所にファイルとして保存します。
- ファイル名(例):
check_cert.rb
ステップ2: スクリプトを実行する
使い方は「引数でまとめて指定する」か「実行後に入力する」の2パターンあります。
- パターンA:引数でドメインを指定して実行(おすすめ)
調べたいサイトをスペース区切りで並べて一気に実行できます。URLでもドメインだけでもOKです。
ruby check_cert.rb google.com github.com https://rubygems.org
- パターンB:対話形式で実行
引数を何も渡さずに実行すると、画面上で入力を求められます。
ruby check_cert.rb
実行すると チェックしたいサイト(ドメイン/URL)を入力してください: と表示されるので、そこに yahoo.co.jp などを入力してEnterを押します。
実際の動き(出力のイメージ)
スクリプトを実行すると、内部で通信が行われ、結果がターミナルに以下のように表示されます。残り日数に応じて自動で文字に色がつくため、一目で状況がわかります。
Checking: https://google.com ...
サイト https://google.com の有効期限は 2026/07/15 です。残り 50 日です。(←緑色で表示)
Checking: https://example.com ...
サイト https://example.com の有効期限は 2026/06/05 です。残り 10 日です。(←赤色で表示)
Checking: http://http-only-site.com ...
最終遷移先がHTTPSではないため、スキップします: http://http-only-site.com (←黄色で表示)
Checking: https://invalid-domain-xyz.com ...
サイト https://invalid-domain-xyz.com の証明書取得に失敗しました: Failed to open TCP connection to invalid-domain-xyz.com:443(←赤色で表示)
エラーハンドリングの動き
- リダイレクトがある場合:
http://github.comと入力しても、自動的にhttps://github.comへ転送(リダイレクト)されたことを検知し、最終的なHTTPSのページに対して証明書をチェックします。
- 接続できない場合:
- ドメインが間違っていたり、サーバーがダウンしている場合は、プログラムがクラッシュせずに「Failed to open TCP connection(接続に失敗しました)」とエラー理由を教えてくれます。
まとめというか本題
筆者はLinuxターミナルの/etc/update-motd.dに仕込むことで自サイトの証明書更新のタイミングを計っています。何せ、Let's Encryptはわずか90日。更に短くなるというのもほぼ確定しましていますから。
そして、筆者が、ここまで執拗にこれをチェックするのは「私怨」が本質。
自分をかなり追い詰めていき、体と心を大きく崩してしまった前の職場。その元職場が
- 2020年代であるにもかかわらず常時SSL化していない
という、IT系会社にあるまじき状態を鼻で笑うためというのもこのスクリプト作成のきっかけ。
- しかも、最近、屋台骨であるWebサービスの証明書が失効してタイムアウトになっている
という更にお察し状態なのはまた別のお話。
コメントを残す