バレット キー パターン

Azure
Azure Storage

アプリケーションからのデータ転送をオフロードするため、クライアントに特定リソースへの限定的な直接アクセスを提供するトークンを使用します。 これは特に、クラウドでホストされるストレージ システムやキューを使用するアプリケーションで役に立ち、コストを最小限にしてスケーラビリティとパフォーマンスを最大化できます。

コンテキストと問題

クライアント プログラムや Web ブラウザーは、多くの場合、アプリケーションのストレージとの間でファイルまたはデータ ストリームを読み書きする必要があります。 アプリケーションは通常、データをストレージからフェッチしてクライアントにストリーミングするか、クライアントからアップロードされたストリームを読み取ってそれをデータ ストアに格納することによって、データの移動を処理します。 ただし、このアプローチでは、計算、メモリ、帯域幅などの貴重なリソースが取られます。

データ ストアには、データのアップロードとダウンロードを直接処理する機能があり、このデータを移動するための処理を実行するようアプリケーションに要求しません。 しかし通常は、クライアントが、ストアのセキュリティ資格情報に対するアクセス権を持っているようにする必要があります。 これは、データ転送のコストと、アプリケーションをスケール アウトするための要件を最小限にして、パフォーマンスを最大化するうえで便利な方法となります。 ただしこれは、アプリケーションがデータのセキュリティを管理できなくなることを意味します。 クライアントがデータ ストアへの接続を備え、直接アクセスを行うようになると、アプリケーションがゲートキーパーとして機能することはできません。 アプリケーションはもはやプロセスを制御しておらず、データ ストアからの後続のアップロードやダウンロードを防げません。

これは、信頼されていないクライアントにサービスを提供する必要のある分散システムでは現実的なアプローチではありません。 それよりも、アプリケーションはきめ細かな方法でデータへのアクセスを安全に制御できる一方で、この接続を設定し、その後クライアントがデータ ストアと直接通信して必要な読み取りまたは書き込み操作を実行できるようにして、サーバーでの負荷を軽減できる必要があります。

解決策

ストアがクライアントの認証と承認を管理できないデータ ストアへのアクセスを制御する問題を解決する必要があります。 一般的な 1 つの解決策は、データ ストアのパブリック接続へのアクセスを制限し、データ ストアが検証できるキーまたはトークンをクライアントに提供することです。

このキーまたはトークンは、通常、バレット キーと呼ばれます。 これにより、特定のリソースへの時間制限付きアクセスを提供し、ストレージやキューに対する読み取りと書き込み、Web ブラウザーでのアップロードとダウンロードなど、定義済みの操作のみを許可します。 アプリケーションはすばやく容易にバレット キーを作成してクライアント デバイスや Web ブラウザーに発行し、クライアントに必要な操作の実行を許可できます。アプリケーションがデータ転送を直接処理する必要はありません。 これによって、アプリケーションとサーバーに起因する、処理のオーバーヘッドと、パフォーマンスやスケーラビリティへの影響がなくなります。

図に示すように、クライアントは特定の期間のみ、アクセス許可に関して特定の制限があるデータ ストア内の特定のリソースに、このトークンを使用してアクセスします。 指定された期間の後にキーは無効になり、リソースへのアクセスは許可されなくなります。

図 1 - パターンの概要

データの範囲など、他の依存関係があるキーを構成することもできます。 たとえば、データ ストアの機能に応じて、キーでデータ ストア内のテーブル全体を指定することも、テーブル内の特定の行だけを指定することもできます。 クラウド ストレージ システム内では、キーによって、コンテナーを指定することも、コンテナー内の特定項目だけを指定することもできます。

キーは、アプリケーションによって無効にすることも可能です。 これは、クライアントがサーバーに、データ転送操作が完了したことを通知する場合に役立つアプローチです。 サーバーはその後、そのキーを無効にして、それ以上のアクセスを停止できます。

このパターンを使用すると、ユーザーを作成して認証し、アクセス許可を与え、その後ユーザーを再度削除する必要がないため、リソースへのアクセスの管理を簡略化できます。 場所、アクセス許可、および有効期間の制限も容易になり、すべて実行時にキーを生成するだけです。 重要な要素は、受信者がリソースを意図した目的にのみ使用できるように、有効期間と、特にリソースの場所をできるだけ厳しく制限することです。

問題と注意事項

このパターンの実装方法を決めるときには、以下の点に注意してください。

有効状態とキーの期間を管理します。 漏えいやセキュリティ侵害が発生した場合、キーによってターゲット項目のロックが効果的に解除され、有効期間中は悪用が可能になります。 キーは通常、発行された方法に応じて、取り消すことまたは無効することができます。 サーバー側のポリシーを変更するか、署名に使用されたサーバー キーを無効にすることが可能です。 承認されていない操作がデータ ストアに対して発生するリスクを最小限にするため、短い有効期間を指定してください。 ただし、有効期間が短すぎると、キーの有効期限が切れる前にクライアントが操作を完了できない可能性があります。 保護されたリソースに対して複数のアクセスが必要な場合は、有効期間が終わる前に、承認されたユーザーがキーを更新できるようにします。

キーによって提供するアクセスのレベルを制御します。 通常、キーでは、クライアントがデータ ストアにデータをアップロードできてはいけない場合、読み取り専用アクセスなど、操作を完了するために必要な操作のみを実行できるようにする必要があります。 ファイルのアップロードの場合、書き込み専用のアクセス許可を提供し、場所と有効期間を示すキーを指定するのが普通です。 キーが適用されるリソースまたは一連のリソースを正確に指定することが非常に重要です。

ユーザーの動作を制御する方法を検討します。 このパターンを実装することは、ユーザーにアクセスが付与されたリソースに対する制御の一部が失われることを意味します。 用いることができる制御のレベルは、サービスまたはターゲット データ ストアで使用できるポリシーとアクセス許可の機能によって制限されます。 たとえば、通常、ストレージに書き込まれるデータのサイズや、ファイルにアクセスするためにキーを使用できる回数を制限するキーを作成することはできません。 これにより、意図したクライアントによって使用された場合でもデータ転送のコストが予期せず大きくなる可能性があり、アップロードやダウンロードの繰り返しを引き起こすコードのエラーによって、これが発生する可能性があります。 ファイルをアップロードできる回数を制限するには、可能な場合には 1 つの操作が完了した時点でそれをアプリケーションに通知するようクライアントに強制します。 たとえば、一部のデータ ストアでは、操作を監視してユーザー動作を制御するためにアプリケーション コードで使用できるイベントが発生します。 ただし、1 つのテナントのすべてのユーザーが同じキーを使用するマルチテナント シナリオでは、個々のユーザーにクォータを適用するのは困難です。

アップロードされたすべてのデータを検証し、必要に応じて不要部分を削除します。 キーへのアクセスを取得した悪意のあるユーザーは、システムを侵害するように設計されたデータをアップロードする可能性があります。 一方、承認されたユーザーが、無効で、処理されるとエラーやシステム エラーになり得るデータをアップロードする可能性もあります。 これに対する保護のため、アップロードされたすべてのデータが検証され、使用前に悪意のある内容がないかチェックされるようにします。

すべての操作を監査します。 多くのキー ベースのメカニズムは、アップロードやダウンロードなどの操作とエラーを記録できます。 これらのログは通常、監査プロセスに組み込むことができ、ユーザーがファイル サイズやデータ量に基づいて課金される場合は課金にも使用できます。 このログを使用して、キー プロバイダーの問題や、保存されたアクセス ポリシーの偶発的削除によって発生する可能性がある認証エラーを検出します。

キーを安全に配信します。 ユーザーが Web ページでアクティブにする URL に埋め込むことも、ダウンロードが自動的に発生するようにサーバーのリダイレクト操作で使用することもできます。 常に HTTPS を使用して、セキュリティで保護されたチャネルでキーを配信します。

転送中の機密データを保護します。 アプリケーション経由の機密データの配信は、通常、TLS を使用して行われ、データ ストアに直接アクセスするクライアントにはこれを強制する必要があります。

このパターンの実装時に注意すべきその他の問題は次のとおりです。

  • クライアントが、操作の完了をサーバーに通知しない、またはできない場合、唯一の制限はキーの有効期限であり、アプリケーションはアップロードやダウンロードの回数をカウントしたり、複数のアップロードやダウンロードを防止したりするなどの監査操作を実行できなくなります。

  • 生成できるキーのポリシーの柔軟性が限られる可能性があります。 たとえば、一部のメカニズムでは、時間を指定した有効期間のみを使用できます。 他のメカニズムでは、十分な細分性の読み取り/書き込みアクセス許可を指定することができません。

  • キーやトークンの有効期間の開始時刻を指定する場合は、クライアントの時計が少し同期していなくてもよいように、現在のサーバー時刻よりも少し前であるようにします。 指定しない場合の既定値は、通常、現在のサーバー時刻です。

  • キーを含む URL はサーバーのログ ファイルに記録されます。 キーは通常、ログ ファイルが分析のために使用される前に有効期限が切れますが、ログ ファイルへのアクセスを確実に制限してください。 ログ データが監視システムに送信されたり別の場所に保存されたりする場合は、キーの漏えいを防ぐため、有効期間が切れる後までの待機時間を実装することを検討してください。

  • クライアント コードが Web ブラウザーで実行される場合、Web ブラウザー内で実行されるコードを有効にしてページを提供するものとは異なるドメイン内のデータにアクセスするため、ブラウザーがクロス オリジン リソース共有 (CORS) サポートしている必要がある場合もあります。 古いブラウザーやデータ ストアには CORS をサポートしないものがあり、これらのブラウザーで実行されるコードでは、バレット キーを使用して、クラウド ストレージ アカウントなど、別のドメイン内のデータへのアクセスを提供できない場合があります。

このパターンを使用する状況

このパターンは次の状況で役立ちます。

  • リソースの読み込みを最小限にして、パフォーマンスとスケーラビリティを最大化する場合。 リソースをロックする必要がないバレット キーを使用すると、リモート サーバーの呼び出しが不要です。発行できるバレット キーの数に制限はありません。また、アプリケーションを介したデータ転送を実行することで生じる単一障害点が回避されます。 バレット キーの作成は、通常、キーを含む文字列を署名する単純な暗号操作です。

  • 運用コストを最小限に抑える場合。 ストアとキューへの直接アクセスを有効にすることは、リソースとコストの面で効率的であり、ネットワーク ラウンド トリップの数が減少する結果になる可能性があります。また、必要な計算リソースの数を削減できる場合もあります。

  • クライアントが定期的にデータのアップロードまたはダウンロードを行う場合。特に、大容量のボリュームがある場合や、各操作に大きなファイルが含まれる場合。

  • ホスティングの制限またはコスト上の考慮事項のために、アプリケーションで使用できる計算リソースが限られている場合。 このシナリオでは、アプリケーションがデータ転送の処理から解放されるため、同時のデータ アップロードやダウンロードが多数ある場合はこのパターンがさらに役立ちます。

  • データがリモート データ ストアまたは異なるデータ センターに格納される場合。 アプリケーションがゲートキーパーとして機能する必要がある場合、データ転送をデータ センター間やクライアントとアプリケーション間のパブリック ネットワークまたはプライベート ネットワークにわたって行い、その後アプリケーションとデータ ストアの間で行う追加の帯域幅のために料金が生じる可能性があります。

このパターンは、次の状況では有効でない場合があります。

  • データが格納される前やクライアントに送信される前に、アプリケーションがデータに対していくつかのタスクを実行する必要がある場合。 たとえば、アプリケーションで検証を行う、アクセスの成功をログに記録する、データに対する変換を実行する必要がある場合。 ただし、一部のデータ ストアやクライアントは、圧縮や圧縮解除などの単純な変換をネゴシエートして実行できます (たとえば、Web ブラウザーは通常、gzip 形式を処理できます)。

  • 既存のアプリケーションの設計により、パターンの組み込みが困難になっている場合。 このパターンを使用するには、通常、データを送受信する場合とは異なるアーキテクチャのアプローチが必要とされます。

  • 監査証跡を管理したり、データ転送操作が実行される回数を制御したりする必要があり、これらの操作を管理するためにサーバーが使用できる通知を、使用中のバレット キー メカニズムがサポートしていない場合。

  • 特にアップロード操作中に、データのサイズを制限することが必要な場合。 これに対しては、操作完了後にアプリケーションでデータのサイズをチェックするか、指定した期間の後またはスケジュールに従ってアップロードのサイズをチェックすることが唯一の解決策です。

ワークロード設計

設計者は、Azure Well-Architected Framework の柱で説明されている目標と原則に対処するために、ワークロードの設計でどのようにバレー キー パターンを使用できるかを評価する必要があります。 次に例を示します。

重要な要素 このパターンが柱の目標をサポートする方法
セキュリティ設計の決定により、ワークロードのデータとシステムの機密性整合性、および可用性が確保されます。 このパターンにより、クライアントは、長期的または永続的な資格情報を必要とせずにリソースに直接アクセスできます。 すべてのアクセス要求は、監査可能なトランザクションで始まります。 付与されたアクセスには、スコープと期間の両方で制限が適用されます。 このパターンでは、付与されたアクセスを取り消すことも簡単です。

- SE:05 ID およびアクセス管理
コスト最適化は、ワークロードの投資収益率の維持と改善に重点を置いています。 この設計では、すべてのクライアント要求を直接処理するコンポーネントは追加せず、処理をクライアントとリソースの間の排他的な関係としてオフロードします。 これは、クライアント要求が頻繁に発生する場合や、大量のプロキシ リソースが必要となるほどクライアント要求が大きい場合に劇的な効果を発揮します。

- CO:09 フローコスト
パフォーマンスの効率化は、スケーリング、データ、コードを最適化することによって、ワークロードが効率的にニーズを満たすのに役立ちます。 アクセスをプロキシ経由にする中間リソースを使用せず、処理をクライアントとリソースの間の排他的な関係としてオフロードします。すべてのクライアント要求を高パフォーマンスで処理することが求められるアンバサダー コンポーネントは必要ありません。 このパターンを使用する利点が最大限に発揮されるのは、トランザクションでプロキシを利用する価値がない場合です。

- PE:07 コードとインフラストラクチャ

設計決定と同様に、このパターンで導入される可能性のある他の柱の目標とのトレードオフを考慮してください。

Azure は、BLOB、テーブル、およびキュー内のデータへのきめ細かいアクセス制御と、Service Bus のキューおよびトピックのため、Azure Storage での Shared Access Signature をサポートしています。 Shared Access Signature トークンを構成し、特定のテーブル、テーブル内のキーの範囲、キュー、BLOB、または BLOB コンテナーに対する読み取り、書き込み、更新、削除などの特定のアクセス権を提供できます。 有効性の期間を指定することができます。 この機能は、アクセスにバレット キーを使用する場合に適しています。

たとえば、数百台のモバイル クライアントやデスクトップ クライアントが頻繁に大きいバイナリをアップロードするワークロードがあるとします。 このパターンを使用しない場合、ワークロードで選択できる方法は基本的に 2 つです。 1 つ目は、ストレージ アカウントに直接アップロードを実行するための永続的なアクセス権と構成をすべてのクライアントに提供する方法です。 もう 1 つは、ゲートウェイ ルーティング パターンを実装して、クライアントがプロキシ経由でストレージにアクセスするエンドポイントを設定する方法ですが、これがトランザクションにとって付加価値になるとは限りません。 どちらの方法にも次のような問題があり、パターンのコンテキストで対処することになります。

  • 事前共有シークレットが長く存続します。 異なるクライアントに異なるキーを提供する方法があまりない可能性があります。
  • 現在受信している大きいファイルを処理できる、十分なリソースを持つコンピューティング サービスを実行するための費用が追加されます。
  • アップロード プロセスにコンピューティングとネットワーク ホップのレイヤーが追加されるため、クライアントとの対話速度が低下する可能性があります。

バレット キー パターンを使用すると、セキュリティ、コストの最適化、パフォーマンスの問題にパターンで対処できます。

API から最初にアクセス トークンを取得した後、ストレージ アカウントにアクセスするクライアントを示す図。

  1. クライアントは、最終責任時点で、ゼロにスケールする軽量の Azure 関数でホストされる API に対して認証を行い、アクセスを要求します。

  2. API は要求を検証し、期間とスコープが制限された SaS トークンを取得して返します。

    API によって生成されたトークンは、クライアントに次の制限を適用します。

    • 使用するストレージ アカウント。 つまり、クライアントがこの情報を事前に知っている必要はありません。
    • 使用する特定のコンテナーとファイル名。これにより、トークンは最大 1 つのファイルで使用できることが保証されます。
    • 短期間 (3 分など) の操作時間枠。 期間が短いため、トークンの TTL がその実用性を超えて持続することはありません。
    • BLOB の作成のみに対するアクセス許可。ダウンロード、更新、削除は許可されません。
  3. このトークンは、ファイルをストレージ アカウントに直接アップロードするために、短い時間枠内でクライアントによって使用されます。

API は、その API の独自の Microsoft Entra ID マネージド ID に基づくユーザー委任キーを使用して、これらのトークンを承認されたクライアントに生成します。 ストレージ アカウントとトークン生成 API の両方でログ記録が有効になっているため、トークン要求とトークンの使用状況の相関関係を確認することができます。 API では、マルチテナントなどの状況で使用するストレージ アカウントやコンテナーを決定するために、クライアントの認証情報またはその他の利用可能なデータを使用できます。

完全なサンプルは、GitHub のバレー キー パターンの例で公開されています。 以下のコード スニペットは、その例から引用して調整したものです。 最初のスニペットは、Azure 関数 (ValetKey.Web) で独自のマネージド ID を使用して、ユーザー委任の Shared Access Signature トークンを生成する方法を示しています。

[Function("FileServices")]
public async Task<StorageEntitySas> GenerateTokenAsync([HttpTrigger(...)] HttpRequestData req, ..., 
                                                        CancellationToken cancellationToken)
{
  // Authorize the caller, select a blob storage account, container, and file name.
  // Authenticate to the storage account with the Azure Function's managed identity.
  ...

  return await GetSharedAccessReferenceForUploadAsync(blobContainerClient, blobName, cancellationToken);
}

/// <summary>
/// Return an access key that allows the caller to upload a blob to this
/// specific destination for about three minutes.
/// </summary>
private async Task<StorageEntitySas> GetSharedAccessReferenceForUploadAsync(BlobContainerClient blobContainerClient, 
                                                                            string blobName,
                                                                            CancellationToken cancellationToken)
{
  var blobServiceClient = blobContainerClient.GetParentBlobServiceClient();
  var blobClient = blobContainerClient.GetBlockBlobClient(blobName);

  // Allows generating a SaS token that is evaluated as the union of the RBAC permissions on the managed identity
  // (for example, Blob Data Contributor) and then narrowed further by the specific permissions in the SaS token.
  var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow.AddMinutes(-3),
                                                                            DateTimeOffset.UtcNow.AddMinutes(3),
                                                                            cancellationToken);

  // Limit the scope of this SaS token to the following:
  var blobSasBuilder = new BlobSasBuilder
  {
      BlobContainerName = blobContainerClient.Name,     // - Specific container
      BlobName = blobClient.Name,                       // - Specific filename
      Resource = "b",                                   // - Blob only
      StartsOn = DateTimeOffset.UtcNow.AddMinutes(-3),  // - For about three minutes (+/- for clock drift)
      ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(3),  // - For about three minutes (+/- for clock drift)
      Protocol = SasProtocol.Https                      // - Over HTTPS
  };
  blobSasBuilder.SetPermissions(BlobSasPermissions.Create);

  return new StorageEntitySas
  {
      BlobUri = blobClient.Uri,
      Signature = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName).ToString();
  };
}

次のスニペットは、API とクライアントの両方で使用されるデータ転送オブジェクト (DTO) です。

public class StorageEntitySas
{
  public Uri? BlobUri { get; internal set; }
  public string? Signature { get; internal set; }
}

クライアント (ValetKey.Client) は、API から返された URI とトークンを使用して、追加のリソースを必要とすることなく、クライアントからストレージへのフル パフォーマンスでアップロードを実行します。

...

// Get the SaS token (valet key)
var blobSas = await httpClient.GetFromJsonAsync<StorageEntitySas>(tokenServiceEndpoint);
var sasUri = new UriBuilder(blobSas.BlobUri)
{
    Query = blobSas.Signature
};

// Create a blob client using the SaS token as credentials
var blob = new BlobClient(sasUri.Uri);

// Upload the file directly to blob storage
using (var stream = await GetFileToUploadAsync(cancellationToken))
{
    await blob.UploadAsync(stream, cancellationToken);
}

...

次のステップ

このパターンを実装する場合は、次のガイダンスが関連している可能性があります。

このパターンを実装する場合は、次のパターンも関連している可能性があります。

  • ゲートキーパー パターン。 このパターンはバレット キー パターンと併用し、クライアントとアプリケーションまたはサービスの間でブローカーとして機能する専用のホスト インスタンスを使用してアプリケーションとサービスを保護できます。 ゲートキーパーは、要求を検証して不要部分を削除し、クライアントとアプリケーションの間で要求とデータを渡します。 セキュリティの追加の層を提供し、システムの攻撃対象領域を減らすことができます。
  • 静的コンテンツ ホスティング パターン。 高価なコンピューティング インスタンスの要件を低減するするため、これらのリソースをクライアントに直接提供できるクラウド ベースのストレージ サービスに静的リソースをデプロイする方法について説明します。 リソースを一般に公開する予定がない場合は、バレット キー パターンを使用してそれらを保護できます。