ミッション クリティカルなワークロードに関するセキュリティの考慮事項
ミッション クリティカルなワークロードは、本質的にセキュリティで保護する必要があります。 アプリケーションまたはそのインフラストラクチャが侵害された場合、可用性が危険にさらされます。 このアーキテクチャの焦点は、アプリケーションがパフォーマンスを維持し、かつあらゆる状況で使用できるように、信頼性を最大化することです。 セキュリティ制御は、主に可用性と信頼性に影響を与える脅威を軽減する目的で適用されます。
Note
ビジネス要件には、より多くのセキュリティ対策が必要な場合があります。 ミッション クリティカルなワークロードに関するazure Well-Architected Framework のセキュリティに関する考慮事項 ガイダンスに従って、実装のコントロールを拡張することを強くお勧めします。
ID 管理とアクセス管理
アプリケーション レベルで、このアーキテクチャでは、カタログ項目の作成やコメントの削除など、一部の制限された操作に API キーに基づくシンプルな認証スキームが使用されます。 ユーザー認証やユーザー ロールなどの高度なシナリオは、 基準アーキテクチャの範囲を超えています。
アプリケーションでユーザー認証とアカウント管理が必要な場合は、id とアクセスの管理の に従ってください。 一部の戦略には、マネージド ID プロバイダーの使用、カスタム ID 管理の回避、可能な場合のパスワードレス認証の使用などがあります。
最小特権アクセス
ユーザーとアプリケーションが、機能を果たすために必要な最小限のレベルのアクセス権を取得できるように、アクセス ポリシーを構成します。 通常、開発者は運用インフラストラクチャにアクセスする必要はありませんが、デプロイ パイプラインにはフル アクセスが必要です。 Kubernetes クラスターではコンテナー イメージがレジストリにプッシュされませんが、GitHub ワークフローではその可能性があります。 フロントエンド API は通常、メッセージ ブローカーからメッセージを取得しません。バックエンド ワーカーは必ずしも新しいメッセージをブローカーに送信するとは限りません。 これらの決定はワークロードによって異なります。割り当てるアクセス レベルには、各コンポーネントの機能が反映されている必要があります。
Azure ミッション クリティカルな参照実装の例を次に示します。
- Azure Event Hubs で動作する各アプリケーション コンポーネントは、Listen (
BackgroundProcessor
) または Send (CatalogService
) アクセス許可を持つ接続文字列を使用します。 このアクセス レベルにより、すべてのポッドに、その機能を満たすために必要な最小限のアクセス権のみが確保されます。 - Azure Kubernetes Service (AKS) エージェント プールのサービス プリンシパルには、Azure Key Vault の Secrets に対する Get および List アクセス許可しかありません。
- AKS Kubelet ID には、グローバル コンテナー レジストリにアクセスするための AcrPull アクセス許可しかありません。
マネージド ID
ミッション クリティカルなワークロードのセキュリティを向上させるには、可能な場合は、接続文字列や API キーなどのサービス ベースのシークレットを使用しないようにします。 Azure サービスがその機能をサポートしている場合は、マネージド ID を使用することをお勧めします。
この参照実装では、AKS エージェント プール ("Kubelet ID") 内のサービス割り当て 管理 ID を使用して、グローバル Azure Container Registry とスタンプのキー コンテナーにアクセスします。 アクセスを制限するために、適切な組み込みロールが使用されます。 たとえば、次の Terraform コードでは、kubelet ID に AcrPull
ロールのみが割り当てられます。
resource "azurerm_role_assignment" "acrpull_role" {
scope = data.azurerm_container_registry.global.id
role_definition_name = "AcrPull"
principal_id = azurerm_kubernetes_cluster.stamp.kubelet_identity.0.object_id
}
シークレット
可能な場合は、Azure リソースにアクセスするときにキーの代わりに Microsoft Entra 認証を使用します。 Azure Cosmos DB や Azure Storage などの多くの Azure サービスでは、キー認証を完全に無効にするオプションがサポートされています。 AKS では、Microsoft Entra ワークロード IDがサポートされています。
Microsoft Entra 認証を使用できないシナリオでは、各デプロイ スタンプにはキーを格納するための Key Vault の専用インスタンスがあります。 これらのキーはデプロイ時に自動的に作成され、Terraform を使用して Key Vault に格納されます。 エンド ツー エンド環境の開発者を除き、人間のオペレーターはシークレットと対話できません。 さらに、Key Vault アクセス ポリシーは、シークレットへのアクセスをユーザー アカウントに許可しないように構成されています。
Note
このワークロードではカスタム証明書は使用されませんが、同じ原則が適用されます。
AKS クラスターでは、 Key Vault Provider for Secrets Store を使用して、アプリケーションでシークレットを使用できます。 CSI ドライバーは Key Vault からキーを読み込み、個々のポッドにファイルとしてマウントします。
#
# /src/config/csi-secrets-driver/chart/csi-secrets-driver-config/templates/csi-secrets-driver.yaml
#
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: {{ .Values.azure.managedIdentityClientId | quote }}
keyvaultName: {{ .Values.azure.keyVaultName | quote }}
tenantId: {{ .Values.azure.tenantId | quote }}
objects: |
array:
{{- range .Values.kvSecrets }}
- |
objectName: {{ . | quote }}
objectAlias: {{ . | lower | replace "-" "_" | quote }}
objectType: secret
{{- end }}
このリファレンス実装では、Azure Pipelines で Helm を使用して、Key Vault のすべてのキー名を含む CSI ドライバーをデプロイします。 また、ドライバーは、マウントされたシークレットが Key Vault で変更された場合に更新する役割も担います。
コンシューマー側では、両方の .NET アプリケーションで組み込み機能を使用して、ファイル (AddKeyPerFile
) から構成を読み取ります。
//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
// + using Microsoft.Extensions.Configuration;
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// Load values from Kubernetes CSI Key Vault driver mount point.
config.AddKeyPerFile(directoryPath: "/mnt/secrets-store/", optional: true, reloadOnChange: true);
// More configuration if needed...
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
CSI ドライバーの自動再読み込みと reloadOnChange: true
の組み合わせにより、Key Vault でキーが変更されたときに、新しい値がクラスターに確実にマウントされます。 このプロセスでは、アプリケーションでのシークレットローテーションは保証されません。 この実装では、変更を適用するためにポッドを再起動する必要があるシングルトン Azure Cosmos DB クライアント インスタンスを使用します。
カスタム ドメインと TLS
Web ベースのワークロードでは、HTTPS を使用して、クライアントから API への通信や API から API への通信など、すべての対話レベルで中間者攻撃を防ぐ必要があります。 期限切れの証明書は引き続き一般的な障害の原因であり、エクスペリエンスが低下しているため、証明書のローテーションを自動化してください。
参照実装では、 contoso.com
などのカスタム ドメイン名を持つ HTTPS が完全にサポートされています。 また、 int
環境と prod
環境の両方に適切な構成を適用します。 e2e
環境用のカスタム ドメインを追加することもできます。 ただし、この参照実装では、 e2e
の有効期間が短く、Azure Front Door で SSL 証明書でカスタム ドメインを使用する場合のデプロイ時間が長くなるため、カスタム ドメイン名は使用されません。
デプロイの完全な自動化を有効にするには、Azure DNS ゾーンを使用してカスタム ドメインを管理する必要があります。 インフラストラクチャ デプロイ パイプラインは、Azure DNS ゾーンに CNAME レコードを動的に作成し、これらのレコードを Azure Front Door インスタンスに自動的にマップします。
Azure Front Door で管理される SSL 証明書が有効になっているため、手動で SSL 証明書を更新する必要がなくなります。 TLS 1.2 は最小バージョンとして構成されています。
#
# /src/infra/workload/globalresources/frontdoor.tf
#
resource "azurerm_frontdoor_custom_https_configuration" "custom_domain_https" {
count = var.custom_fqdn != "" ? 1 : 0
frontend_endpoint_id = "${azurerm_frontdoor.main.id}/frontendEndpoints/${local.frontdoor_custom_frontend_name}"
custom_https_provisioning_enabled = true
custom_https_configuration {
certificate_source = "FrontDoor"
}
}
カスタム ドメインでプロビジョニングされていない環境には、既定の Azure Front Door エンドポイントを使用してアクセスできます。 たとえば、 env123.azurefd.net
などのアドレスでアクセスできます。
Note
クラスター イングレス コントローラーでは、いずれの場合もカスタム ドメインは使用されません。 代わりに、 [prefix]-cluster.[region].cloudapp.azure.com
などの Azure 提供の DNS 名が Let's Encrypt と共に使用され、これらのエンドポイントに対して無料の SSL 証明書を発行できます。
このリファレンス実装では、Jetstack の cert-manager
を使用して、Let's Encrypt の SSL/TLS 証明書をイングレス ルール用に自動的にプロビジョニングします。 Let's Encrypt から証明書を要求するClusterIssuer
などのその他の構成設定は、src/config/cert-manager/chart に格納されている別のcert-manager-config
helm チャートを使用してデプロイされます。
この実装では、Issuer
ではなくClusterIssuer
を使用して、各名前空間に発行者が含まれるのを回避します。 詳細については、 cert-manager のドキュメント および cert-manager リリース ノートを参照してください。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
構成
すべてのアプリケーション ランタイム構成は、シークレットや非センシティブな設定を含め、Key Vault に格納されます。 Azure App Configuration などの構成ストアを使用して、設定を格納できます。 ただし、1 つのストアを使用すると、ミッション クリティカルなアプリケーションで障害が発生する可能性のあるポイントの数が減ります。 ランタイム構成には Key Vault を使用して、全体的な実装を簡略化します。
キー コンテナーは、デプロイ パイプラインによって設定する必要があります。 実装では、必要な値は、Terraform (データベース接続文字列など) から直接取得されるか、デプロイ パイプラインから Terraform 変数として渡されます。
e2e
、int
、prod
など、個々の環境のインフラストラクチャとデプロイの構成は、ソース コード リポジトリの一部である変数ファイルに格納されます。 この方法には、次の 2 つの利点があります。
- 環境内のすべての変更が追跡され、デプロイ パイプラインを通過した後に環境に適用されます。
- デプロイはブランチ内のコードに基づいているため、個々の
e2e
環境を異なる方法で構成できます。
1 つの例外は、パイプラインの機密値のストレージです。 これらの値は、Azure DevOps 変数グループにシークレットとして格納されます。
コンテナーのセキュリティ
コンテナー化されたすべてのワークロードのコンテナー イメージをセキュリティで保護する必要があります。
この参照実装では、SDK ではなくランタイム イメージに基づくワークロード Docker コンテナーを使用して、フットプリントと潜在的な攻撃対象領域を最小限に抑えます。 ping
、wget
、curl
などの他のツールはインストールされていません。
アプリケーションは、イメージ ビルド プロセスの一部として作成された特権のないユーザー workload
で実行されます。
RUN groupadd -r workload && useradd --no-log-init -r -g workload workload
USER workload
この参照実装では、Helm を使用して、個々のコンポーネントをデプロイする必要がある YAML マニフェストをパッケージ化します。 このプロセスには、Kubernetes のデプロイ、サービス、ポッドの水平自動スケール構成、セキュリティ コンテキストが含まれます。 すべての Helm チャートには、Kubernetes のベスト プラクティスに従った基本的なセキュリティ対策が含まれています。
これらのセキュリティ対策は次のとおりです。
readOnlyFilesystem
: 各コンテナーのルート ファイルシステム/
は読み取り専用に設定され、コンテナーがホスト ファイル システムに書き込まれるのを防ぎます。 この制限により、攻撃者がさらに多くのツールをダウンロードし、コンテナーにコードを保持することを防ぎます。 読み取り/書き込みアクセスを必要とするディレクトリは、ボリュームとしてマウントされます。privileged
: すべてのコンテナーが非特権として実行されるように設定されます。 特権としてコンテナーを実行すると、コンテナーにすべての機能が与えられます。また、デバイス制御グループ コントローラーが適用するすべての制限も解除されます。allowPrivilegeEscalation
: コンテナー内が親プロセスよりも多くの特権を取得できないようにします。
これらのセキュリティ対策は、Microsoft 以外のコンテナーや Helm チャート (可能な場合は cert-manager
など) にも構成されます。 Azure Policy を使用して、これらのセキュリティ対策を監査できます。
#
# Example:
# /src/app/charts/backgroundprocessor/values.yaml
#
containerSecurityContext:
privileged: false
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
prod
、int
、およびすべてのe2e
環境を含む各環境には、スタンプがデプロイされている各リージョンへのグローバル レプリケーションを持つ Container Registry の専用インスタンスがあります。
Note
この参照実装では、Docker イメージの脆弱性スキャンは使用されません。 Microsoft Defender for container registries (GitHub Actions と可能性があります) を使用することをお勧めします。
トラフィック イングレス
Azure Front Door は、このアーキテクチャのグローバル ロード バランサーです。 すべての Web 要求は Azure Front Door 経由でルーティングされ、適切なバックエンドが選択されます。 ミッション クリティカルなアプリケーションでは、Web アプリケーション ファイアウォール (WAF) など、他の Azure Front Door 機能を利用する必要があります。
Web アプリケーション ファイアウォール
Azure Front Door の重要な機能は WAF です。これは、Azure Front Door が通過しているトラフィックを検査できるためです。 防止モードでは、すべての疑わしい要求がブロックされます。 実装では、2 つのルール セットが構成されています。 これらのルール セットは Microsoft_DefaultRuleSet
され、 Microsoft_BotManagerRuleSet
されます。
ヒント
WAF を使用して Azure Front Door をデプロイする場合は、 Detection モードから開始することをお勧めします。 自然な顧客トラフィックでその動作を注意深く監視し、検出ルールを微調整します。 誤検知を排除した後、または誤検知がまれな場合は、 Prevention モードに切り替えます。 このプロセスが必要なのは、すべてのアプリケーションが異なり、一部のペイロードは、その特定のワークロードに対して正当であるにもかかわらず、悪意があると見なされる可能性があるためです。
ルーティング
CatalogService
やHealthService
など、Azure Front Door を経由する要求のみが API コンテナーにルーティングされます。 Nginx イングレス構成を使用して、この動作を強制します。 X-Azure-FDID
ヘッダーの存在と、それが特定の環境のグローバル Azure Front Door インスタンスに適しているかどうかを確認します。
#
# /src/app/charts/catalogservice/templates/ingress.yaml
#
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
# ...
annotations:
# To restrict traffic coming only through our Azure Front Door instance, we use a header check on the X-Azure-FDID.
# The pipeline injects the value. Therefore, it's important to treat this ID as a sensitive value.
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecRule &REQUEST_HEADERS:X-Azure-FDID \"@eq 0\" \"log,deny,id:106,status:403,msg:\'Front Door ID not present\'\"
SecRule REQUEST_HEADERS:X-Azure-FDID \"@rx ^(?!{{ .Values.azure.frontdoorid }}).*$\" \"log,deny,id:107,status:403,msg:\'Wrong Front Door ID\'\"
# ...
デプロイ パイプラインは、このヘッダーが適切に設定されていることを確認するのに役立ちますが、Azure Front Door を介してではなく、各クラスターを直接プローブするため、スモーク テストでもこの制限をバイパスする必要があります。 参照実装では、スモーク テストがデプロイの一部として実行されるという事実が使用されます。 この設計により、ヘッダー値を認識し、スモーク テスト HTTP 要求に追加できます。
#
# /.ado/pipelines/scripts/Run-SmokeTests.ps1
#
$header = @{
"X-Azure-FDID" = "$frontdoorHeaderId"
"TEST-DATA" = "true" # Header to indicate that posted comments and ratings are for tests and can be deleted again by the app.
}
セキュアなデプロイ
オペレーショナル エクセレンスのためのベースラインの適切に設計された原則に従うために、すべてのデプロイを完全に自動化します。 実行をトリガーしたりゲートを承認したりする以外に、手動の手順は必要ありません。
セキュリティ対策を無効にする悪意のある試行や誤った構成を防ぐ必要があります。 参照実装では、インフラストラクチャとアプリケーションのデプロイの両方に同じパイプラインが使用されるため、潜在的な構成ドリフトの自動ロールバックが強制されます。 このロールバックは、インフラストラクチャの整合性を維持し、アプリケーション コードと連携するのに役立ちます。 変更は、次のデプロイで破棄されます。
Terraform では、パイプラインの実行中にデプロイの機密性の高い値が生成されるか、Azure DevOps によってシークレットとして提供されます。 これらの値は、ロールベースのアクセス制限で保護されます。
Note
GitHub ワークフローには、シークレット値用の個別のストアの 類似の概念 が用意されています。 シークレットは暗号化され、GitHub Actions で使用できる環境変数です。
これらの成果物には、シークレット値やアプリケーションの内部動作に関する情報が含まれる可能性があるため、パイプラインによって生成されるすべての成果物に注意することが重要です。 参照実装の Azure DevOps デプロイでは、Terraform 出力を含む 2 つのファイルが生成されます。 1 つのファイルはスタンプ用で、1 つのファイルはグローバル インフラストラクチャ用です。 これらのファイルには、インフラストラクチャを侵害する可能性のあるパスワードは含まれません。 ただし、これらのファイルは、クラスター ID、IP アドレス、ストレージ アカウント名、Key Vault 名、Azure Cosmos DB データベース名、Azure Front Door ヘッダー ID など、インフラストラクチャに関する情報を明らかにするため、機密性の高いファイルと見なす必要があります。
Terraform を使用するワークロードでは、シークレットを含む完全なデプロイ コンテキストが含まれているため、状態ファイルの保護に余分な労力を要する必要があります。 状態ファイルは通常、ワークロードとは別のライフ サイクルを持ち、デプロイ パイプラインからのみアクセスできるストレージ アカウントに格納されます。 このファイルへのその他のアクセス権をログに記録し、適切なセキュリティ グループにアラートを送信する必要があります。
依存関係の更新
アプリケーションが使用するライブラリ、フレームワーク、およびツールは、時間の経過と同時に更新されます。 これらの更新プログラムには、多くの場合、攻撃者にシステムへの不正アクセスを許可する可能性があるセキュリティ問題の修正プログラムが含まれているため、定期的に完了することが重要です。
参照実装では、NuGet、Docker、npm、Terraform、および GitHub Actions 依存関係の更新に GitHub の Dependabot を使用します。 dependabot.yml
構成ファイルは、アプリケーションのさまざまな部分が複雑であるため、PowerShell スクリプトを使用して自動的に生成されます。 たとえば、各 Terraform モジュールには個別のエントリが必要です。
#
# /.github/dependabot.yml
#
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/src/app/AlwaysOn.HealthService"
schedule:
interval: "monthly"
target-branch: "component-updates"
- package-ecosystem: "docker"
directory: "/src/app/AlwaysOn.HealthService"
schedule:
interval: "monthly"
target-branch: "component-updates"
# ... the rest of the file...
- 最新のライブラリを使用することと、オーバーヘッドを保守可能に保つことのバランスをとって、更新は毎月トリガーされます。 さらに、Terraform などの主要なツールは継続的に監視され、重要な更新プログラムは手動で実行されます。
- プル要求 (PR) は、
main
ではなく、component-updates
ブランチを対象とします。 - npm ライブラリは、
@vue-cli
などのサポート ツールではなく、コンパイル済みアプリケーションに移動する依存関係のみを確認するように構成されています。
Dependabot は更新ごとに個別の PR を作成し、運用チームを圧倒する可能性があります。 参照実装では、最初に component-updates
ブランチで更新プログラムのバッチを収集し、次に e2e
環境でテストを実行します。 これらのテストが成功した場合は、 main
ブランチを対象とする別の PR が作成されます。
防御的なコーディング
API 呼び出しは、コード エラー、誤動作したデプロイ、インフラストラクチャの障害など、さまざまな理由で失敗する可能性があります。 API 呼び出しが失敗した場合、呼び出し元またはクライアント アプリケーションは、アプリケーションに関する有用なデータ ポイントを敵対者に提供する可能性があるため、広範なデバッグ情報を受け取るべきではありません。
参照実装では、失敗した応答の相関 ID のみを返すことで、この原則を示します。 例外メッセージやスタック トレースなど、エラーの理由は共有されません。 この ID を使用し、 Server-Location
ヘッダーを使用して、オペレーターは Application Insights を使用してインシデントを調査できます。
//
// Example ASP.NET Core middleware, which adds the Correlation ID to every API response.
//
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.Use(async (context, next) =>
{
context.Response.OnStarting(o =>
{
if (o is HttpContext ctx)
{
context.Response.Headers.Add("Server-Name", Environment.MachineName);
context.Response.Headers.Add("Server-Location", sysConfig.AzureRegion);
context.Response.Headers.Add("Correlation-ID", Activity.Current?.RootId);
context.Response.Headers.Add("Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
}
return Task.CompletedTask;
}, context);
await next();
});
// ...
}
次のステップ
参照実装をデプロイして、リソースとその構成を完全に理解します。