-
Notifications
You must be signed in to change notification settings - Fork 33
/
KeyPersistenceTests.cs
175 lines (146 loc) · 6.73 KB
/
KeyPersistenceTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace KubeClient.Extensions.DataProtection.Tests
{
using Mocks;
using Models;
using TestCommon;
using TestCommon.Logging;
/// <summary>
/// Tests for DataProtection key persistence using K8s secrets.
/// </summary>
public class KeyPersistenceTests
: TestBase
{
/// <summary>
/// The name of the secret used to sure DataProtection keys.
/// </summary>
static readonly string TestSecretName = "test-secret";
/// <summary>
/// The namespace of the secret used to sure DataProtection keys.
/// </summary>
static readonly string TestSecretNamespace = "test-namespace";
/// <summary>
/// Create a new DataProtection key persistence test-suite.
/// </summary>
/// <param name="testOutput">
/// Output for the current test.
/// </param>
public KeyPersistenceTests(ITestOutputHelper testOutput)
: base(testOutput)
{
}
/// <summary>
/// Verify that the data protector (i.e. <see cref="IDataProtectionProvider"/>) can be resolved from the DI container.
/// </summary>
/// <remarks>
/// This results in the data-protection system loading and initialising our provider.
/// </remarks>
[Fact]
public async Task Can_Create_Data_Protector()
{
using Subject<SecretV1> secretWatchSubject = new Subject<SecretV1>(); // Needed because the key store always watches for changes.
ConcurrentDictionary<string, SecretV1> secrets = new ConcurrentDictionary<string, SecretV1>();
await using MockKubeApi mockApi = MockKubeApi.Create(TestOutput, api =>
{
api.HandleResources<SecretV1, SecretListV1>(secrets, secretWatchSubject);
});
using ServiceProvider serviceProvider = BuildServiceProvider(mockApi);
IDataProtectionProvider dataProtection = serviceProvider.GetRequiredService<IDataProtectionProvider>();
IDataProtector dataProtector = dataProtection.CreateProtector(purpose: "Test");
SecretV1 secretResource;
string secretResourceKey = MockKubeApi.GetResourceKey(TestSecretName, TestSecretNamespace);
Assert.True(
secrets.TryGetValue(secretResourceKey, out secretResource)
);
Assert.NotNull(secretResource);
Assert.Empty(secretResource.Data);
}
/// <summary>
/// Verify that the data protector can encrypt (and then decrypt) some data.
/// </summary>
[Fact]
public async Task Can_RoundTrip_Data()
{
using Subject<SecretV1> secretWatchSubject = new Subject<SecretV1>(); // Needed because the key store always watches for changes.
ConcurrentDictionary<string, SecretV1> secrets = new ConcurrentDictionary<string, SecretV1>();
await using MockKubeApi mockApi = MockKubeApi.Create(TestOutput, api =>
{
api.HandleResources<SecretV1, SecretListV1>(secrets, secretWatchSubject);
});
using ServiceProvider serviceProvider = BuildServiceProvider(mockApi);
IDataProtectionProvider dataProtection = serviceProvider.GetRequiredService<IDataProtectionProvider>();
IDataProtector dataProtector = dataProtection.CreateProtector(purpose: "Test");
SecretV1 secretResource;
string secretResourceKey = MockKubeApi.GetResourceKey(TestSecretName, TestSecretNamespace);
Assert.True(
secrets.TryGetValue(secretResourceKey, out secretResource)
);
Assert.NotNull(secretResource);
Assert.Empty(secretResource.Data);
const string expectedPlainText = "PlainText";
string protectedData = dataProtector.Protect(expectedPlainText);
Assert.True(
secrets.TryGetValue(secretResourceKey, out secretResource)
);
Assert.NotNull(secretResource);
Assert.NotEmpty(secretResource.Data);
string actualPlainText = dataProtector.Unprotect(protectedData);
Assert.Equal(expectedPlainText, actualPlainText);
}
/// <summary>
/// Build a (client-side) service provider for use in tests.
/// </summary>
/// <param name="mockApi">
/// The mock Kubernetes API that the client will communicate.
/// </param>
/// <param name="configureDataProtection">
/// An optional delegate that can be used to customise the data-protection system.
/// </param>
/// <param name="configureServices">
/// An optional delegate that can be used to configure additional services for dependency-injection.
/// </param>
/// <returns>
/// The configured service provider.
/// </returns>
ServiceProvider BuildServiceProvider(MockKubeApi mockApi, Action<IDataProtectionBuilder> configureDataProtection = null, Action<IServiceCollection> configureServices = null)
{
if (mockApi == null)
throw new ArgumentNullException(nameof(mockApi));
IKubeApiClient testApiClient = KubeApiClient.Create(
mockApi.CreateClient(),
new KubeClientOptions
{
ApiEndPoint = mockApi.BaseAddress,
AuthStrategy = KubeAuthStrategy.None,
KubeNamespace = "default",
LoggerFactory = new LoggerFactory().AddTestOutput(TestOutput),
LogPayloads = true,
}
);
var services = new ServiceCollection();
services.AddSingleton(testApiClient);
IDataProtectionBuilder dataProtection = services.AddDataProtection()
.AddKeyManagementOptions(keyManagement =>
{
keyManagement.AutoGenerateKeys = true;
})
.PersistKeysToKubeSecret(testApiClient, TestSecretName, TestSecretNamespace);
if (configureDataProtection != null)
configureDataProtection(dataProtection);
if (configureServices != null)
configureServices(services);
return services.BuildServiceProvider(new ServiceProviderOptions
{
ValidateOnBuild = true,
ValidateScopes = true,
});
}
}
}