はじめに

私のグループでは、セクション内の運用効率化を目的としたツール開発をしています。先日、形になったものをドキュメント化して社内で共有したのですが、本記事はそれをブログ向けに書き換えたものになります。

個人リポジトリではありますが、2023/12 時点で以下のツールが利用可能になっています。すべて MIT ライセンスです。

ツール リポジトリ 用途 ライセンス
tlc3 GitHub Web サイトにアクセスし、TLS 証明書の情報 (期限日など) を取得する MIT
alpen GitHub ログファイルを解析し、JSON などの構造化フォーマットに変換する MIT

今回は tlc3 について紹介します。

概要

Web サイトにアクセスし、TLS 証明書の情報 (期限日など) を取得します。これを使うことで、証明書更新作業前後のエビデンスをコマンド一発で取得できるようになります。名前の由来は TLS cert cheker CLI の略です。以下の特徴があります。

  • 複数のサイトに対して並行にアクセスするので高速
  • 現在時刻から何日後に証明書が切れるかを出力する
  • 対象ドメインは改行区切りのテキストでも渡せる
  • デフォルトの出力は JSON だが、マークダウンおよびバックログ形式のテーブルで出力できる
  • ローカルの DNS リゾルバを使って名前から IP を引く (補助的な機能なので、引けない場合は何もセットしない)
  • bash, zsh, pwsh であれば補完スクリプトを出力できる
  • 出力する時刻はローカルのタイムゾーンで表現される (マシン依存)
  • ランタイムを内包したシングルバイナリなので、実行ファイルを置くだけで動く

世に似たようなツールはたくさんあり、それらに比べると本ツールは機能も最小限ではありますが、マークダウンおよびバックログ形式のテーブルで出力できてそのままエビデンスとしてコピペできる点が強みです。

インストール

Mac であれば homebrew でインストールできます。

brew install nekrassov01/tap/tlc3

Windows, Linux の場合はリリースページからバイナリをダウンロードし、パスの通ったディレクトリに放り込んでください。

ヘルプ

NAME:
   tlc3 - TLS cert checker CLI

USAGE:
   tlc3 [global options] [arguments...]

VERSION:
   0.0.8

DESCRIPTION:
   CLI application for checking TLS certificate information

GLOBAL OPTIONS:
   --completion value, -c value                           completion scripts: bash|zsh|pwsh
   --domain value, -d value [ --domain value, -d value ]  domain:port separated by commas
   --list value, -l value                                 path to newline-delimited list of domains
   --output value, -o value                               output format: json|markdown|backlog (default: "json")
   --timeout value, -t value                              network timeout: ns|us|ms|s|m|h (default: 5s)
   --insecure, -i                                         skip verification of the cert chain and host name (default: false)
   --no-timeinfo, -n                                      hide fields related to the current time in table output (default: false)
   --help, -h                                             show help
   --version, -v                                          print the version

使用例

ドメインをカンマ区切りで渡す

通常の利用方法としては、複数のドメインをカンマ区切りで渡します。デフォルトで現在時刻から換算した期限までの日数 (DaysLeft) を出力します。また、補助的な機能として名前から IP を引いてきます。

$ tlc3 -d iret.media,iret.co.jp
[
  {
    "DomainName": "iret.co.jp",
    "AccessPort": "443",
    "IPAddresses": [
      "3.114.135.121",
      "54.178.93.128"
    ],
    "Issuer": "CN=Amazon RSA 2048 M01,O=Amazon,C=US",
    "CommonName": "iret.co.jp",
    "SANs": [
      "iret.co.jp",
      "www.iret.co.jp"
    ],
    "NotBefore": "2023-03-29T09:00:00+09:00",
    "NotAfter": "2024-04-27T08:59:59+09:00",
    "CurrentTime": "2023-12-01T19:00:00+09:00",
    "DaysLeft": 147
  },
  {
    "DomainName": "iret.media",
    "AccessPort": "443",
    "IPAddresses": [
      "13.249.160.70",
      "13.249.160.89",
      "13.249.160.97",
      "13.249.160.99"
    ],
    "Issuer": "CN=Amazon RSA 2048 M02,O=Amazon,C=US",
    "CommonName": "iret.media",
    "SANs": [
      "iret.media"
    ],
    "NotBefore": "2023-05-08T09:00:00+09:00",
    "NotAfter": "2024-06-06T08:59:59+09:00",
    "CurrentTime": "2023-12-01T19:00:00+09:00",
    "DaysLeft": 187
  }
]

ドメインのリストをファイルで渡す

ドメインのリストをファイル (改行区切りのテキスト) で渡すことができます。対象ドメインが多数ある場合に便利です。ダブルクォートやシングルクォートで括られていても動作します。

$ cat list.txt
iret.media
iret.co.jp
$ tlc3 -l list.txt
# 出力は上と同じ

マークダウン記法のテーブルで出力する

マークダウン記法の表で出力することで、Backlog の Wiki や課題に直接貼り付けることができます。弊社ではプロジェクト管理に BackLog を使用しているのでこの機能が必要でした。

$ tlc3 -d iret.media,iret.co.jp -o markdown
| DomainName | AccessPort | IPAddresses                                                      | Issuer                               | CommonName | SANs                         | NotBefore                 | NotAfter                  | CurrentTime               | DaysLeft |
| ---------- | ---------- | ---------------------------------------------------------------- | ------------------------------------ | ---------- | ---------------------------- | ------------------------- | ------------------------- | ------------------------- | -------- |
| iret.co.jp |        443 | 3.114.135.121<br>54.178.93.128                                   | CN=Amazon RSA 2048 M01,O=Amazon,C=US | iret.co.jp | iret.co.jp<br>www.iret.co.jp | 2023-03-29T09:00:00+09:00 | 2024-04-27T08:59:59+09:00 | 2023-12-01T19:07:00+09:00 | 147      |
| iret.media |        443 | 13.249.160.70<br>13.249.160.89<br>13.249.160.97<br>13.249.160.99 | CN=Amazon RSA 2048 M02,O=Amazon,C=US | iret.media | iret.media                   | 2023-05-08T09:00:00+09:00 | 2024-06-06T08:59:59+09:00 | 2023-12-01T19:07:00+09:00 | 187      |

バックログ記法のテーブルで出力する

Backlog 記法でも出力できます。余談ですが、この機能がほしかったので自前でライブラリ化したりもしました。

$ tlc3 -d iret.media,iret.co.jp -o backlog
| DomainName | AccessPort | IPAddresses                                                      | Issuer                               | CommonName | SANs                         | NotBefore                 | NotAfter                  | CurrentTime               | DaysLeft |h
| iret.co.jp |        443 | 3.114.135.121&br;54.178.93.128                                   | CN=Amazon RSA 2048 M01,O=Amazon,C=US | iret.co.jp | iret.co.jp&br;www.iret.co.jp | 2023-03-29T09:00:00+09:00 | 2024-04-27T08:59:59+09:00 | 2023-12-01T19:21:00+09:00 |      147 |
| iret.media |        443 | 13.249.160.70&br;13.249.160.89&br;13.249.160.97&br;13.249.160.99 | CN=Amazon RSA 2048 M02,O=Amazon,C=US | iret.media | iret.media                   | 2023-05-08T09:00:00+09:00 | 2024-06-06T08:59:59+09:00 | 2023-12-01T19:21:00+09:00 |      187 |

現在時刻に関連する項目を非表示にする

資料に残すなどの目的で、現在時刻に関連する動的な項目がないほうがよい場合は、-n,--no-timeinfo オプションで非表示にできます。このオプションは出力が JSON の場合は無視されます。マークダウンおよびバックログ形式でのみ有効です。

$ tlc3 -d iret.media,iret.co.jp -o markdown -n
| DomainName | AccessPort | IPAddresses                                                      | Issuer                               | CommonName | SANs                         | NotBefore                 | NotAfter                  |
| ---------- | ---------- | ---------------------------------------------------------------- | ------------------------------------ | ---------- | ---------------------------- | ------------------------- | ------------------------- |
| iret.co.jp | 443        | 3.114.135.121<br>54.178.93.128                                   | CN=Amazon RSA 2048 M01,O=Amazon,C=US | iret.co.jp | iret.co.jp<br>www.iret.co.jp | 2023-03-29T09:00:00+09:00 | 2024-04-27T08:59:59+09:00 |
| iret.media | 443        | 13.249.160.70<br>13.249.160.89<br>13.249.160.97<br>13.249.160.99 | CN=Amazon RSA 2048 M02,O=Amazon,C=US | iret.media | iret.media                   | 2023-05-08T09:00:00+09:00 | 2024-06-06T08:59:59+09:00 |

証明書の検証をスキップする

-i,--insecure オプションを指定することで、証明書の検証をスキップすることができます。このオプションは中間者攻撃にさらされるリスクがあるので、問題がないことが明らかな場合にのみ使用してください。通常は以下のようなプロンプトが出て y/N を返さなければならないので、自動化には不向きです。

$ tlc3 -d iret.media,iret.co.jp -i
? [WARNING] insecure flag skips verification of the certificate chain and hostname. skip it? [y/N]

プライベート環境での使用で、証明書の検証をスキップしたい、かつインタラクティブな操作を排除したい場合は、以下の環境変数を設定すれば聞いてこなくなります。あえてこのような実装にして、自己責任で使っていただく意図があります。

export TLC3_NON_INTERACTIVE=true

シェル補完

前述の通り、シェル補完をサポートしています。bash, zsh, pwsh が対象です。fish は対応していません。標準出力をファイルにリダイレクトし、.bashrc などで読み込んでください。

bash の場合

$ tlc3 -c bash
#! /bin/bash

# This script inspired by https://github.com/urfave/cli
# NOTE: Complex completions such as flag combination checks are not supported

_cli_init_completion() {
  COMPREPLY=()
  _get_comp_words_by_ref "$@" cur prev words cword
}

_tlc3() {
  [[ "${COMP_WORDS[0]}" == "source" ]] && return 0
  local cur words opts comp
  if declare -F _init_completion >/dev/null 2>&1; then
    _init_completion -n "=:" || return
  else
    _cli_init_completion -n "=:" || return
  fi
  cur="${COMP_WORDS[COMP_CWORD]}"
  if [[ "$cur" == "-"* ]]; then
    comp="${words[*]} ${cur} --generate-bash-completion"
  else
    comp="${words[*]} --generate-bash-completion"
  fi
  opts=$(eval "${comp}" 2>/dev/null)
  # shellcheck disable=SC2207
  COMPREPLY=($(compgen -W "${opts}" -- "${cur}"))
}

command -v tlc3 >/dev/null 2>&1 && complete -o bashdefault -o default -o nospace -F _tlc3 tlc3

zsh の場合

$ tlc3 -c zsh
#compdef tlc3

# This script inspired by https://github.com/urfave/cli
# NOTE: Complex completions such as flag combination checks are not supported

_tlc3() {
  local -a opts
  local cur
  cur="${words[-1]}"

  if [[ "$cur" == "-"* ]]; then
    opts=($(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:${#words[@]}-1} ${cur} --generate-bash-completion))
  else
    opts=($(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:${#words[@]}-1} --generate-bash-completion))
  fi

  if [[ "${opts[1]}" != "" ]]; then
    _describe 'values' opts
  else
    _files
  fi
}

command -v tlc3 >/dev/null 2>&1 && compdef _tlc3 tlc3

pwsh の場合

$ tlc3 -c pwsh
# This script inspired by https://github.com/urfave/cli
# NOTE: Complex completions such as flag combination checks are not supported

Register-ArgumentCompleter -Native -CommandName "tlc3" -ScriptBlock {
  param($commandName, $wordToComplete, $cursorPosition)
  (Invoke-Expression "$wordToComplete --generate-bash-completion").ForEach{
    [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
  }
}

注意点

  • TLS の最小バージョンは 1.2 です。それ以下の場合はエラーになります。今後もし必要であれば逃げ道を用意しようと考えています。
  • 現状、NotBefore, NotAfter, CurretTime などの時刻項目はローカルのタイムゾーンに合わせて文字列に変換されますが、クライアント PC で実行した場合もサーバーで実行した場合も同じタイムゾーンに統一したいといった要望を満たす操作を現状ではサポートしていません。これも今後逃げ道を用意したいと考えています。
  • このツールはまだ実運用で使っていません。もしかしたらテストであぶり出せなかった不具合等あるかもしれないのでご了承ください。たとえば DaysLeft の数字を見てギリギリ 1 日前に証明書更新作業を組む、というようなことはしないでください (できる範囲で境界値テストはしていますが)
  • クライアント PC から使う場合で、かつ通信が社内プロキシを通る場合、このツールが返すすべての証明書情報における Issuer の表示が社内 CA のものになることが想定されます。

今後の展望

現在はサーバー証明書の情報を一括で高速に取得するツールという立ち位置ですが、できれば取り入れたい機能として以下を考えています。

  • 日付項目におけるタイムゾーンの指定
  • TLS 最小バージョンの指定 (セキュリティを考慮した上で)
  • 以下のような、より本格的な機能
    • 証明書チェーンの検査
    • ホスト名の検査
    • OCSP Stapling によるオンライン検査
    • ローカルの証明書ファイルや鍵ファイルの検査

おわりに

この活動がどのていど運用工数の削減につながるかはわかりませんが、工数削減に寄与しながら自分の技術力を磨くことができるので、いずれにせよやって損はないなと考えています。

また、このツールのテストではそれなりに苦労したので、テストについても記事にする予定です。