こんにちは、アメブロのフロントエンドエンジニアのときです。
アメブロでは2017年4月に、PC/SPブラウザの閲覧面のhttps化対応を行いました。 今回は、大規模サービスにおけるhttps化のフロントエンドの対応についてお話しさせていただきます。
※現在は、https、httpどちらからでもアクセスができる状態で、徐々にhttps一本に移行し、「常時https化」を目標としています。
https化って何がいいの?
まず、既存のサービスをhttps化することで、サービスやユーザにはどういった影響があるのでしょうか。 メリットとデメリットをかいつまんでご説明します。
メリット
- セキュリティ強化、信頼性アピール
- SEO向上
- 最新の技術を取り入れることができるようになる
セキュリティ強化、信頼性アピール
直接的なメリットとしてはやはり、HTTPS通信のみでサイトを配信することによる、セキュリティ強化です。HTTP通信では、盗聴や改ざんを容易に行うことができ、フィッシングサイトに誘導するなどしてユーザの個人情報を不正に取得することができてしまいます。一方HTTPS通信では、サーバの認証・暗号化された通信を行うため、前述のような攻撃を防ぎ、より安全にサイトを利用できるようになります。
また、2017年1月頃から、Chromeを始めとした主要ブラウザで、パスワードの入力などを含むページがHTTPS通信かどうかがロケーションバーに表示されるようになりました。 https化を行わないと、ログイン画面などで「このサイトは安全ではありません」と表示されてしまうことになります。これではサービスの信頼性に関わります。ユーザの個人情報を扱ったり、メディア媒体としての性質をもつアメブロとしては、信頼性を高めることは急務でありました。
SEO向上
さらに、2014年にGoogleがサイトがhttpsかどうかをランキングシグナルに使用すると発表しました。今はまだ検索結果に大きくは影響しないとのことですが、https化していることが影響するのであれば、他の競合他社が対応している中で遅れをとることはサービスとして死活問題になり得ます。逆に言うと、HTTPS通信に対応しているサービスを使うことが、ユーザにとってはSEO向上のメリットに繋がります。
関連:
最新の技術を取り入れることができる
また、副次的なメリットとして、最新の技術を取り入れることが可能になります。
最近話題のService Workerや、iOS9から使えるようになったUniversal Link などは、https化されていることが前提条件になります。最新の技術を使い、ユーザにとって快適なサービスを提供するためにも(エンジニアの知的好奇心を満たすためにも)、https化対応は欠かせないプロセスになります。
(https化対応直後、さっそくService Workerのprecacheのテストが始まりました。こちらも乞うご期待です?)
デメリット
https化を行うことでのデメリットとしては、下記の4点が挙げられます。
- 一時的なindex低下
- App Indexingの無効化
- HTTP通信の処理の無効化、外部プラグインへの悪影響
- Refererが適切に送信されない
一時的なindex低下
httpのURLからhttpsのURLに変わるため、Googleはこれを、「URLの変更を伴うサイト移転」として扱います。そのため、インデックスされ直すまでは一時的に流入が減るものと考えられます。ただ、サイトの規模によっては時間がかかることもありますが、いずれ回復するものと考えて良いようです。 なお、アメブロの場合は、現状httpのURLにアクセスが来てもリダイレクトは行っておらず(=常時https化ではない)、botから見ると2つのURLが存在する状態です。徐々にhttpsに移行していくため、canonicalにはhttpsのURLを設定しています。
検索流入面に関する懸念点はありましたが、結果的には、https公開後アクセス数の高いブログからすぐにインデックスされ始めていたので、大きな影響はなかったように思います。
参考: URL の変更を伴うサイト移転
App Indexingの無効化
もともとアメブロのAndroidアプリはApp Indexingに対応していましたが、http固定のフックになっていたため、アプリをバージョンアップしないと、App Indexingが正常に動作しません。古くから続くサービスではHTTP通信前提で実装されているケースもあるので、アプリの修正が必要かどうかも検討事項に入れておくべきでしょう。アメブロの場合は、ギリギリで間に合ったのでDAUには影響ありませんでした?
たとえ間に合わなかったとしても、アプリのDAUがブラウザに流れるだけなので、https化のメリットを考えると、サービス全体で数値を見れば問題ないものと判断することもできます。
HTTP通信の処理の無効化、外部プラグインへの悪影響
基本的には、HTTP通信のものは表示されなくなってしまう、もしくは表示されるがアドレスバー上に「非セキュア」の表示が出てしまうため、サービス内で利用している外部プラグインの精査が必要でした。
アメブロには、「フリープラグイン」や「フリースペース」という、ユーザが自分でJavaScriptや外部のプラグインを自由に追加できる機能があります(PCのみ)。 しかし今回のhttps化の「セキュリティ強化」という目的においては、インラインスクリプトでユーザに自由に記述させることはできず、やむなくscriptの除去となりました。外部ファイル(HTTPSのみ)のロードは可能なため、HTTPS通信に対応したリソースを読み込むだけで実行できるプラグインに関しては、引き続き利用していただくことができます。
こういった仕様の変更に関しては、一番ユーザにとって影響の大きなものですので、スタッフブログなどで事前告知を行い、周知に努めました。関連サービス・開発者様による迅速なご対応のおかげで、リリースまでに対応プラグインを増やすこともできました。
Refererが適切に送信できない
https化すると、ページのRefererが適切に取得できなくなります。
対応については後述するとして、結論としては 「すべてのブラウザで同一のRefererを送信することはできない」 です。https化においての現状解決できないデメリットではありますが、それ以上に得られるメリットを優先した形になります。
フロントエンドの対応
さて、https化についての説明はここまでにしておいて、実際にフロントエンドで行った対応を紹介していこうと思います。
Mixed Contentsの撲滅
HTTPS通信で表示したページ中にHTTP通信のリソース(画像やCSS, jsファイルなど)が含まれていると、そこを起点に攻撃される可能性があります。このような、HTTPSとHTTPが入り混じったコンテンツを、Mixed Contents(混合コンテンツ)といいます。Mixed Contentsが含まれている場合、ブラウザは下記のようにエラーやWarningを表示し、場合によってはリソースを実行することができません。
Warningは表示に影響はないものの、適切な状態とは言えず、Errorはリソースの実行すらできない状態です。HTTPのリソースに依存したスクリプトも、軒並みコケることになります。
Mixed Contentsの詳しい解説はこちらが参考になります: 混合コンテンツの種類と関連するセキュリティ上の脅威
アメブロの場合はほぼ全ての広告が真っ白になってしまいました。
そのため、https化対応においてフロントエンドで対応することのほとんどが、「http:
を取る」「sをつける」ことです。 前者は、http:
を取ることで相対パス参照となり、アクセス元のスキーム(HTTPS or HTTP)に応じたリクエストになることを利用したハックです。
- Before
<img alt="sample image" src="http://sample.com/img/hoge.jpg"> <script src="http://sample.com/js/hoge.js"></script>
- After
<img alt="sample image" src="//sample.com/img/hoge.jpg"> <script src="//sample.com/js/hoge.js"></script> // あるいは <img alt="sample image" src="https://sample.com/img/hoge.jpg"> <script src="https://sample.com/js/hoge.js"></script>
アメブロ閲覧面で取得する全てのリソースがHTTPS通信となり、ロケーションバーにグリーンのマークが表示されるAll Greenの状態を目標としました。
- Before: Not Secure?
- After: Secured! All Green✅
当初の想定:「http:
をとるだけの簡単なおしごと!」
対応方針
- 実装
- 静的リソースのloadはスキームレスに:
http://hoge.com
->//hoge.com
- 広告や外部ライブラリのhttps対応: スキームレスにしたり、HTTPS通信用のクエリを追加したり
- 静的リソースのloadはスキームレスに:
- 動作確認
- 計測ツールの動作確認: HTTPSで配信されてはいるが計測に問題は出ていないか
- HTTPSで確認可能な開発環境にコードをdeployして逐次確認
- QC室によるPV上位ブログの全画面目視確認
- 周知
- 新規追加モジュール、スキンのhttps対応の周知
当初の想定では、「grepで探して一括置換すればおk、ほぼ確認作業かな」とか思っていたのですが、案の定様々な問題が浮上してきました。
「このファイルどこ」「httpsだと動かない」「広告が表示されてない」「アプリで画像が表示されない」「IE11だけバグる」「リファラ見てるのに困る」etcetc…
様々な問題が浮上…
つらみ1: バージョン管理されていない所在不明のファイルたち
アメブロでは昨年、SPブラウザ閲覧面のリニューアルを行っており、一部ページにおいてはシステムが刷新され、非常にモダンで開発しやすい環境になっています(こちらの記事で紹介しています)。
しかし依然PCブラウザ閲覧面・管理面などは、古いシステムで稼働しています。12年の年月を経るうちに、汚いコードや、複雑過ぎて解読不可能な仕様も存在しています。そういったファイル群の多くが、バージョン管理すらされていないものでした。現状コードのバージョン管理にはGitHubを用いていますが、依然利用していたSVNに残されたままのコードや、開発者のPCから直接FTPで上げられたような、バージョン管理外のコードも多数存在します。
何か変だぞ! と思ってコードを探そうにも、grepする場所が無限大なのです。
私自身異動してきたばかりだったこともあり、勤続年数の長いエンジニアやプロデューサーに1つずつ確認しながらの修正となりました。もちろんそんなファイルの仕様なんて誰も把握していないので、コードを1つずつ読み解く必要があります。
つらみ2: ブロガーごとに異なる分岐の数々
アメブロには、オフィシャルブロガー・トップブロガー・一般ブロガーなど、様々なステータスのブロガーが存在します。そしてステータスごとに、表示すべきモジュール(広告含む)が分かれており、一覧した仕様は存在せず、属人化した脳内仕様書頼みでした。
初期表示は問題なくても、「オフィシャルブロガーだけMixed Contents」「クリックしたらMixed Contents」「ページングしたらMixed Contents」など、後からエラーが溢れかえってしまう状態でした。
リリース1ヶ月前の2月末時点で、All Greenの画面は1つもありませんでした。
CSP違反レポートの活用
大規模サービスでの全コードの把握・目視確認には限界があるため、CSP違反レポートを活用することにしました。
CSP(Contents Security Policy)とは、信頼できないリソースを検知・ブロックする、ホワイトリスト型のセキュリティ機能です。
対象のページにContent-Security-Policy HTTPヘッダーに許可したいリソースを定義して用います。
将来的にはMixed Contentsはすべてブロックすべきなのですが、移行段階ではどういった影響が出るか想定できません。完全にhttps化移行を確認できるまでは、違反時にブロックはせず、Reportのみとするよう、Content-Security-Policy
の代わりにContent-Security-Policy-Report-Only
ヘッダーを用います。
Content-Security-Policy-Report-Only:default-src 'self' https:; script-src 'self' https: 'unsafe-inline' 'unsafe-eval'; img-src 'self' https: data:; style-src 'self' https: 'unsafe-inline'; font-src 'self' https: data:;
※簡単のため一部抜粋
Mixed Contentsの検知目的に使うため、参照元のオリジンとHTTPSのものは許可したいので、すべて'self' https
を設定しています。
画像やwebfontにはBASE64化しているものもあるため、data:
も追加しておきます。
script, styleのunsafe-inline
やunsafe-eval
は、本来は避けるべき設定です。しかしサードパーティのモジュールに依存している以上、インラインスクリプトの除去は現実的ではありません。設定した上で、ユーザが記述できるインラインスクリプトやスタイルに関しては、サーバ側で除去を行なうなどして処理しています。
CSPに違反した時に、場所と違反リソースをJSON形式でレポートしてくれるのが、CSP違反レポートです。
先ほどのヘッダに下記のreport-uri
ディレクティブを追加します。
report-uri https://send-to-report-url
レポートの送信先としては、REPORT URIが無料で使えて便利です。
REPORT URIの管理画面上で、ポリシー違反のあったURLと違反リソースが表示されます。
基本的にはスキームの除去か、sをつけるだけなので、箇所がわかれば後は直していくだけです。
検証環境のみCSP違反レポートが送信されるよう分岐をし、URL一覧に沿って細かく実行していき、違反レポートをしらみ潰しにしていきました。並行して、QC室(サービスのクオリティ担保のため、様々なテストを行う独立組織)があらゆるパターンでのテストを行ってくれたので、レアケースの分岐にも対応していきました。
CSPについては下記が参考になります。
スキームレスからhttps固定に方針変更
REPORT URIに違反レポートを表示する際に失敗したなと感じたのが、
- HTTPSでアクセスしたときのみ、レポートが送信されるようにすべき
- スキームレスではなく、https固定の方が良い
の2点です。
一般的には、https化対応のためには「スキーム(http:)を取る」ことが多いかと思います。
network-path reference
(相対パス参照)を利用したもので、HTTPSでアクセスした際には、https://
のスキームでリソースをリクエストし、HTTPでアクセスした際にはhttp://
のスキームでリクエストします。かつてはHTTPS通信が遅かったこともあり、参照元を特定できない埋め込み型のプラグインなどでは、こういった実装になっていることが多いようです。
2012年4月頃のGoogleのHTML/CSS Style Guideでも推奨されていたそうで、スキームレスを推奨する記事がたくさんあります。私たちも当初はその方針で進めていました。
ただ、CSPでは、HTTP通信時にも、ポリシー違反の全てがレポートされてしまいます。スキームレスにしたリソースは、HTTP環境下ではhttpのスキームでリクエストされ、違反扱いになってしまうのです。しかしこれは常時https化完了後には発生しなくなるものなので、この違反レポートは不要です。
HTTPSでアクセスした時のみレポートさせるか、スキームレスではなくhttps固定にしておけば、不要なレポートだらけにならずに済んだかと思います。
これだけであれば、CSPヘッダーの設定条件のみ変えれば良さそうですが、他にも
- metaタグのcanonical属性は絶対URL
- ソーシャルボタンに用いるURLは絶対URL
- 一部広告の埋め込みに参照元の絶対URLを使っている
- ネイティブアプリのエディタでは、スキームレスの画像などを表示できないケースがある(後述)
など、箇所によってスキームの扱いを分けるよりは、いずれHTTPSに移行していくことが決まっている今、https固定とした方がシンプルだという結論に達しました。
なお、2014年12月時点で既に、Chromeの開発者に「相対パス参照はもはやアンチパターンだ」と名言されています。
Now that SSL is encouraged for everyone and doesn’t have performance concerns, this technique is now an anti-pattern. If the asset you need is available on SSL, then always use the https:// asset.
現行のGoogleのHTML/CSS Style Guideでも、プロトコルの省略は推奨されていません。
今後のhttps化対応としてスキームレスにする理由はないかもしれません。
Use the HTTPS protocol for embedded resources where possible.
Always use the HTTPS protocol (https:) for images and other media files, style sheets, and scripts, unless the respective files are not available over HTTPS.
Google HTML/CSS Style Guide – 2.1.1 Protocol
ネイティブアプリでのスキームレスの取り扱い
「ネイティブアプリのエディタでは、スキームレスの画像などを表示できないケースがある」と書きましたが、ネイティブアプリのWebView内でスキームレスのリソースを読み込むと、スキームが file://
扱いになり、画像が表示できないケースがありました。
Android, iOSで発生条件は違うものの、エディタ画面で画像が表示できなかったため、ネイティブアプリにWebViewを組み込んでいるサービスでは、気をつけた方が良いかもしれません。
リファラーポリシーについて
デメリットのところで、「すべてのブラウザで同一のRefererを送信することはできない」と書きました。
https化すると、HTTPSのページからHTTPのリソースにリクエストした場合、Refererが適切に取得できなくなります。というのも、URL(クエリを含む)には機密情報が含まれている可能性があるため、セキュア(HTTPS)のページから非セキュア(HTTP)のページへのリクエストであり、非セキュアなページに機密情報を渡すべきではない、とRFCに定められているからです。
Clients SHOULD NOT include a Referer header field in a (non-secure) HTTP request if the referring page was transferred with a secure protocol.
15.1.3 Encoding Sensitive Information in URI’s
そのため、http -> httpsへのリクエストや、https -> httpsへはRefererは送信されますが、https -> httpへのリクエストでは送信されなくなります。
対応として、metaタグを使った実装をすることで、リクエスト時のリファラの取り扱いを指定することができます。
<meta name="referrer" content="unsafe-url">
上記のようにポリシー unsafe-url
を設定すると、http, httpsを問わず、URLを全て送信します。これがhttps対応前の元の挙動に一番近いものとなりますが、これはRFCの方針に沿わない実装であり、適切ではないと考えています。また、ブラウザによってはunsafe-url
はサポートされていないため、非対応ブラウザでは引き続きRefererが何も送信されない状態となります。
アメブロは、
https://ameblo.jp/${userId} // TOPページ
https://ameblo.jp/${userId}/entry-${entryId}.html // 記事詳細ページ
というURL構成のため、「どのブログからのアクセスか」「どの記事からのアクセスか」を判定するためには、クエリを除くURLを送信できるのがベストです。しかし、一番適当だと思われる same-origin
は主要ブラウザでサポートされていません。(Referrer Policy – Can I use)
ブラウザごとの実装状況にバラつきがあり、どれを選択しても同一の挙動にすることができません。となれば、Refererありきの実装は(すぐにとは言わずとも)廃止にすべきです。そのため、現状最善と思われる origin
を設定することにしました。
今、アメブロの記事からのRefererは全て、一律
https://ameblo.jp/
のようになっているかと思います。これであれば、どのブログ・記事かはわからないものの、アメブロからのリクエストであることは取得できます。しかし、ブログのアクセス解析を提供する外部サービスに影響が出ている状態のため、Refererの代替手段を模索中です。
賛否分かれるところだと思いますので、こちらはブラウザの実装状況に合わせ随時見直しを行っていきたく思います。
最後に
(もうやりたくないけど)今もう一度対応するならこうする! というものをまとめておくと、
- 事前準備
- 画面一覧
- 仕様書(せめてモジュールの分岐の有無だけでも)
- HTTPS確認環境
- 実装方針
- スキームレスではなくhttps固定で置換
- ネイティブアプリでの挙動の調査
- URLの記述の統一方針の決定
- ユーザーの自由記述可能なコンテンツの取り扱いの決定
- httpsアクセス時のみCSPレポートヘッダを付与する
- スキームレスではなくhttps固定で置換
- リファラーポリシーの決定と関連部署・会社への周知
- サービスの特性とセキュリティを考慮した上で、最適なポリシーを選択する
- リファラを参照している外部サービス、プラグイン、ログの確認と周知
- 外部プラグインのhttps対応の有無
- 人海戦術でのテスト
大規模サービスでは、ひたすらテストあるのみです。QC室の皆様には大変お世話になりました。
エンジニアとしての実装的に旨味はあまりありませんが、モダンでイケてるサービス(死語)になるために避けては通れないプロセスです。Refererのように未だ過渡期な点もありますが、あらゆるサービスがhttps化対応されれば、状況も変わってくるかと思います。
All Greenになった時の喜びはひとしおですので、皆sつけていきましょう?