fujiike.jpについて 2020-08-16
fujiike.jpは、いろいろ考えたことを書いていくところです。
いろいろあるので、いろいろ書いていく。
fujiike.jpは、いろいろ考えたことを書いていくところです。
この記事ではログの設計について整理・検討していく。
ログは主に以下のような目的で使われる。
サービスのデータベースに記録されるようなデータはBIツール[^1]などを用いて集計・分析することができるが、エラーの調査やデータベースに記録する必要性の薄いデータに関しては、ログという形で管理・保存することが多い。
ログの収集・管理そのものを外部ツール[^2]に頼ることもあるが、生データは自社サービスで保持するようなケースを想定して検討する。
外部ツールを用いる場合においても、どのようなデータが外部ツールに対して提供されているかを整理し、把握することは運用において正しく判断するための素材となる。
この種類のデータが活躍するのは、そもそもデータベースに記録する前に失敗するようなケースや、リクエスト情報など日常的にデータベースに書き込み処理をするには負荷が大きすぎるようなケース。
またログは保存できなくても本来のサービス運営に支障のないデータであることが求められる。
(逆に言えば、必ず保存できなければならない場合、ログとして記録するのではなく、サービスの必要なデータとして主たるデータベースに記録するのが望ましいと思う。)
ログは上記の目的や要求から、以下のような要件を満たすことが求められる。
また、望ましい性能傾向として、主たる要件と相反する以下のような性能が期待される。
ログは記録されるときに主目的を意識して記録されることが多い。
そのため、ほとんどのログは以下のいずれかのパターンに分類できる
また、他に以下のような観点で分類することができる。
ログにはレベル(重要度)が定義されていることが望ましい。
利用目的の 異常検知
の項目に対応する部分であり、基本的には対応方針及び検知方法とセットで検討されるべき項目になる。
ログレベルには参考になる事例がいくつかある[^3]が、この記事では以下のように段階分けして運用することを推奨したい。
ログレベル | 対応方針 | 検知方法 |
---|---|---|
FATAL | 問題が解決するまで全社・チーム全体で対応 | slack及びメールで即時に在籍者全員に通知 |
CRITICAL | 問題が解決するまでチーム全体で対応 | slackのalert系チャネルに通知 |
WARNING | チームで必ず把握し、対応時期を決めて修正・改善 | 発生頻度に対し閾値を決めてslackのnotice系チャネルに通知 担当チームが明確にできる場合は担当チームのalertチャネルに通知 |
INFO | 常時把握の必要なし。必要に応じて調査などに利用 | メトリクスを管理するダッシュボードなどで、閾値を超えることがないか監視 |
※ この記事ではslackを利用している企業を前提としています。必要に応じて利用しているチャットツールで読み替えてください。
ログは書き込みを行うアプリケーションの種類や、アプリケーションのライフサイクルの中のどこで書き込まれたか、開発にどのような言語・フレームワークを用いているか、などによって記録できる情報が異なる。
そのため、どこから書き込まれる情報ではどんな情報が取得できるのか、という情報整理を行えることが望ましい。
以下に記載するのは、主要な事例の一部。
そのうちの一部は実装部分で触れる。
ログに求められる要件の中で、
漏洩しても法的に、または社会通念上、問題ないデータである
と記載したように、ログは流出することを常に警戒して記録されるべきものである。
本来「サービス運営上必要不可欠ではない」データであるにも関わらず、重大なセキュリティインシデントに発展するケースがある。
いくつか著名な事例へのリンクを記載する。
いずれもログやそれに類するデータに含まれるべきでない情報が記録されており、それが漏洩したことによってセキュリティインシデントとして問題になっている。
ログデータはここまでに整理した情報の通り、パターンや書き込み元の種類・タイミングによって出力可能な項目が異なる。
いくつかのパターンを想定して出力項目の定義の事例を検討する。
※ ちなみにこれが「正解」じゃないので、これさえ入っていれば大丈夫とか思わないでくださいね(念のため・・・)
項目 | 用途・説明など(あれば) |
---|---|
アクセス日時・処理日時 | 何気に揺れるから日付型はフォーマットをサービス単位で固めておいたほうがいいと思う |
ログレベル | |
アプリケーションのバージョン | アプリバージョン固有の問題を特定するために使う カナリアリリースとかやってるとめっちゃ役立つ |
実行環境の情報 | prdとかstg1とか。地味に便利 |
コンテナID | コンテナ時代ならコンテナIDわかると便利 |
処理したサーバーのIPアドレス | どのサーバーで処理したか知りたいこと多い。コンテナIDと役割は似ている |
項目 | 用途・説明など(あれば) |
---|---|
stacktrace | これがないと本当にデバッグが辛いので絶対に記録したい |
エラーメッセージ | ちゃんとアプリケーション側で作りましょう |
URL・エンドポイント | HTTPスキームとかも含む |
リクエストパラメータの中身 | passwordとか絶対にマスキングしないといけないから気をつけて! ※ セキュリティインシデントの事例参照 |
HTTPメソッド | |
エラー発生時点での変数・定数のログ | 取れる場合はできるだけ出力したい |
stacktraceやエラーメッセージだけだと、再現条件が不明瞭になってしまうケースが多い。 最低限、該当箇所のエラーの再現条件をわかるようにすることが目標となる(これがないと迷宮入りになりやすい)。 そのため、依存している定数と、渡されてきた変数はログに渡しておくこと。
可能であれば、そのエラーを発生させるリクエストの生成条件まで特定したいため、遡れる限りの情報を取りたいのが正直なところ。
項目 | 用途・説明など(あれば) |
---|---|
stacktrace | これがないと本当にデバッグが辛いので絶対に記録したい(二度目) |
エラーメッセージ | ちゃんとアプリケーション側で作りましょう(二度目) |
エラー発生時点での変数・定数のログ | 取れる場合はできるだけ出力したい(二度目) |
実行されたページのURL・エンドポイント | HTTPスキームとかも含む |
クエリストリング | 直接参照するケースが多いため |
実行されたJSファイル名 | |
依存しているcookieのキーと値 | 処理がcookieに依存していれば再現に必要 |
アクセス元端末のOSの種類 | OS固有の問題を特定するために使う |
アクセス元端末のOSのバージョン | OSバージョン固有の問題を特定するために使う |
ブラウザの名称 | ブラウザ固有の問題を特定するのに使う |
ブラウザのバージョン情報 | ブラウザバージョン固有の問題を特定するのに使う |
UserAgent文字列 |
キャッシュクリアなどの目的で、ビルドのタイミングでファイル名にdigestを追加するようなケースが最近は主流だが、こうなってくると純粋にファイル名や該当箇所を拾ってきても、minifyされていてわからんとか、既知のエラーがビルドバージョンごとに別のエラーのように出力されるとかいったことが発生する。
このため、特にReactやVue.jsのようなフレームワークを利用していたり、webpackで独自にビルドしているようなケースでは、stacktraceやエラー発生箇所の情報は、コンパイル前の情報が拾えるようなライブラリを用いてログに書き出す方式が望ましいと思う。
ブラウザ上のエラーはサービス規模が大きくなってくると本当に辛くて、再現できないケースが増えてくる。
そのため、ガチで再現してエラーゼロみたいな世界観を目指す場合、とにかくリクエストを再現することに全力を尽くすことになる。
そうなると以下のような情報が必要になる。
項目 | 用途・説明など(あれば) |
---|---|
そのJSを実行したDOMのネットワーク通信のログ | コケる前にそもそも特定のライブラリの読み込みにコケてた、とか割とある |
レンダリングしているDOMの情報 | Chrome拡張とかでDOMがいじられてるケースがあって再現不可能なバグとかけっこうある |
実行時に読み込まれていたChrome拡張などの外部情報 | 同上 |
項目 | 用途・説明など(あれば) |
---|---|
stacktrace | これがないと本当にデバッグが辛いので絶対に記録したい(二度目) |
エラーメッセージ | ちゃんとアプリケーション側で作りましょう(二度目) |
エラー発生時点での変数・定数のログ | 取れる場合はできるだけ出力したい(二度目) |
外部から受け取った情報 | HTTPリクエストでいうところのパラメータ |
項目 | 用途・説明など(あれば) |
---|---|
URL・エンドポイント | HTTPスキームとかも含む |
リクエストパラメータの中身 | passwordとか絶対にマスキングしないといけないから気をつけて! ※ セキュリティインシデントの事例参照 |
HTTPメソッド | |
HTTPレスポンスステータス | |
アクセス元IPアドレス | DOS攻撃などを弾くためにほしい GeoIP系の情報とかは分析に便利 ※ 情報漏洩気をつけてね |
アクセス元端末のOSの種類 | OS固有の問題を特定するために使う |
アクセス元端末のOSのバージョン | OSバージョン固有の問題を特定するために使う |
レスポンスタイム | パフォーマンス改善、遅いエンドポイントの特定とかに必要 |
HTTPバージョン | 1.1とか2とか |
項目 | 用途・説明など(あれば) |
---|---|
HTTPレスポンスbody | とにかくデータ量がとんでもないことになるし、ユーザー固有の情報含まれてたりするから微妙 |
HTTPリクエストヘッダ丸ごと | 便利だけど漏洩したらヤバい情報いっぱい入ってそう |
GeoIP系の情報 | 分析のときにあると嬉しい。 だんだんUA取れなくなる方向に来ているから微妙 |
UserAgent | 分析のときにあると嬉しい。 だんだんUA取れなくなる方向に来ているから微妙 |
Referer | 分析のときにあると嬉しい。Yahoo!ニュース砲だ!とか解析するのに使える。 だんだんUA取れなくなる方向に来ているから微妙 |
HTTPリクエストヘッダは丸ごと取っておくと便利そうだけどセキュリティ的にはどうなんだろう。 https://developer.mozilla.org/ja/docs/Web/HTTP/Headers
リクエストヘッダのサイズとかbodyのサイズとかってどうなんだろう。
取るといいと思う。実行しようとしたけどコケた、みたいなときに、必ずしもエラーをちゃんと吐いてくれるとは限らない。
項目 | 用途・説明など(あれば) |
---|---|
実行するタスクの名称 | ちゃんと名前つけて記録しておきたい |
外部から受け取った情報 | HTTPリクエストでいうところのパラメータ |
項目 | 用途・説明など(あれば) |
---|---|
実行するタスクの名称 | ちゃんと名前つけて記録しておきたい |
外部から受け取った情報 | HTTPリクエストでいうところのパラメータ |
処理にかかった時間 | 重いタスクとか抽出できるので記録しておきたい |
[^1]: BIツールの例としては、 redash, metabse, looker などが挙げられる。 [^2]: ログ収集まで含めて連携する外部ツールの例としては、 Google Analytics, Amazon CloudWatch, Cloud Logging, sentry, datadog, KARTEなどがある。当然ながら、ツールの目的によってカバーしている範囲が異なる。 [^3]: log4j, PSR3 など参考になる情報は多い。利用するロギングツールによってデフォルトのエラーレベルのパターンはある程度指定されている。どのエラーレベルを、自社・自チームのどの対応方針で扱うかは都度定義する必要がある。
この記事は、 Makuake Advent Calendar 2020 16日目の記事です。
この記事では、自分が「セキュアなプロダクトづくり」をするときに考えていることを書きながら整理してみます。
最後までコード出てきません。
自分がいまプロダクトの中で扱っている領域は、一般的なソフトウェアエンジニアよりはセキュリティに関する関心度合いが高い、と言っていいと思う。
セキュリティはソフトウェアエンジニアにとっては、大抵あとから学ぶもので、体系的な知識が身についている人の数は少ないと感じる。自分自身もご多分に漏れず、必要に迫られていくつかの観点からセキュリティに携わるようになった立場だ。
そこで、現場レベルで意識していることをまとめて、同様の悩みを抱える人の参考になるといいと思った。いわゆるセキュリティ担当などではないため、そこは注意してほしい。
よりきちんと学びたい人は、 IPA などの公的な資料にあたるといいと思う。
今回の「セキュリティ」「セキュア」といった用語の対象範囲は「WEBサービス」や「WEBシステム」であり、それ以外のいかなる範囲についても想定していません。
またこれを守っていても必ずしも安全というわけではなく、現場で運用しうる範囲でこういったことを気をつけるといいと思う、という範囲の見解の共有にとどまります。
セキュリティ対策にはいくつかの角度があり、それぞれで対策を施すことでプロダクトの安全性を向上させたり、維持することができる。
自分の観点をまとめると以下のような感じ。
それぞれについて具体的に見ていく。
セキュリティ対策と言ってまず思いつくのはこれだと思う。
攻撃は何もないところから発生することはほとんどなく(※ゼロデイ攻撃と言われるような例外を除く)、脆弱性というソフトウェア側の弱点が存在し、その穴を突くようにして攻撃が成立する、というのが一般的だ。
とはいえ、脆弱性そのものよりは、攻撃方法に著名な名前がついているケースが多い。
攻撃方法のリストは 代表的な 10 種類の脆弱性 (ソフトウェア等におけるセキュリティ上の弱点) などがわかりやすい。
自分の場合、クライアントサイドとAPIサーバーを分けたシステム開発をすることがほとんどのため、以下のような観点で脆弱性がないかを日常的に警戒している。
※ ()内は関連しそうなキーワード
基本的にはこうしたポイントを押さえつつ、頭から終わりまで、実行される(であろう)スクリプトやプログラムの中に、よく知られる攻撃手法が成立する余地がないか気にしていくことになるのだけれど、近年は基本的なプログラムのセキュリティが向上していることもあって、成功する攻撃手法が限定されてきているようだ。
後述する「被害範囲を減らす」項目で権限管理などができていて、VPCなどの設定がきちんとされているケースにおいては、クライアントサイドのJavaScriptをハックして、偽装した権限で不正な操作をしたり、ユーザーをフィッシングサイトに誘導するといったことが主流になりつつあるようで、リクエストヘッダの検証やレスポンスヘッダでの制御などの重要性が高まっているように思う。
プロダクトはどんどん更新されるので、ある時点で脆弱性が発見されなかったからといってつねに安全であるとは限らない。
脆弱性がないかを継続的に監視するにあたっての基準は、ある程度普及した機関によって出されているものを基準にすると網羅的にチェックできる。
例えば、 OWASP が出しているチェックリストなどは参考になる。
自分は参照しやすいこともありよく、 ueno1000/secreq にあるPDFを参照している。
これらのチェックリストに批准しているかどうかは、いくつかの方法でチェックできる。(方法によっては完全ではないがかなりの割合に対応できる)
下記の方法で、プロジェクトの状況や会社の規模に合ったものを組み合わせて選んでいくのが良さそう。
攻撃が成立してしまった際に発生するのは、成立した箇所に与えられている権限の範囲でなんでもできるという状態だ。
そのため、1サーバーで整理しているシステムのsudo権限などが奪われてしまうとほとんどなんでもできてしまう。
これは攻撃成立時のリスクが非常に高い状態と言える。
普段から可能であれば、AWSがGCPでいうところのIAMや、サーバー内の実行ユーザーなどの権限、セキュリティグループなどの接続許可設定を、それぞれ必要最小限に絞っておくということをオススメしたい。
(これ、一般的なインフラチームやSREなどでは本来当たり前の水準なんだと思うのだけど、かなりレベルの高いチームでないとなかなか守れていないと思う。観測範囲N=7社ぐらいの感想)
かくいう自分もプライベートでは割とガバい感じでやってしまうことが多い(S3 Full Accessつけるとか・・・)ので頑張っていきたい。
管理画面の運営ユーザーなども、運営ユーザーごとに適切な権限が振り分けられていれば、ハックされてもその人が操作できる範囲でしか悪事が働けないなど、被害を「不幸中の幸い」の規模に止めることができる(かもしれない)。
DoS攻撃やDDoS攻撃などはこの角度での対策ができていないといつでもサイトをダウンさせられてしまう感じになる。
(いや、まあDDoSぐらいになるとかなり強いチーム・ソフトウェアじゃないとどうにもならないと思うけれど・・・)
上記のような対策をしていてもなお、様々な形でソフトウェアは攻撃に晒されるので、攻撃が成立してしまうと異常をきたすメトリクスなどを日常的に監視できていると良さそう。
マクアケではこの辺りはSREチームの恩恵でかなり監視されていると感じてます(唐突なマクアケ推し)。
非常にざっくりした全体像だけれど、セキュリティについて気にしていることを書いてみた。
これ以外にも、例えばAPIキーの管理方法とかソフトウェアの外で起こる問題もたくさんあるので、より広い観点でも気にすることはあるけど、ここに挙げたようなことを意識していなかったような段階であれば、意識するだけでプロダクトのセキュリティレベルは結構上がるんじゃないかな、と思ったりしています。
本日はこれにて終わりです。
この記事は、 Makuake Advent Calendar 2020 12日目の記事です。
CSVダウンロード機能などを開発する際、以下のような悩みがあった。
それでなくとも、システムの言語が変わるたびにCSV出力のやり方を学び直すのもなんだかツラいので、
WEB前提ならクライアントサイドで生成できる方が汎用性が高いと考えた。
今回は Papa Parse を利用した方法を紹介する。
便利すぎて今後CSV出力は全部これでやりたいってなったのでぜひ紹介したいやつ。
今回のサンプルでは Vue.js のコンポーネントで利用することを想定し、
CSVとして利用可能なblobを生成する処理と、それをブラウザ上でダウンロードできるようにする処理を紹介する。
事前に yarn add papaparse
または npm install papaparse
などで、パッケージの取り込みを行う。
package.json で管理しているなら、 "papaparse": "^5.2.0",
などを追記してもよい。
// ファイルの配置は @/modules/csv.js を想定した
import Papa from 'papaparse'
const getDownloadableBlob = (json) => {
const csv = Papa.unparse(json)
// utf8のCSVでも、bomという情報を持たせるとエクセルが形式を解釈してくれて文字化けせずに開ける
const bom = new Uint8Array([0xEF, 0xBB, 0xBF])
const blob = new Blob([bom, csv], { type: 'text/csv' })
return blob
}
export default {
getDownloadableBlob
}
<template>
<!--
aタグのdownload属性を使ってCSVダウンロードを実現するため、
DOMはaタグである必要がある。
-->
<a
@click="handleClickDownloadCsv"
id="csvDownloader"
>
CSVダウンロード
</a>
</template>
<script>
import csv from '@/modules/csv'
export default {
methods: {
handleClickDownloadCsv () {
// keyにCSVのヘッダ文字列を、
// 配列の各valueに各列のデータを入力する。
const json = [
{
ID: 1,
苗字: '田中',
名前: '太郎',
メールアドレス: 'sample1@email.com'
},
{
ID: 2,
苗字: '田中',
名前: '次郎',
メールアドレス: 'sample2@email.com'
}
]
const blob = csv.getDownloadableBlob(json)
const filename = `sample.csv`
if (window.navigator.msSaveBlob) { // IE/Edge判定
// IE/Edgeの場合は
window.navigator.msSaveBlob(blob, filename)
} else { // Chrome, Firefox, Safari判定
const dom = document.getElementById('csvDownloader')
// ダウンロード時のファイル名指定には aタグの download 属性を使う
dom.download = filename
// blobデータからダウンロード用のURLを生成する
dom.href = window.URL.createObjectURL(blob)
dom.click()
}
}
}
}
</script>
ポイントだと個人的に思うところはコードのコメントに記載しているので、別で詳細な解説はありません。ご了承のほど何卒。
Blob と createObjectURL はそれ自体慣れていない人はちょっと勉強するといろいろわかりやすくなると思うので、
blob & createObjectURL について
などを読むといい感じだと思います。
とにかく便利なのでちょっとしたCSV生成なら絶対これをお勧めしたいぐらい好き。
APIから取ってきた情報を整形して流し込むだけでCSVにできたりするので、
商品一覧画面などでCSV生成を作成するときも、共通のデータソースを参照しやすいなどメリットが多いと感じる。
CSV生成で設計に悩んだらぜひ検討してみてください。
おわり。