You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Custom Configuration Providers is a collection of configuration providers for .Net that extend Microsoft.Extensions.Configuration to pull configuration data from a variety of sources. Details on how this custom configration provider is built can be found here:
3
+
Custom Configuration Providers is a collection of configuration providers for .Net that extend Microsoft.Extensions.Configuration to pull configuration data from a variety of sources. Details on how this custom configuration provider is built can be found here:
4
4
[Implement a custom configuration provider in .NET](https://docs.microsoft.com/en-us/dotnet/core/extensions/custom-configuration-provider)
5
5
6
-
# AWS Secrets Manager Configuration Provider
7
-
## Overview
8
-
Using this custom configuration provider, you can put AWS Secrets Manager keys into your appsettings files and the values stored in AWS Secrets Manager will be loaded into the config at startup time as if they were in the appsettings file already. What this auto-population of the secret configuration valus allow us to do is to leverage the [Options pattern in .Net](https://docs.microsoft.com/en-us/dotnet/core/extensions/options) along with Dependy Injection to automatically load these settings into the classes that need them. In addition, having the settings pre-loaded at startup time means we can eliminate the need to mock an AwsSecretsManager and try to fake the settings at execution time. We can simply use a test version of our appsettings file or in-memory configuration provider with the test values pre-populated and leave AWS Secrets Manager out of our unit tests completely.
9
-
10
-
So, here's the appsettings before and after running AddAwsSecretsManager:
11
-
12
-
<table>
13
-
<thead>
14
-
<tr>
15
-
<th>
16
-
appsettings.json file
17
-
</th>
18
-
<th>
19
-
IConfiguration
20
-
</th>
21
-
<th>
22
-
IConfiguration after .AddAwsSecretsManager()
23
-
</th>
24
-
</tr>
25
-
</thead>
26
-
<tbody>
27
-
<tr>
28
-
<td>
29
-
<pre lang="json">
6
+
## AWS Secrets Manager Configuration Provider
7
+
8
+
### Overview
9
+
10
+
Using this custom configuration provider, you can put AWS Secrets Manager keys into your appsettings files and the values stored in AWS Secrets Manager will be loaded into the config at startup time as if they were in the appsettings file already. Using single AWS secrets keys we can selectively load just the secrets our app needs.
11
+
12
+
By resolving the secret values are startup allows us to do is to leverage the [Options pattern in .Net](https://docs.microsoft.com/en-us/dotnet/core/extensions/options) along with Dependency Injection to automatically load these settings into the classes that need them, meaning our code can be written in a way that it doesn't care where the settings come from. This simplifies our testing because our unit tests can use an in-memory configuration provider with the test values pre-populated leaving them without the need to mock Secrets Manager or make connections from our CI/CD pipeline to a test instance of Secrets Manager.
13
+
14
+
So, here is an example of some values stored in the "/dev/db/options" key of our AWS Secrets Manager instance:
15
+
16
+
```json
30
17
{
31
-
"Database": {
32
-
"AwsSecret": "/dev/db/options"
33
-
}
18
+
"server": "db1.myurl.com"
19
+
"port": "3306"
20
+
"timeout": "5"
21
+
"userName": "ApiReadOnly"
22
+
"password": "NunyaBizness!"
34
23
}
35
-
</pre>
36
-
</td>
37
-
<td>
38
-
"Database:AwsSecret": "/dev/db/options"<br>
39
-
</td>
40
-
<td>
41
-
"Database:server": "db1.myurl.com"<br>
42
-
"Database:port": "3306"<br>
43
-
"Database:timeout": "5"<br>
44
-
"Database:userName": "ApiReadOnly"<br>
45
-
"Database:password": "NunyaBizness!"<br>
46
-
</td>
47
-
</tr>
48
-
</tbody>
49
-
</table>
50
-
51
-
## How it works
52
-
Let's say you have the following secret stored in AWS Secrets Manager under the key "/dev/db/options".
53
-
Setting | Value
54
-
------- | -----
55
-
server | someserver.us-west-2.aws.amazon.com
56
-
port | 3306
57
-
timeout | 5
58
-
username | ApiReadOnly
59
-
password | NunyaBizness!
60
-
61
-
Using this provider you could put the following in your `appsettings.{environment}.json` file:
24
+
```
25
+
26
+
Here are our appsettings:
27
+
62
28
```json
63
-
{
64
-
"Database": {
65
-
"AwsSecret": "/dev/db/options"
29
+
{
30
+
"Database": {
31
+
"AwsSecret": "/dev/db/options"
32
+
}
66
33
}
67
-
}
68
34
```
69
-
> **Note:**
70
-
>
71
-
> In the next step, you'll see how you can choose any string you want in place of "AwsSecret"
72
35
73
-
Add the provider along with an AwsSecretsManager client (this is to allow for injecting mock versions or other versions of the client) and the name of the key you chose to put in your appsettings (e.g. "AwsSecret"):
36
+
Here is what our IConfiguration values look like before and after we call .AddAwsSecretsManager, specifying "AwsSecret" as our placeholder key:
Custom configuration providers work as extension methods for ConfigurationBuilder. As long as the custom provider is included in your source and it's namespace is in your "using" statements, you should be able to just call ".AddAwsSecretsManager(...)".
49
+
50
+
In your app's startup code, add the provider to your configuration builder along with an AwsSecretsManager client (this is to allow for injecting mock versions or other versions of the client) and the name of the placeholder key you chose to put in your appsettings (e.g. "AwsSecret"):
> * We're supplying an AmazonSecretsManagerClient to simplify testing and to choose the region in our app startup.
87
-
> * In `AddAwsSecretsManager()` we're specifying the key to search and replace (e.g. "AwsSecret") with AWS Secrets Manager values. You can choose whatever string you want.
88
-
89
-
When you build the config builder, the custom configuration provider will look in the appsettings files you've loaded for the key "AwsSecret" take it's value "/dev/db/options" and replace the whole key with the values retrieved from AWS Secrets Manager (in the IConfiguration tree in memory, not the appsettings file) like so:
90
-
<table>
91
-
<thead>
92
-
<tr>
93
-
<th>
94
-
appsettings.json file
95
-
</th>
96
-
<th>
97
-
IConfiguration
98
-
</th>
99
-
<th>
100
-
IConfiguration after .AddAwsSecretsManager()
101
-
</th>
102
-
</tr>
103
-
</thead>
104
-
<tbody>
105
-
<tr>
106
-
<td>
107
-
<pre lang="json">
108
-
{
109
-
"Database": {
110
-
"AwsSecret": "/dev/db/options"
111
-
}
112
-
}
113
-
</pre>
114
-
</td>
115
-
<td>
116
-
"Database:AwsSecret": "/dev/db/options"<br>
117
-
</td>
118
-
<td>
119
-
"Database:server": "db1.myurl.com"<br>
120
-
"Database:port": "3306"<br>
121
-
"Database:timeout": "5"<br>
122
-
"Database:userName": "ApiReadOnly"<br>
123
-
"Database:password": "NunyaBizness!"<br>
124
-
</td>
125
-
</tr>
126
-
</tbody>
127
-
</table>
128
-
129
-
## Options Pattern and Dependency Injection
66
+
> * In `AddAwsSecretsManager()` we're specifying the placeholder key to search and replace (e.g. "AwsSecret") with AWS Secrets Manager values. You can choose whatever string you want.
67
+
68
+
When you call "Build" on the config builder, the custom configuration provider will look in the appsettings files you've loaded for the placeholder key you specified (e.g. "AwsSecret") take it's value "/dev/db/options" and replace the value with the values retrieved from AWS Secrets Manager (i.e. in the IConfiguration tree in memory, not the appsettings file) like so:
With the AWS Secrets expanded using the configuration provider, we can use the settings in the Options Pattern to map the settings to a class, add it to the Dependency Injection IServiceCollection and inject it automatically into classes that need it.
131
81
132
-
### Create a class to hold the settings
82
+
#### Create a class to hold the settings
83
+
133
84
```csharp
134
85
publicclassDatabaseOptions
135
86
{
@@ -140,19 +91,23 @@ public class DatabaseOptions
140
91
publicstringPassword { get; set; }
141
92
}
142
93
```
94
+
143
95
> Note:
144
-
>
145
-
> Since AWS Secrets Manager stores key-value pairs as <string,string> options classes shoud contain strings. It is possible to add code to the Configuration Provider that parses the JSON into proper types, but it complicates the code that I think has more cost than benefit.
96
+
>
97
+
> Since AWS Secrets Manager stores key-value pairs as <string,string> options classes should contain strings. It is possible to add code to the Configuration Provider that parses the JSON into proper types, but it complicates the code in a way that I think has more cost than benefit. Simpler to treat them as strings and convert them to the needed types by the classes that consume them.
98
+
99
+
#### Inject them into a Dependency Injection collection
146
100
147
-
### Inject them into a Dependency Injection collection
@@ -162,10 +117,12 @@ public ServiceThatUsesDatabaseOptions(IOptions<DatabaseOptions> databaseOptions)
162
117
}
163
118
```
164
119
165
-
## Simplified Testing
120
+
### Simplified Testing
121
+
166
122
Since we're pre-loading the real values in the ConfigurationBuilder at startup using the AWS Secrets Manager Configuration Provider, everything downstream is using the real secrets. Therefore, when we're testing, we can just put our test values directly into our test config and eliminate AWS Secrets Manager from our unit tests.
167
123
168
-
### Example unit test
124
+
#### Example unit test
125
+
169
126
```csharp
170
127
publicDependencyInjectionMockUnitTests()
171
128
{
@@ -191,10 +148,10 @@ public DependencyInjectionMockUnitTests()
0 commit comments