-
Notifications
You must be signed in to change notification settings - Fork 25.1k
DataProtection and scaling /8 #33104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f524f55
7e7f446
09a8106
ba71c7b
36f9423
1973c82
73d70c1
907cfc6
6ea6eec
8c27b91
5dea93a
ba6186b
0d6f57d
33d328c
fff74ab
4963274
065ba19
2201cd3
ec06d56
e058df8
6a7ebb0
96a80b3
50e65a4
807f8f6
6e07927
8537b5f
5e53be9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
--- | ||
title: Configure ASP.NET Core Data Protection in distributed or load-balanced environments | ||
author: acasey | ||
description: Learn how to configure Data Protection in ASP.NET Core for multi-instance apps. | ||
ms.author: acasey | ||
ms.date: 7/18/2024 | ||
content_well_notification: AI-contribution | ||
uid: security/data-protection/configuration/scaling | ||
--- | ||
|
||
# Configure ASP.NET Core Data Protection in distributed or load-balanced environments | ||
|
||
:::moniker range=">= aspnetcore-8.0" | ||
|
||
ASP.NET Core [Data Protection](xref:security/data-protection/introduction) is a library that provides a cryptographic API to protect data. Data Protection protects anti-forgery tokens, authentication cookies, and other sensitive data. However, in some distributed environments that don't put data protection keys in shared storage, when an app scales horizontally by adding more instances: | ||
|
||
* It's necessary to explicitly configure Data Protection to establish a shared storage location for Data Protection keys. | ||
* There’s ***NO*** guarantee that the HTTP POST request, used to submit a form, will be routed to the same instance that served the initial page via an HTTP GET request. If the requests are handled by different instances, the anti-forgery tokens aren’t synchronized, and an exception occurs. Sticky sessions via [ARR Affinity](/azure/app-service/manage-automatic-scaling?#how-does-arr-affinity-affect-automatic-scaling) routes user requests to the same node. However, ARR can reduce the scalability of a web farm. | ||
|
||
The following distributed environments provide automatic key storage in a shared location: | ||
amcasey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* [Azure apps](/aspnet/core/security/data-protection/configuration/default-settings). For more information see <xref:security/data-protection/configuration/default-settings#key-management>. | ||
* Newly created Azure Container Apps built using ASP.NET Core. For more information see [Autoscaling considerations | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've asked ACA for a date we can use in place of "newly created". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you get a date? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @claudiaregio Can you remember how we scoped this? I've suddenly remembered that this might only affect Aspire apps for now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
](/azure/container-apps/dotnet-overview#autoscaling-considerations). | ||
|
||
The following scenarios do ***NOT*** provide automatic key storage in a shared location: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This list still feels off to me. What if we framed it as a list of caveats and dropped the mention of non-Azure and ARR? If ARR is important, we could say that it's an alternative (that applies everywhere, not just non-Azure) though, personally, I think we covered that adequately in the intro. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amcasey Can you use the suggestion feature to rough draft your approach? I can wordsmith your suggestion |
||
|
||
* Separate [deployment slots](/azure/app-service/deploy-staging-slots), such as Staging and Production. | ||
* Azure Container Apps built using ASP.NET Core Kestrel 7.0 or earlier. For more information see [Autoscaling considerations | ||
Rick-Anderson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
](/azure/container-apps/dotnet-overview#autoscaling-considerations). | ||
* Distributed apps that don't have a shared storage location or synchronization mechanism for Data Protection keys. | ||
|
||
## Managing Data Protection keys outside the app | ||
Rick-Anderson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
An app with multiple instances might encounter a [System.Security.Cryptography.CryptographicException](/dotnet/api/system.security.cryptography.cryptographicexception) with the message `The key {A6EF5BC2-FDCC-4C0C-A3A5-CDA9A1733D70}` `was not found in the key ring.` This error occurs when instances become out of sync, causing data protected on one instance, such as an anti-forgery token, to fail when unprotected on another instance. This can happen, for example, if a form is served by one instance but posted to another that has not yet updated its key ring. When this issue arises, users may need to resubmit a form or re-authenticate if the issue involves an authentication token. | ||
|
||
One common reason app instances end up with different sets of keys is that, in the absence of a usable key (e.g. due to expiration, lack of access to the backing repository, etc), an instance will generate a new key of its own. Until that key has propagated to all other instances (which can take up to two days), there's a risk that data protected with that new key will sent to an instance that doesn't know how to unprotect it. | ||
|
||
Generally, app instances don't know about each other, so coordinating the generation and distribution of new keys (e.g. when they are periodically rotating) requires explicit configuration. One way to avoid having instances generate and use keys that are unknown to other instances is to prevent them from generating keys at all. The details of how to accomplish this vary slightly from app to app, but the general approach is straightforward. | ||
|
||
First, app instances [disable key generation](xref:security/data-protection/configuration/overview#disableautomatickeygeneration). Next, a new component is introduced that connects to the same key repository and performs a dummy protect operation once a day or so. | ||
|
||
For example, with Azure blob storage as the key repository, the key manager could be a basic console app run on a schedule: | ||
|
||
:::code language="csharp" source="~/security/data-protection/configuration/scaling/samples/AzBlobKey/Program.cs"::: | ||
|
||
The `appsettings.json` file contains the URIs for the key repository and key vault: | ||
|
||
:::code language="json" source="~/security/data-protection/configuration/scaling/samples/AzBlobKey/appsettings.json" highlight="2-5"::: | ||
|
||
Note that app instances throw exceptions if they perform any `Protect` or `Unprotect` operations before the key manager has run for the first time. To prevent exceptions, start the key manager so it before creating app instances. In most scenarios, Azure Key Vault starts the key manager before the app instances. | ||
|
||
:::moniker-end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't figure out a nice way to phrase "ACA will do this for you automatically, if you let it". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. review my rewrite and use the suggestion feature if needed. |
||
|
||
[!INCLUDE[](~/security/data-protection/configuration/scaling/includes/scaling7.md)] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
:::moniker range="< aspnetcore-8.0" | ||
Rick-Anderson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<!-- | ||
Duplicate of primary doc, only change: | ||
using .NET 8.0 | ||
--> | ||
|
||
:::moniker-end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.4.0" /> | ||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Keys" Version="1.3.0" /> | ||
<PackageReference Include="Azure.Identity" Version="1.13.1" /> | ||
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.9.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="9.0.0" /> | ||
</ItemGroup> | ||
|
||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using Azure.Identity; | ||
using Microsoft.AspNetCore.DataProtection; | ||
|
||
var hostBuilder = new HostApplicationBuilder(); | ||
|
||
// hostBuilder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amcasey not needed so commented out. Will remove line or comment characters. |
||
|
||
var blobStorageUri = hostBuilder.Configuration["AzureURIs:BlobStorage"]!; | ||
var keyVaultURI = hostBuilder.Configuration["AzureURIs:KeyVault"]!; | ||
|
||
// Use the same persistence and protection mechanisms as your app. | ||
hostBuilder.Services | ||
.AddDataProtection() | ||
.PersistKeysToAzureBlobStorage(new Uri(blobStorageUri), new DefaultAzureCredential()) | ||
.ProtectKeysWithAzureKeyVault(new Uri(keyVaultURI), new DefaultAzureCredential()); | ||
|
||
using var host = hostBuilder.Build(); | ||
|
||
// Perform a dummy operation to force key creation or rotation, if needed. | ||
var dataProtector = host.Services.GetDataProtector("Default"); | ||
dataProtector.Protect([]); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,13 @@ | ||||||
{ | ||||||
"AzureURIs": { | ||||||
"BlobStorage": "https://<storage-account>.blob.core.windows.net/<container>/keys.xml", | ||||||
"KeyVault": "https://<key-vault-name>.vault.azure.net/keys/<key-name>/" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Pls review my suggested change from your version. |
||||||
}, | ||||||
"Logging": { | ||||||
"LogLevel": { | ||||||
"Default": "Information", | ||||||
"Microsoft.AspNetCore": "Warning" | ||||||
} | ||||||
}, | ||||||
"AllowedHosts": "*" | ||||||
Comment on lines
+6
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can remove this JSON but that's what the templates produce and in the article I yellow highlight the AKV URIs |
||||||
} |
Uh oh!
There was an error while loading. Please reload this page.