独立したウェブアプリ

ウェブは、他に類を見ないアプリケーション プラットフォームです。このフレームワークで構築されたアプリは、コードの変更やコンパイルを必要とせずに、あらゆるオペレーティング システムで即座にアクセスできます。ユーザーがアプリにアクセスするたびに、常に最新バージョンが提供されます。インストール可能でオフラインでも動作し、高性能で、リンクだけで簡単に共有できます。ウェブ アプリケーションを構築すれば、どこでも動作します。

ウェブは デフォルトで安全かつセキュアであることを目指しているため、そのセキュリティ モデルは非常に保守的である必要があります。追加される新機能は、URL を介して一般ユーザーが誤ってアクセスしても安全である必要があります。このセキュリティ モデルを「ドライブバイ ウェブ」と呼びます。これは多くのアプリケーションで有効であり、コンテンツ セキュリティ ポリシークロスオリジン分離を使用してセキュリティを強化できますが、すべてのユースケースで機能するわけではありません。Direct SocketsControlled Frame など、デベロッパーが必要とする非常に重要で強力な API の多くは、ウェブによるドライブに十分な安全性を確保できません。

これらのアプリケーションには、現在ウェブで構築するオプションはありません。また、ウェブのセキュリティ モデルが十分でないと考えるユーザーもいます。サーバーが信頼できるという前提を共有せず、代わりに個別にバージョン管理され署名されたスタンドアロン アプリケーションを好むユーザーもいます。新しい信頼性の高いセキュリティ モデルが必要です。独立したウェブアプリ(IWA)は、既存のウェブ プラットフォーム上に構築された、分離、バンドル、バージョン管理、署名、信頼されたアプリケーション モデルを提供し、これらのデベロッパーを支援します。

ウェブ上の信頼のスペクトル

ウェブのセキュリティと機能は、スペクトルで考えることができます。

ウェブ上の信頼のスペクトルを示すイラスト。左側には、ドライブバイ ウェブを表す地球儀が表示されます。中央には、プログレッシブ ウェブアプリがあります。右側には、独立したウェブアプリを表す金魚鉢と金魚が描かれています。3 つのアイコンはすべて黒の実線で水平に結ばれており、プログレッシブ ウェブアプリと独立したウェブアプリは赤の破線で区切られています。

左側のドライブバイ ウェブは、最もアクセスしやすくする必要があるため、信頼性の低いセキュリティ モデルであり、ユーザーのシステムへのアクセスが最も制限されています。ブラウザにインストールされたウェブアプリは、信頼性がやや高く、ユーザーのシステムにやや深く統合できます。通常、ユーザーはアプリのドライブバイ ウェブ版とブラウザにインストールされた版を問題なく切り替えることができます。

次に、信頼性の高い独立したウェブ アプリケーションがあります。

ネイティブ アプリのような操作感で、システムとの深い統合や強力な機能にアクセスできます。ユーザーは、それらとドライブバイ ダウンロードの間を移動できません。このレベルのセキュリティや機能が必要な場合は、元に戻すことはできません。

このスペクトラムのどこを目指すべきかを判断する際は、プログレッシブ ウェブアプリなど、可能な限り信頼性の低いセキュリティ モデルをデフォルトにしてください。これにより、リーチを最大化し、セキュリティ上の懸念事項を最小限に抑え、デベロッパーとユーザーにとって最も柔軟なものになります。

安全性を重視した設計

独立したウェブアプリは、ウェブ アプリケーションに信頼性の高いセキュリティ モデルを提供します。ただし、これを有効にするには、ドライブ バイ ウェブが信頼性について想定しているいくつかの前提を再考する必要があります。サーバーや DNS などのウェブの基本的な構成要素は、もはや明示的に信頼することはできません。ネイティブ アプリに関連性が高いと思われる攻撃ベクトルが、突然重要になります。そのため、IWA が提供する新しい高信頼性セキュリティ モデルにアクセスするには、ウェブアプリをパッケージ化、分離、ロックダウンする必要があります。

パッケージ化されている

分離されたウェブアプリのページとアセットは、通常のウェブ アプリケーションのように、ライブサーバーから配信したり、ネットワーク経由で取得したりすることはできません。代わりに、新しい高信頼性セキュリティ モデルにアクセスするには、ウェブアプリは実行に必要なすべてのリソースを Signed WebBundle にパッケージ化する必要があります。署名付きウェブ バンドルは、サイトの実行に必要なすべてのリソースを取得し、それらを .swbn ファイルにまとめて、完全性ブロックと連結します。これにより、ウェブアプリを安全にダウンロードして、オフラインで共有したりインストールしたりできます。

ただし、サイトのコードの信頼性を確認するうえで問題があります。TLS 鍵は、機能するためにインターネット接続が必要です。TLS 鍵の代わりに、安全にオフラインで保持できる鍵で IWA に署名します。幸いなことに、すべての本番環境ファイルを 1 つのフォルダに集めることができれば、大幅な変更を加えることなく IWA に変換できます。

署名鍵を生成する

署名鍵は Ed25519 または ECDSA P-256 鍵ペアで、秘密鍵はバンドルの署名に使用され、公開鍵はバンドルの検証に使用されます。OpenSSL を使用して、Ed25519 または ECDSA P-256 鍵を生成して暗号化できます。

# Generate an unencrypted Ed25519 key
openssl genpkey -algorithm Ed25519 -out private_key.pem

# or generate an unencrypted ECDSA P-256 key
openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem

# Encrypt the generated key. This will ask for a passphrase, make sure to use a strong one
openssl pkcs8 -in private_key.pem -topk8 -out encrypted_key.pem

# Delete the unencrypted key
rm private_key.pem

署名鍵には、別の目的もあります。ドメインはサーバーのように制御不能になる可能性があるため、インストールされた IWA を識別するために使用することはできません。代わりに、IWA はバンドルの公開鍵によって識別されます。この公開鍵は署名の一部であり、秘密鍵に関連付けられています。これはドライブバイ ウェブの仕組みに対する大きな変更です。そのため、IWA では HTTPS ではなく、新しいスキームisolated-app://)も使用されます。

アプリをバンドルする

署名鍵が用意できたら、ウェブアプリをバンドルします。これを行うには、公式の NodeJS パッケージを使用して IWA をバンドルしてから署名します(Go コマンドライン ツールも利用可能です)。まず、wbn パッケージを使用して、すべての IWA の本番環境ファイル(ここでは dist)を含むフォルダを指定し、署名なしのバンドルにラップします。

npx wbn --dir dist

これにより、そのディレクトリの署名なしウェブバンドルが out.wbn. に生成されます。生成されたら、以前に作成した暗号化された Ed25519 または ECDSA P-256 鍵を使用して、wbn-sign で署名します。

npx wbn-sign -i out.wbn -k encrypted_key.pem -o signed.swbn

これにより、signed.swbn という名前の署名なしウェブバンドルから署名付きウェブバンドルが生成されます。署名が完了すると、ツールはウェブバンドル ID とその独立したウェブアプリのオリジンも出力します。独立したウェブアプリのオリジンは、ブラウザで IWA を識別する方法です。

Web Bundle ID: ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic
Isolated Web App Origin: isolated-app://ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic/

WebpackRollup、またはそれらのプラグインをサポートするツール(Vite など)を使用している場合は、これらのパッケージを直接呼び出す代わりに、これらのパッケージをラップするバンドラ プラグイン(WebpackRollup)のいずれかを使用できます。これにより、署名付きバンドルがビルドの出力として生成されます。

アプリをテストする

IWA をテストするには、Chrome の組み込み IWA デベロッパー プロキシを介して開発サーバーを実行するか、バンドルされた IWA をインストールするかのいずれかの方法があります。これを行うには、Chrome または ChromeOS 120 以降を使用し、IWA フラグを有効にして、Chrome のウェブアプリ内部からアプリをインストールする必要があります。

  1. chrome://flags/#enable-isolated-web-app-dev-mode フラグを有効にする
  2. chrome://web-app-internals にある Chrome のウェブアプリ内部ページにアクセスして、IWA をテストします。

[ウェブアプリの内部] ページでは、Install IWA with Dev Mode Proxy または Install IWA from Signed Web Bundle のいずれかを選択できます。

開発モード プロキシを使用してインストールする場合、他の IWA インストール要件を満たしていることを前提として、ローカル開発サーバーから実行されているサイトを含む任意の URL をバンドルせずに IWA としてインストールできます。インストールが完了すると、その URL の IWA がシステムに追加され、適切なセキュリティ ポリシーと制限が適用され、IWA 専用の API にアクセスできるようになります。ランダムな識別子が割り当てられます。このモードでは、Chrome DevTools を使用してアプリケーションをデバッグすることもできます。署名付き Web Bundle からインストールする場合は、署名付きのバンドルされた IWA をアップロードすると、エンドユーザーがダウンロードしたかのようにインストールされます。

[Web App Internals] ページでは、デベロッパー モード プロキシまたは署名付きウェブ バンドルからインストールされたアプリケーションの更新チェックを強制的に実行して、更新プロセスをテストすることもできます。

分離性

独立したウェブアプリでは信頼が重要です。その第一歩は、実行方法です。アプリがブラウザで実行されているか、スタンドアロン ウィンドウで実行されているかによって、アプリでできること、できるべきことに対するユーザーのメンタルモデルは異なります。一般的に、スタンドアロン アプリの方がアクセス権が多く、強力であると考えられています。IWA は信頼性の高い API にアクセスできるため、このメンタルモデルに沿ってスタンドアロン ウィンドウで実行する必要があります。これにより、ブラウザと視覚的に区別されます。しかし、これは単なる視覚的な分離にとどまりません。

独立したウェブアプリは、ブラウザ内のウェブサイトとは異なるプロトコル(isolated-apphttp または https)で実行されます。つまり、同一オリジン ポリシーにより、同じ会社が作成したものであっても、各 IWA はブラウザ内で実行されるウェブサイトから完全に分離されます。IWA ストレージも互いに分離されています。これにより、クロスオリジン コンテンツが異なる IWA 間、または IWA とユーザーの通常のブラウジング コンテキスト間で漏洩しないようにします。

しかし、IWA がインストール後に任意のコードをダウンロードして実行できる場合、分離もサイトのコードのバンドルと署名も信頼の確立には役立ちません。これを確保しながら、IWA がコンテンツのために他のサイトに接続できるようにするため、IWA は厳格なコンテンツ セキュリティ ポリシーのセットを適用します。

  • バンドルからの JavaScript のみを許可しますが、ソースに関係なく Wasm の実行は許可します。(script-src
  • JavaScript が安全な localhost 以外のクロスオリジン ドメインから取得し、WebSocket エンドポイントと WebTransport エンドポイント、blob URL と data URL に接続できるようにします(connect-src)。
  • DOM 操作関数の使用方法を規制することで、DOM クロスサイト スクリプト インジェクション(XSS)攻撃から保護します(require-trusted-types-for
  • 任意の HTTPS ドメインからのフレーム、画像、音声、動画を許可します(frame-srcimg-srcmedia-src
  • バンドルと BLOB のフォントを許可します(font-src
  • インライン CSS またはバンドルからの CSS を許可する (style-src)
  • <object><embed><base> 要素は使用できません(object-srcbase-uri
  • 他の CSP 対象リクエスト(default-src)では、バンドル内のリソースのみを許可します。
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval';
  connect-src 'self' https: wss: blob: data:;
  require-trusted-types-for 'script';
  frame-src 'self' https: blob: data:;
  img-src 'self' https: blob: data:;
  media-src 'self' https: blob: data:;
  font-src 'self' blob: data:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'none';
  default-src 'self';

これらの CSP では、潜在的に悪意のあるサードパーティ コードから完全に保護することはできません。IWA はクロスオリジン分離されており、サードパーティ リソースが影響を与える可能性を減らすようにヘッダーが設定されています。

  • バンドル内のリソース、または CORS をサポートしていると明示的にマークされたクロスオリジン リソースのみを許可します。クロスオリジン リソース ポリシー(CORP)ヘッダーが設定されているか、crossorigin 属性が設定されている必要があります。(Cross-Origin-Embedder-Policy
  • CORS なしのクロスオリジン リクエストを禁止する(Cross-Origin-Resource-Policy
  • ブラウジング コンテキストをクロスオリジン ドキュメントからプロセス分離し、window.opener 参照とグローバル オブジェクト アクセスを防止(Cross-Origin-Opener-Policy
  • サイトがフレームまたは iframe 内に埋め込まれるのを防ぐ(CSP、frame-ancestors
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
Content-Security-Policy: frame-ancestors 'self'

これらの制限が実施されていても、IWA が防ぐべき攻撃がもう 1 つあります。シーケンス ブレーク攻撃です。シーケンス ブレーク攻撃は、悪意のあるサードパーティ コンテンツが、内部設定ページに直接移動するなど、予期しない方法でページに移動することで、混乱を招き、悪用される可能性のあるユーザー エクスペリエンスを作成しようとした場合に発生します。IWA では、外部サイトからの任意のディープリンクを禁止し、start_urlプロトコル ハンドラ共有ターゲット起動ハンドラなどの明確に定義されたエントリ ポイントに移動することでアプリケーションを開くことのみを許可することで、これを防ぎます。

ロックダウン

パッケージ化と分離により、実行が許可されるものとその出所に関する一連の保証が提供されますが、ウェブ上の権限は動的であるため、これらだけではウェブ アプリケーションが必要な機能のみを使用していることを保証できません。機能によってセキュリティ上の考慮事項が異なるため、ユーザーや管理者は、アプリをインストールまたは更新する前に、Android や iOS などの他のネイティブ アプリと同様に、IWA が使用する可能性のある権限を監査したいと考えます。

これを容易にするため、独立したウェブアプリはデフォルトですべての権限リクエストをブロックします。デベロッパーは、ウェブアプリ マニフェストに permissions_policy フィールドを追加することで、必要な権限をオプトインできます。このフィールドには、IWA、または子フレーム制御フレームや iframe など)がリクエストする可能性がある各権限の権限ポリシー ディレクティブ権限ポリシー許可リストのキーと値のペアが含まれます。ここで権限を追加しても、自動的に付与されるわけではありません。代わりに、その機能のリクエストが行われたときにリクエストできるようになります。

フリート トラッキング IWA を構築しているとします。IWA でユーザーの位置情報をリクエストしたり、埋め込み地図で位置情報をリクエストしたりする必要があるかもしれません。また、埋め込みサイトを全画面表示にして、ユーザーに没入感のあるビューを提供することもできます。そのためには、ウェブアプリ マニフェストで次の権限ポリシーを設定します。

"permissions_policy": {
   "geolocation": [ "self", "https://map.example.com" ],
   "fullscreen": [ "*" ]
}

WebBundle では Permissions Policy ヘッダーも指定できるため、両方で宣言された権限のみが許可され、両方で許可リストに登録されたオリジンのみが許可されます。

名前付きでバージョン管理されている

通常のウェブアプリは、ドメイン名を使用してユーザーに自身を識別させます。また、そのドメインで提供されるコードを変更することで更新できます。ただし、分離されたウェブアプリのセキュリティ制約により、ID と更新は異なる方法で処理する必要があります。プログレッシブ ウェブアプリと同様に、独立したウェブアプリでも、ユーザーにアプリを識別してもらうために ウェブアプリ マニフェスト ファイルが必要です。

ウェブアプリ マニフェスト

独立したウェブアプリは、PWA と同様に、ウェブアプリ マニフェストの主要なマニフェスト プロパティを共有しますが、若干の違いがあります。たとえば、display は少し動作が異なります。browserminimal-ui は両方とも minimal-ui 表示に強制的に設定され、fullscreenstandalone は両方とも standalone 表示に強制的に設定されます(追加の display_override オプションは想定どおりに動作します)。さらに、versionupdate_manifest_url という 2 つのフィールドも追加する必要があります。

  • version: 独立したウェブアプリで必須。1 つ以上の整数をドット(.)で区切った文字列。バージョンは、123 などのシンプルなものや、SemVer1.2.3)などの複雑なものにできます。バージョン番号は、正規表現 ^(\d+.?)*\d$ と一致する必要があります。
  • update_manifest_url: 省略可ですが、ウェブ アプリケーション更新マニフェストを取得できる HTTPS URL(またはテスト用の localhost)を指す、推奨フィールドです。

独立したウェブアプリの最小限のウェブアプリ マニフェストは次のようになります。

{
  "name": "IWA Kitchen Sink",
  "version": "0.1.0",
  "update_manifest_url": "https://example.com/updates.json",
  "start_url": "/",
  "icons": [
    {
      "src": "/images/icon.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "any"
    },
    {
      "src": "/images/icon-mask.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "maskable"
    }
  ]
}

ウェブ アプリケーションの更新マニフェスト

ウェブ アプリケーション更新マニフェストは、特定のウェブ アプリケーションの各バージョンを記述する JSON ファイルです。JSON オブジェクトには、version という 1 つの必須フィールドが含まれています。これは、versionsrcchannels を含むオブジェクトのリストです。

  • version - アプリケーションのバージョン番号。ウェブアプリ マニフェストの version フィールドと同じです。
  • src - そのバージョンのホストされているバンドル(.swbn ファイル)を指す HTTPS URL(テストの場合は localhost)。相対 URL は、ウェブ アプリケーションの更新マニフェスト ファイルに対する相対 URL です。
  • channels - このバージョンが属する更新チャンネルを識別する文字列のリスト。特別な default チャネルは、他のチャネルが選択されていない場合に使用されるプライマリ チャネルを記述するために使用されます。

channels フィールドを含めることもできます。これは、チャンネル ID のオブジェクトで、各 ID にオプションの name プロパティを指定して、人が読める名前を指定します(default チャンネルも含む)。name プロパティを含まないチャンネル、または channels オブジェクトに含まれないチャンネルは、ID を名前として使用します。

最小限の更新マニフェストは次のようになります。

{
  "versions": [
    {
      "version": "5.2.17",
      "src": "https://cdn.example.com/app-package-5.2.17.swbn",
      "channels": ["next", "5-lts", "default"]
    },
    {
      "version": "5.3.0",
      "src": "v5.3.0/package.swbn",
      "channels": ["next", "default"]
    },
    {
      "version": "5.3.1",
      "src": "v5.3.1/package.swbn",
      "channels": ["next"]
    },
  ],
  "channels": {
    "default": {
      "name": "Stable"
    },
    "5-lts": {
      "name": "5.x Long-term Stable"
    }
  }
}

この例では、Stable というラベルが付けられる default5.x Long-term Stable というラベルが付けられる 5-ltsnext というラベルが付けられる next の 3 つのチャネルがあります。ユーザーがチャンネル 5-lts を使用している場合はバージョン 5.2.17、チャンネル default を使用している場合はバージョン 5.3.0、チャンネル next を使用している場合はバージョン 5.3.1 が提供されます。

ウェブ アプリケーションの更新マニフェストは任意のサーバーでホストできます。更新は 4 ~ 6 時間ごとにチェックされます。

管理者による管理

初回リリースでは、独立したウェブアプリは Chrome Enterprise で管理されている Chromebook にのみ、管理者が管理パネルからインストールできます。

まず、管理パネルで [デバイス] > [Chrome] > [アプリと拡張機能] > [ユーザーとブラウザ] に移動します。このタブでは、組織全体のユーザー向けに Chrome ウェブストア、Google Play、ウェブからアプリや拡張機能を追加できます。アイテムを追加するには、画面右下の黄色いフローティング追加ボタン(+)を開き、追加するアイテムのタイプを選択します。

開くと、正方形の中に正方形のアイコンが表示され、[独立したウェブアプリを追加] というラベルが付いています。このアイコンをクリックすると、IWA を OU に追加するためのモーダルが開きます。これを行うには、IWA のウェブバンドル ID(アプリの公開鍵から生成され、アプリがバンドルされて署名された後に表示されます)と、IWA のウェブアプリ アップデート マニフェストの URL の 2 つの情報が必要です。インストールが完了すると、管理パネルの標準オプションを使用して管理できるようになります。

  • インストール ポリシー: IWA のインストール方法(自動インストール、自動インストールして ChromeOS シェルフに固定、インストールを禁止)。
  • ログイン時に起動: IWA の起動方法を指定します。ユーザーが手動で起動できるようにするか、ユーザーがログインしたときに IWA を強制的に起動して終了できるようにするか、ユーザーがログインしたときに強制的に起動して終了できないようにするかを指定します。

保存すると、次回その OU の Chromebook にポリシーの更新が適用されたときにアプリがインストールされます。インストールされると、ユーザーのデバイスは 4 ~ 6 時間ごとにウェブアプリ アップデート マニフェストからアップデートを確認します。

IWA の強制インストールに加えて、他のウェブ アプリケーションと同様に、IWA の一部の権限を自動的に付与することもできます。これを行うには、[デバイス] > [Chrome] > [ウェブ機能] に移動し、[オリジンを追加] ボタンをクリックします。Origin / site pattern field に、IWA のウェブ バンドル ID を貼り付けます(isolated-app:// がプロトコルとして自動的に追加されます)。そこから、ウィンドウ管理、ローカル フォント管理、画面モニタリング API など、さまざまな API のアクセスレベル(許可/ブロック/未設定)を設定できます。必須の画面モニタリング API など、有効にするために管理者の追加のオプトインが必要になる可能性がある API の場合は、選択を確認するための追加のダイアログが表示されます。完了したら、変更を保存します。これで、ユーザーは IWA を使用できるようになります。

拡張機能を使用する

独立したウェブアプリは拡張機能とすぐに連携しませんが、所有している拡張機能を接続できます。そのためには、拡張機能のマニフェスト ファイルを編集する必要があります。マニフェストの externally_connectable セクションでは、拡張機能がやり取りできる外部ウェブページや他の Chrome 拡張機能を定義します。externally_connectable 内の matches フィールドに IWA のオリジンを追加します(isolated-app:// スキームを必ず含めてください)。

{
  "externally_connectable": {
    "matches": ["isolated-app://79990854-bc9f-4319-a6f3-47686e54ed29/*"]
  }
}

これにより、拡張機能は分離されたウェブアプリで実行できるようになりますが、コンテンツを挿入することはできません。拡張機能と IWA の間でメッセージを渡すことしかできません。