ある研究者の手記

セキュリティとかゲームとかプログラミングとかそのへん

テキスト出力されたログファイルから元のログフォーマットを分析するツールを作った

タイトルの通りなのですが、昔ちょっとやっていたテーマに関連したツールをGo言語の練習がてら作ってみました*1

ログファイルから元のログフォーマットを分析するとは

ここで言うログのフォーマットというのは所謂フォーマット文のことを指します。

log.Printf("Requested from %s", ipAddr)

このコードから以下のようなログが出力されます。

2018/05/23 23:25:00 Requested from 10.0.2.1
2018/05/23 23:25:10 Requested from 192.168.1.5
2018/05/23 23:25:24 Requested from 10.0.1.5

元になったフォーマットは %s の部分にIPアドレスらしきものが埋め込まれて下図のようなテキストとして出力されます。この例は非常に簡単なので下から上を推測するのは容易ですが、内容が複雑になってくると「これ値なのか固定文なのかどっちだ?」ということがまれによくあります。この下の出力から上のフォーマット文(に近いもの)を推測するのが今回作成したツールになります。このツールは 1) すでに出力されたログファイルからフォーマットを推定する、そして 2) 推定したフォーマットを利用し、そのフォーマットに該当するログがログファイルのどのあたりに出現したのかを示す という2つの機能を実装しています。

なんでこんなツールが必要なのか

実際には、正規化・構造化されたログデータのみを扱う環境であればこのツールは不要ですが、以下のような状況で役立ちます。

  • ログの全体像を把握したい場合 : セキュリティ分析の文脈で特に多いと思いますが、今まで見たことのない大量のログをみてそこから知見を導き出さないと行けない場合があります。そういったときにひたすら less コマンドで眺めようとしても人間には厳しいので、全体としてどういうログがあるのか? そしてどういう分布をしているのか? ということがわかると分析のとっかかりが非常に楽になります。特にセキュリティ分析で必要なのは多くの場合全体の99%を占める通常のサービスに関するログではなく、何か異常が起こったポイントになります。異常が起こった際のログというのは通常見られないエラーや処理が発生しやすいため、異常なログ=珍しいフォーマットのログがどこに出現するのかを把握できると、そこにまず注目して分析するという足がかりを作ることができます。
  • テキスト形式で出力されるログを再利用しないとならない場合 : すでにサービスなどが稼働しておりテキストではログを出力するという場合、そのログを正規表現にかけるなどして中に含まれている値を抽出する必要があります。仕様書がある場合はいいですが、そうでない場合はソースコードを見るか、もしくは正規表現を書く→網羅でいているか確認する→正規表現を直す、みたいなことを繰り返さないとならずかなり面倒です*2。このツールだと抜くべき値の正規表現の推定まではしてくれませんが、既存のログから分かる範囲ではどこまでやればいいかを網羅できるので作業的に楽になります。

使い方

Go言語を使う環境が整っていれば go get github.com/m-mizutani/logptn でインストールされます。

GitHub - m-mizutani/logptn: Generate Log Format from real log data

例として(短いものですが)以下のようなログをツールに入力してみます。

$ cat test.log
Feb  1 07:56:49 pylon sshd[5153]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.3  user=root
Feb  1 07:56:51 pylon sshd[5153]: Failed password for root from 192.168.0.3 port 7176 ssh2
Feb  1 07:56:51 pylon sshd[5153]: Connection closed by 192.168.0.3 [preauth]
Feb  1 08:01:26 pylon sshd[5156]: Invalid user upload from 192.168.0.3
Feb  1 08:01:26 pylon sshd[5156]: input_userauth_request: invalid user upload [preauth]
Feb  1 08:01:26 pylon sshd[5156]: pam_unix(sshd:auth): check pass; user unknown
Feb  1 08:01:26 pylon sshd[5156]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.3
Feb  1 08:01:28 pylon sshd[5156]: Failed password for invalid user upload from 192.168.0.3 port 51058 ssh2
Feb  1 08:01:28 pylon sshd[5156]: Connection closed by 192.168.0.3 [preauth]
Feb  1 08:05:01 pylon CRON[5159]: pam_unix(cron:session): session opened for user root by (uid=0)
Feb  1 08:05:01 pylon CRON[5159]: pam_unix(cron:session): session closed for user root
Feb  1 08:05:54 pylon sshd[5162]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.3  user=root
Feb  1 08:05:56 pylon sshd[5162]: Failed password for root from 192.168.0.3 port 33005 ssh2
Feb  1 08:05:56 pylon sshd[5162]: Connection closed by 192.168.0.3 [preauth]
Feb  1 08:10:28 pylon sshd[5165]: Invalid user mythtv from 192.168.0.3
Feb  1 08:10:28 pylon sshd[5165]: input_userauth_request: invalid user mythtv [preauth]
Feb  1 08:10:28 pylon sshd[5165]: pam_unix(sshd:auth): check pass; user unknown
Feb  1 08:10:28 pylon sshd[5165]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.3
Feb  1 08:10:30 pylon sshd[5165]: Failed password for invalid user mythtv from 192.168.0.3 port 59978 ssh2
Feb  1 08:10:30 pylon sshd[5165]: Connection closed by 192.168.0.3 [preauth]
Feb  1 08:15:01 pylon CRON[5168]: pam_unix(cron:session): session opened for user root by (uid=0)
Feb  1 08:15:01 pylon CRON[5168]: pam_unix(cron:session): session closed for user root
Feb  1 08:15:26 pylon sshd[5171]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=10.2.3.4  user=root
Feb  1 08:15:28 pylon sshd[5171]: Failed password for root from 10.2.3.4 port 60733 ssh2
Feb  1 08:15:28 pylon sshd[5171]: Connection closed by 10.2.3.4 [preauth]
Feb  1 08:17:01 pylon CRON[5173]: pam_unix(cron:session): session opened for user root by (uid=0)
Feb  1 08:17:01 pylon CRON[5173]: pam_unix(cron:session): session closed for user root
Feb  1 08:20:35 pylon sshd[5177]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=10.2.3.4  user=root
Feb  1 08:20:37 pylon sshd[5177]: Failed password for root from 10.2.3.4 port 44877 ssh2
Feb  1 08:20:37 pylon sshd[5177]: Connection closed by 10.2.3.4 [preauth]
Feb  1 08:25:01 pylon CRON[5180]: pam_unix(cron:session): session opened for user root by (uid=0)
Feb  1 08:25:01 pylon CRON[5180]: pam_unix(cron:session): session closed for user root
Feb  1 08:25:16 pylon sshd[5183]: Invalid user user from 10.2.3.4

このデータを入力させると以下のような出力をします。

./logptn test.log
2018/05/20 13:30:55 arg:test.log
     4 [4ffb267b] Feb  1 *:*:* pylon sshd[*]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=*  user=root
     4 [845f4659] Feb  1 *:*:* pylon sshd[*]: Failed password for root from * port * ssh2
     6 [847ccf35] Feb  1 *:*:* pylon sshd[*]: Connection closed by * [preauth]
     3 [de051cd9] Feb  1 08:*:* pylon sshd[*]: Invalid user * from *
     2 [8e9e2a13] Feb  1 08:*:* pylon sshd[*]: input_userauth_request: invalid user * [preauth]
     2 [22190c74] Feb  1 08:*:* pylon sshd[*]: pam_unix(sshd:auth): check pass; user unknown
     2 [83fba2bf] Feb  1 08:*:* pylon sshd[*]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.3
     2 [f1ba83ea] Feb  1 08:*:* pylon sshd[*]: Failed password for invalid user * from 192.168.0.3 port * ssh2
     4 [e4a6f815] Feb  1 08:*:01 pylon CRON[*]: pam_unix(cron:session): session opened for user root by (uid=0)
     4 [5256845b] Feb  1 08:*:01 pylon CRON[*]: pam_unix(cron:session): session closed for user root

この出力では、左から「そのフォーマットが出現した回数」「フォーマットID」「推定されたフォーマット」になっています。また、推定されたフォーマットにおいて、値として埋め込まれると思われる部分を * という記号に置き換えています。この例ではサンプルが少ないため、IPアドレスの部分が * になっていないものもありますが、サンプル数が増えるとこれも * に置き換わります。上記は人間が読みやすいテキスト形式での出力になっていますが、別のプログラムで扱えるようにjson形式でも出力できます。

./logptn test.log -d sjson | jq . 
{
  "formats": [
    {
      "segments": [
        "Feb  1 ",
        null,
        ":",
        null,
        ":",
        null,
        " pylon sshd[",
        null,
        "]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=",
        null,
        "  user=root"
      ],
      "count": 4
    },
(snip)

さらに、そのフォーマットがログ全体のどの行数あたりに現れたのかをHTML形式で一覧にして表示することもできます。

$ ./logptn  ./var/log/secure -d heatmap -o secure.html

上記のコマンドでログのフォーマット、およびそれが何行目あたりに出現したのかを示すヒートマップを作成できます。ヒートマップは左が推定されたフォーマット、上のヘッダが行数(何行目〜何行目)、右が合計ログ数になっています。下の画像がちょっと小さくて見づらいですが、HTMLファイル自体はここからもダウンロード可能です。

f:id:mztnex:20180520141045p:plain 大きい画像

性能

計算量としては O(NM) になり、N がログファイルに含まれるログの総数、 M が推定されたフォーマットの数になります。いろいろなログファイルで試してみましたが M は10〜100ぐらいに収束するのでだいたいは N 、つまりログの総数が影響します。雑にしか計測していませんが、M=40 ほどになるデータに対して MacBookPro Early 2015 (2.7 GHz Intel Core i5) で動かして、おおよそ30,000 logs/sec 程度で動作しました。多分コード的にはもっと最適化できるんですが、まだそこまでは手を付けてません。

動作の仕組み

このあとはどうやってフォーマットを作成しているかという話なので、興味のある方だけどうぞ。

f:id:mztnex:20180524021502p:plain

すでに出力されているテキストログから元になったログフォーマットを推定する話は昔から研究としてありますが*3、今回 logptn で実装したのは非常に簡易なアルゴリズムになっています。昔は自分もいろいろとこねくり回した方法を考えたのですが、どれだけ複雑なアルゴリズムを使ったところで「まあ所詮は推測にすぎないよね」という割り切りを得たので、極力シンプルに実装しました。

手順

このフォーマット推定のアルゴリズムは4段階に別れており、それぞれ順番に解説します。

Phase1) Import logs

このアルゴリズムはバッチ型(ある程度の固まった量のログデータをまとめてから処理するタイプ)になります。一方で次々到着するログを逐次的に処理するオンライン型やストリーム型と呼ばれる手法もありますが、今回はもとになるデータセットは事前に決まっている(あとから増えない)ものとします。

取り込みに関しては全く難しいことはしておらず、現在は完全に1行1ログとして分割して切り分けています。全体の流れとしては複数行のログでも対応できないことはないアルゴリズムになっていますが、複数行で切り分ける基準がログごとに様々すぎるので、現状では対応していません。

Phase2) Chunking

データを1つずつのログ(現在は1行のテキスト、と同じ)に分割したあとは、そのログに含まれる単語などに分解する Chunking を実施します。これはログ内に埋め込まれる値は単語などの短い単位として出力される、という前提をおくことで、値として出力される単語の長さが違うことでフォーマットがばらつくのを防ぐのが目的です。例えば 0.0.0.0255.255.255.255 という2つが別のログに現れたとしても同じ「IPアドレス」として認識してほしいですが、これを一文字ずつ比較しようとすると2つはかなり異なる文字列長になります(0.0.0.0 が7文字、255.255.255.255 が15文字)もちろんこういう文字列長の違いをうまく吸収してくれるアルゴリズムなら気にしなくていいのですが、だいたいは問題を単純化するために事前にログを単語(ここではchunkとよんでいます)に分割しています。

分割については、これが自然言語的な英語であれば単純に空白で区切ればいいのですが、ログファイルというのは様々な記号が含まれてそれによって区切られている場合もあるので、なかなか空白だけというわけにはいきません。実際にはある特定の記号が出てきた場合に区切るという実装にしており、現状 logptn だと \t!,:;[]{}()<>=|\\*\"' がデフォルトの区切り文字となっています。これらのうちどれかがでてきたらchunkとして切り分けてきます。(詳しくはこちら を参照)この記号の選び方は完全に自分の経験に基づくヒューリスティックなものなので明確な根拠はないですが、まあだいたいうまくいっている感じです。一方、これらの記号にもとづいてこのフェーズである程度正しく文を分割できることが前提となっているため、日本語のようなマルチバイト文字のログについてはおそらくうまくいきません。

また、ヒューリスティックを入れていいと考えるなら正規表現などによって日付やURL、Eメールアドレスなど値として埋め込まれると考えられるような形式の値をを特定して切り出してしまえばより精度があげられます。実際、それを見越して正規表現でそういった機能も実装したのですが、Goの正規表現モジュールが想像以上に遅くて厳しかったので、現状デフォルトの機能としては外しています。(一応、 --enable-regex オプションを使うと有効化はされます)

実際にこの方法で文を分割すると、以下のようになります。

  • Before: pam_unix(cron:session): session closed for user root
  • After: pam_unix, (, cron, :, session, ), :, , session, , closed, , for, , user, , root

Phase3) Clustering

Chukingされたログが出揃ったら次は類似しているログをクラスタリングします。クラスタリングも非常にシンプルなアルゴリズムを使っています(名前知らないだけで既存のクラスタリング手法かもですが)。以下の手順をログ1つずつ順番に試します。

  1. Chunk長(1つのログから生成されたChunkの数)が同じクラスタがなかったら手順終了
  2. 同じChunk長のクラスタ全部に対して距離を計算する
  3. クラスタとログの距離は、クラスタの中心になっているログとどれくらい近いかで計算する。ログ間の距離は全体でChunkが一致する割合を見て、割合が高いほど近いと判断する。
  4. クラスとの距離がthreshold(デフォルトでは0.7)を超えていたら、もっとも近いクラスタに組み込まれて手順終了
  5. もしthresholdを下回るクラスタのみだったらそのまま終了

クラスタに組み込まれずに終了した場合は、そのログを元に新しいクラスタを生成します。これをすべてのログに対して実施します。

Phase4) Estimate Format

クラスタが生成されたらあとはフォーマットを推定するのみです。これも非常にシンプルなアプローチで、クラスタ内のすべてのログに対して積をとっているイメージです。クラスタ内のログを L1, L2, ... , Ln としたとき、まず L1L2 で積をとって、L' を生成し、そのあとは LiL' で積をとる、という処理を繰り返します。

  • L1: Requested from 10.0.2.3
  • L2: Requested from 192.168.0.1
  • L': Requested from null

同じクラスタだとChunkの長さがすべて同じになるので、互いのログのChunkを先頭から比較します。同じ内容であればそのまま、もし異なる内容であれば null とします。この null がフォーマットの中で値が入ると考えられる部分となります。このあと L3 と比較する際は null の部分はどのChunkと比較しても必ず null になります。

この操作をクラスタ内のすべてのログに対して実施すると、最終的にそのクラスタのすべてのログに適合できるフォーマットが生成(推定)できるということになります。

この手法の弱点

前述したとおり、このアルゴリズムはChunkingがある程度正しくできていて、かつ1 Chunk=埋め込まれる値になることが強い前提となっています。そのため、たとえばChunkに分割されやすい任意長の文字列が登場するようなログ(極端に言えば、例えばユーザによる入力をそのままログに書き出すようなログ)に対しては非常に低い精度になると見込まれます。また任意長でないとしても同様にChunk分割によって1つの値が複数に分割されてしまうようなログには耐性がないと言えます。

また、現状のアルゴリズムだと複数のクラスタから同じフォーマットが生成される可能性があります。クラスタを生成する時のアルゴリズムがわりと雑なのでたまたま距離の計算でしきい値を超えてしまったなどの場合に、本来同じクラスタであるべきログが2つ以上のクラスタに分離してしまいます。これについてはまだすっきりした方法を思いついてないですが、生成後のフォーマット同士を比較してマージするというような処理が必要かなと考えています。

参考文献

*1:言い訳がましいのですがまだGo言語まともに書き始めて1ヶ月ぐらいという有様なので実装物について、流儀的にこういうの違うよ、とかGoならこういうふうにも書けるよ、みたいなコメントは大歓迎です

*2:そんな環境のほうがおかしいだろというツッコミはあると思いますが、稀によくあるシチュエーションでした。特に前職

*3:参考文献として幾つか論文へのリンクを貼っておきました

2018年春アニメはネット配信だけで視聴できるのか

背景

先日、会社でアニメの雑談していたときに「そういえば今時のネット配信はテレビ放映中のアニメのカバレッジってどれくらいなんだろう?」と気になりました。だいぶ配信が整備されてきたようなそうでもないような…。

個人的には四半期に一度せっせと録画の準備をしたり、HDDの残り容量を気にしていろいろケアしたり、録画に失敗したりというので消耗するのはそろそろやめたかったのでなるべくならネット配信で見てしまいたい派です。ということで、現実的にどれくらいの視聴ができるのかというのを知るべく、2018年春アニメのネット配信状況をまとめて見ました。

結果

2018年春アニメ ネット配信一覧 - Google スプレッドシート

※ 雑に作った&後から情報が更新されることもあるようなので、間違いなどあればtwitterなどで指摘してもらえればと思います

結論から言うと「完全にネット配信だけで生きていくことはできないが、頑張ればわりといける」です。

まとめはある程度配信数を揃えていそうなサイトで、かつ月額定額制で(少なくともその期にやってるアニメは)定額のうちに入っているものを対象にしたつもりです。(ニコニコ、DMMなどは1週間で無料期間が終わってPay per viewになるので除外しました)

以下、まとめていた時の気づき。

  • 配信数はバンダイチャンネルdアニメストアの二大勢力。しかしそれでも30本ちょいで今季から新しく始まった72作品の半分に届いていない
  • 日中や夕方にやるような本来子供向けのアニメはあまり配信されていない傾向にある。そのためみたいコンテンツが深夜アニメ枠ならかなりがカバーされている
  • AmazonビデオやNetflixは二大勢力には数で劣るものの、独占配信のコンテンツがいくつかある

Web系企業に転職して最高だったという話をしたい

11月にSI企業からCookpadにセキュリティエンジニアとして転職して1ヶ月たったのですが、いろいろ感銘をうけたのでその気持を忘れぬうちに文章に残しておきたいと思います。

disclaimer

  • 個人の主観であり、客観的にSI企業が悪いとかWeb系が良いとか言っているわけではありません。
  • かなり前職disな話っぽくなってしまっていますが、そこは企業としての性質の違いだとご理解いただければ幸いです。
  • 当該企業からはお金を頂いています。予めあしからずご了承ください。

しがらみが少ない

CookpadはWeb系の中でもかなり規模が大きい方だとは思うのですが、それでも前職のグローバル含めた規模のおそらく1/1000ぐらいであり、自分にとってはとても風通しの良い体質に思えます。

新しく何かを始めようとするときも、関係する人と立ち話で「こんな感じにしようと思うんだけどどうですかね」みたいなところをざっと決めて作りながら物事を進めていく…というやり方だと感じています。Rough Consensus and Running Codeという世界ですね。もちろんこの1ヶ月の間に細かい手戻りも何度かあったりしたのですが、ちゃんと合意を取りながらすすめると言ったやりかたを超えるスピード感で仕事をするのが重要と感じています。(もちろんこれは対象の規模などにもよりますが)

また、サービスなどに直結するような内容でも素早く動いていくことを重視しているように感じています。前職では何かしようとするとだいたい2〜3つ以上の力学というか外圧のようなものがあり、それをまずどうにかしないといけませんでした。グループ内部ではわりと好きにやらせてもらっていたのですが、その外に出ようとした瞬間に壁を突破したり調整したりが必要でそこで疲弊してしまうことが少なくありませんでした。現職では担当者同士ですぐ話をして次のステップへ進めるというサイクルが短く回っているため、むしろ振り落とされないよう頑張ってついていかねばと思う場面も少なくありません。

強いエンジニアが多い

とりあえず、右を見ても左を見ても豪傑ばかりという印象です。

前職でも研究所に学術的研究に秀でた方は多くいらっしゃいましたが、正直なところエンジニアリングが得意という人は全体でもかなり少なかったです。本当はエンジニアリングも得意だけど、そういうのが仕事の内容的に見えてこないというだけだったかもしれませんが、全体としてエンジニアリングにあまり積極的でない空気感を感じていました。

当然ながら現職ではエンジニアリングは非常に重要な位置づけとなっており、これを蔑ろにすると(多分)人権を失います。象徴的なイベントとして、入社後にちょうど社内ISUCONが開催され私も参加させてもらったのですが、出張や休暇などでいない人を除いて全技術職が参加必須というのに驚きました。技術職と言っても様々な分野の方(インフラ、サーバサイド、フロントエンド、モバイルアプリ、研究職)がいらっしゃるわけですが、皆さんほぼ基本的な技術は一通りできるのは当たり前で、その上で自分の得意分野を活かして競技に参加していました。

また、CTOが「今日一日業務が止まるのはとても手痛いが、それでもやる価値がある」と言っていたのがとても印象的で、ちゃんとエンジニアの育成に力を入れているんだなと思いました。

セキュリティの分野についても入り組んだ攻撃や防御の話に通じている人はあまりいませんが、実践のサービス開発と運用の部分については長年の蓄積を持つ人が多く、学ばせてもらうところが多くあります。そういった環境に身を置けるというのはエンジニアとしてありがたいことだと思います。

当事者意識があり、サービス・環境の改善にとても前向き

自分たちが作っている・使っている環境を常に良くしていこうという文化を強く感じます。前職の場合、基本的には本社なり別部署が決めたものを(それがどんなに使いづらいものでも)言われるがままに使うというのが基本でした。規模の大きさを考えると仕組みや使うものを決める人と使う人を分けたほうが仕事が明確になって良いのでしょうが、使いづらさやだめなところがいつまでも改善されないというフラストレーションがありました。

現職場では自分達が使うものを自分たちで選び、時には作るといったことをするため、常に「どうすると良くなるか?」といった方向を見ているなと感じています。これは今あるものを良くするためにも重要なことだし、その良くなったものをベースにまた次の新しいことを始めるのにも活きてきます。また、そうするべきかという議論や検討はもちろん必要ですが、ソフトウェアであれば自分で作ってしまっても良いわけで、ものづくりが好きな自分としてはそれも魅力の一つになっています。

自由にオープンソースにコミットできる

正直、これが一番強烈だったかもしれせん。

入社する前から外部に対してアウトプットしていくことは評価にも含まれており望ましいという話は聞いていたのですが、実際にとても自由な状況でした。OSSに関するポリシーを見てみると "従業員は自分の良識に基づいて、業務時間中に開発したソフトウェアをOSSで公開できる" という説明を見つけ、あまりの神々しさに見た瞬間目が潰れるかと思いました。目がーっ!

前職では知財やそれに準じる成果物が非常に厳しく管理されており、私的時間に書いたコードですらOSSとして公開あるいはcontributeするのに内部レビューと承認が必要でした。おかげでOSS的活動が好きな自分としては少なからずストレスではあったのですが、それが完全に開放され翼を授かった気持ちです。というかむしろ、前職との違いに頭がクラクラしています。

おかげで、これまでgithub上でPRを送るというOSS活動に縁がなかったのですが、先日始めてPRを送りmergeされてました。実に大したことじゃないんですが、ささやかながら嬉しい気持ちになっています。今後は自分で書いているOSSだけでなく、他のOSSにも積極的に貢献していきたいと考えてます。

f:id:mztnex:20171203114238j:plain

その他

あまり本質ではないのですが、その他感動したことなどをいくつか。

いわゆる今時の仕組みを使った開発・運用

社内で動いているシステムやフレームワークを見て「おお、これが噂に聞いたgitでconfigのバージョン管理をして自動デプロイされるというあれか…!」と感動していました。github eterpriseまでは前職でも使っていたのですが、普通にコードのバージョン管理をするだけで(それでも社内ではかなり珍しい感じでしたが)そういった今時っぽい使い方はしていませんでした。まあがっつり運用というほどではなかったので、そこまでする必要がなかったと言えばそうなんですがね…。

開発マシンなど

マシン自体は前職もそこまで悪くなかったですが、現職ではさらにもう一回り上のスペックになっています(MacBookPro メモリ16GB)。基本、全員が4Kディスプレイを使っています。

キーボードやマウスについても、自分は最近 RealforceLogicool G900 がベストな組み合わせなので「ちょいと値は張るけど新しく職場用に自分で買うかー」と思っていました。が、この話をしたら「いや、普通に発注してくださいよw」と言われするっと申請したらしゅっと支給されてとても感動しました。

無限のコーヒーやスナックがある

まったく本質ではないし、前職にいたときもコーヒー買うお金をケチったことなどないのですが、やはり無料で供給されると思うと気分が全く違いますね。ここしばらくカフェインをコードに変換する仕事をしていました。

おわりに

まだ転職してからたかだか1ヶ月なのでこれから見えてくる大変なことや苦労も色々あるとは思うのですが、今のところ職務内容も含めて、転職してよかったと思える生活をしています。

この記事は別にリクルート目的というわけではないので特にリンクなどは貼りませんが、現職ではセキュリティエンジニアだけでなくいろんな職種を募集しているので、興味のある方は気軽に声をかけてもらえればと思います。

追記

コメントとか見てたら「給与や労働時間の話がないのは闇」って書かれていて、みんなよく訓練されてるなぁと感心してしまいました。気になる方も多いと思うので一応追記しておきます。

  • 給与は前職で「独身一人暮らしが雑に出費しても貯金できる程度」もらってると書きましたが、そこからちょい上乗せするくらいもらっています。転職時にあんまり給与交渉しなかったので場合によってはもっとあがっていたかもしれません。
  • 勤務時間は基本1日8時間となっていてフレックスタイム制です。残業時間は先月で確か20時間ちょいぐらいだったと記憶していますが、これは働きはじめでいろいろ慣れていなかったこともあり、効率的にやればもっと短縮できそうではあります。

情報セキュリティ関連の求人

先日転職したわけですが、転職活動序盤に「そもそも情報セキュリティ関連の求人ってどういうのがあるんだろう?」と思ってばーっといろいろな職種を見渡していました。その結果、概ねこういうカテゴリの募集があるのか、という知見を得たのでまとめてみます。これから情報セキュリティ関連の職種への就職・転職を考えている人の参考になれば幸いです。

なお、筆者の個人的嗜好によってバイアスがかかっている可能性は多分にあり、まったくもって網羅性を保証するものではありません。

脆弱性診断

自社で開発するプロダクトやサービスを検査し、脆弱性を発見する仕事。また開発物のチェックだけでなく、セキュアな開発のガイドラインを作成するというような仕事が付け加えられている場合もある。プロダクトやサービスに直接関連する開発の知識・経験が求められる。

SOCオペレータ

サーバやネットワークを監視してセキュリティインシデントを発見・対応する仕事。非常に規模が大きい自社サービスを抱えているところでは自社内だけを対応するケース(プライベートSOC)もあるが、おおむね契約したお客さんの環境を監視するManaged Security Serivceの要員を募集している場合が多い。監視機器の環境構築・導入・保守も担当する。インシデントを発見した場合に報告するだけなのか、インシデント対応をサポートするのかなどは会社によって異なる。

社内全体のリスク管理

個別のプロダクトやサービスの脆弱性を検査するだけでなく、社内全体のリスク評価の実施して改善案をだしたり、インシデント発生時の演習のとりまとめをやったりする。CSIRT (Computer Security Incident Response Team) に近い業務を求められるものもある模様。かなり上級職で給与レンジも高いが、幅広く深い知識と経験が要求される感じがした。

ITシステム部門(セキュリティ担当)

大手日系企業などに多い。社内のIT利用を統括するIT部門においてセキュリティ関連の業務を担う。社内でIT機器を使う際のセキュリティ関連ガイドラインを作成したり、ISMSなどのレギュレーション対応、セキュリティベンダへの発注、監査対応など諸々を対応する模様。自分で手を動かしてテクニカルなことをやる場面は少なそう。

インフラエンジニア

IT部門とやや似ているが、どちらかというと自社サービスのアプリケーション、ミドルウェア以外の部分(OS、ネットワーク、サーバ管理など)を担当し、その中でセキュリティ業務も担当すると言った感じ。どちらかというとセキュリティはおまけ要素っぽさがある。

セキュリティプロダクトベンダのテクニカルセールスやサポート

外資系ベンダに多い。日本でプロダクトを売る時に導入や運用において起こる技術的な課題を解決する。またはプロダクトを導入後の問題に対して対応するようなポジション。外資系だとおそらく上司が海外にいるとか普通にあるっぽいので求人で英語力必須とある。

コンサルタント

お客さんのセキュリティ上の課題を洗い出し、ソリューションまでの道筋を示したりする。観測した範囲だとセキュリティ専門にやるというよりはITコンサルの中の一部の業務という感じっぽい。ここまでくるとあんまりエンジニアという枠ではないかも。

その他

  • リサーチャー:脅威分析などを専門にやるポジション。募集要項を見る限り学会などでの発表を目指すような研究者ではない(トーマツ等)
  • セキュリティプロダクトベンダの開発エンジニア:国内で募集している例はかなり少ない(トレンドマイクロぐらい?)
  • インシデントレスポンスチーム:まれに見かけた。自社内のインシデント対応を担当

退職します

2017年10月31日をもって,6年7ヶ月お世話になった日本IBMを退職します.

何やってたの?

最初に入社したときは研究部門(東京基礎研究所)につとめていてIBMクラウド上のログ管理のシステムやSIEM関連の開発をやっていました.2015年4月から1年半ほどIBM Tokyo SOC (Security Operation Center) で働かせていただいたのですが,いろいろな力学によって2016年9月ごろにはまた研究部門に戻っていました.

研究所では,自分はどちらかというと開発よりのプロジェクトをやることが多く,実験したり論文書いたりというようなアカデミックな活動はあまりできていません.ただ,やはりまわりは一流の研究者というか超人のような人たちが多く,色々刺激されたり勉強させてもらいました.特に学生時代の研究は我流で突き進んでいたところが少なからずあったので,改めて研究のやりかたについて学べたのはありがたかったです.

もともと学生時代にIDS(侵入検知システム)まわりをやっていたこともあり,SOCではその経験を活かしつつ, 実際の現場ではどのようになっているかということを肌感覚として大いに学ばせてもらいました. 最初の頃は純粋にアナリスト業務をやっていましたが、途中から業務効率化や分析の方が面白くなってしまい、後半はそういったツールの開発に取り組んでいました。Tokyo SOCレポート*1もほそぼそと執筆を続けさせてもらい楽しかったです.

現職どうだった?

素直に書いてみたいと思います.

  • 研究所は完全裁量労働で働く時間は相当な自由が効きましたが,一方で海外勢と一緒にやるプロジェクトだったりすると夜中の電話会議もそれなりの頻度でありました.
  • 個人的には給与に不満はありませんでした.独身一人暮らしが雑に出費しても貯金できる程度にもらっていました.
  • (少なくとも自分の周りは)仕事に対しては真摯で筋の通った方が多く,直接の人間関係で困ったことなどはほぼありませんでした.
  • 非常に古く大きい企業(創業から106年,全世界で社員数40万人弱*2 )であるため,そういった組織特有のしがらみというか壁を感じる場面は少なくなかったです.

何で辞めるの?

新しい経験を積みたくなった,というのが最も大きな理由です.

開発に従事するのもSOCで監視をするのもそれぞれ楽しさはありますが,情報セキュリティという視点でみると それぞれ局所的になってしまっているなと感じていました.一口に情報セキュリティといっても, 現実にやならければいけないことは多岐にわたりそれぞれが密に連携している...と思っています. 今後,何か一つの技術や活動に注力するとしても,一度全体を見渡して情報セキュリティの設計・実装・運用に携わってみたいと思うようになりました.

そのためには現在の会社だとどうしても「BtoBのベンダ」という立ち位置になって局所的な話になりがちになってしまうため, もっと自分が全体に責任をもって取り組めるユーザ企業でセキュリティに取り組んでみたいと思って退職を決めました.

どこに行くの?

11月1日よりCookpad社にてセキュリティエンジニアとしてお世話になる予定です.セキュリティ界隈でまたしばらく活動させて頂くことになるかと思いますし,アカデミック方面にもできれば関わり続けたいと思っております.本職の開発の方々には及ぶべくもありませんが,開発自体は割りと好きな方なのでセキュリティ関連で面白いものを作っていくということもやりたいですね.

ということで,今後共何卒よろしくお願いいたします.

ZIP SIM使ってみた@ニューヨーク

TL;DR

とりあえず次回も使ってもいいかな,という印象

使ってみたもの・環境など

出張で1週間ほどUSに行くことになり,せっかくSIM free版のiPhoneを持っているのでなるべく安く済ませてみようということで日本で買えるprepaid SIMにチャレンジしてみました

www.amazon.co.jp

  • 機種: iPhone SE (SIM free版)
  • 使った場所: NewYorkCity および White Plains周辺
  • 使ってた期間: 1週間

良かった点

  • 地下鉄やトンネル内などではさすがに電波がとどかなかったが,それ以外の屋外ではほぼ問題なく使えた
  • 通信速度はとても速いとは思わないまでも,過剰なストレスを感じるほどではない.普通
  • ホテルのWiFiと組み合わせて使ったところ,通信量は7日間でおよそ300MB強だったので容量も問題なし
    • ただし平日はほとんど仕事でつかっておらず,日中もフルで使ったのは土日だけなので1週間観光であちこち行くとかだと若干心もとないかも
    • 追加容量プラン("top up")もあるので必要に応じて追加金できる

良くなかった点

  • このSIMに限った話ではないのかもしれないが,屋内が比較的弱い.例えばJFK空港内とかはかろうじて通信できるが不安定
  • 「SIMを指してから5分以内にSMSでUSの郵便番号(ZIP code)を送るとその地域の電話番号が割り振られる」という説明だったのに,指した瞬間にSMSが飛んできて勝手に番号が割り当てられた.まあ通話使わないのでそんなに困らなかったけど・・

tips

  • APNの設定を変更する必要がある
    • 普段IIJ mioを使っているのだがちゃんとプロファイルを削除しないと競合して上手く動かない
    • APNの設定をしたあと一度iPhoneを再起動したらデータ通信できるようになった

悪意のあるドメイン名のブラックリストをまとめて取得・管理するツール mdstore を公開しました

悪意のあるドメイン名のブラックリストを取得・管理する mdstore というツールを作って公開しました。

github.com

世の中にはフィッシング詐欺マルウェアによる攻撃に使われるドメイン名のブラックリストを公開しているサービスがいくつかあります(参考)。近年はDGA(Domain Generation Algorithm)を利用したマルウェアもあるためブラックリスト化だけでは追いつかない場合もありますが、それでも既知の攻撃に利用されるドメイン名の名前解決を発見もしくは防止できれば、水際で攻撃による被害を食い止められる可能性があります。

ただ、このようなブラックリストは提供しているサイトによってカバーしている領域などが違うため、ブラックリストごとに違うドメインが扱われています。多くのドメインを網羅しようとすると複数のサイトからデータを取得する必要がありますが、フォーマットが異なるなどの理由から一手間必要です。

このツールは複数のブラックリストをローカルにダウンロードし、DBに投げ入れて管理・利用することで、この一手間を簡略化しようというものです。2017年1月7日現在、以下の3サイトからデータを取得します。

注意:ブラックリストを提供している各サイトの利用規約では基本的にinternalな利用は認めているようですが、特に商用利用などをする場合には詳細をご自身でよく確認してください。

使い方

セットアップ

本ツールはnodeで動作し、redisにデータを格納します。動作を確認しているバージョンは以下のとおりです。

  • node: v7.2.1, v6.0, v6.1
  • redis: v3.2.6

インストールは環境に合わせてよしなにやってください。その後、npmを使ってmdstoreをインストールします。

$ npm install -g mdstore

パスを通すのが面倒でなければカレントディレクトリへのインストール(上記コマンドから-g を抜く)でも問題ありません。

さらにツール利用前にredis-serverが動作していることを確認してください。

  1. install mdstore by npm npm install -g mdstore
  2. start redis server, e.g. redis-server &

またデフォルトのredis server接続先 (localhost, port 6379, db 0) 以外を使いたい場合はオプションで指定できます。

  • -s or --host: redis server host
  • -p or --port: redis server port
  • -d or --db: redis server db

ブラックリストの更新

update コマンドを使うことで各サイトのブラックリストをダウンロードし、DBへの格納までを実施します。すでに対象のドメイン名が存在する場合は、取得したという履歴が追記されます。現在サポートしているサイトは3サイトだけですが、hpHostsが特に件数が多いため完了までに2〜3分かかります。

$ mdstore update
update: OK

ドメインを探す

get コマンドを使うことであるドメイン名が存在するかどうかを調べることができます。下記では例として 151.ru というドメイン名が存在しているかどうかをクエリしています。

% mdstore get 151.ru
2017-01-06T14:44:05.347Z { source: 'hphosts', ts: 1483713845.347 }

左のカラムが対象となるブラックリストをダウンロードした時刻になります。右側のtsのフィールドがタイムスタンプでこれをDateに変換したものです。ドメイン名がブラックリストに追加された時刻ではない点に注意してもらえればと思います。sourceはブラックリストの取得元を表しており、hphostsdnsbhmvpsのように表示されます。

その他、データの取得元で掲載されている項目に応じて追加のメタ情報が表示されます。

/etc/hosts の生成

ローカルのredisに保存してあるデータをもとに、悪意のあるドメイン名を 127.0.0.1 に強制的に変換する /etc/hosts を生成します。これを /etc/hosts と置き換えることで悪意のあるサーバと通信する可能性を低減させることができます。

$ mdstore hosts > hosts.txt
$ head hosts.txt
127.0.0.1       localhost
::1     localhost
127.0.0.1       www.wwsupport.net
127.0.0.1       www.memdesign.co.uk
127.0.0.1       www.titanweb.net
127.0.0.1       www.livingston.rs
127.0.0.1       iphonesupport.co.uk
127.0.0.1       up1702.info
127.0.0.1       dcstest.wtlive.com
127.0.0.1       ad.doubleclick.net.34325.9225.302br.net
$ sudo cp hosts.txt /etc/hosts         # Linuxの場合
$ sudo cp hosts.txt /private/etc/hosts # macOSの場合

データへのアクセス

CLIでアクセスする以外には、ローカルのredisに格納したデータはmdstoreのライブラリを使って参照することができます。以下のようなnodeのコードでアクセスできます。DNSの問い合わせログを持っている場合、自分でコードを書くことで不審なサイトへのアクセスがなかったかを確認できます。

var mdstore = new (require('mdstore')).Redis();
mdstore.update((err) => {
  // synced
        mdstore.get('is.the.domain.malicious.com', (err, res) => {
          if (res.length > 0) {
                  console.log('yes, the domain name is malicious');
          } else {
                  console.log('no, this is benign');
                }
        });
});

また、当然ですがredis serverに直接クエリすることも可能です。ただし、履歴データはMessagePackでエンコードされているので直接人間が読むのはちょっと難しいです。一応、以下のような1 linerで表示させることはできます。

$ redis-cli --raw lindex 151.ru 0 | node -e "process.stdin.pipe(require('msgpack-lite').createDecodeStream()).on('data', console.log);"