VPN 経由で iPhone アプリから社内 API を呼び出す方法
トンネルに触れる前に、失敗したリクエストを NSURLError コードで分類し、実際に壊れている層だけを修復します。
API は同じ iPhone の Safari では応答するのに、アプリのリクエストはタイムアウトします。この差はたいていトンネルのせいではありません——社内サービスを呼ぶアプリも普通の HTTPS リクエストを送っており、端末の既存 VPN はそのリクエストに経路があるかどうかだけを決めます。 アプリにもう一つトンネルを書き込むのは、タイムアウトの修復ではなく製品上の判断です。以下では、アプリがすでに持っているエラー表面を使って、失敗が起きる場所ごとに分類します。
トンネルではなくエラーから始める
URLSession は転送の失敗を NSError 値として NSURLErrorDomain の中で報告し、コードは最も安価に得られる証拠です。設定を変える前に、そのコードを失敗したホスト名とタスクの経過時間と一緒にログに残してください。タイムアウト、名前の失敗、信頼の失敗は画面上では同じに見え、三つの異なる修復を要します。まず分類し、そのクラスの節を読んでください。
タスクのメトリクスは、接続がそもそも確立されたかどうかという次の問いに答え、経路がなかったリクエストとサーバーの応答が遅すぎたリクエストを分けます。どれもトンネルの変更を必要とせず、必要なのはリクエスト自身の診断情報を読める場所に出力することです。
名前解決はほかの何よりも先に失敗する
次のような短い社内名は api.corp.example たいてい端末が持たない検索ドメインに依存するため、アプリは建物の中にしか存在しない名前を解決してしまいます。 NSURLErrorCannotFindHost がその兆候です。完全なホスト名は VPN が導入するどのリゾルバでも解決できる必要があり、検索ドメインが保証されないときはアプリが完全修飾名を要求すべきです。名前をアドレスに置き換え、証明書の検証を切るのは、失敗を直すのではなく移動させるだけです。
プライベートな DNS レコードと、その名前ちょうどに発行された証明書が対応する組み合わせです。それ以外はすべて、利用者に届いてしまう回避策です。
タイムアウトは名前の問題ではなく経路の問題
NSURLErrorTimedOut は、名前が解決されて何も応答しなかったことを意味し、経路、パケットポリシー、あるいはサービスを指します。トンネルがアプリ単位で割り当てられている場合、同じサーバーに届く別のアプリは、その対応付けが共有されないため、あなたのビルドについて何も語りません。実行しているバンドルがその割り当ての内側にあるかを確認し、次にそのサービス経路が許可されているかを確認してください。割り当ての外にインストールされた開発ビルドは、トンネルの完全に外側にいて、壊れた API のように見えることがあります。
ブラウザでの比較は、端末上のどれかのアプリに経路があることを示します。あなたのビルドにもあることを決して証明しないので、二つのテストは別々に行う必要があります。
信頼の失敗は証明書の名前の問題
NSURLErrorServerCertificateUntrusted は、その下の TLS エラーと合わせて、チェーンか身元の検証が失敗したことを意味します。社内 CA は端末で信頼されている必要があり、別のホスト名に発行された証明書はどれだけネットワークを再設定しても誤ったままです。Apple の App Transport Security のガイダンス はこうした URL 読み込み接続にも引き続き適用され、平文 HTTP サービスの周りにトンネルを置いても、そのサービスが端から端まで安全になるわけではありません。
身元か信頼の基点を直し、例外リストは空のままにしてください。任意読み込みの包括的な例外は、開発中は失敗を隠し、その後はすべての利用者のために残ります。
HTTP の拒否はネットワークではなくアプリの側
ハンドシェイクが完了したうえでの 401 や 403 は、リクエストが届いてサービスが拒否したという意味なので、残る問いはアカウント、トークンの対象、スコープ、鍵のどれかです。状態コードとホスト名を記録し、ベアラートークンや応答本文は決して記録しないでください。その時点でトンネルは仕事をしており、さらなるネットワーク変更は本当の修正を遅らせるだけです。
この表は端末の横に置く分類で、アプリが実際に報告できる失敗ごとに一行です——読む前にコードを出力してください。選んだ行が、どの節を順に追うかを決めます。
| Where it fails | What the app sees | First check |
|---|---|---|
| name resolution | NSURLErrorCannotFindHost (-1003) | resolve the fully qualified name from the device itself |
| no path, or no answer | NSURLErrorTimedOut (-1001) | is this bundle inside the per-app mapping, and is the service path allowed |
| TLS trust | NSURLErrorServerCertificateUntrusted (-1202) | does the certificate name match the host, and is the internal CA trusted |
| service refusal | HTTP 401 or 403 after a completed handshake | account, token audience, scope or key |
どの行もその下の行より安く試せ、それぞれが同じリクエストの別の層を除外します。一致する行が見つかるまで表を下り、そこでネットワークを変えるのをやめてください。