Skip to content

Commit

Permalink
Add a key management simulator for data protection (#55348)
Browse files Browse the repository at this point in the history
* Add a key management simulator for data protection

It's not properly a sample, since it requires IVT, but it's much easier to read and use as a standalone app than as a test.  (It would also make a poor test, since perfect performance is not expected and thus there's no way to define success.)

Simulates running an app with a given number of instances for a given number of days and computes a score (lower is better) indicating how likely missing key errors were during that simulated time period.  Also tracks calls to the storage and encryption backends so we can see whether e.g. retries have a significant perf impact.

In the future, it would be nice if instances started and stopped over the course of the run, rather than all living for the entire duration.
  • Loading branch information
amcasey committed May 2, 2024
1 parent a0652e0 commit 8cc20ec
Show file tree
Hide file tree
Showing 8 changed files with 643 additions and 11 deletions.
20 changes: 20 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1799,10 +1799,13 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroBenchmarks", "MicroBenchmarks", "{6469F11E-8CEE-4292-820B-324DFFC88EBC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Caching.MicroBenchmarks", "src\Caching\perf\MicroBenchmarks\Microsoft.Extensions.Caching.MicroBenchmarks\Microsoft.Extensions.Caching.MicroBenchmarks.csproj", "{8D2CC6ED-5105-4F52-8757-C21F4DE78589}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{9DC6B242-457B-4767-A84B-C3D23B76C642}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.OpenApi.Microbenchmarks", "src\OpenApi\perf\Microbenchmarks\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj", "{D53F0EF7-0CDC-49B4-AA2D-229901B0A734}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyManagementSimulator", "src\DataProtection\samples\KeyManagementSimulator\KeyManagementSimulator.csproj", "{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -10884,6 +10887,22 @@ Global
{D53F0EF7-0CDC-49B4-AA2D-229901B0A734}.Release|x64.Build.0 = Release|Any CPU
{D53F0EF7-0CDC-49B4-AA2D-229901B0A734}.Release|x86.ActiveCfg = Release|Any CPU
{D53F0EF7-0CDC-49B4-AA2D-229901B0A734}.Release|x86.Build.0 = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|arm64.ActiveCfg = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|arm64.Build.0 = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|x64.ActiveCfg = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|x64.Build.0 = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|x86.ActiveCfg = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Debug|x86.Build.0 = Debug|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|Any CPU.Build.0 = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|arm64.ActiveCfg = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|arm64.Build.0 = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|x64.ActiveCfg = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|x64.Build.0 = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|x86.ActiveCfg = Release|Any CPU
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -11775,6 +11794,7 @@ Global
{8D2CC6ED-5105-4F52-8757-C21F4DE78589} = {6469F11E-8CEE-4292-820B-324DFFC88EBC}
{9DC6B242-457B-4767-A84B-C3D23B76C642} = {2299CCD8-8F9C-4F2B-A633-9BF4DA81022B}
{D53F0EF7-0CDC-49B4-AA2D-229901B0A734} = {9DC6B242-457B-4767-A84B-C3D23B76C642}
{5B5F86CC-3598-463C-9F9B-F78FBB6642F4} = {8275510E-0E6C-45A8-99DF-4F106BC7F075}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
1 change: 1 addition & 0 deletions src/DataProtection/DataProtection.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"src\\DataProtection\\samples\\CustomEncryptorSample\\CustomEncryptorSample.csproj",
"src\\DataProtection\\samples\\EntityFrameworkCoreSample\\EntityFrameworkCoreSample.csproj",
"src\\DataProtection\\samples\\KeyManagementSample\\KeyManagementSample.csproj",
"src\\DataProtection\\samples\\KeyManagementSimulator\\KeyManagementSimulator.csproj",
"src\\DataProtection\\samples\\NonDISample\\NonDISample.csproj",
"src\\DataProtection\\samples\\Redis\\Redis.csproj",
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public KeyRing(IKey defaultKey, IEnumerable<IKey> allKeys)

public Guid DefaultKeyId { get; }

// For testing
internal IReadOnlyCollection<Guid> GetAllKeyIds()
{
return _keyIdToKeyHolderMap.Keys;
}

public IAuthenticatedEncryptor? GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked)
{
isRevoked = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,18 @@ internal sealed class KeyRingProvider : ICacheableKeyRingProvider, IKeyRingProvi
AutoRefreshWindowEnd = DateTime.UtcNow.AddMinutes(2);

AppContext.TryGetSwitch(DisableAsyncKeyRingUpdateSwitchKey, out _disableAsyncKeyRingUpdate);

// We use the Random class since we don't need a secure PRNG for this.
#if NET6_0_OR_GREATER
JitterRandom = Random.Shared;
#else
JitterRandom = new Random();
#endif
}

// Internal for testing
internal Random JitterRandom { get; set; }

// for testing
internal ICacheableKeyRingProvider CacheableKeyRingProvider { get; set; }

Expand Down Expand Up @@ -470,18 +480,12 @@ private IKeyRing GetCurrentKeyRingCoreNew(DateTime utcNow, bool forceRefresh)
}
}

private static TimeSpan GetRefreshPeriodWithJitter(TimeSpan refreshPeriod)
private TimeSpan GetRefreshPeriodWithJitter(TimeSpan refreshPeriod)
{
// We'll fudge the refresh period up to -20% so that multiple applications don't try to
// hit a single repository simultaneously. For instance, if the refresh period is 1 hour,
// we'll return a value in the vicinity of 48 - 60 minutes. We use the Random class since
// we don't need a secure PRNG for this.
#if NET6_0_OR_GREATER
var random = Random.Shared;
#else
var random = new Random();
#endif
return TimeSpan.FromTicks((long)(refreshPeriod.Ticks * (1.0d - (random.NextDouble() / 5))));
// we'll return a value in the vicinity of 48 - 60 minutes.
return TimeSpan.FromTicks((long)(refreshPeriod.Ticks * (1.0d - (JitterRandom.NextDouble() / 5))));
}

private static DateTimeOffset Min(DateTimeOffset a, DateTimeOffset b)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,16 @@ public XmlKeyManager(IOptions<KeyManagementOptions> keyManagementOptions, IActiv

internal IXmlRepository KeyRepository { get; }

// Internal for testing
// Can't use TimeProvider since it's not available in framework
internal Func<DateTimeOffset> GetUtcNow { get; set; } = () => DateTimeOffset.UtcNow;

/// <inheritdoc />
public IKey CreateNewKey(DateTimeOffset activationDate, DateTimeOffset expirationDate)
{
// For an immediately-activated key, the caller's Now may be slightly before ours,
// so we'll compensate to ensure that activation is never before creation.
var now = DateTimeOffset.UtcNow;
var now = GetUtcNow();
return _internalKeyManager.CreateNewKey(
keyId: Guid.NewGuid(),
creationDate: activationDate < now ? activationDate : now,
Expand Down Expand Up @@ -377,7 +381,7 @@ public void RevokeKey(Guid keyId, string? reason = null)
{
_internalKeyManager.RevokeSingleKey(
keyId: keyId,
revocationDate: DateTimeOffset.UtcNow,
revocationDate: GetUtcNow(),
reason: reason);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@
<InternalsVisibleTo Include="Microsoft.AspNetCore.DataProtection.Extensions.Tests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.DataProtection.Tests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
<InternalsVisibleTo Include="KeyManagementSimulator" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<OutputType>exe</OutputType>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.DataProtection" />
<Reference Include="Microsoft.AspNetCore.DataProtection.Extensions" />
</ItemGroup>

</Project>

0 comments on commit 8cc20ec

Please sign in to comment.