Mackerelプラグイン vs WHOIS情報

少し前にMackerelでwhois情報の有効期限をチェックするプラグインを作ったのですが、その時のWHOISにまつわる苦労話を少し深堀りしようかと思います。

## TL;RD;

WHOISベースでのドメイン有効期限監視は面倒

## 言い訳

この手の記事のドメインの例示には`example.com`などを使うのが礼儀なのですが、生きた情報が無いと分かりづらいため、Google系のドメインを例として使用しています。


## そもそも何をするプラグインか

チェックプラグインの一種で、指定したドメインのWHOIS情報をチェックして、有効期限までの日数に応じて`WARNING`,`CRITICAL`判定をします。

```shell-session
$ ./mackerel-plugin-check-whois --domain=attakei.net
Whois OK: attakei.net is expired at 114 days
$ ./mackerel-plugin-check-whois --domain=attakei.net --warning=180
Whois WARNING: attakei.net is expired at 114 days
$ ./mackerel-plugin-check-whois --domain=attakei.net --critical=180
Whois CRITICAL: attakei.net is expired at 114 days
```

## 苦労1: jpドメインのWHOIS情報パースが面倒

今回、WHOIS情報の取得とパースには、[likexian/whois-go](https://github.com/likexian/whois-go), [likexian/whois-parser-go](https://github.com/likexian/whois-parser-go)を使っています。

WHOISの情報フォーマットはドメインレジストリによってまちまちなのですが、`whois-parser-go`は`jp`ドメイン系もテストケースに加えているため、普通に使えばパースして情報をとれる...と思ってました。[^1]

```go:whois.go
package main

import (
	"fmt"
	"github.com/likexian/whois-go"
	"github.com/likexian/whois-parser-go"
)

func main() {
	whois_raw, err := whois.Whois("google.jp")
	result, err := whois_parser.Parse(whois_raw)
	if err == nil {
		// Print the domain expiration date
		fmt.Print("Expiration:\t\t")
		fmt.Println(result.Registrar.ExpirationDate)
	}
}
```

これをローカルPCで実行すると。。。

```shell-session
$ go run whois.go
Expiration:
```

有効期限が取れません。。。なぜだ！

WHOISのローデータを見るとこうなります。

```
[ JPRS database provides information on network administration. Its use is    ]
[ restricted to network administration purposes. For further information,     ]
[ use 'whois -h whois.jprs.jp help'. To suppress Japanese output, add'/e'     ]
[ at the end of command, e.g. 'whois -h whois.jprs.jp xxx/e'.                 ]

Domain Information: [ドメイン情報]
[Domain Name]                   GOOGLE.JP

[登録者名]                      Google Inc.
[Registrant]                    Google Inc.

[Name Server]                   ns1.google.com
[Name Server]                   ns2.google.com
[Name Server]                   ns3.google.com
[Name Server]                   ns4.google.com
[Signing Key]

[登録年月日]                    2005/05/30
[有効期限]                      2019/05/31
[状態]                          Active
[最終更新]                      2018/06/01 01:05:08 (JST)

Contact Information: [公開連絡窓口]
[名前]                          Google Inc.
[Name]                          Google Inc.
[Email]                         dns-admin@google.com
[Web Page]
[郵便番号]                      94043
[住所]                          Mountain View
                                1600 Amphitheatre Parkway
                                US
[Postal Address]                Mountain View
                                1600 Amphitheatre Parkway
                                US
[電話番号]                      16502530000
[FAX番号]                       16502530001
```

「有効期限」という表現になっています。まぁ日本のドメインの情報を見るのはほとんどが日本人かと思うので、まぁ問題は…ない…の…だろう…か

```
JPRS database provides information on network administration. Its use is
restricted to network administration purposes. For further information,
use 'whois -h whois.jprs.jp help'. To suppress Japanese output, add'/e
the end of command, e.g. 'whois -h whois.jprs.jp xxx/e'.                
```

「日本語出力を抑止したいなら`/e`をつけてください」とあります。~~何だその変な仕様~~
実行するとこんな感じになってくれて、確かにこれならパースしてくれます。[^2]

```
$ whois -h whois.jprs.jp google.jp/e
[Querying whois.jprs.jp]
[whois.jprs.jp]
[ JPRS database provides information on network administration. Its use is    ]
[ restricted to network administration purposes. For further information,     ]
[ use 'whois -h whois.jprs.jp help'. To suppress Japanese output, add'/e'     ]
[ at the end of command, e.g. 'whois -h whois.jprs.jp xxx/e'.                 ]

Domain Information:
[Domain Name]                   GOOGLE.JP

[Registrant]                    Google Inc.

[Name Server]                   ns1.google.com
[Name Server]                   ns2.google.com
[Name Server]                   ns3.google.com
[Name Server]                   ns4.google.com
[Signing Key]

[Created on]                    2005/05/30
[Expires on]                    2019/05/31
[Status]                        Active
[Last Updated]                  2018/06/01 01:05:08 (JST)

Contact Information:
[Name]                          Google Inc.
[Email]                         dns-admin@google.com
[Web Page]
[Postal code]                   94043
[Postal Address]                Mountain View
                                1600 Amphitheatre Parkway
                                US
[Phone]                         16502530000
[Fax]                           16502530001
```

この形式なら英語の情報を確実に取れるので、プラグイン側はこう書かざるを得なくなった。

```go:part-of-check_whois.go
func fetchWhois(domain string) (raw string, err error) {
	// If query to JPRS, set option to return English forcely
	if strings.HasSuffix(domain, ".jp") {
		raw, err = whois.Whois(domain+"/e", "whois.jprs.jp")
	} else {
		raw, err = whois.Whois(domain)
	}
	return raw, err
}

func fetchExpired(domain string) (expired time.Time, err error) {
	// 本当はこうしたかった
	// raw, err := whois.Whois(domain)
	raw, err := fetchWhois(domain)
	// 後の処理は省略
}
```

ちなみに、`kr`ドメインは韓国語・英語の並列表記でした。せめて日本もそうして欲しいのですが。

## 苦労2: 属性型jpとjpでWHOIS情報フォーマットが違う問題

素振り感覚で作ったので、`google.co.jp`みたいな属性型jpドメインに対応できてません。
軽く調べるとやっぱり面倒な話がついて回りました。

`whois`で`google.co.jp`を見ると一目瞭然です。

```
Domain Information: [ドメイン情報]
a. [ドメイン名]                 GOOGLE.CO.JP
e. [そしきめい]                 ぐーぐるかぶしきかいしゃ
f. [組織名]                     グーグル株式会社
g. [Organization]               Google Japan
k. [組織種別]                   株式会社
l. [Organization Type]          corporation
m. [登録担当者]                 DL152JP
n. [技術連絡担当者]             TW124137JP
p. [ネームサーバ]               ns1.google.com
p. [ネームサーバ]               ns2.google.com
p. [ネームサーバ]               ns3.google.com
p. [ネームサーバ]               ns4.google.com
s. [署名鍵]
[状態]                          Connected (2019/03/31)
[登録年月日]                    2001/03/22
[接続年月日]                    2001/03/22
[最終更新]                      2018/04/01 01:06:23 (JST)
```

汎用jpとフォーマットが違います。
[JPRSの説明](https://jprs.jp/about/dom-search/jprs-whois/whois-guide-view.html)によると、状態の後ろの日付が期限となっているそうで。
**そもそも、何でフォーマットが違うのだろうか** (今回はその先は追っていない)
当然パーサーも対応してません。

~~汎用jpと属性型jpの判定分岐が面倒なので、~~現時点ではまだ未対応です。

## 苦労3: 最新の有効期限とは限らない問題

「Mackerelを使ってアラートを出す」ということは、延長手続きを取ったらそのまま期限が延びてアラートが解消されるのが理想的です。

が、一部のドメインで延長処理をしてもWHOISにギリギリまで反映されない事象が発生。
これでは、延長処理をしてもアラートが解除されないため意味があまりなくなってしまいました。[^3][^4]

実質的にこのせいで色々ペンディング。需要がありそうなら、いつか直します。


## まとめ

すでに健全なのが分かっているのに、アラートが出続けている状態があまり健全でないので、運用状態にはしていません。
それでも、SSL証明書とあわせてドメインの有効期限もMackerelで監視したい場合は、ぜひ使ってみてください。

[^1]: [テストケースに入っているサンプルを参照](https://github.com/likexian/whois-parser-go/tree/master/examples)
[^2]: なお、ロケール依存と思われるのですが、手元のPC以外で普通の呼出で英語情報が出るケースがあります
[^3]: 手元にキャプチャ等は無いですが、2018/11/30期限のドメインを延長しても、12/01まで2019/11/30期限にならないケースがある模様
[^4]: レジストリなのかレジストラなのか、いまいち追いきれていない