WWDC Quick Look 💓 By SwiftGGTeam
Ready, set, relay: Protect app traffic with network relays

Ready, set, relay: Protect app traffic with network relays

元の動画を見る

ハイライト

Apple は iOS 17 と macOS Sonoma に新しい Network Relay API を提供しました。開発者は MASQUE と Oblivious HTTP プロトコルを使って App のネットワークトラフィックにプライバシー保護レイヤーを追加でき、企業管理者は従来の VPN に代わる軽量な方法として、社内リソースへの安全なアクセスにリレーを利用できます。


主要内容

問題:VPN がすべてのシナリオに最適ではない理由

ネットワークトラフィックを保護したい開発者にとって、従来の選択肢は限られています。何もせずユーザーの IP アドレスをサーバーに晒すか、VPN を使うかの二択ですが、VPN には固有の問題があります。

  1. スコープが広すぎる:VPN は通常デバイス全体のトラフィックを引き受け、App は自分が関心のあるリクエストだけに保護を適用できません。
  2. ユーザー体験が悪い:VPN 接続は遅く、ネットワーク切り替え時に再接続が必要で、ユーザーが手動でオン/オフする必要があります。
  3. 信頼モデルが理想的でない:VPN プロバイダーはユーザーの実 IP とアクセス先を見えるため、プライバシーを中間者に委ねることになります。

企業シナリオでも、VPN の設定と配布は IT チームの負担です。従業員は VPN クライアントのインストール、設定入力、切断時の再接続対応が必要です。

Apple のアプローチ:ネットワークリレー

Apple は iOS 17 / macOS Sonoma で Network Relay サポートを導入しました。リレーは IETF 標準化された2つのプロトコルに基づく特殊なプロキシサーバーです。

  • MASQUE:任意の TCP と UDP 接続をプロキシでき、バックエンドサーバーの変更は不要です。
  • Oblivious HTTP:HTTP トラフィック専用に設計され、REST API シナリオに適しています。

リレーの中核的な利点はチェーン(chain)できることです。2つのリレーサーバーを設定できます。1つ目はユーザーの IP を知るがコンテンツは暗号化されているため内容を知らない。2つ目はアクセス内容を知るがユーザー IP を知らない。どの単一の主体も両方を同時に得られません。これが iCloud Private Relay の裏側技術です。

開発者にとっては、デバイス上の他のトラフィックに影響せず、自分の App だけにリレーを設定できます。企業にとっては、VPN より軽量で、接続が速く、指定ドメインのトラフィックだけをプロキシし、既存の ID プロバイダーと統合できます。


詳細

App 内で単一リレーを設定する

リレーの設定は数行のコードで済みます。Network framework で ProxyConfiguration オブジェクトを作成します(04:52):

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

キーポイント:

  • NWEndpoint.url はリレーサーバーの URL を受け取り、プロトコルは HTTPS である必要があります
  • RelayHop はリレーチェーンの1ホップを表し、http3RelayEndpoint はリレーとの通信に HTTP/3 を指定します
  • ProxyConfigurationrelayHops は配列で、シングルホップまたはマルチホップ(chain)をサポートします
  • 上記コードは設定オブジェクトを作成するだけで、まだ接続には適用されていません

3つの利用方法:Network framework、URLSession、WebKit

リレー設定は Apple の3つの主要ネットワーク API に注入できます。

方法1:Network framework05:40

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

var context = NWParameters.PrivacyContext(description: "my relay")
context.proxyConfigurations = [relayConfig]

let parameters = NWParameters.tls
parameters.setPrivacyContext(context)

let connection = NWConnection(host: "www.example.com", port: 443, using: parameters)
connection.start(queue: .main)

キーポイント:

  • NWParameters.PrivacyContext は iOS 17 / macOS Sonoma で追加された型で、プライバシー設定をカプセル化します
  • proxyConfigurationsProxyConfiguration の配列を受け取ります
  • setPrivacyContext でプライバシーコンテキストを接続パラメーターにバインドします
  • カスタムプロトコルや非 HTTP トラフィックなど、低レベル制御が必要なシナリオに適しています

方法2:URLSession06:07

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

let config = URLSessionConfiguration.default
config.proxyConfigurations = [relayConfig]

let mySession = URLSession(configuration: config)
let url = URL(string: "https://www.example.com/api/v1/employees")!
let (data, response) = try await mySession.data(from: url)

キーポイント:

  • URLSessionConfigurationproxyConfigurations プロパティが追加されました
  • 設定後、その URLSession のすべてのリクエストがリレー経由でルーティングされます
  • Swift concurrency の async/await API でデータを取得します
  • サーバーはリレーの IP アドレスしか見えず、ユーザーの実 IP は取得できません

方法3:WebKit06:30

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

let webkitConfig = WKWebViewConfiguration()
webkitConfig.websiteDataStore = WKWebsiteDataStore.nonPersistent()
webkitConfig.websiteDataStore.proxyConfigurations = [relayConfig]
let webView = WKWebView(frame: .zero, configuration: webkitConfig)

let url = URL(string: "https://www.example.com/api/v1/employees")!
webView.load(URLRequest(url: url))

キーポイント:

  • WKWebsiteDataStoreproxyConfigurations プロパティが追加されました
  • nonPersistent() データストアを使用するため、cookie やキャッシュを保持せず、プライバシーが向上します
  • WebView 内のすべてのネットワークリクエストがリレー経由になります
  • OAuth ログインフローなど、内蔵ブラウザーシナリオに適しています

企業シナリオ:構成プロファイルでリレーをデプロイ

企業は従業員に手動設定をさせる必要はありません。管理者は MDM(モバイルデバイス管理)で構成プロファイルを配布できます(09:15):

<dict>
    <key>PayloadType</key>
    <string>com.apple.relay.managed</string>
    <key>Relays</key>
    <array>
        <dict>
            <key>HTTP3RelayURL</key>
            <string>https://relay.example.com</string>
            <key>PayloadCertificateUUID</key>
            <string>5AB702EC-32F3-48A9-94FE-8EA1C67ACF46</string>
        </dict>
    </array>
    <key>MatchDomains</key>
    <array>
        <string>internal.example.com</string>
    </array>
</dict>

キーポイント:

  • PayloadTypecom.apple.relay.managed で、新しい構成プロファイルタイプです
  • HTTP3RelayURL でリレーサーバーアドレスを指定します
  • PayloadCertificateUUID は同一プロファイル内の証明書を参照し、TLS 検証に使います
  • MatchDomains でリレーがプロキシするドメインを限定し、他のトラフィックには影響しません
  • VPN からの大きな改善:必要なトラフィックだけをプロキシし、全体ではありません

企業シナリオ:NetworkExtension でプログラム的に設定

企業独自の管理 App がある場合、NetworkExtension フレームワークでプログラム的にリレーを設定できます(09:42):

import NetworkExtension

let newRelay = NERelay()
let relayURL = URL(string: "https://relay.example.com:443/")
newRelay.http3RelayURL = relayURL
newRelay.http2RelayURL = relayURL

newRelay.additionalHTTPHeaderFields = ["Authorization" : "PrivateToken=123"]

let manager = NERelayManager.shared()
manager.relays = [newRelay]
manager.matchDomains = ["internal.example.com"]

manager.isEnabled = true
do {
    try await manager.saveToPreferences()
} catch let saveError {
    // Handle error
}

キーポイント:

  • NERelay は NetworkExtension フレームワークの新しいクラスで、リレー設定を表します
  • HTTP/3 と HTTP/2 の両方をサポートし、フォールバックとして使えます
  • additionalHTTPHeaderFields でカスタム HTTP ヘッダーを追加し、認証(Authorization token など)に使えます
  • NERelayManager.shared() はシングルトンで、システム全体のリレー設定を管理します
  • matchDomains でリレーの適用範囲を指定ドメインに限定します
  • saveToPreferences() で設定をシステムに永続化し、ユーザー承認が必要です

重要ポイント

  1. ヘルスケア/金融系 App にネットワークプライバシー保護を追加:機密データ(健康記録、財務情報、位置データ)を扱う App では、URLSessionConfiguration.proxyConfigurations でリレーを設定し、サーバーがユーザーの実 IP を取得できないようにします。価値がある理由:プライバシー規制(GDPR、CCPA)はデータ収集・処理に厳格な要件を課し、IP を隠すことはコンプライアンスリスク低減に有効です。始め方:MASQUE 対応のリレープロバイダーを選び、URLSessionConfigurationproxyConfigurations を設定し、サーバーログにリレー IP しか見えないことを確認します。

  2. 社内 API アクセスで企業 VPN をリレーに置き換え:企業 App が社内リソースにアクセスする際、従業員に VPN クライアントをインストールさせる必要はありません。MDM で com.apple.relay.managed 構成プロファイルを配布し、リレーは MatchDomains のドメインだけをプロキシします。価値がある理由:VPN は接続が遅く、切断時に手動再接続が必要で、非企業トラフィックを妨げます。リレーはこれらの課題を回避します。始め方:MASQUE 対応リレーサーバーを構築し、構成プロファイルを作成、MDM でプッシュし、NERelayManager または URLSession で利用します。

  3. two-hop リレーアーキテクチャでより強いプライバシー保護relayHops 配列に2つの RelayHop を設定します。1つ目はユーザー IP を知るがコンテンツは見えない、2つ目はコンテンツを見るがユーザー IP を知りません。価値がある理由:シングルリレーではプロバイダーが「誰か」と「何にアクセスしているか」の両方を知ります。two-hop では単一主体が両方を関連付けられません。始め方:2つの異なるリレープロバイダーを選び、2つの RelayHopProxyConfiguration(relayHops: [hop1, hop2]) に渡します。

  4. WebView コンテンツにネットワーク分離を追加:App 内に WebView でサードパーティーコンテンツを表示する場合、WKWebsiteDataStore.nonPersistent()proxyConfigurations を組み合わせ、WebView のリクエストをリレー経由にし、ローカルデータを保持しません。価値がある理由:内蔵ブラウザーはプライバシー漏洩の温床です。始め方:WKWebViewConfiguration を作成し、websiteDataStorenonPersistent() に設定して proxyConfigurations を構成します。

  5. カスタム HTTP ヘッダーでパスワードレス認証NERelay.additionalHTTPHeaderFieldsAuthorization ヘッダーを追加し、リレーサーバーがパスワードなしでリクエスト元を検証できます。価値がある理由:従来の VPN は追加のユーザー名/パスワード認証が必要です。カスタム HTTP ヘッダーは SSO と統合し、無感知認証を実現できます。始め方:企業 IdP で API token を生成し、additionalHTTPHeaderFields で渡し、リレーサーバー側で token を検証します。


関連セッション

コメント

GitHub Issues · utterances