Not only is the Internet dead, it's starting to smell really bad.:2017年04月30日分

2017/04/30(Sun)

[NetBSD] Let's Encrypt(certbot + dehydrated)で遊んでる程度でも地雷を踏みぬく素晴らしいユーザランド

@昨今はブラウザがHTTPだと安全でない安全でないうるさいので…

つーことでしばらく前にこのチラシの裏にも証明書入れたんですが、証明書に金かけられないので無料の

を使ってたんですが、こいつも Chromeやら MozillaにBANされる雑魚だったようで慌てて

に変更するハメに。

@certbotがまずクラッシュ

StartSSLの期限切れでrenewしたらこの事態になってることに気づいたのもあって、時間も無いので深く調べずに公式がお勧めしてる

をpkgsrcから導入したんですが、こいつが

# certbot-auto certonly --standalone -d ドメイン

を実行しても使用言語のPythonがオレオレN6上でバグってんのかcore吐いて死んでくれやがります、うーんこの。

障害原因を調べようにもやたらと牛刀割鶏な作りだし--standaloneがクラッシュするって事はPython本体のHTTPServerクラスのdebugになりかねんのでめんどくさい、また来世で。

とりあえず深追いしたくないのでnginxの設定に

location /.well-known/acme-challenge/ {
    alias <砂場>;
}

を追加して

# certbot-auto certonly --webroot -w <砂場> -d <ドメイン>

を実行して別の方法で導入は済ませた。

@dehydratedも動かねぇ

とはいえ、チラシの裏はディスク容量も無いしもっと軽量なやつがええなってことで、Bashだけで書かれた(うわBashかよ…)

に切り替えたんだけど、こっちもpkgsrcから突っ込んだところ

# dehydrated -register --accept-terms

の実行中にsed(1)が

sed: RE error: invalid argument to regex routine

とおっしゃられる、ハハハこやつめ(ブチ切れ)。

問題のコードはこの部分やね。

262   # Get public components from private key and calculate thumbprint
263   pubExponent64="$(printf '%x' "$(openssl rsa -in "${ACCOUNT_KEY}" -noout -text | awk '/publicExponent/ {print $2}')" | hex2bin | urlbase64)"

パイプで繋いでるhex2bin及びurlbase64という2つのシェル関数の中でsedが使われてます

320 # Encode data as url-safe formatted base64
321 urlbase64() {
322   # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_'
323   openssl base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:'
324 }
325
326 # Convert hex string to binary data
327 hex2bin() {
328   # Remove spaces, add leading zero, escape as hex string and parse with printf
329   printf -- "$(cat | _sed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')"
330 }

コマンドそのものは_sedシェル関数でラップされてますがこれは

300 # Different sed version for different os types...
301 _sed() {
302   if [[ "${OSTYPE}" = "Linux" ]]; then
303     sed -r "${@}"
304   else
304     sed -E "${@}"
305   fi
306 }

Linuxの場合(つまりGNU sedと言いたいのだろう)であれば-rオプション、それ以外(つまりBSD sed以下略)なら-Eを使うようになってます。

unameでsedのチェックってクッソ雑やなーディストリとかによって差異あるやろとか、そもそも最近のGNU sedなら

	-E
	-r
	--regexp-extended
		Use extended regular expressions rather than basic regular expressions. Extended regexps are those that egrep accepts; they can be clearer
		because they usually have fewer backslashes. Historically this was a GNU extension, but the -E extension has since been added to the
		POSIX standard (http://austingroupbugs.net/view.php?id=528), so use -E for portability. GNU sed has accepted -E as an undocumented
		option for years, and *BSD seds have accepted -E for years as well, but scripts that use -E might not port to other older systems. See
		Extended regular expressions.

と-Eも実装されてる(-rと等価)ので意味ないよね…

@とりあえず原因は特定

まぁ心のマサカリは封印しつつ今起きてる障害の最小ケースを作成すると、Base64の末尾につくパディング目的の「=」を除去する目的の正規表現である

$ echo -n 'A' | sed -E -e 's/=*$//g'
sed: RE error: invalid argument to regex routine

を実行するとこでエラー出てることがわかりまんた。

  • echoに-nをつけない(つまり最後に改行がある)時はエラーにならない
  • 入力の最後に'='がある(つまり正規表現で置換が実行される)時はエラーにならない

ちゅー挙動、これどう考えても正規表現エンジンのバグよね。

まぁそもそも末尾のパディング除去なら

$ echo -n 'A' | sed -E -e 's/=+$//'

と「*(0個以上)」でなく「+(1個以上)」にして、g(最初の1つだけでなく全て検索)も要らないよなと思うんだけど、これだとエラー出ない。

つーことで相変わらずpkgsrcを6.1でテストしてる人間がおらんことが証明されてしまった、つか誰も騒いでないなら7.1とかでは直ってるんだろうと思ったけど

$ echo -n 'A' | sed -E -e 's/=*$//g' | od -x
0000000     0a41
0000001

バグ直ってるように見えて実は勝手に改行コード(0xA)が付与される新たなバグが埋められてて草不可避、サイレントに壊れてるってよりタチが悪いんですが。

ちなみにFreeBSD(11.0)とかOpenBSD(6.1)は問題ないもよう。

$ echo -n 'A' | sed -E -e 's/=*$//g' | od -x
0000000     0041
0000001

@暫定回避策

ふつうBSD sedはlibcのregex(3)に正規表現は丸投げなのでそっちのバグを修正しないとならんけど、今日のところは暫定でGNU sed(gsed)を呼ぶようにして回避でおしまい。

--- dehydrated.orig     2017-04-30 15:10:33.000000000 +0000
+++ dehydrated  2017-04-30 15:14:45.000000000 +0000
@@ -301,6 +301,8 @@ init_system() {
 _sed() {
   if [[ "${OSTYPE}" = "Linux" ]]; then
     sed -r "${@}"
+  elif [[ "${OSTYPE}" = "NetBSD" ]]; then
+    gsed -r "${@}"
   else
     sed -E "${@}"
   fi

今度こそ正しいunameの使い方である(ひどい)

さっきも書いたように「*」でなく「+」を使う方法もあるけど。

--- dehydrated.orig     2017-04-30 15:10:33.000000000 +0000
+++ dehydrated  2017-04-30 16:13:01.000000000 +0000
@@ -320,7 +320,7 @@ clean_json() {
 # Encode data as url-safe formatted base64
 urlbase64() {
   # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_'
-  openssl base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:'
+  openssl base64 -e | tr -d '\n\r' | _sed -e 's:=+$::' -e 'y:+/:-_:'
 }

 # Convert hex string to binary data

でも、他にもバグあるかもだし一番テストされてるであろうGNU sedに寄せるのが安全よね。