PHPでSSL証明書期限を監視する処理を作ってみた

PHPでSSL証明書期限を監視してChatWorkに通知する方法を紹介します!


## やり方

```
<?php

$domain_names = [
'google.com',
'yahoo.co.jp'
];

define('DEFAULT_ROOM_ID', 'ChatWorkの通知するルームID');
define('DEFAULT_USER_TOKEN', 'ChatWorkのユーザートークン');

$warning_messages = [];
$nossl_messages = [];

foreach ($domain_names as $domain_name) {

unset($_warning_message);
unset($_nossl_message);

    $response = shell_exec("(timeout 1 openssl s_client -servername {$domain_name} -connect {$domain_name}:443 2>/dev/null | openssl x509 -noout -enddate | grep notAfter | sed -e s#notAfter=##)");
    $expire_date = $response ? (new DateTime($response, new DateTimeZone('ASIA/TOKYO')))->format('Y-m-d') : null;
    if (!$expire_date) $_nossl_message = "【SSL証明書期限】{$domain_name} (期限切れもしくは取得失敗)";
    if (strtotime($expire_date) < mktime(0, 0, 0, date('m') + 1, date('d'), date('Y'))) $_warning_message = "【SSL証明書期限】{$domain_name} ({$expire_date})";
    if (!empty($_warning_message)) {
        $warning_messages[] = $_warning_message;
        echo $_warning_message . "\n";
    }

    if (!empty($_nossl_message)) {
        $nossl_messages[] = $_nossl_message;
        echo $_nossl_message . "\n";
    }
}

if (!empty($warning_messages)) SendMsg::exec_notice("[toall]" . implode("\n", $warning_messages), '期限が1ヶ月前のSSL一覧通知');
if (!empty($nossl_messages)) SendMsg::exec_notice("[toall]" . implode("\n", $nossl_messages), '期限切れもしくはHTTPSじゃ無いSSL一覧通知');
return;

class SendMsg {
    //チャットワーク通知
    public static function exec_notice($message, $title = null, $room_id = null, $user_token = null) {
        if (!$room_id)  $room_id = DEFAULT_ROOM_ID;
        
        if (!$user_token) $user_token = DEFAULT_USER_TOKEN;
        
        // デフォルト値
        $curl = null;

        // タイムアウト 2秒
        $timeout_sec = 2;

        // ルームIDを指定してCURL初期化
        $curl = curl_init('https://api.chatwork.com/v2/rooms/' . $room_id . '/messages');

        // ユーザトークン
        $headers = ['X-ChatWorkToken: ' . $user_token];
        // 通知内容
        $body = '';
        if (is_null($title)) {
            $body = $message;
        } else {
            $body = '[info][title]';
            $body .= $title;
            $body .= '[/title]';
            $body .= $message;
            $body .= '[/info]';
        }
        $fields = ['body' => $body];
        // オプションを設定
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($fields));
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        // 通知実行
        $ret = curl_exec($curl);
        // 処理結果
        if ($ret === false) {
            return false;
        }
        return (strpos($ret, 'message_id') !== false);
    }
}
```

## 解説

:::note info
ChatWorkへの通知設定を必ず行ってください
:::
```
ChatWorkの通知したいルームのIDとユーザートークンを指定する

define('DEFAULT_ROOM_ID', 'ChatWorkの通知するルームID');
define('DEFAULT_USER_TOKEN', 'ChatWorkのユーザートークン');
```


:::note info
shell_exec()　シェルコマンドを実行します
シェルコマンドをカッコ（）内に指定することで証明書を読み込んでくれます。
  ※ カッコがない場合はunable to load certificateエラーが出力されます
timeout 1 で強制的に1秒経過後したら次のドメインを読み込みます
openssl以降コマンドでSSL証明書を読み込んで証明書期限(notAfter)をgrepで調べてsedで切り出しています
:::
```
$response = shell_exec("(timeout 1 openssl s_client -servername {$domain_name} -connect {$domain_name}:443 2>/dev/null | openssl x509 -noout -enddate | grep notAfter | sed -e s#notAfter=##)");
```

:::note warn
取得できる有効期限のタイムゾーンがGMTになっているのでASIA/TOKYOに変更しています
:::
```
$expire_date = $response ? (new DateTime($response, new DateTimeZone('ASIA/TOKYO')))->format('Y-m-d') : null;
```

:::note info
1ヶ月後に証明書が切れる対象を通知するようにしています
mktime(0,0,0,date('n')+1,date('j'),date('Y')))
:::
```
if (strtotime($expire_date) < mktime(0, 0, 0, date('m') + 1, date('d'), date('Y'))) $_warning_message = "【SSL証明書期限】{$domain_name} ({$expire_date})";
```

:::note info
コマンドで実行すると$domain_namesに指定したドメインを監視して有効期限が近づくとChatWorkに通知してくれます
Cron設定しておくことをオススメします！
:::
```
php  ./xxx.php >> logs/xxx_watch.log
```


## 最後に

読んでいただきありがとうございます。
今回の記事はいかがでしたか？
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。