在宅勤務環境の継続的改善

コーポレートエンジニアリング担当 VP の @kani_b です。 新型コロナウイルス感染症の拡大リスクを鑑みて、従業員や関係者の皆さまの安全確保を目的に、クックパッドでは 2/18 (火) から、国内拠点の全従業員(正社員、契約社員、パート・アルバイト、派遣社員、通常在席の業務委託)を対象に在宅勤務の原則化を実施しています。現在は5月末まで継続する予定としています。

クックパッド、新型コロナウイルス感染症の拡大に伴う在宅勤務(Work from Home)を5月末まで継続のお知らせ | クックパッド株式会社

また、クックパッドでは、今の状況にあわせた、料理に関する様々な取り組みを進めています。そうした取り組みを集めたページをオープンしていますので、こちらもぜひご覧ください。
私たちは、料理でつながろう | クックパッド株式会社

さて、在宅勤務が開始された 2 月に、在宅勤務に対する取り組みについてブログ記事で紹介しました。早いもので、在宅勤務開始からすでに2 ヶ月が経過しました。1 年のうちの 1/6 を在宅勤務で過ごしたことになります。
クックパッドの在宅勤務環境 - クックパッド開発者ブログ

この記事では、先ほどご紹介したような様々な取り組みを支える、在宅勤務のための体制づくりについて、前回の記事からのアップデートをご紹介します。

設備やシステムの改善

在宅勤務が長期化するにつれて、短期的には許容されていた課題を解決していく必要が出てきました。 まず、設備やシステムに対してどのような改善を行っているかを紹介します。

オフィスチェア・オフィスデスクのレンタル

まずはじめに課題となったのは、自宅環境に在宅勤務に適した椅子や机がなく、座椅子やベッドなどでの作業を余儀なくされている例が多く存在することでした。 机はもちろんですが、椅子は特に生産性、そして心身の健康に大きく影響します。 そこで、2 月末に在宅勤務の延長が決定された段階で、オフィスチェアやデスクを会社負担にてレンタルし、各家庭に配送することにしました。 ちなみに、購入ではなくレンタルとしているのは、購入と比較して少ない費用で多くの従業員により良い椅子を届けられることや、在宅勤務終了後に居室の現状復帰が可能である (そもそも机や椅子などを置くことを想定していない場合が多い) ことなどを考慮しています。

このほかに、オフィスで利用しているモニターについても希望者には各家庭へ配送しているため、オフィスに近い環境をつくることができるようになりつつあります。

写真にある左の机は、会社から送付しているモニターと、実際にレンタルしたデスク・チェアを配置したものです。 f:id:kani_b:20200422031107j:plain

インターネット環境の改善

前回の記事でも紹介しましたが、クックパッドでは在宅勤務開始当初より 4G 回線を使ったモバイル Wi-Fi ルーターを貸し出し、テザリングの利用を前提に社用の携帯電話などを活用していました。 しかし、遠隔会議や業務によっては画像・動画のやり取りが多く、契約している通信容量を大幅に超過してしまう例や、そもそも帯域幅・レイテンシーが要件に見合わないケースが出てきました。 まずできる対応として、 4G 回線ではなく WiMAX2+ を利用できるモバイルルーターを用意し、随時交換を行ってきました。しかし、環境による速度差が大きいだけでなく、大容量なデータ通信を行う従業員の業務は相変わらずサポートするのが難しい状況にあります。また、モバイル Wi-Fi ルーターの新規調達も需要増大により難しくなってきました。

そのような状況を受け、各家庭にできる限り、いわゆる固定のインターネット環境を用意していただくのが、今後の対応長期化などを見込んだ上では最適と考えました。 そこでまず、在宅勤務を機に自宅にインターネット環境を用意する従業員に向け、工事費や契約事務手数料などの初期費用を負担することにしました。スムーズな利用開始のため、必要に応じてコーポレートエンジニアリング部 (ヘルプデスク) にて開通までの事務手続きや技術サポートを行うことにしています。

また、すでに家庭にインターネット環境があるものの、Wi-Fi 環境が良くなかったり、そもそも接続先 ISP がキャパシティ不足に陥っていたり… という例もあります。そうした方に向けては、社内 Wiki, ドキュメンテーションツールとして使われている Groupad に、Wi-Fi 環境の見直し方や有線 LAN 接続への切り替え方を案内する記事を書いたり、 Slack 内のあちこちでインターネット環境に詳しいエンジニアたちがコミュニティベースのサポートを行ったりしています。

契約書の電子化

この状況下においても安全に企業活動を進めるためにも、可能な限りの電子化に努める必要があります。すでに日本中でも多くの議論が起こっていますが、クックパッドにおいても、契約書類について全面的に電子サインを利用することとしました。 グローバル対応の必要性などから、 DocuSign を利用しています。こうした電子化の動きは、一社だけでなくできるだけ多くの方々にご協力いただくことではじめて成り立ちますので、クックパッドとのお取引がなくとも、ぜひご検討いただけますと嬉しいです。

Zoom Webinar の利用

コミュニケーションツールとしての Zoom 利用については前回のブログに書いた通りですが、全社ミーティングのようなイベントには Zoom Webinar を用いています。 配信者や利用者が普段利用している Zoom クライアントの操作感のまま利用できることはもちろんですが、現在ベータ版として提供されている言語通訳機能がよく活用されています。 この機能は、通訳そのものを行う機能ではなく、通訳者の方が発言者の発言を聞きながら通訳用の音声チャンネルに通訳音声を流すことのできる機能です。 クックパッドには、日本語を話す日本人だけでなく、業務に英語を使う従業員も多数在籍しています。これまでも全社ミーティングなどではレシーバーを用いた同時通訳を提供していました。この機能を利用することで、Webinar 環境においても、利用者側では言語選択をするだけで、同時通訳音声を聞きながら発表を見ることが可能になります。通訳者の方にも遠隔から協力をいただき、誰ひとりオフィスに来る必要なくこのような環境を実現できています。

また、全社ミーティングを Webinar に切り替えたことにより場所の制約がなくなりました。これによって、各自が見やすい環境で視聴でき、音声も聞き取りやすく、質疑応答やその後のフォローアップもしやすくなっています。結果として、オフィスでの全社ミーティングよりも参加者が増加しています。

勤怠システムの Slack 対応

在宅勤務期間中は、勤怠システムへの勤務時間登録を、システムにログインした上で行う必要があります。在宅勤務開始から様々な部署の Slack チャンネルを眺めていたところ、多くの人が勤怠システムに勤務時間を登録した上で Slack に出退勤を知らせるといったことをしていました。 出退勤の報告にはカスタム絵文字が主に使われていたため、絵文字の発言に反応して勤怠システムに打刻を行う bot を開発することで、出退勤の連絡を Slack に書き込むだけで、報告と同時に打刻を行うことができるようになりました。 f:id:kani_b:20200422031245p:plain

ツールの使い方に関する情報発信

課題解決のためのツール導入は解決の入り口でしかなく、「ツールをどう使うか」という文化形成によって解決されることがほとんどです。 在宅勤務の原則化によって、特にコミュニケーションツールを中心に、ツールの使われ方の傾向が変わってきました。それに伴っていわゆる「Slack 疲れ」といった、コミュニケーションへの新しい疲労も起きつつあります。 現在の環境や会社に合ったコミュニケーションの形を模索するため、ツールの使い方についても積極的な発信やそれをもとにした議論を行っています。以下にその一部を紹介します。

  • 「Slack 疲れ」の軽減
    • 「通知を受け手がコントロールする」「正しくメンションを使う」「即レスを期待しない」といったある種のコツがあると考えられる
    • Do not disturb (おやすみモード) の活用や Activity タブの利用などについて解説
    • 先日リリースされたチャンネルのセクション化についても解説
  • 「遠隔会議(Zoom)疲れ」の軽減
    • 部屋の概念がない遠隔会議ではミーティングが延びやすいため、Google カレンダーの会議迅速化オプションなどの活用
    • 気分によってカメラをオフにして話す
    • 在宅勤務を行う上で重要な家族との関係を保つためにも、お子さんが入り込むといった場面をみんなで許容する

社内外でのナレッジシェア

環境改善の他にも、在宅勤務そのものや在宅勤務における仕事をよりよくするためにできることを積極的にシェアして共有しています。公開されている記事について、簡単に紹介しておきます。

ほかにも、

  • 営業を担当する社員によるオンライン商談・お客様向けセミナー実施の Tips
  • 各家庭での料理の情報
  • マイクやカメラ、デスクを整理する便利グッズなどの情報
  • リングフィットアドベンチャーの販売情報

など、様々な情報が Groupad や Slack で共有されています。

おわりに

クックパッドの在宅勤務環境は、このように、環境面の整備だけでなく、社員ひとりひとりの積極的な情報共有文化や、助け合いに支えられています。全員が等しく在宅勤務をする、という経験のない状況を、様々な議論を交えながら少しずつ改善しています。 今回、そして前回の記事もあわせて、打ち手に悩まれている方にお役に立てば幸いです。

また、今回ご紹介したような環境を一緒に支えていくコーポレートエンジニアをはじめ、エンジニアに限らず様々なポジションにて積極的な採用を続けています。採用情報サイトも合わせてぜひご覧ください。

質問がありましたら@kani_bまでお気軽に。

Bridging the Gap Between Engineering and Design

Hello there, it's Dave Fox! I'm an iOS engineer from the Creation Development department in Cookpad. (@wowitzdave)

In this post, I'm going to talk a bit about how I used custom internal tooling and prototyping tools to help Cookpad's design and development workflow.

I'll look at how product and engineering teams can interact with each other more efficiently and ways in which we, as engineers, can empower designers to make their visions become a reality more easily.

Introduction

Last year, I began work on the internal Cookpad iOS renewal project. As part of its development, I was responsible for creating a new UI component to display user images in a style known as the Ken Burns effect.

The effect consists of panning and zooming around images and cross-fading between each one to give a sense of depth and dynamism.

Here's a short look at it in the iOS Cookpad app today...

Technical Implementation

Looking at the above effect, it doesn't seem too complex to implement... Just display some images with a zoom or a pan and fade between them. However, there are many settings and attributes applied to each image's animations and also to the slideshow's root animation as a whole. Each and every one of these values needs to be carefully considered to create the right "feel". Let's take a look at the parameters our animation requires:

For The Overall Slideshow
  • How long (in seconds) should we show each image?
  • How long (in seconds) should the duration of the cross fade be?

Additionally, each image uses either a zoom or a panning animation. These styles also have individual settings...

For Panning Images
  • From which anchor points should the animation start from and end at?
  • What speed should the animation happen at?
For Zooming Images
  • At which anchor point in the image should we zoom from?
  • What scale should we zoom from and to?
  • What speed should the animation happen at?

In order to create the exact effect the product team wants, these are all parameters the designers want to be able to define.

In a feature like this, it is often difficult for designers to imagine exactly what values they want. Often, design and development will work on a "trial and error" basis, tweaking values back and forth. With this many settings though, it would take a lot of time from two people to achieve the final goal. The workflow may look something like this:

f:id:davefox:20200402140954p:plain

The issue with this flow is the "Cross-department communication" bottleneck. Because of rapid iteration, this happens frequently and takes the time of both engineering and design teams to communicate changes. Each iteration takes a lot of time and disturbs the working pattern of all members involved on a feature.

A Product-Centric and Tools-Driven Approach

I had a lot of other tasks to work on as part of the renewal project but I knew the product team would want to iterate on this feature a lot so I decided I wanted to create a workflow which could reduce friction between design and engineering, keep both sides as productive as possible and, at the same time, create a toolset that is easy to use and familiar to designers. This was important to enable design to be as creative as possible.

Giving Designers Greater Control

To achieve this workflow, I decided to make a testbed application that would allow the design team to reiterate and play with all these animation values.

Then, when they were satisfied with the final animation. I could simply take their finalised values and copy them into the main iOS app codebase.

This would make the following workflow possible:

f:id:davefox:20200402141230p:plain

Designers perform both the review and adjustment stages

Testbed Application Implementation

So, I started off with a basic implementation of the Ken Burns effect with some default values and then created a simple application with screens to alter the animation settings and view a live preview of how the animation would look in the final product.

Let’s take a look at what this app ended up looking like:

Overall Settings Zoom Settings Pan Settings
f:id:davefox:20200402141310p:plain Overall animation settings f:id:davefox:20200402141326p:plain Zoom scale and anchor point settings f:id:davefox:20200402141343p:plain Pan from and to anchor point settings

As the design team modifies the animation values, they can preview their settings at any time on the home screen. This screen shows the animation in a number of different sizes and aspect ratios so the designers can see how things will look in a variety of different contexts within the Cookpad iOS application:

The designers can quickly and easily play with these values and, once happy, I have one sit-down meeting with them and integrate their final values into the application.

Results

When I think of the merits of this kind of approach to feature development, I look at two main areas:

  1. Engineering time and effort
  2. Friction between the wants of designers and the final output of engineering.
Time Taken

The design team made many many iterations of these values within the testbed application but because they could iterate in isolation, I didn’t need to spend any of my time on each change. With the "one time integration" approach, bringing the product team's vision into the main app only took me about 15 minutes of my time.

To this end, the amount of time taken from creating the prototype application and finalising the settings within the app was quite short so I think it was definitely worth it to take this approach.

Interaction With Designers

I had good feedback from the design department. Designers work visually and want to tweak settings and values on-the-fly. Giving them a visual interface to perfect this feature is more in tune with how those departments work so I think they found this approach more natural and efficient. It also allowed them to use their creativity to the fullest as there was no engineering bottleneck in the way.

Thanks for Reading!

Have you used tooling like this before? Was it helpful and how did it help you integrate with your design and product teams?

I believe that engineers shouldn't just code. They should also engage in the product development lifecycle. I think that understanding what product teams want and helping them realise their visions makes us better engineers and helps us grow as contributors across the entirety of a project, not just its codebase.

To that end, custom tooling like this can help bring us closer to the product team and helps them realise their visions more easily, often resulting in better, more cohesive products.

Thanks again for checking this post out. I hope it helps you and your team make better products going forward...

サーバーレスで作るセキュリティアラート自動対応フレームワーク

技術部セキュリティグループの水谷 ( @m_mizutani ) です。ここしばらくはフルリモートワーク体制になったので運動不足解消のためウォーキングをしたり筋トレしていたら、リモートワーク前より健康になった疑惑があります。

クックパッドのセキュリティチームでは日々のセキュリティ監視を効率化するため、独自のフレームワークを構築して利用しています。具体的には、セキュリティアラートが発生した際に自動的に様々なデータソースから関連情報を収集し、収集した情報をもとにアラートのリスクを評価、そして評価結果をもとに自動対応をするという一連のワークフローを実現するフレームワーク DeepAlert をAWS上にサーバーレスで構築しました。この記事では、このフレームワークを構築した経緯やアーキテクチャ、仕組みについて解説します。

セキュリティアラートの対応

ここでは、セキュリティ侵害が発生している可能性があるものについて管理者に対応を求めるようなメッセージをセキュリティアラートと呼んでいます。これはセキュリティ防御・監視装置(Firewall、IDS/IPS、WAF、AV、EDR、などなど)から直接アラートとして発せられることもありますし、ログの中から見つかった不審な活動や外部からの連絡によって発覚する事象など、発生の経緯は様々です。共通しているのは組織内でセキュリティ上の問題が発生している可能性があり、状況に応じてなんらかの対応が必要である、ということです。今回は特にセキュリティ防御・監視装置から発報されるものを中心に説明します。

セキュリティアラートは組織内のセキュリティの侵害の可能性を見つけてくれる便利な情報ではありますが、実際に脅威ではないものを発報してしまうケースが多々あります。これは主に次の2つの理由が挙げられます。

  • 防御・監視システムは見える範囲に限りがあり、別システム(特に社内システムなど)の情報とつきあわせないと判断できないこともセキュリティアラートとして発報してしまう
  • 防御・監視システムは一般化されているため、それぞれの会社や事業部ごとに特有の業務や文脈に対応しきれない場合がある

これによっていわゆる誤検知・過検知が発生してしまうため、担当するセキュリティエンジニアが都度調査・分析してアラートの実際の深刻度を判断する必要があります。この作業は組織のセキュリティを維持するための大切な業務ですが、日々持続的に発生するために他の業務を徐々に圧迫してしまい、件数が多くなることで担当者を疲弊させてしまいます。

通常、防御・監視システムにはホワイトリスト機能が備わっており、指定した条件に一致するアラートは発報しないよう設定ができます。しかし、実際には先述した通り他のシステムの情報と突き合わせないと判断が難しいケースやホワイトリスト機能では除外条件を表現しきれないケースがあり、容易に対応できるものでもありません。

まとめると、実際のセキュリティアラートの対応では、まずアラートの調査と分析(影響有無の判断)があり、その後で必要に応じて何らかのアクションをする、といった流れになります。この一連の作業を効率化するために、自律的に複数の情報を組み合わせて必要な対応をしてくれる仕組みを作ることにしました。

設計方針

セキュリティの対応をコード化する

この仕組を作るにあたっては対応の部分をなるべくコード化する、ということを指針として取り組みました。近年、サービス開発の分野ではインフラの管理にDevOps や Infrastructure as Code の概念によってリソースの管理や手続きをコード化する、という取り組みが盛んになっていると思います。これはセキュリティにも応用できる考え方であり、コード化することによって対応の自動化だけでなく、対応内容の明確化や変更履歴の管理、手続きに対してテストができるようになる、といった恩恵を受けることができます。

セキュリティアラートの対応はその組織の構成、活動内容、文化や発生時の文脈に依存するも多くあり、全てのアラートを一律に自動化して対応できるものではありません。しかし、多くのアラートについては決められた手順で関連する情報を調査し、得られた情報をもとに定められた基準に従ってリスクを評価することができると考えられます。自分が前職でSOC(Security Operation Center)に勤めていたときも、明文化こそされていなかったものの定型化された対応は多く存在し、かつアナリストの間で共有されていました*1。もちろん、定型的に判断しきれないアラートや新しい脅威に対してはセキュリティエンジニアによるきめ細やかな分析が必要になりますが、そうでないものはなるべく自動化することで、エンジニアがより本質的な作業に注力できるようになります。

そのため、このフレームワークを作るにあたっては「誰がやっても同じ結果になるものは人手を介さないようにする」という機能の実現を目指し、全体をコード化するという方針で設計しました。

その他の機能要件

セキュリティのコード化以外にも、次のような要件を考慮して設計しました。

  • 容易な機能拡張:関連する情報の検索や最終的な対応は、様々なデータソースやインターフェイスに対応する必要があります。また、状況に応じて機能を追加・削除していくと考えられるため、なるべく自動対応のメインのシステムとは疎結合になるようにするべきと考えました。
  • 低コスト運用:「横断的に複数のデータソースを使ってアラートの精度を上げる」といったアプローチは新しいものではなく、昔からSIEM(Security Information & Event Manager)でも同じような取り組みがされていました。しかし、多くのSIEMはリアルタイムにイベントを処理するような設計となっているため高い処理能力が求められ、高価になってしまう傾向があります。もちろんお金で解決すべきところにはお金を投入するべきですが、セキュリティは直接的にビジネスに貢献するものではないこともあり、工夫次第でコストを抑えられるならそうするべきと考えました。
  • 弾力性:現状、クックパッドでは平均して一日あたり数件のアラートしか発生していませんが、今後の新しい脅威や方針の変化にともなってアラートの流量が増える可能性があります。そうした場合にスケールアップでしか処理量の性能をあげられないとするとすぐに限界がきてしまい、対応が滞ってしまう可能性があります。もともとの設計で速やかにスケールアウト・スケールインができるような弾力性を備えておくことで、突発的な流量の変化にも耐えられるようになります。

セキュリティアラート自動対応フレームワークの実装

アーキテクチャ概要

f:id:mztnex:20200317083451p:plain

設計で説明したような機能を実現するため、 DeepAlertというAWS上にサーバーレスで構築されたフレームワークを実現しました。これはセキュリティアラートを外部から受け取り、Inspector、Reviewer、Emitterという3つの役割を持つAWS Lambda Functionと連携して動作します。それぞれのLambda Functionの役割は次のとおりです。

  • Inspector:アラートに出現したIPアドレス、ドメイン名、ユーザ名に関して内外のデータソースにアクセスし、必要な情報を収集します。例えば外部から接続してきたIPアドレスであればブラックリストに掲載されているか、ドメイン名であればどういったサービスに使われているか、内部システムのユーザ名であればアラート直前までの行動ログなどを収集し、それらの結果をDeepAlertに返します。
  • Reviewer:アラートおよびInspectorが調査した結果を元にそのアラートのリスクを評価します。評価結果はシンプルに safe(影響なし)、unclassified(不明)、urgent(影響あり、要対応)の3種類のみにしています。
  • Emitter:Inspectorの調査結果、そしてReviewerの評価結果を元に対応を請け負います。対応も色々種類があり、調査や評価の結果をSlackなどを通じて通知する、外部の特定のIPアドレスからの接続を遮断する、あるいは対象ホストを隔離する、というような処理を想定しています。この対応も、影響ありの状況だったら即座に遮断したり、影響がなければ記録だけして通知はしない、というような評価結果に基づいた動作の振り分けも考慮しています。

これらのLambda FunctionはDeepAlertとは独立しており、特にInspectorとEmitterは任意の種類、数を接続することが可能になっています。それぞれAWSのSNS(Simple Notification Service)、SQS(Simple Queue Service)、Step Functionsを使うことでDeepAlertと連携しています。より具体的なアーキテクチャが次の図になります。大まかな動作として3段階に分かれており、Inspectorを動かす 1) 調査フェイズ、Reviewerを動かす 2) 評価フェイズ、そしてEmitterを動かす 3) 対応フェイズとなっています。

f:id:mztnex:20200317083605p:plain

この通り、DeepAlertはInspector、Reviewer、Emitterを動かすためのフレームワークとして実装しました。これまで様々な改良を続けてきたのでやや異なる部分はありますが、この仕組で約2年ほど運用し、その間にInspector、Reviewer、Emitterを必要に応じて入れ替えてきました。

Lambda、Step Functions、SNS、SQS、DynamoDBのみでサーバーレス構成として実装したため、料金は完全に利用量に基づいて計算されるようになりました。具体的には後述しますが、流量が少なければ非常に安価に使うことができます。また、各サービスにリソースの上限は設けられているものの、その限界までは人間の手を介することなく自動的にスケールアウト・スケールインしてくれます。これによって運用における金銭的コスト・人的コストの両方を極力抑えられています。

Pluggableな機能拡張

要件のパートで説明したとおり、情報収集をするInspectorと最終的に対応をするEmitterは状況の変化に応じて機能を追加・変更・削除していく頻度が多くなっています。我々はこの仕組を2年ほど運用していますが、その過程でも監視すべき対象が変わったりチームの運用方法にあわせて調査対象や対応方法が変化しています。

そこで、Inspector、EmitterはDeepAlert本体とは疎結合な形でデプロイできるようにしました。DeepAlertのリソースはAWS SAM(Serverless Application Model)およびCloudFormationでまとめてデプロイしていますが、InspectorやEmitterはそれぞれ任意の複数種類のLambdaをDeepAlertとは別のSAMでデプロイしてもいいですし、Cloud9で実装したものをデプロイするでも問題ありません。InspectorとEmitterはそれぞれSNS経由で必要な情報を受け取って起動し、InspectorはSQS(ContentQueue)で調査結果をDeepAlert側に戻します。また、Inspectorが関連する情報を調査する過程で、新たに調査すべき要素(例えば調査対象のユーザが使っていた別のIPアドレスや、マルウェアのハッシュ値からそのマルウェアが使っていたCommand & ControlサーバのIPアドレスなど)が発見された場合も、その情報をSQS(AttributeQueue)を通じてDeepAlertに戻して再度その要素についてInspectorが調査する、というフィードバックの仕組みも実装されています。このようにSNSとSQSだけを用いてintegrationする仕組みにすることで、Inspector、Emitterの動作がDeepAlert全体の動作に影響を与えないようにしています。

ちなみに、これまでクックパッド内では次のようなInspector、Emitterを運用してきました。カッコ内はアクセスするデータストアやサービスになります。(すでに利用しなくなったものも含みます)

  • Inspector
    • IPアドレス、ドメイン名、ファイルのハッシュ値がマルウェアに関連しているかを調査する(VirusTotal、Malwarebytes)
    • 出現したURLのスキャンし、どのようなサイトだったのかの情報を調査する(urlscan.io)
    • 社員の誰がそのIPアドレスを利用していたのかというログの抽出(社内のIPアドレス管理DB)
    • そのホストに自社管理のセキュリティソフトがインストールされているかの確認(CrowdStrike Falcon)
    • IPアドレス、ドメイン名、ユーザ名に関連する直近のログの抽出(社内のセキュリティログ検索基盤)
  • Emitter
    • 評価結果に基づいてアラートの通知(Slack)
    • アラート対応の割り振り(PagerDuty)
    • 調査結果アラート情報をまとめて保存(GitHub Enterprise)

Emitterについては、本来は被害をうけたと見込まれるホストをネットワークから隔離したり、証拠保全のプログラムを実行したり、ということも想定はしていました。しかし、幸いにも私自身が入社して以来、そういったことを即座に実行する必要があるようなインシデントに遭遇したことがなく、サービスに影響するような能動的な対応をどのくらいの確信度で実行するべきかというルール化ができていないため、そういった機能はまだ実装していません。これについては今後の課題としたいと考えています。

一般的なプログラミング言語でコード化したポリシー

先述したとおり、Reviewerは調査で集められたアラートの情報をもとに、そのアラートが実際の被害を及ぼしたのかを評価します。評価の方法や仕組みは Lambda Function にコードとして自由に記述できるようにしました。現在、クックパッド内ではGo言語を使ってポリシーを記述していますが、DeepAlert側で規定した Lambda Function に対する入力と出力のインターフェースに則っていればどのような言語で記述できます。

SIEMをはじめとする多くの製品では独自の記法でポリシーを記述ようになっています。これはポリシーに記述する要素を厳選し、入力する内容を減らすことで容易に表現できることを目的としていると考えられます。このような仕組みになっていることで、単純なポリシーであれば低い学習コストで記述できるようになります。しかし複雑な条件を扱う必要が出てくると、ポリシーを分割して見通しを良くするということができなかったり、任意のテストができないために検証のコストが大きくなってしまう、という課題に直面しがちです。また、デバッグの手段が用意されていない場合も多く、ひたすらトライ&エラーを繰り返して検証する必要がある、という問題にも悩まされます。ポリシーの記述力もあまり柔軟ではない場合が多く、愚直な処理を繰り返し書かなければいけなかったり、ちょっとしたデータ形式の変換などができず消耗するといったこともしばしばありました。

DeepAlertを実装する際、一般的なプログラミング言語でポリシーをコード化することでこれらの問題の多くを解決できると考えて、入出力のインターフェースだけを定義しました。DSLやライブラリを使って評価するような機能を提供しないことによって、言語の種類に対する依存も極力ないようにしました。これによって、通常のプログラミングにおけるコード整理やテストの技法を取り込むことが可能となり、ポリシーが複雑化しても見通しがよくテスト可能な形で記述することができるようになります*2

Reviewerの入出力定義

Reviewerに対する入力のサンプルを以下に示します。

{
    "id": "61a97323-b7dc-4b13-a30d-7b423388da5f",
    "alerts": [
        {
            "detector": "AWS GuardDuty",
            "rule_name": "High Severity Finding",
            "rule_id": "guardduty/high_sev",
            "alert_key": "xxxxxxxxxxxx",
            "description": "Unusual resource permission reconnaissance activity by PowerUser.",
            "timestamp": "2020-03-12T18:07:10Z",
            "attributes": [
                {
                    "type": "ipaddr",
                    "key": "remote IP address (client)",
                    "value": "198.51.100.1",
                    "context": [
                        "remote",
                        "client"
                    ]
                },
                {
                    "type": "username",
                    "key": "AWS username",
                    "value": "mizutani@cookpad.com",
                    "context": [
                        "subject"
                    ]
                }
            ]
        }
    ],
    "sections": [
        {
            "author": "addrmap",
            "type": "host",
            "content": {
                "activities": [
                    {
                        "last_seen": "2020-03-12T00:46:17.000928Z",
                        "principal": "mizutani",
                        "remote_addr": "198.51.100.1",
                        "service_name": "AzureAD",
                        "owner": "Cookpad"
                    },
                    {
                        "last_seen": "2020-03-12T12:39:11Z",
                        "principal": "mizutani",
                        "remote_addr": "198.51.100.1",
                        "service_name": "Falcon",
                        "owner": "Cookpad"
                    }
                ]
            }
        }
    ]
}

元にしているのはAmazon GuardDutyから発報されたアラートです。説明のためにいろいろと省略していますが、基本となる要素は含まれています。(詳細な定義については こちら から参照することができます)

まず alerts がセキュリティ監視・防御システムから発報されたアラートになります。このアラートについても独自のフォーマットになっているため、発報するシステムとDeepAlertの間で1つLambdaを挟んでフォーマットの変換をしています。 attributes の部分にはそのアラートに出現した属性値になります。それぞれIPアドレスやユーザ名といった型を付けているのは従来のSIEMなどと同じですが、 context というフィールドをもたせることでその属性値の意味がわかるようにしています。これは、例えば「Source IP address」という型でアラートの属性値が正規化されていたとしても、それが内部と外部のどちらのネットワークを意味するのかであったり、何か攻撃をした側なのか、それとも攻撃を受けた側なのかということが発報時の文脈によって変わってしまうという問題に対応するための説明用フィールドとなっています。

そして、Inspector によって収集された情報を格納したのが sections になります。ここでは社用PCがどのIPアドレスからどのサービスを使っているかというaddrmapという内製ツールからの情報が付与されています。これを見ることで AzureAD および CrowdStrike Falcon でもアラートがあがったIPアドレスから同様に接続があったことが示されています。こういった情報をReviewerが参照し、ポリシーで影響あり・なしの判断ができるのであればそれを出力として伝える、判断できないのであればセキュリティエンジニアの判断に委ねる、といった処理をしています。

この入力のスキーマについてはSTIXのような既存の脅威情報を記述する構造を利用することも考えましたが、本来の目的が違うこと、我々がやりたいことから見て機能が過剰であること、互換性を維持する意味があまりないことから独自の形式にしました*3

一方、出力についてはシンプルで、severityreason の2つを入れるのみです。先述したとおり、severitysafeunclassifiedurgent の3段階でのみ表現されます。

{
    "severity": "safe",
    "reason": "The device accessing to G Suite is owned by Cookpad"
}

記述されたポリシーの例

具体的なポリシーの記述例を以下に示します。

// AWSへの不審なログインのアラートを評価するポリシーの例
func handleAlert(ctx context.Context, report deepalert.Report) (deepalert.ReportResult, error) {
    for _, alert := range report.Alerts {
        // アラートが対象のものでなかったら評価しない
        if report.RuleID != "guardduty/high_sev" {
            return nil, nil
        }

        // オフィスのIPアドレスからのアクセスの場合はこのポリシーでは評価しない
        if hasOfficeIPAddress(alert.Attributes) {
            return nil, nil
        }
    }

    // Inspectorによる調査結果を抽出
    reportMap, err := report.ExtractContents()
    if err != nil {
        return nil, err
    }

    // アクセス元のホストに関して Inspector が取得した情報をチェック
    for _, hostReports := range reportMap.Hosts {
        for _, host := range hostReports {
            for _, owner := range host.Owner {
                // そのホストの所有者が Cookpad のものであると確認できるログがあった場合、
                if owner == "Cookpad" {
                    return &deepalert.ReportResult{
                        // Safe(影響なし)と判断する
                        Severity: deepalert.SevSafe,
                        Reason:   "The device accessing to G Suite is owned by Cookpad.",
                    }, nil
                }
            }
        }
    }

    return nil, nil
}

func main() {
    lambda.Start(handleAlert)
}

このコードは説明のために簡略化していますが、おおまかな流れは実際のものと変わりません。先程の入力データの例では、不審なログインのアラートに対して、Inspectorが別の社内向けサービスを利用していたという情報を付与していました。このポリシーではその付与された情報を確認して、それが社員のPCが使っているIPアドレスからのアクセスなのか、それとも全く関係ない海外のサーバなどからのものなのかを確認し、もし社員のPCであると考えられる場合は safe(影響なし)という判断を返します。もし判断に足る情報がなければ、何も返さないことで unclassified(不明)と判定されます。

このポリシーとして記述された関数 handleAlert を用いることによって、アラート評価のテストを記述することができるようになります。この例では1つアラートに対するポリシーだけを記述していますが、実際には複数種類のアラートに対応できるようなコードを書く必要があります。新しくポリシーを追加したり、既存のものを変更した時、意図していない変更がまぎれていないかを確認するために常にテストで確認ができることで、自信を持ってポリシーをデプロイすることができるようになります。

その他のアーキテクチャの工夫

  • DynamoDBのベストプラクティスにもあるように、データストアは一つのDynamoDBのテーブルに押し込めています。このテーブルは複数アラートの集約、アラート情報の一時的な保持、新しく出現した属性値の管理、Inspectorの調査結果の保持などに利用しています。
  • 調査フェイズと評価フェイズにおいてLambdaの実行制御にStep Functionsを挟んでいるのは、アラートの到着から調査を開始するまでにわざと遅延を入れるためです
    • Inspectorの調査活動でも特にログを検査するタイプのものは、アラートが到着した直後ではまだログを参照できる状態になっていないことがあるため、数分程度待ってからInspectorを起動します
    • また、Inspectorが疎結合で任意の数実行されることから、DeepAlert側では同期的にInspectorの制御はしていません。そのため非同期に実行されたInspectorの結果を待つためにもStep Functionsを使っています

DeepAlert導入の効果

深刻度の自動評価による運用負荷の軽減

この仕組みを運用し始めておよそ2年ほどになりますが、2019年の実績では約50%ほどのアラートを人間が確認する前に影響なしであることを確認できました。これによって、対応に割く時間を大幅に減らす事ができました。

実際には影響がなかった場合でも全体の傾向の変化があった場合には気づきたいので、Slackで「影響なしのアラートが発生した」ということだけは通知させています。

f:id:mztnex:20200317083704p:plain

また、「影響なし」以外の判断がされたものについても、GitHub Enterpriseでアラートの詳細を記載したIssueを作成し、その上で対応の管理をしています。Issueについても最初にセキュリティアラートとしてDeepAlertが受信した情報だけでなく、Inspectorが取得した情報もあわせて記載しています。例として以下に示しているIssueでは、セキュリティログ検索システムへのリンクやVirusTotalへInspectorが問い合わせて取得した情報もあわせて掲載しています。これらの情報だけでは機械的に判定ができなかったわけではありますが、セキュリティエンジニアが自分で調査する際にも同じような情報をもとに作業することになるので、これらの情報が予め掲載されているということは作業時間の短縮に繋がります。このような点から自動評価できなかったアラートに対しても運用の負荷が下がっていると言えます。

f:id:mztnex:20200317083720p:plain

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 中略 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

f:id:mztnex:20200317083735p:plain

コスト

結論から言うと、直近半年のデータから計算された実費用は一ヶ月あたりで約 $1.3 でした。アーキテクチャの説明でも述べた通り、DeepAlertはクラウドの金銭的運用コストおよび弾力性を考えてLambda、Step Functions、DynamoDB、SQS、SNSといったリソースのみで構成されています。原則としてどれも使ったリソース量に応じてのみ課金される構成が可能であり、対処するアラートの流量が少なかったり、アラートの発生頻度にむらがあるような場合でもコストを小さく抑えやすくなっています。

まとめ

DeepAlertは、既存のSIEMのように「複数のイベントを組み合わせて(精度の高い)セキュリティアラートを発報する」という考え方ではなく、「(精度の低い)セキュリティアラートに対して情報を付与し、精度を高める」といった戦略になっています。そのため、純粋な機能面でのできること自体はSIEMのサブセットという位置づけになってしまいますが、運用のしやすさやコストメリットなどから、このアプローチが最も効果的であると判断して取り組んできました。このように既存の製品だけにとらわれず、それが本当に必要であれば自分たちで作る、といったところまでやりきれるのが、事業会社でセキュリティをやる楽しさの一つなのではないかなと思います。

クックパッドではこうしたセキュリティにまつわる課題を一緒に解決していくエンジニアを絶賛募集しています。興味のある方はぜひこちらをご参照いただくか、ご質問などあれば水谷( @m_mizutani )などまでお声がけください。

*1:これは自分たちの業務を楽にするというだけでなく、SOC全体のクオリティを一定に保つ、という効果もあったと考えています

*2:かならずしも見通しがよいコードが書かれることが保証されるものではありませんが、できる余地があることが重要だと考えています

*3:ただしSTIXなどからDeepAlertの形式に変換するというのは意味があるかもしれないので、必要があれば実装したいと考えています

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/