読者です 読者をやめる 読者になる 読者になる

Web サービスの完全 HTTPS 化

インフラストラクチャー部長の星 (@kani_b) です。

2017年1月5日をもって、クックパッド における全ページで HTTPS が使われるようになりました。 完全 HTTPS 化をするにあたり、その理由や具体的な進め方について紹介します。 以前 SRE Tech Talks #2 にて一部発表した内容も含みますので、ご興味のある方はあわせてスライドもご覧ください。

完全 HTTPS 化に踏み切った理由

以前のクックパッドは、ログインや登録情報の参照など、いわゆる個人情報や認証情報を扱う箇所のみに HTTPS が使われていました。 このように「必要な箇所にのみ HTTPS を使う」構成は、ある程度歴史のある Web サービスにおいてよく使われている構成です。 この状態から、完全 HTTPS 化に踏み切った理由を説明します。

サービスをよりセキュアにするため

HTTPS の利用を考えるにあたりまず思い浮かぶ利点は、「通信を暗号化できる」そして「通信先を認証できる」ことでしょう。前述の通り、これまでは機密性の高い情報を扱う箇所のみで HTTPS を利用してきました。これだけでも、守りたい情報が簡単に窃取できてしまう状態は避けることができます。しかし、現代は公衆無線 LAN などオープンなネットワークがかなり普及しており、また国家レベルでの盗聴なども明らかになってきています。

「レシピサイトくらいで大げさな」と思われるかもしれませんが、食事は人間の生活と密接に紐付いており、思わぬ情報を得られる場合があります。例えばクックパッドはレシピの検索機能を提供しています。この機能にはこれまで HTTP が使われてきました。しかし、実際の検索動向を見てみると、例えば「ダイエット」であったり、特定の病気 (糖尿病など) に適したレシピなど、プライバシに大きく関わるキーワードが含まれることがあります。

こう見ると、「では検索は HTTPS にしたほうが良い」という気持ちが働きます。しかし、検索だけではなく、他の機能についても同様のことが考えられます。「その機能が実際どのように使われるのか」を完全に想定することは困難です。よって、全ての通信が暗号化されている状態をまず前提とすることにしました。もちろん、HTTPS の上でやり取りされる情報がどのように扱われるか、はまた別の話であり、アプリケーションレイヤにおいてどのように情報を扱うかは今後も考慮していく必要があります。

プラットフォームの進化

iOS の App Transport Security (ATS) 対応必須化などをはじめ、プラットフォーム側で通信を HTTPS にする流れも進んでいます。現在完全 HTTPS 化が進められている主な要因はここにあるのではないかと思います。Chrome においても近年は HTTPS 絡みの変更が盛んです。Chrome 56 でリリースされた、 HTTP ページにログインフォームが表示されている場合に “安全でないページ” という警告を出す機能は、多くの HTTP ページでログイン用のモーダルを表示していたクックパッドにも影響を及ぼす変更でした。

また、検索エンジン側の変更も要因の一つです。Google は検索ランキングにおいて HTTPS の利用有無をランキングアルゴリズムに利用することを発表しています。また多くの検索エンジンが検索画面そのものを HTTPS 化しており、 HTTPS 化なしに自社サービスへの流入などを正確に計測することは難しくなります。こうした外部のプラットフォームが HTTPS 化へと舵を切ってきたことも、移行理由の一つになりました。

開発のやりにくさ

完全 HTTPS 化をしていないサービスの多くで、ログインフォームや登録情報の参照など一部の画面のみが HTTPS 化されています。クックパッドでも長い間同様に HTTPS が使われている画面を使い分けていましたが、開発者が「この画面には HTTPS が必要かどうか」を判断して使い分けていたため、本来 HTTPS であるべき画面がそうでない、などの事故が起きうる状況でした。また、HTTPS 画面で提供されているエンドポイントにアクセスするために CORS に対応する必要があるなど、普段の開発にも影響が及んでいました。

新技術への対応

HTTPS は、現在出てきている新しい技術の必須要件とされることも多くなりました。例えば HTTP/2 はその代表格 (正確には HTTP/2 自体が HTTPS を要求しているわけではないが、インターネットサービスにデプロイするためにはほぼ必須) でしょう。他にも、ServiceWorker や Web Push, iOS の Shared Web Credentials などは HTTPS が利用されていることが要件となっています。 こうした新しい技術を活かしていくためにも、完全 HTTPS 化は必要でした。

以上が、クックパッドを完全 HTTPS 化するに至った理由です。Web サービスにおいて、もはや完全 HTTPS 化をしないポジティブな理由はないと考えます。

完全 HTTPS 化までの道のり

完全 HTTPS 化する理由がまとまったところで、次に実際どのように HTTPS 化したか、具体的な進め方について説明します。完全 HTTPS 化は、概ね以下のように進めました。

  1. HTTPS テスト環境を作成する
  2. mixed content をなくす
  3. 段階的リリース
  4. 全体リリース

完全 HTTPS 化は社内のアプリケーション、検索エンジンはもちろんのこと、提携他社からのアクセス、メディア媒体掲載時に利用される URL などあらゆる範囲に影響するため、社内での宣言は早めに行いました。結果として、多くの部署の協力を得ながら完全 HTTPS 化を進めることができました。

HTTPS テスト環境の作成

まずは HTTPS になった際のアプリケーションをテストできる環境をつくる必要があります。 クックパッドでは、Rails の HTTPS 必須化スイッチである force_ssl を利用し、特別な Cookie をリクエストに差し込んだ場合に force_ssl が有効化された専用のアプリケーションサーバにルーティングされるようにしていました。こちらは、段階的リリースのタイミングでは内部アクセスや Cookie の扱いに不都合があったため、リバースプロキシでリダイレクトする形に変更しています。

mixed content をなくす

mixed content とは、HTTPS の中に HTTP のリソースが含まれることです。多くのブラウザは、mixed content になっているリソースはロードしない、あるいは動作させないような制約を持っているため、完全 HTTPS 化にあたり mixed content をなくすことは必須です。最も大変なのがこの対応だと思います。

アプリケーションや CSS の中に埋まっている HTTP URL を探しだし、HTTPS に修正します。クックパッドにおいても、いくつか HTTP URL が記載されてしまっているケースがありました。 こういった URL は、 protocol-relative URL (//: ではじまる URL) に置き換えたり、アプリケーション側でリクエストプロトコルを見て URL を生成するように修正します。

大きなコードベースにおいて、この作業は根気の要るものです。また、この作業をしている間もサービスは開発され続けているため、終わりがありません。また、mixed content はコードそのもの以外 (ユーザの持つデータなど) に起因することも多いため、実際に本番に出してテストしていくことが大きな助けになります。そのため今回は、いくつかの主要機能を定めておき、それらに mixed content が発生しない段階で次のステップに進むことにしました。

ネットワーク広告の HTTPS 対応

ネットワーク広告は、その仕組み上実際に配信される広告クリエイティブが HTTPS を利用しているかが非常に重要です。クックパッドが完全 HTTPS 化に着手した2015年8月頃は、まだ多くの事業者が HTTPS に対応していない、あるいは対応を保証できない状態でした。つまり、ネットワーク広告を配信すると、mixed content が起きてしまう可能性が高かったのです。

しかし、ATS がリリースされたことによるものか、現在では多くの事業者が HTTPS 対応を進めています。クックパッドでも一部ネットワーク広告が利用されているため、2015年の着手時にはこれが原因となり一度ペンディングとなったものの、流れが変わったことで再び進めることができるようになりました。このあたりの事情は @suzu_v さんのスライドに非常にわかりやすくまとまっています。

完全 HTTPS 化完了後、ネットワーク広告の売上について事業部と確認も行いましたが、特に影響はありませんでした。

段階的リリース

完全 HTTPS 化による影響を確認するため、段階的なリリースを行いました。 リバースプロキシのレイヤで、特定の Cookie を用いて全ユーザのうち数%が完全 HTTPS 化されたアプリケーションサーバにアクセスするようにします。 アプリケーション側のエラーやパフォーマンスをトラックしつつ、ユーザからのご意見やお問い合わせをユーザサポート部門と連携しながら確認します。 結果として、いくつかのリダイレクトミスと不具合を見つけ、修正しました。

CSP Report の活用

今回の移行では利用できなかったのですが、Content Security Policy (CSP) の機能を使うことで、より効率的に mixed content の情報を集めることができます。CSP のディレクティブとして block-all-mixed-content ディレクティブがあり、これを指定するとブラウザは mixed content を一切読み込みません。また、CSP の機能として、ポリシ違反が発生した際に指定した URL にレポートを送出する機能 (report-uri *1 ) があります。 これらを活用することで、ユーザのブラウザで起きた mixed content 情報を収集することが可能です。

そのままユーザにリリースしてしまうと mixed content が存在した場合本当にリソースが読み込まれなくなってしまうため、Content-Security-Policy ヘッダでなく Content-Security-Policy-Report-Only ヘッダを利用します。

Content-Security-Policy-Report-Only: block-all-mixed-content; report-uri https://example.com/csp-report

上記のようなヘッダを送出すると、もし mixed content が発生した場合指定した URL にそのブラウザから JSON レポートが POST されます。内容は以下のようなものです。

{
    "csp-report": {
        "blocked-uri": "http://example.com/some_picture.png", 
        "disposition": "report", 
        "document-uri": "https://example.com/mixed_content.html", 
        "effective-directive": "block-all-mixed-content", 
        "line-number": 6, 
        "original-policy": "block-all-mixed-content; report-uri https://example.com/csp-report", 
        "referrer": "", 
        "source-file": "https://example.com/mixed_content.html", 
        "status-code": 0, 
        "violated-directive": "block-all-mixed-content"
    }
}

JSON が送られてくるだけですので、受け取るサーバの実装も簡素なことに加え、 Elasticsearch などに投入することで簡単に分析することが可能です。 クックパッドでは、一部のユーザに CSP を送出しています。また、レポートの受信には Amazon API Gateway を使い、受け取った JSON を Amazon Kinesis Firehose に送信して Amazon Elasticsearch Service で分析を行えるようにしています。実装が必要な箇所は API Gateway が JSON を受け取る箇所のみですので、非常に楽です。

全体リリース

段階的リリースにおいて徐々に公開範囲を広げていき、問題がなければ全体リリースへと進みます。 アプリケーションが HTTPS 接続を受け入れられる状態にした上で、リバースプロキシでリダイレクトを行いました。問題が判明した際すぐに切り戻せるよう、以下の点に気をつけていました。

  • HTTP 301 ではなく 307 (Temporary Redirect) の利用
  • Cookie 属性や HSTS など切り戻しのしにくい変更を行わない
  • ユーザサポート部門との協力

リリース後に監視を行いましたが、大きな問題は見当たりませんでした。

切り戻しのしにくい変更

完全 HTTPS 化における “後戻りのしにくい” 変更として、Cookie への secure 属性の付与や HTTP Strict Transport Security (HSTS) があります。

Cookie の secure 属性は、Cookie を HTTPS 環境下でのみ送出する属性であり、設定ミスや攻撃による意図しない HTTP アクセスでの Cookie 漏出を防ぐことができます。 完全 HTTPS 化が完了すればこの属性を付与することは何ら問題ないのですが、万一 HTTP への切り戻しを行った際に、例えば既存のユーザセッションが全て無効 (セッション Cookie が送出されない) になるといった事態を招きます。 そのため、完全 HTTPS 化を行った上で、サービスに影響がないことを確認できたタイミングで付与する必要があります。

HTTP Strict Transport Security (HSTS) は、Web サービス側からブラウザに対し「次回以降このドメインには HTTPS でアクセスしてほしい」旨を伝え記憶してもらう仕組みです。 完全 HTTPS 化により、 HTTP ページへのアクセスを HTTPS ページにリダイレクトします。つまり、リダイレクトされるまでの初回アクセスは HTTP になってしまいます。 HSTS を付与することで、例えばユーザがブラウザにドメインのみを入力してアクセスする場合でも、そのドメインにアクセスしたことががあれば HTTPS を利用するようになります。

この仕組みも、ブラウザの挙動を変更するため導入には注意が必要です。HSTS が設定されたドメインで、切り戻しのために HTTPS から HTTP へのリダイレクトを単に行うとリダイレクトループが発生します。 HSTS は max-age を 0 にした HSTS ヘッダを送出することで無効にすることができますので、secure 属性よりは切り戻しやすいといえます。しかし、HSTS は常に HTTPS で送出される必要がある (無効にしたい場合でも) という点には注意が必要です。 例えばロードバランサの負荷が心配なケースで、一度 HSTS を全体で有効にしてしまうと、切り戻したあとも継続して HTTPS アクセスを (リダイレクトが行われるまで) 受け続ける必要があります。

上記のことを踏まえ、クックパッドではまず HSTS を有効にして意図せず HTTP でアクセスされているページや動かない機能がないことを社内のエンジニアや事業部門に確認してから secure 属性の付与を行いました。 アプリケーション側でヘッダ送出を行うことも可能ですが、設定の見通しをよくするためリバースプロキシを利用しています。

全体リリース後

画像やあまり利用されていない機能などでトラブルが起きることもあるため、ご意見やお問い合わせを見ながら個別に対応を行っています。 また、URL が変更されるため、集計バッチなどの動作にも気を配る必要がありました。

完了後の反響など

上記のような進め方で、無事にクックパッドを完全 HTTPS 化することができました。 移行にあたり、パフォーマンスなども懸念として上がっていましたが、現在特に問題にはなっていません。HTTPS のオーバーヘッドは当然存在しますが、近年では端末のリッチ化やネットワークの高速化、安定化により大きな問題にはなりにくいと考えています。また、HTTP/2 や TLS 1.3 などプロトコルの進化により、よりオーバーヘッドは減らせると考えています。 また、上述の通りネットワーク広告の売上や、検索エンジンの順位などにも影響はみられませんでした。

おわりに

クックパッドを完全 HTTPS 化した背景や具体的な進め方についてご紹介しました。現在は、公開している全てのサービスを HTTPS で提供しています。 完全 HTTPS 化は、エンジニアに限らず様々な人を巻き込む必要があり、場合によっては少し根気も要る作業になりますが、段階的リリースや CSP などを使うことでよりよく進めていくことも可能です。

個人的には、完全 HTTPS 化に限らず、ユーザが安全にインターネットを利用できるようにすることは Web サービス事業者の一つの責務と考えています。完全 HTTPS 化は、その中でも実行のための障壁がなくなりつつある改善の一つです。

ユーザの安全や新技術など、Web が次の段階に進んでいくためにも HTTPS 対応は今後より必須になることでしょう。この記事が、まだ HTTPS 移行が済んでいない方のお役に立てば幸いです。

*1:report-uri は CSP Level 3 において廃止されているため、今後は report-to を使っていく必要があります

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://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('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/