Seiichi Yonezawa

“How creativity is helped by failure”

開発日記 #40

今日は久しぶりに夜更かししてしまいました。そんな今回の表題はTurbolinks 5です。Rails 5のTurbolinksはプログレスバーが表示されるようになって、なんとなく使い勝手がよくない気がして避けて来ました。このサイトも以前はTurbolinks Classicを利用していましたが現在は使っていません。せっかくRailsを利用しているのにどうしてTurbolinksを使わないのでしょうか。Turbolinksに関する情報がろくに見つからないのも原因ではありますが、最近またJavaScriptを遊んでいる一環としてそろそろTurbolinksにも挑戦することにしました。

まずはっきりさせておきたいのは、Turbolinksは何も知らなくてもデフォルトでapplication.jsにrequireしていれば自動的に適用されます。ということは、Turbolinksを勉強しようと意気込んでみたところで、基本的に書き換える必要はないとも言えます。感覚的にはHTML5を使いたいけど、何から始めたらよいだろうかという感覚に似ているのでしょうか。HTML5に関しては<!DOCTYPE html>を宣言してしまえば基本的にXHTMLの感覚で書いていてもHTML5です。Turbolinksも同じで、Rails 5を使ってさえいれば問題はないはず。

とはいえ、昨今のウェブにはJavaScriptが欠かせません。JSを利用するにはTurbolinksを知らなければなりません。ちょうどHTML5で言うところのdata属性みたいなものでしょうか。また、Turbolinksとdata属性は切っても切れない関係です。さて、今日習得した内容といえば、data-turbolinks-permanentという属性です。これとidを指定してやるとその指定したタグはリロードを除いて基本的に更新されなくなります。

<div id="container" data-turbolinks-permanent>
  <%= Time.current %>
</div>

Turbolinks Classicでもこの説明はなされていますが、案外意識して調べないと見落としてしまいます。ただし、この方法だと#container以内のDOMは当然更新されないのでJavaScriptなどでclassを更新したりしてあげる必要がありそう。他にもいろいろと調べることはありそうなのですが、ぼちぼち調べていこうと思っています。

パスワードを看破したい

とあるメールの添付ファイルのzipファイルにパスワードがかけられていました。パスワードは暗証番号と同じというメッセージが添えられていました。そこでふと思いついたのですが、単純なパスワードを破るにはどうすればよいだろうかと。まずは非常に単純なbashスクリプトを書きました。

#!/bin/bash
for i in {1..10000}
do
    unzip -o -P $i target.zip
done

このスクリプトは数字4桁分のパスワードを試行します。しかし、実行してみるとすぐさま問題に直面します。正しくないパスワードでもところどころに0KBのファイルが表示されてしまい、もしパスワードが正しくても-oオプションで不正なファイルで上書きされてしまうかもしれません。

How to recover a lost zip file password

この2つめの解答では、John the ripperというコマンドが必要なようですが、辞書ファイルの代わりに配列に書き換えればunzipのみでもできそうなので、もう少し修正してみることにしました。

#!/bin/bash
echo "ZIP-JTR Decrypt Script";
if [ $# -ne 1 ]; then
    echo "Usage $0 <zipfile>";
    exit;
fi
unzip -l $1
for i in {1..10000}
do
    echo -ne "\rtrying \"$i\" " 
    unzip -o -P $i $1 >/dev/null 2>&1 
    STATUS=$?
    if [ $STATUS -eq 0 ]; then
        echo -e "\nArchive password is: \"$i\"" 
        break
    fi
done

よく見てみるとunzip部分は最初とあまり変わらないのですが、パスワードが違う場合は出力をせずに$?が正常終了になるまで試行しているようです。また、上のスクリプトは4桁まで試行していますが、実際の答えは5桁の数列だったのでおよそ解読に3分ほどかかりました。意外と数字だけの組み合わせももっと時間がかかるようですね。例えばこの例の場合01234というパスワードはいつまでもヒットしないでしょうし、そもそも桁数を決め打ちにすることも馬鹿馬鹿しいもので、本来はwhileループを使うべきでしょう。ただ永久に終わらない可能性も考慮しなければなりませんが。

いわば原始的で力業な解読方法ですが、上記はブルートフォースアタックです。Wikipediaのパスワード長と解読時間の関係を見るとたとえ数字だけでも6桁や8桁はなかなか膨大な数字です。今回のパスワードが5桁だからこそネタでよかったのですが10桁だったらと考えると恐ろしいものです。また、このサイトもその気になればEmailとパスワードの組み合わせに加えてパスワードは記号入りなのでおそらく外から漏れない限りは絶対看破されないであろうと思っています。余談ですがDuckDuckGoでpassword strong 20と検索すると手軽にパスワード生成できるのが便利です。

会員情報の移行

現在の案件では、某求人サイトの会員情報をRailsに移行するために、データベースの中身を見る機会がありました。そのシステムは現在も稼働中なのですが、これから移行するのはDeviseで書かれたシステムで、旧サイトはphpで構築されていたのですがパスワードは暗号化せずにテキストのまま保存してありました。パスワードのハッシュ化はDeviseの関数を利用したかったので単純なSQLでの移行をせずに、1件ずつインスタンスを作成し、save!していくことになりました。今回は実際に私が作業したというよりは、半ばペアプログラミングで横から作業の様子を眺めていたのですが、そうある経験ではありませんので残しておこうと思います。

真っ先にぶち当たった問題はパスワードが4文字に設定しているユーザーの存在でした。銀行の暗証番号じゃあるまいし、そんな短いパスワードでいいのかと思ってしまいますが、確かウェブサイトで使われている最も多いパスワードがpasswordだったということも考えると別に珍しい話でもないのかもしれません。また、驚くべきことに4文字以内のユーザーも少なからず存在していたようで、今でこそパスワードは英数字に記号や大文字を混ぜないとエラーを出すサイトも増えてきましたが、この案件に限らず当時はパスワードもとりあえず設置しておけばよいくらいの認識だったことは想像に難しくないかもしれません。

それ以上に残念なのは、会員のインポートを行うにあたって今回の方針ではユーザーのパスワードは原則そのまま移行して、ログイン後も特にパスワードの変更を求めないそうです。ということは、遠い昔に求人サイトを利用して、そのまま放置しておいたとしても、丁寧に名前やアドレス、そして短いパスワードはそっくりそのままデータベースに残り続けるということなのです。これは求人サイトに限らず、ネット通販や宿泊サイト等でも起こりうるし、会員情報を求めるサイトに対してはほぼすべてに起こりうる問題だと思います。Tim Berners-Lee: I invented the web. Here are three things we need to change to save itという記事ではもう少し掘り下げて説明しています。

若干逸れてしまいましたが、会員には4文字以内のパスワードもいれば、逆に32文字以上のパスワードを使う人もいました。ただ、今回の移行には明確にパスワードは20文字までと定められているので、32文字のパスワードも通さなければなりません。これがいわゆるdegradeなので申し訳なくも思うのですが、その人のパスワードはすべて[a-z]{32}で表現されていました。ピンポイントに総当たりで攻撃される場合は歯が立たないかもしれませんが、たくさん会員がいる場合は案外有用かもしれませんね。また、おそらく最も強固なパスワードはカタカナのみのパスワードでした。入力する手間が多そうですけれども、さすがにハッカーがカタカナのパスワードを想定しないと思います。

パスワード問題を回避したと思いきや、今度はメールアドレスの問題が浮上しました。というのも、Deviseは特に設定しなくともメールアドレスを正規表現で簡易的にチェックしてくれるのですが、その正規表現が緩すぎて以前メールが送信できないという問題がありました。そのため、独自に強固な正規表現を使って[email protected]\softbank.jpというメールアドレスを弾くようにしたのですが、上述したパスワードの存在があるので当然もっとひどいメールアドレスが運悪くそのまま登録されていたのです。

最初はメールアドレスの字数制限を大幅に超えるアドレスでした。パスワードのように、メールアドレスも20文字に収まらないアドレスは当然たくさんあるわけで。これは逆にこれから稼働するサイトは大丈夫なのかなぁと思いつつも制限をゆるくしました。しかし、続いて登場したのは同じく20文字以上のアドレスでしたが、これがなぜか通らない。その時使われていた正規表現は/\A[^@\s]+@[^@\s]+\z/これでした。意味合い的には「@と空白以外の文字列が含まれていない文字の連続が@で区切られている」つまり、メールアドレスに空白が含まれていて、なおかつそのアドレスで会員登録ができてしまったということです。いつ頃登録されたものかはわかりませんが、おそらくこれを登録した本人もそのまま放置していることを願いつつ。なぜならメールアドレスは後から変更できない仕様なのです。

結局Deviseの設定を/.+/このように変更して、モデル側に2通りのvalidates :emailを設定することになりました。片方は新規会員用で、こちらは従来のアドレスで登録し、移行した会員は更新時にのみチェックすることに。というのも、スペースが含まれているものはまだ可愛い方で、@すら含まれていないメールアドレスも存在していたからです。結局いくら優れたライブラリを利用したり、優れたサイトがどうあるべきか研究していても、いざ使う側がそのハードルを下げれば下げる程にシステムそのものがだめになっていく感じがします。まだそこまで大きくはありませんが、セキュリティ的なリスクも、こういった不要なif文の抜け穴から生まれるのかもしれません。ただ残念ながら私にはそれを止める権限がありませんのでこのサイトを利用する会員は気の毒だなぁと思います。

開発日記 #39

今週は更新するネタもなく、それどころかやる気というか根本的なモチベーションが全くありませんでした。しかし、2日も休んだ罪悪感からか、平日にやる気が戻ってきました。何かやろうと予定するとうまくいかないものですが、適当な作業ならそこそこ続く不思議。今に始まった話ではありませんが。さて、今週はサイトを何か作るのに仮の画像を用意しようと思ったのですが、毎回適当な画像をどこから拾ってこようか悩みます。単一色の画像や、Placehold.itの画像もいいのですが、やはりそれっぽい画像がいい。かといって、画像をいちいちダウンロードするのも面倒です。

何かあるたびにスクレイピングのスクリプトを書くのは楽しいのですが、今回はLorem Pixelからいくつか画像をダウンロードしてみました。ここは画像のバリエーションも多いし、基本的にいうことはないのですが、何枚か重複画像が出てしまいました。さて、前置きが長くなりましたが画像の重複をどうやって解消するかというのが今回のネタです。

RubyにはArray#uniqがあるので、もし配列上なら画像は簡単に整理できるのですが、なんせ画像です。じゃあいちいち画像をImageMagickのようなライブラリで読み込んでやる必要がある?いえいえ、インターネットからファイルをダウンロードする場合はハッシュアルゴリズムというものがあって、その画像が本物かどうかを確かめることができましたね。つまり、画像だろうが基本的にファイルサイズをもとに重複があるかどうか調べる事ができるわけです。

原理がわかったので早速書いて見ました。あまり綺麗なコードではありませんけど、なんとなくコード片を残したいと思ったので……。

Dir["*.jpg"].each do |file|
  next unless File.exists?(file)
  filesize = File.size(file)
  targets = Dir["*.jpg"].select { |target| File.size(target) == filesize && file != target }
  targets.each { |e| File.unlink(e) if File.exists?(e) }
end

非常に手続き型ですね。もうちょっと綺麗に書く方法は他にあるだろうと思いつつも、今日はこのあたりで。

n進数を求める方法

最近リニューアルしてからというものの、ログインに失敗するようになりました。原因は幸いすぐ突き止めることができたのですが、キャッシュの中にcsrf_meta_tagsを入れてしまっていたようで、しかもキャッシュは開発環境では通常動かないのでデプロイしてからしばらく気づきませんでした。headタグはあまりキャッシュに適さないのかもしれませんね。

また、最近読書を進めている一貫として、以前Amazonで購入した本に「二進数、八進数、十進数の回文で10以下のもっとも少ない数字を求めよ」という問題がありました。なかなか手をつけずにいたのですが、その本の問題の中では最も簡単な問題のようで、これを解かなければ前にすすめないというわけ。

その本のタイトルは「プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問」というタイトルなので、数学に絡めた方法で解かなければならないのだと思い、RubyのNumeric#to_sでは与えた基数のn進数を求めることができたはず。でもそれを使うのはきっと違うのだろうと思い、飛行機の中でなんとか二進数の説明を読みながら二進数、八進数をそれぞれ求めるメソッドを見出しました。

しかーし、ようやく正解がわかって解答ページをめくってみたところ、Rubyやそれ以外の言語の10進数から2進数に変換するメソッドが解説されていてちょっと悲しくなってしまいました。とはいえ、まあ一番簡単な問題ですしね。思いの外解くのに四苦八苦しましたが、なんとか解けました。こういったクイズのようなプログラムも楽しめたらいいなと最近思い始めました。

ログを見る習慣

今日はいわゆるステージング環境というサーバーで、Railsのログを見る方法について考えてみました。
サーバーのログというと、Apacheのようなまさしくアクセスログのような情報がまとまっているものだと思っていました。しかし、Railsのログはまさしく普段見ているログと何ら変わりなく、SQLに色がついていたりします。問題はこの色、いわゆるEscape文字ですが、lessのようなコマンドでは文字がエスケープされてしまうため、一時期はログからエスケープを除去する方法を考えていました。ですが、それはさすがに時間がかかりすぎるため、重い腰を上げて調べました。

less -R log/production.log

bash - color escapes codesより、このように引数-Rを加えることで非破壊的にログを調べることができるようになりました。また、/で単語を検索したあとはnNで次の候補に移動できることもついでに覚えました。このおかげで無事トラブルシューティングができてご満悦状態。

また、もう一つ気にかかったのは、ある時点でIncreasing the amount of inotify watchersこのエラーが頻発して開発すらままならない状況になりました。Wikiにあるコマンドはもちろん、tmpディレクトリを空にしたり、大量にあったdbをすべて空にしたり、gemをすべてインストールしなおしたり、挙げ句の果てにはVagrant Boxを破壊してみましたがどれも効果はありませんでした。

さすがにOSをインストールしても直らないのでお手上げかとも思ったんですが、ちょうどpublicになんと数GBもの画像が必要だったので配置してありました。つまり、Railsを起動しようとする時にこれらのファイルの変更もすべて監視しようとした結果エラーが表示されてしまっていたわけです。画像のリンクは切れてしまうのが残念ですが、大部分は必要のないファイルなので一気に削除すると、無事元どおりに。

こちらは開発環境依存で、ログに残らない問題でしたが開発の効率はいかに早く問題点を見つけることが重要なので(まあ、私は普段保守担当というのもありますが)こういったスキルは伸ばしていきたいなぁと思う次第です。

自分を鑑みるとき

少々大げさなタイトルですが、今日は自分の力を過信しすぎているかもなという出来事がありました。それはデプロイ時のことなのですが、普段開発ではあまり気にかけないアセットのことです。現に、このサイトもbootstrap等のライブラリはgemを利用していました。つまり、外部のライブラリは使い方がわかっているようで、そんなにわかっていないのでした。

今回、slickというライブラリを拝借しました。JavaScriptのライブラリではCSSとJSがそれぞれ分かれていることも珍しくないため、下記のようなディレクトリ構造を採用しました。この形式だと、ライブラリを置き換える時にディレクトリ構造を書き換えずにそのまま上書きできるわけです。

vendor/
└── assets
    ├── javascripts
    ├── slick
    │   └── slick
    └── stylesheets

この例では、vendor/slick/slick/slick.cssvendor/slick/slick/slick.jsがあり、目的のファイルまで階層が深いです。このファイルをimportあるいはrequireするのにconfig/initializers/assets.rbRails.application.config.assets.pathsにパスを追加します。そして、開発環境で無事リンクできていることを確認できたのでstaging環境へプッシュ。すると、このライブラリだけ404エラーが表示されるではありませんか。

似たような現象で、TinyMCEも元々npmからインストールしていたのですが、結局gemに乗り換えました。しかし、今回はちゃんと原因を探してみようと思いました。殊勝な環境なので、このサイトのように管理権限がないのでなかなかやりにくいでしたが、サーバ担当者からいただいたログを見てみると、ファイルそのものが存在していないことに気づきました。

つまり、先ほどのRails.application.config.assets.pathsではなく、Rails.application.config.assets.precompileに追加し直してheadタグから直接リンクする形式に変えました。こうしてみるとそんなに難しくはないのですが、そこに至るまではassetsのどこかにリンクを書き換えれば読み込めるはずだと思っていました。それに、アセットごときに時間を取られてなるものかと高を括っていたのもよくありませんでしたね。一度こうだと思い込んだら、それからしばらく思考停止になっていても気づけない。

Railsは便利ですし、おそらくどの言語よりも簡単です。けれども、簡単にするためには然るべき場所に正しいコードを当てはめるだけの知識が必要です。たかがアセット、されどアセット。結構焦ってしまいましたが終わりはあっさりと。日々の研鑽は重要ですね。Webpackとかyarnもそういえば勉強しないとなあ。

リニューアルしました

Rails 5に移行してからしばらくの間はデザインをそのまま引き継いでいていたのですが、今日ようやくリニューアルの第一歩を踏むことができました。以前の開発日記にも残していたのですが、本当はSTIでPostから様々なクラスを派生させる予定でした。でも結局Journalクラスしか使っていないため、思い切ってまたよくある1カラムのブログレイアウトに変更しました。リニューアル前は2カラムで作りたいと思っていたのですが、無駄な空白のままだったので、2カラムを採用するのはもう少し後でもいいかなと。

Rails 5に移行できたと思いきや、1時間弱程度Passengerのエラーが表示されていました。エラーの内容はDeviseによるものだったのですが、devise_forを使う位置でコールバックが定義されないこともあるようです。ただ、Passengerがエラーした時はログがどこに保存されるかまだよくわかっていません。このブログを表示している部分も未完成の状態ですが、特に管理画面にあたる部分がほとんどできていないので、またしばらくはこれらの画面の改修を含めて直していければなと思っています。

読書がしたい

先週は代休で3連休だったのですが、あまり開発自体は進みませんでした。やりたいことはある程度固まってきてるはずなのですが、なかなかうまくいかないうちにモチベーションも下がってきて、結局時間を無駄にしてしまうという負の連鎖に陥ってしまいました。

そんな中、今日は趣向を変えてなんでもAmazonで翔泳社祭りなるセールが開催されていたので、何冊か本を買ってみました。前回のセールで買った本はまだ全部読破していませんが、Effective Rubyはいい本でした。書評は読む前ではなくて、読んだ後にすべきなのですが、唯一セールと関係ない『マインドセット「やればできる! 」の研究』という本を買いました。これは無料サンプルだけ読んでいたのですが、なかなか買う機会がありませんでした。とりあえずこの本や、まだ積んでる本も含めてぼちぼち読書を進めていきたいと思っています。

モチベーションの維持にも、こういった技術書は必要だと思うんですが、自分の技量にあった本を選ぶのも意外と見落としがちですが重要です。本音からすると、ドメイン駆動開発の本や、ディープラーニングの本も読んでみたかったのですが、まずは自分のレベルに合わせて進めていく予定です。

開発日記 #38

今週は忙しかったうえに、休日も1日潰れてしまい、今日はその回復に充てていたため特に何もできませんでした。こうした趣味以外ではWindows環境でVagrantの仮想マシンでRailsサイトの開発をしているのですがVirtualboxのファイルをWindowsにマウントしているので、Railsのヘルパを保存更新してもブラウザの画面は更新されていない、ということがありました。テストを満足に書けないのも悪いのですが、毎回サーバーを再起動させるのでは効率が悪い。

Rails server doesn't see code changes and reload filesにありましたが、development.rbにもともと

This feature depends on the listen gem.

こんな但し書きがありました。ちなみにListenはGuardで使われているgemでしたね。listenそのものはWindowsもサポートしているようなのですが、環境としてはUbuntuなのでその辺りが不具合だったのでしょうか。普段は古いRubyばかり扱っているので、なかなか新鮮でした。個人的には昔のRailsってwhereが使えなかったり、配列の表示がjoinで表示されたりしますが、そんなことばかり言ってるうちにちゃんとRailsが満足に書けるのかというと、まだまだ効率的に進められている気がしないので、勉強しないとなぁと思う次第でした。