├── .editorconfig
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── build-and-publish.yml
│ ├── build-and-test.yml
│ └── snyk-test.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Directory.Build.props
├── Examples
├── Example.Secrets.Aws.AspNetCore
│ ├── City.cs
│ ├── Example.Secrets.Aws.AspNetCore.csproj
│ ├── Program.cs
│ ├── README.md
│ ├── Startup.cs
│ └── appsettings.json
└── Example.Secrets.Custom
│ ├── Example.Secrets.Custom.csproj
│ ├── Program.cs
│ ├── ReversedSecret.cs
│ └── appsettings.json
├── LICENSE.md
├── README.md
├── RockLib.Secrets.All.sln
├── RockLib.Secrets.Aws
├── AwsSecret.cs
├── AwsSecretsConfigurationBuilderExtensions.cs
├── CHANGELOG.md
├── RockLib.Secrets.Aws.csproj
└── RockLib.Secrets.Aws.sln
├── RockLib.Secrets
├── AssemblyAttributes.cs
├── CHANGELOG.md
├── ConfigurationBuilderExtensions.cs
├── ISecret.cs
├── ISecretsConfigurationBuilder.cs
├── RockLib.Secrets.csproj
├── RockLib.Secrets.sln
├── SecretExceptionContext.cs
├── SecretsConfigurationBuilder.cs
├── SecretsConfigurationProvider.cs
└── SecretsConfigurationSource.cs
├── Tests
├── RockLib.Secrets.Aws.Tests
│ ├── AssemblySettings.cs
│ ├── AwsSecretTests.cs
│ ├── AwsSecretsConfigurationBuilderExtensionsTests .cs
│ ├── FakeSecretsManager.cs
│ ├── RockLib.Secrets.Aws.Tests.csproj
│ └── appsettings.json
└── RockLib.Secrets.Tests
│ ├── AssemblySettings.cs
│ ├── ConfigurationBuilderExtensionsTests.cs
│ ├── MockSecret.cs
│ ├── RockLib.Secrets.Tests.csproj
│ ├── SecretsConfigurationBuilderTests.cs
│ ├── SecretsConfigurationProviderTests.cs
│ └── SecretsConfigurationSourceTests.cs
├── docs
├── AwsSecret.md
├── Configuration.md
├── ConfigurationBuilder.md
├── GettingStarted.md
└── Usage.md
└── icon.png
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 |
6 | [*.cs]
7 | # Styling
8 | indent_style = space
9 | indent_size = 4
10 | csharp_indent_case_contents = true
11 | csharp_indent_switch_labels = true
12 | csharp_new_line_before_catch = true
13 | csharp_new_line_before_else = true
14 | csharp_new_line_before_finally = true
15 | csharp_new_line_before_members_in_anonymous_types = false
16 | csharp_new_line_before_members_in_object_initializers = false
17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers
18 | csharp_new_line_between_query_expression_clauses = true
19 | csharp_prefer_braces = false:suggestion
20 | csharp_prefer_simple_default_expression = true:suggestion
21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion
22 | csharp_preserve_single_line_blocks = true
23 | csharp_preserve_single_line_statements = true
24 | csharp_space_after_cast = false
25 | csharp_space_after_colon_in_inheritance_clause = true
26 | csharp_space_after_keywords_in_control_flow_statements = true
27 | csharp_space_before_colon_in_inheritance_clause = true
28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
29 | csharp_space_between_method_call_name_and_opening_parenthesis = false
30 | csharp_space_between_method_call_parameter_list_parentheses = false
31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
32 | csharp_space_between_method_declaration_parameter_list_parentheses = false
33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
34 | csharp_style_expression_bodied_accessors = true:suggestion
35 | csharp_style_expression_bodied_constructors = false:suggestion
36 | csharp_style_expression_bodied_methods = false:suggestion
37 | csharp_style_expression_bodied_properties = true:suggestion
38 | csharp_style_inlined_variable_declaration = true:suggestion
39 | csharp_style_var_elsewhere = true:suggestion
40 | csharp_style_var_for_built_in_types = true:suggestion
41 | csharp_style_var_when_type_is_apparent = true:suggestion
42 | dotnet_sort_system_directives_first = false
43 | dotnet_style_explicit_tuple_names = true:suggestion
44 | dotnet_style_object_initializer = true:suggestion
45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion
46 | dotnet_style_predefined_type_for_member_access = true:suggestion
47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
51 | dotnet_style_qualification_for_field = false:suggestion
52 | dotnet_style_qualification_for_method = false:suggestion
53 | dotnet_style_qualification_for_property = false:suggestion
54 |
55 |
56 | # Analyzer Configuration
57 | # These are rules we want to either ignore or have set as suggestion or info
58 |
59 | # CA1014: Mark assemblies with CLSCompliant
60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014
61 | dotnet_diagnostic.CA1014.severity = none
62 |
63 | # CA1725: Parameter names should match base declaration
64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725
65 | dotnet_diagnostic.CA1725.severity = suggestion
66 |
67 | # CA2227: Collection properties should be read only
68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227
69 | dotnet_diagnostic.CA2227.severity = suggestion
70 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
12 |
13 | ## Type of change:
14 |
15 | 1. Non-functional change (e.g. documentation changes, removing unused `using` directives, renaming local variables, etc)
16 | 2. Bug fix (non-breaking change that fixes an issue)
17 | 3. New feature (non-breaking change that adds functionality)
18 | 4. Breaking change (fix or feature that could cause existing functionality to not work as expected)
19 |
20 | ## Checklist:
21 |
22 | - Have you reviewed your own code? Do you understand every change?
23 | - Are you following the [contributing guidelines](../blob/main/CONTRIBUTING.md)?
24 | - Have you added tests that prove your fix is effective or that this feature works?
25 | - New and existing unit tests pass locally with these changes?
26 | - Have you made corresponding changes to the documentation?
27 | - Will this change require an update to an example project? (if so, create an issue and link to it)
28 |
29 | ---
30 |
31 | _[Reviewer guidelines](../blob/main/CONTRIBUTING.md#reviewing-changes)_
32 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-publish.yml:
--------------------------------------------------------------------------------
1 | name: Build and Publish
2 |
3 | ####################################################################################################
4 | ## WORKFLOW TRIGGER
5 | ####################################################################################################
6 | on:
7 | # Workflow will run when a release is published.
8 | release:
9 | types: [ released, prereleased ]
10 |
11 | ####################################################################################################
12 | ## WORKFLOW JOBS
13 | ####################################################################################################
14 | jobs:
15 | # Calls the shared build-and-publish workflow.
16 | call_build_and_publish:
17 | name: Call build-and-publish workflow
18 | uses: RockLib/RockLib.Workflows/.github/workflows/build-and-publish.yml@main
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-test.yml:
--------------------------------------------------------------------------------
1 | name: Run Unit Test
2 |
3 | ####################################################################################################
4 | ## WORKFLOW TRIGGER
5 | ####################################################################################################
6 | on:
7 | # Workflow will run on pull requests to the main branch.
8 | pull_request:
9 | branches: [ main ]
10 |
11 | ####################################################################################################
12 | ## WORKFLOW JOBS
13 | ####################################################################################################
14 | jobs:
15 | # Calls the shared unit-test workflow.
16 | call_unit_test:
17 | name: Call unit-test workflow
18 | uses: RockLib/RockLib.Workflows/.github/workflows/unit-test.yml@main
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/.github/workflows/snyk-test.yml:
--------------------------------------------------------------------------------
1 | name: Run Snyk Test
2 |
3 | ####################################################################################################
4 | ## WORKFLOW TRIGGER
5 | ####################################################################################################
6 | on:
7 | # Workflow will run after unit-test is completed.
8 | workflow_run:
9 | workflows: [ Run Unit Test ]
10 | types: [ completed ]
11 |
12 | ####################################################################################################
13 | ## WORKFLOW JOBS
14 | ####################################################################################################
15 | jobs:
16 | # Calls the shared snyk-test workflow.
17 | call_snyk_test:
18 | name: Call snyk-test workflow
19 | uses: RockLib/RockLib.Workflows/.github/workflows/snyk-test.yml@main
20 | secrets: inherit
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at RockLibSupport@quickenloans.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to RockLib
2 |
3 | Please take a moment to review this document in order to make the contribution
4 | process easy and effective for everyone involved.
5 |
6 | Following these guidelines helps to communicate that you respect the time of
7 | the developers managing and developing this open source project. In return,
8 | they should reciprocate that respect in addressing your issue, assessing
9 | changes, and helping you finalize your pull requests.
10 |
11 | As for everything else in the project, the contributions to RockLib are governed by our [Code of Conduct](CODE_OF_CONDUCT.md).
12 |
13 |
14 | ## Using the issue tracker
15 |
16 | First things first: **Do NOT report security vulnerabilities in public issues!** Please disclose responsibly by letting [the RockLib team](mailto:RockLibSupport@quickenloans.com?subject=Security) know upfront. We will assess the issue as soon as possible on a best-effort basis and will give you an estimate for when we have a fix and release available for an eventual public disclosure.
17 |
18 | The GitHub issue tracker is the preferred channel for [bug reports](#bugs),
19 | [features requests](#features) and [submitting pull
20 | requests](#pull-requests).
21 |
22 |
23 | ## Bug reports
24 |
25 | A bug is a _demonstrable problem_ that is caused by the code in the repository.
26 | Good bug reports are extremely helpful - thank you!
27 |
28 | Guidelines for bug reports:
29 |
30 | 1. **Use the GitHub issue search** — check if the issue has already been
31 | reported.
32 |
33 | 2. **Check if the issue has been fixed** — try to reproduce it using the
34 | latest `master` branch in the repository.
35 |
36 | 3. **Isolate the problem** — ideally create a reduced test case.
37 |
38 | A good bug report shouldn't leave others needing to chase you up for more
39 | information. Please try to be as detailed as possible in your report. What is
40 | your environment? What steps will reproduce the issue? What OS experiences the
41 | problem? What would you expect to be the outcome? All these details will help
42 | people to fix any potential bugs.
43 |
44 | Example:
45 |
46 | > Short and descriptive example bug report title
47 | >
48 | > A summary of the issue and the browser/OS environment in which it occurs. If
49 | > suitable, include the steps required to reproduce the bug.
50 | >
51 | > 1. This is the first step
52 | > 2. This is the second step
53 | > 3. Further steps, etc.
54 | >
55 | > `` - a link to the reduced test case
56 | >
57 | > Any other information you want to share that is relevant to the issue being
58 | > reported. This might include the lines of code that you have identified as
59 | > causing the bug, and potential solutions (and your opinions on their
60 | > merits).
61 |
62 |
63 | ## Feature requests
64 |
65 | Feature requests are welcome. But take a moment to find out whether your idea
66 | fits with the scope and aims of the project. It's up to *you* to make a strong
67 | case to convince the project's developers of the merits of this feature. Please
68 | provide as much detail and context as possible.
69 |
70 |
71 | ## Pull requests
72 |
73 | Good pull requests - patches, improvements, new features - are a fantastic
74 | help. They should remain focused in scope and avoid containing unrelated
75 | commits.
76 |
77 | **Please ask first** before embarking on any significant pull request (e.g.
78 | implementing features, refactoring code), otherwise you risk spending a lot of
79 | time working on something that the project's developers might not want to merge
80 | into the project.
81 |
82 |
83 | ### For Contributors
84 |
85 | If you have never created a pull request before, welcome :tada: :smile: [Here is a great tutorial](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
86 | on how to create a pull request..
87 |
88 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
89 | and configure the remotes:
90 |
91 | ```bash
92 | # Clone your fork of the repo into the current directory
93 | git clone https://github.com//
94 | # Navigate to the newly cloned directory
95 | cd
96 | # Assign the original repo to a remote called "upstream"
97 | git remote add upstream https://github.com/RockLib/
98 | ```
99 |
100 | 2. If you cloned a while ago, get the latest changes from upstream:
101 |
102 | ```bash
103 | git checkout master
104 | git pull upstream master
105 | ```
106 |
107 | 3. Create a new topic branch (off the main project development branch) to
108 | contain your feature, change, or fix:
109 |
110 | ```bash
111 | git checkout -b
112 | ```
113 |
114 | 4. Please follow Chris Beams' [seven rules of a great Git commit message](https://chris.beams.io/posts/git-commit/#seven-rules):
115 |
116 | 1. [Separate subject from body with a blank line](https://chris.beams.io/posts/git-commit/#separate)
117 | 2. [Limit the subject line to 50 characters](https://chris.beams.io/posts/git-commit/#limit-50)
118 | 3. [Capitalize the subject line](https://chris.beams.io/posts/git-commit/#capitalize)
119 | 4. [Do not end the subject line with a period](https://chris.beams.io/posts/git-commit/#end)
120 | 5. [Use the imperative mood in the subject line](https://chris.beams.io/posts/git-commit/#imperative)
121 | 6. [Wrap the body at 72 characters](https://chris.beams.io/posts/git-commit/#wrap-72)
122 | 7. [Use the body to explain what and why vs. how](https://chris.beams.io/posts/git-commit/#why-not-how)
123 |
124 | 5. Make sure to update, or add to the tests when appropriate.
125 |
126 | 6. If you added or changed a feature, make sure to document it accordingly in
127 | the `README.md` file.
128 |
129 | 7. Push your topic branch up to your fork:
130 |
131 | ```bash
132 | git push origin
133 | ```
134 |
135 | 8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
136 | with a clear title and description.
137 |
138 |
139 | **IMPORTANT**: By submitting a patch, you agree to license your work under the
140 | same license as that used by the project.
141 |
142 | ## Maintainers
143 |
144 | If you have commit access, please follow this process for merging patches and cutting new releases.
145 |
146 | ### Reviewing changes
147 |
148 | 1. Check that a change is within the scope and philosophy of the component.
149 | 2. Check that a change has any necessary tests.
150 | 3. Check that a change has any necessary documentation.
151 | 4. If there is anything you don’t like, leave a comment below the respective
152 | lines and submit a "Request changes" review. Repeat until everything has
153 | been addressed.
154 | 5. If you are not sure about something, mention specific people for help in a
155 | comment.
156 | 6. If there is only a tiny change left before you can merge it and you think
157 | it’s best to fix it yourself, do so and leave a comment about it so the
158 | author and others will know.
159 | 7. Once everything looks good, add an "Approve" review. Don’t forget to say
160 | something nice 👏🐶💖✨
161 | 8. If the commit messages follow Chris Beams' [seven rules of a great Git commit
162 | message](https://chris.beams.io/posts/git-commit/#seven-rules):
163 |
164 | 1. Use the "Merge pull request" button to merge the pull request.
165 | 2. Done! You are awesome! Thanks so much for your help 🤗
166 |
167 | 9. If the commit messages _do not_ follow our conventions:
168 |
169 | 1. Use the "Squash and merge" button to clean up the commits and merge at
170 | the same time: ✨🎩
171 | 2. Add a new commit subject and body.
172 |
173 | ---
174 |
175 | *This document is based on the [contributing](https://github.com/hoodiehq/hoodie/blob/master/CONTRIBUTING.md) document from the [Hoodie](https://github.com/hoodiehq/hoodie) project.*
176 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
6 | all
7 | Rocket Mortgage
8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved.
9 | latest
10 | enable
11 | net48;net8.0
12 | NU1603,NU1701
13 | true
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Aws.AspNetCore/City.cs:
--------------------------------------------------------------------------------
1 | namespace Example.Secrets.Aws.AspNetCore
2 | {
3 | public enum City
4 | {
5 | Detroit,
6 | Chicago,
7 | London,
8 | Tokyo,
9 | Auckland
10 | }
11 | }
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Aws.AspNetCore/Example.Secrets.Aws.AspNetCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | enable
4 | CA1812
5 | net8.0
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Aws.AspNetCore/Program.cs:
--------------------------------------------------------------------------------
1 | using Example.Secrets.Aws.AspNetCore;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Newtonsoft.Json.Linq;
4 | using RockLib.Secrets;
5 |
6 | var builder = WebApplication.CreateBuilder(args);
7 | builder.Configuration.SetSecretExceptionHandler(context => Console.WriteLine(context.Exception))
8 | .AddRockLibSecrets();
9 |
10 | builder.Services.AddHttpClient("OpenUV",
11 | client => client.DefaultRequestHeaders.Add("x-access-token", builder.Configuration["OpenUVApiKey"]));
12 |
13 | var application = builder.Build();
14 |
15 | application.MapGet("/{city}", async (IHttpClientFactory factory, [FromRoute] City city) =>
16 | {
17 | GetCoordinates(city, out var latitude, out var longitude);
18 |
19 | using var httpClient = factory.CreateClient("OpenUV");
20 | var response = await httpClient.GetAsync(
21 | new Uri($"https://api.openuv.io/api/v1/uv?lat={latitude}&lng={longitude}")).ConfigureAwait(false);
22 | var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
23 |
24 | dynamic parsedContent = JObject.Parse(content);
25 | return parsedContent.result.uv;
26 | });
27 |
28 | if (application.Environment.IsDevelopment())
29 | {
30 | application.UseDeveloperExceptionPage();
31 | }
32 |
33 | application.UseHttpsRedirection();
34 | application.UseRouting();
35 | application.UseAuthorization();
36 | #pragma warning disable ASP0014 // Suggest using top level route registrations
37 | application.UseEndpoints(endpoints =>
38 | {
39 | endpoints.MapControllers();
40 | });
41 | #pragma warning restore ASP0014 // Suggest using top level route registrations
42 |
43 | application.Run();
44 |
45 | static void GetCoordinates(City city, out double latitude, out double longitude)
46 | {
47 | switch (city)
48 | {
49 | case City.Detroit:
50 | latitude = 42.331389;
51 | longitude = -83.045833;
52 | break;
53 |
54 | case City.Chicago:
55 | latitude = 41.881944;
56 | longitude = -87.627778;
57 | break;
58 |
59 | case City.London:
60 | latitude = 51.507222;
61 | longitude = -0.1275;
62 | break;
63 |
64 | case City.Tokyo:
65 | latitude = 35.689722;
66 | longitude = 139.692222;
67 | break;
68 |
69 | case City.Auckland:
70 | latitude = -36.840556;
71 | longitude = 174.74;
72 | break;
73 |
74 | default:
75 | throw new ArgumentOutOfRangeException(nameof(city), city, null);
76 | }
77 | }
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Aws.AspNetCore/README.md:
--------------------------------------------------------------------------------
1 | # Example.Secrets.Aws.AspNetCore
2 |
3 | This example app provides an API with a single endpoint, which returns the UV index of selected cities. The endpoint is `/uv/{city}` and the available cities are Detroit, Chicago, London, Tokyo, and Auckland. For example, `/uv/Tokyo`.
4 |
5 | In order to run this example, there are some prerequisites:
6 |
7 | 1. [An API Key for the free service at OpenUV](#openuv)
8 | 2. [The API Key is stored in an AWS secret](#aws-secret)
9 | 3. The machine running the example app has an AWS profile that has access to the above AWS secret. See [Getting Started with the AWS SDK for .NET
10 | ](https://docs.aws.amazon.com/sdk-for-net/v2/developer-guide/net-dg-setup.html) for details.
11 |
12 | ## OpenUV
13 |
14 | The Open UV API was selected for the Example project because it allowed for the API Key to be passed using an HTTP Request Header, and account/API Key creation is simple and free.
15 |
16 | To obtain an Open UV API key:
17 |
18 | 1. Create an Open UV Index API account.
19 | - Navigate to https://www.openuv.io/.
20 | - Select Sign In - this will prompt the user to Sign in with Google.
21 | 2. Generate an OpenUV API Key.
22 | - Navigate to https://www.openuv.io/console.
23 | - Copy the generated API Key, or generate a new key.
24 |
25 | *NOTE: The API Key header name is `x-access-token`.*
26 |
27 | ## AWS Secret
28 |
29 | The Open UV Api Key needs to be stored as an AWS Secret:
30 |
31 | 1. Log in to the AWS console.
32 | 2. Navigate to Secrets Manager.
33 | 3. Click on the 'Store a new secret' button.
34 | 4. Select 'Other type of secrets'.
35 | 5. Add a secret with a key of 'OpenUVApiKey' and a value of the Open UV API Key above.
36 | 6. Click 'Next'.
37 | 7. Name the secret 'RockLib.Example.Secret'.
38 | 8. Click through the remainder of the wizard until the secret is created.
39 |
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Aws.AspNetCore/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.AspNetCore.Builder;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Hosting;
9 |
10 | namespace Example.Secrets.Aws.AspNetCore
11 | {
12 | public class Startup
13 | {
14 | public Startup(IConfiguration configuration)
15 | {
16 | Configuration = configuration;
17 | }
18 |
19 | public IConfiguration Configuration { get; }
20 |
21 | // This method gets called by the runtime. Use this method to add services to the container.
22 | public void ConfigureServices(IServiceCollection services)
23 | {
24 | services.AddHttpClient("OpenUV", client => client.DefaultRequestHeaders.Add("x-access-token", Configuration["OpenUVApiKey"]));
25 | services.AddControllers();
26 | }
27 |
28 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
29 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
30 | {
31 | if (env.IsDevelopment())
32 | {
33 | app.UseDeveloperExceptionPage();
34 | }
35 |
36 | app.UseHttpsRedirection();
37 |
38 | app.UseRouting();
39 |
40 | app.UseAuthorization();
41 |
42 | app.UseEndpoints(endpoints =>
43 | {
44 | endpoints.MapControllers();
45 | });
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Aws.AspNetCore/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 |
11 | "OpenUVApiKey": "this secret has not been applied",
12 |
13 | "RockLib.Secrets": [
14 | {
15 | "Type": "RockLib.Secrets.Aws.AwsSecret, RockLib.Secrets.Aws",
16 | "Value": {
17 | "ConfigurationKey": "OpenUVApiKey",
18 | "SecretId": "RockLib.Example.Secret",
19 | "SecretKey": "OpenUVApiKey"
20 | }
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Custom/Example.Secrets.Custom.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | enable
4 | CA1812
5 | Exe
6 | net8.0
7 |
8 |
9 |
10 |
11 |
12 |
13 | PreserveNewest
14 | true
15 | PreserveNewest
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Custom/Program.cs:
--------------------------------------------------------------------------------
1 | using Example.Secrets.Custom;
2 | using Microsoft.Extensions.Configuration;
3 | using RockLib.Secrets;
4 |
5 | // This example defines two secrets to be added to configuration. The 'SomeApiKey'
6 | // secret is added programmatically and the 'MyConnectionString' secret is defined
7 | // in the appsettings.json file under the 'RockLib.Secrets' section.
8 |
9 | var builder = new ConfigurationBuilder();
10 |
11 | builder.AddJsonFile("appsettings.json");
12 | builder.AddRockLibSecrets()
13 | .AddSecret(new ReversedSecret("SomeApiKey", "terces dnoces eht si sihT"));
14 |
15 | var configuration = builder.Build();
16 |
17 | Console.WriteLine("MyConnectionString: " + configuration["MyConnectionString"]);
18 | Console.WriteLine("SomeApiKey: " + configuration["SomeApiKey"]);
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Custom/ReversedSecret.cs:
--------------------------------------------------------------------------------
1 | using RockLib.Secrets;
2 | using System.Linq;
3 |
4 | namespace Example.Secrets.Custom
5 | {
6 | internal sealed class ReversedSecret : ISecret
7 | {
8 | private readonly string _secret;
9 |
10 | public ReversedSecret(string key, string secret)
11 | {
12 | ConfigurationKey = key;
13 | _secret = secret;
14 | }
15 |
16 | public string ConfigurationKey { get; }
17 |
18 | public string GetValue()
19 | {
20 | return new string(_secret.ToCharArray().Reverse().ToArray());
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Examples/Example.Secrets.Custom/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "MyConnectionString": "The secret for 'MyConnectionString' has not been applied",
3 | "SomeApiKey": "The secret for 'SomeApiKey' has not been applied",
4 |
5 | "RockLib.Secrets": [
6 | {
7 | "Type": "Example.Secrets.Custom.ReversedSecret, Example.Secrets.Custom",
8 | "Value": {
9 | "Key": "MyConnectionString",
10 | "Secret": ";drowssaPym=drowssaP;emanresUym=dI resU;esaBataDym=esabataD;sserddArevreSym=revreS"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2018-2021 Rocket Mortgage
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RockLib.Secrets
2 |
3 | *A configuration provider for Microsoft.Extensions.Configuration backed by runtime secrets.*
4 |
5 | ### RockLib.Secrets
6 |
7 | ### RockLib.Secrets.Aws
8 |
9 | ---
10 |
11 | - [Getting started](docs/GettingStarted.md)
12 | - How to:
13 | - [Add secrets directly to a configuration builder](docs/ConfigurationBuilder.md)
14 | - [Define secrets in configuration](docs/Configuration.md)
15 | - [Consume secrets from configuration](docs/Usage.md)
16 | - [Configure AWS secrets](docs/AwsSecret.md)
17 | - API Reference:
18 | - [RockLib.Secrets](https://www.nuget.org/packages/RockLib.Secrets)
19 | - [RockLib.Secrets.Aws](https://www.nuget.org/packages/RockLib.Secrets.Aws)
20 |
--------------------------------------------------------------------------------
/RockLib.Secrets.All.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34616.47
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets", "RockLib.Secrets\RockLib.Secrets.csproj", "{6E303E27-044B-420D-AD41-D07959C9E32F}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets.Aws", "RockLib.Secrets.Aws\RockLib.Secrets.Aws.csproj", "{C011E4CB-227F-4369-A6A4-E2880B73F0FA}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E49937C6-C91E-4FA8-97EA-567D6E9571F8}"
11 | ProjectSection(SolutionItems) = preProject
12 | docs\AwsSecret.md = docs\AwsSecret.md
13 | docs\Configuration.md = docs\Configuration.md
14 | docs\ConfigurationBuilder.md = docs\ConfigurationBuilder.md
15 | docs\GettingStarted.md = docs\GettingStarted.md
16 | README.md = README.md
17 | docs\Usage.md = docs\Usage.md
18 | EndProjectSection
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6B7B23BA-A05D-4030-AEF7-6DF4467CAAB8}"
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{AF5ACD25-1ACA-417C-B08C-6240D79595F2}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Secrets.Aws.AspNetCore", "Examples\Example.Secrets.Aws.AspNetCore\Example.Secrets.Aws.AspNetCore.csproj", "{6159D1AD-791A-4835-8D6B-4A35CB147280}"
25 | EndProject
26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Secrets.Custom", "Examples\Example.Secrets.Custom\Example.Secrets.Custom.csproj", "{BC010E7A-712E-40E1-95AF-E13AB95FDEF9}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets.Aws.Tests", "Tests\RockLib.Secrets.Aws.Tests\RockLib.Secrets.Aws.Tests.csproj", "{30887B96-1ABA-44BE-8A9B-842429E01B4B}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets.Tests", "Tests\RockLib.Secrets.Tests\RockLib.Secrets.Tests.csproj", "{453034CF-0EEF-4244-9558-49686D0F605E}"
31 | EndProject
32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{21241C4A-E465-4A29-99A2-4E7EEEF5482B}"
33 | ProjectSection(SolutionItems) = preProject
34 | .editorconfig = .editorconfig
35 | CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
36 | CONTRIBUTING.md = CONTRIBUTING.md
37 | Directory.Build.props = Directory.Build.props
38 | LICENSE.md = LICENSE.md
39 | README.md = README.md
40 | EndProjectSection
41 | EndProject
42 | Global
43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
44 | Debug|Any CPU = Debug|Any CPU
45 | Release|Any CPU = Release|Any CPU
46 | EndGlobalSection
47 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
48 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {6159D1AD-791A-4835-8D6B-4A35CB147280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {6159D1AD-791A-4835-8D6B-4A35CB147280}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {6159D1AD-791A-4835-8D6B-4A35CB147280}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {6159D1AD-791A-4835-8D6B-4A35CB147280}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {BC010E7A-712E-40E1-95AF-E13AB95FDEF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {BC010E7A-712E-40E1-95AF-E13AB95FDEF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {BC010E7A-712E-40E1-95AF-E13AB95FDEF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {BC010E7A-712E-40E1-95AF-E13AB95FDEF9}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {30887B96-1ABA-44BE-8A9B-842429E01B4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65 | {30887B96-1ABA-44BE-8A9B-842429E01B4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
66 | {30887B96-1ABA-44BE-8A9B-842429E01B4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {30887B96-1ABA-44BE-8A9B-842429E01B4B}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {453034CF-0EEF-4244-9558-49686D0F605E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {453034CF-0EEF-4244-9558-49686D0F605E}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {453034CF-0EEF-4244-9558-49686D0F605E}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {453034CF-0EEF-4244-9558-49686D0F605E}.Release|Any CPU.Build.0 = Release|Any CPU
72 | EndGlobalSection
73 | GlobalSection(SolutionProperties) = preSolution
74 | HideSolutionNode = FALSE
75 | EndGlobalSection
76 | GlobalSection(NestedProjects) = preSolution
77 | {6159D1AD-791A-4835-8D6B-4A35CB147280} = {AF5ACD25-1ACA-417C-B08C-6240D79595F2}
78 | {BC010E7A-712E-40E1-95AF-E13AB95FDEF9} = {AF5ACD25-1ACA-417C-B08C-6240D79595F2}
79 | {30887B96-1ABA-44BE-8A9B-842429E01B4B} = {6B7B23BA-A05D-4030-AEF7-6DF4467CAAB8}
80 | {453034CF-0EEF-4244-9558-49686D0F605E} = {6B7B23BA-A05D-4030-AEF7-6DF4467CAAB8}
81 | EndGlobalSection
82 | GlobalSection(ExtensibilityGlobals) = postSolution
83 | SolutionGuid = {A43A5F42-B927-48DA-9BC9-D6123CFE9BDC}
84 | EndGlobalSection
85 | EndGlobal
86 |
--------------------------------------------------------------------------------
/RockLib.Secrets.Aws/AwsSecret.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using Amazon.SecretsManager;
5 | using Amazon.SecretsManager.Model;
6 | using Newtonsoft.Json.Linq;
7 |
8 | namespace RockLib.Secrets.Aws
9 | {
10 | ///
11 | /// An implementation of the interface for use with AWS.
12 | ///
13 | public class AwsSecret : ISecret
14 | {
15 | private readonly string _exceptionMessageFormat;
16 |
17 | ///
18 | /// Initializes a new instance of the class for key/value secrets.
19 | ///
20 | /// The configuration key for the secret.
21 | /// The Amazon Resource Name (ARN) or the friendly name of the secret.
22 | /// The key of the secret in AWS.
23 | ///
24 | /// The client used for routing calls to AWS. If null, an instance of will be used.
25 | ///
26 | public AwsSecret(string configurationKey, string secretId, string? secretKey = null,
27 | IAmazonSecretsManager? secretsManager = null)
28 | {
29 | ConfigurationKey = configurationKey ?? throw new ArgumentNullException(nameof(configurationKey));
30 | SecretId = secretId ?? throw new ArgumentNullException(nameof(secretId));
31 | SecretKey = secretKey;
32 | SecretsManager = secretsManager;
33 |
34 | if (secretKey is null)
35 | {
36 | _exceptionMessageFormat = $"No secret was found with the AwsSecretName '{SecretId}' for the key '{ConfigurationKey}': {{0}}";
37 | }
38 | else
39 | {
40 | _exceptionMessageFormat = $"No secret was found with the AwsSecretName '{SecretId}' and AwsSecretKey '{SecretKey}' for the key '{ConfigurationKey}': {{0}}";
41 | }
42 | }
43 | ///
44 | /// Gets the configuration key for the secret.
45 | ///
46 | public string ConfigurationKey { get; }
47 |
48 | ///
49 | /// Gets the Amazon Resource Name (ARN) or the friendly name of the secret.
50 | ///
51 | public string SecretId { get; }
52 |
53 | ///
54 | /// Gets the "Secret Key" of the secret.
55 | ///
56 | public string? SecretKey { get; }
57 |
58 | ///
59 | /// Gets the client used for routing calls to AWS.
60 | ///
61 | public IAmazonSecretsManager? SecretsManager { get; private set; }
62 |
63 | ///
64 | /// Gets the value of the secret.
65 | ///
66 | /// The secret value.
67 | public string GetValue()
68 | {
69 | var request = new GetSecretValueRequest
70 | {
71 | SecretId = SecretId
72 | };
73 |
74 | // Set the manager to the AWS one if it wasn't provided.
75 | SecretsManager ??= new AmazonSecretsManagerClient();
76 |
77 | // NOTE: This is NOT ideal. We should be awaiting the call.
78 | // But ISecret only has a sync version for GetValue().
79 | // Furthermore, providing an async version (GetValueAsync()) doesn't help,
80 | // because configuration is synchronous.
81 | // This issue, if resolved, would fix this: https://github.com/dotnet/runtime/issues/36018
82 | // But it's still open :(. So we have to block the call here for now.
83 | var response = SecretsManager.GetSecretValueAsync(request).GetAwaiter().GetResult();
84 |
85 | if (response is null)
86 | {
87 | throw new KeyNotFoundException(
88 | string.Format(CultureInfo.InvariantCulture,
89 | _exceptionMessageFormat, "Response was null."));
90 | }
91 |
92 | if (response.SecretString is not null)
93 | {
94 | if (SecretKey is not null)
95 | {
96 | var secret = JObject.Parse(response.SecretString)[SecretKey];
97 |
98 | if (secret is not null)
99 | {
100 | return secret.ToString();
101 | }
102 |
103 | throw new KeyNotFoundException(
104 | string.Format(CultureInfo.InvariantCulture,
105 | _exceptionMessageFormat, $"Response did not contain item with the name '{SecretKey}'."));
106 | }
107 |
108 | return response.SecretString;
109 | }
110 |
111 | if (response.SecretBinary is not null)
112 | {
113 | return Convert.ToBase64String(response.SecretBinary.ToArray());
114 | }
115 |
116 | throw new KeyNotFoundException(
117 | string.Format(CultureInfo.InvariantCulture,
118 | _exceptionMessageFormat, "Response did not contain a value for SecretString or SecretBinary."));
119 | }
120 | }
121 | }
--------------------------------------------------------------------------------
/RockLib.Secrets.Aws/AwsSecretsConfigurationBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using Amazon.SecretsManager;
2 | using System;
3 |
4 | namespace RockLib.Secrets.Aws
5 | {
6 | ///
7 | /// Extension methods for secrets configuration builders related to AWS.
8 | ///
9 | public static class AwsSecretsConfigurationBuilderExtensions
10 | {
11 | ///
12 | /// Adds an to the .
13 | ///
14 | /// The builder the secret is beind added to.
15 | /// The configuration key for the secret.
16 | /// The Amazon Resource Name (ARN) or the friendly name of the secret.
17 | /// The key of the secret in AWS.
18 | ///
19 | /// The client used for routing calls to AWS.
20 | ///
21 | /// The same .
22 | public static ISecretsConfigurationBuilder AddAwsSecret(this ISecretsConfigurationBuilder builder,
23 | string configurationKey, string secretId, string? secretKey, IAmazonSecretsManager secretsManager)
24 | {
25 | #if NET6_0_OR_GREATER
26 | ArgumentNullException.ThrowIfNull(builder);
27 | #else
28 | if (builder is null)
29 | {
30 | throw new ArgumentNullException(nameof(builder));
31 | }
32 | #endif
33 |
34 | return builder.AddSecret(new AwsSecret(configurationKey, secretId, secretKey, secretsManager));
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/RockLib.Secrets.Aws/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # RockLib.Secrets.Aws Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## 5.0.0 - 2025-02-05
9 |
10 | #### Changed
11 | - Finalized 5.0.0 release
12 | - RockLib.Secrets.6.0.0-alpha.1 -> RockLib.Secrets.6.0.0
13 |
14 | ## 5.0.0-alpha.1 - 2025-02-05
15 |
16 | #### Changed
17 | - RockLib.Secrets.5.0.0 -> RockLib.Secrets.6.0.0-alpha.1
18 |
19 | ## 4.0.0 - 2025-01-24
20 |
21 | #### Changed
22 | - Removed .NET 6.0 as a target framework.
23 |
24 | ## 3.0.2 - 2024-07-22
25 |
26 | #### Changed
27 | - RockLib.Secrets.4.0.0 -> RockLib.Secrets.4.0.1 for vulnerability fix.
28 |
29 | ## 3.0.1 - 2024-03-13
30 |
31 | #### Changed
32 | - Updated RockLib.Secrets NuGet package reference to `4.0.0`.
33 |
34 | ## 3.0.0 - 2024-03-12
35 |
36 | #### Changed
37 | - Finalized 3.0.0 release.
38 |
39 | ## 3.0.0-alpha.1 - 2024-03-12
40 |
41 | #### Changed
42 | - Removed support for .NET Core 3.1 and added .NET 8.
43 | - Updated all NuGet package references to the latest versions.
44 |
45 | ## 2.0.2 - 2023-04-11
46 |
47 | #### Fixed
48 | - Changed the `secretKey` and `secretsManager` parameters to the `AwsSecret` constructor so configuration only requires two values.
49 |
50 | ## 2.0.1 - 2022-07-18
51 |
52 | #### Changed
53 | - Updated the following package references to their latest versions:
54 | - `RockLib.Secrets` - `2.0.2`
55 | - `AWSSDK.SecretsManager` - `3.7.2.65`
56 |
57 | ## 2.0.0 - 2022-04-14
58 |
59 | #### Added
60 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency.
61 |
62 | #### Changed
63 | - Supported targets: net6.0, netcoreapp3.1, and net48.
64 | - Updated all NuGet package dependencies to their latest versions:
65 | - `Microsoft.SourceLink.GitHub` - `1.1.1`
66 | - `AWSSDK.SecretsManager` - `3.7.2.29`
67 | - `RockLib.Secrets` - `3.0.0`
68 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values.
69 | - The static field, `AwsSecret.DefaultSecretsManager`, has been removed. Typically this would just end up being an instance of `AmazonSecretsManagerClient`. This also means that `AwsConfigurationBuilderExtensions.SetAmazonSecretsManager` is no longer necessary and has been deleted. If you need to create an instance of `AwsSecret`, you can use an instance of `AmazonSecretsManagerClient` to pass to the `secretsManager` parameter. Having this manager type resolved through a registration in your IoC container is the ideal solution for this.
70 |
71 | ## 1.0.8 - 2021-08-12
72 |
73 | #### Changed
74 |
75 | - Changes "Quicken Loans" to "Rocket Mortgage".
76 | - Updates RockLib.Secrets to latest version, [2.0.6](https://github.com/RockLib/RockLib.Secrets/blob/main/RockLib.Secrets/CHANGELOG.md#206---2021-08-12).
77 | - Updates AWSSDK.SecretsManager to latest version, [3.7.1.4](https://github.com/aws/aws-sdk-net/blob/master/SDK.CHANGELOG.md#37950-2021-08-12-1814-utc).
78 | - Updates Newtonsoft.Json to latest version, [13.0.1](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.1).
79 |
80 | ## 1.0.7 - 2021-05-06
81 |
82 | #### Changed
83 |
84 | - Updates RockLib.Secrets package to latest versions, which include SourceLink.
85 |
86 | ## 1.0.6 - 2021-04-22
87 |
88 | #### Added
89 |
90 | - Adds SourceLink to nuget package.
91 |
92 | ----
93 |
94 | **Note:** Release notes in the above format are not available for earlier versions of
95 | RockLib.Secrets. What follows below are the original release notes.
96 |
97 | ----
98 |
99 | ## 1.0.5
100 |
101 | Adds net5.0 target.
102 |
103 | ## 1.0.4
104 |
105 | Adds icon to project and nuget package.
106 |
107 | ## 1.0.3
108 |
109 | Updates to align with nuget conventions.
110 |
111 | ## 1.0.2
112 |
113 | Updates RockLib.Secrets package to latest version.
114 |
115 | ## 1.0.1
116 |
117 | Adds support for plaintext aws secrets.
118 |
119 | ## 1.0.0
120 |
121 | Initial release.
122 |
--------------------------------------------------------------------------------
/RockLib.Secrets.Aws/RockLib.Secrets.Aws.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Embedded
4 | An Aws implementation of the ISecretsProvider.
5 | True
6 | True
7 | icon.png
8 | RockLib.Secrets.Aws
9 | LICENSE.md
10 | https://github.com/RockLib/RockLib.Secrets
11 | A changelog is available at https://github.com/RockLib/RockLib.Secrets/blob/main/RockLib.Secrets.Aws/CHANGELOG.md.
12 | false
13 | rocklib secrets aws
14 | 5.0.0
15 | True
16 | 5.0.0
17 |
18 |
19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml
20 |
21 |
22 | true
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/RockLib.Secrets.Aws/RockLib.Secrets.Aws.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29926.136
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets.Aws", "RockLib.Secrets.Aws.csproj", "{C011E4CB-227F-4369-A6A4-E2880B73F0FA}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets.Aws.Tests", "..\Tests\RockLib.Secrets.Aws.Tests\RockLib.Secrets.Aws.Tests.csproj", "{33DF10A6-5E77-4A4F-BD85-CD61F8229376}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {C011E4CB-227F-4369-A6A4-E2880B73F0FA}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {33DF10A6-5E77-4A4F-BD85-CD61F8229376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {33DF10A6-5E77-4A4F-BD85-CD61F8229376}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {33DF10A6-5E77-4A4F-BD85-CD61F8229376}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {33DF10A6-5E77-4A4F-BD85-CD61F8229376}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {A43A5F42-B927-48DA-9BC9-D6123CFE9BDC}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/RockLib.Secrets/AssemblyAttributes.cs:
--------------------------------------------------------------------------------
1 | using RockLib.Configuration.ObjectFactory;
2 | using RockLib.Secrets;
3 | using System.Collections.Generic;
4 |
5 | [assembly: ConfigSection("RockLib.Secrets", typeof(List))]
--------------------------------------------------------------------------------
/RockLib.Secrets/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # RockLib.Secrets Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## 6.0.0 - 2025-02-06
9 |
10 | #### Changed
11 | - Finalized 6.0.0 release.
12 |
13 | ## 6.0.0-alpha.1 - 2025-02-06
14 |
15 | #### Changed
16 | - Updated the following packages:
17 | - RockLib.Configuration from 4.0.3 to 5.0.0
18 | - RockLib.ObjectFactor from 3.0.0 to 4.0.0
19 |
20 | ## 5.0.0 - 2025-01-24
21 |
22 | #### Changed
23 | - Removed .NET 6.0 as a target framework.
24 | - Updated the following packages:
25 | - RockLib.Configuration from 4.0.1 to 4.0.3
26 |
27 | ## 4.0.1 - 2024-07-19
28 |
29 | #### Changed
30 | - RockLib.Configuration.4.0.0 -> RockLib.Configuration.4.0.1
31 |
32 | ## 4.0.0 - 2024-03-12
33 |
34 | #### Changed
35 | - Finalized 4.0.0 release.
36 |
37 | ## 4.0.0-alpha.1 - 2024-03-12
38 |
39 | #### Changed
40 | - Removed support for .NET Core 3.1 and added .NET 8.
41 | - Updated all NuGet package references to the latest versions.
42 |
43 | ## 3.0.1 - 2022-07-18
44 |
45 | #### Changed
46 | - Updated the `RockLib.Configuration.ObjectFactory` package reference to the latest version (`2.0.2`).
47 |
48 | ## 3.0.0 - 2022-04-12
49 |
50 | #### Added
51 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency.
52 |
53 | #### Changed
54 | - Supported targets: net6.0, netcoreapp3.1, and net48.
55 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values.
56 |
57 | ## 2.0.6 - 2021-08-12
58 |
59 | #### Changed
60 |
61 | - Changes "Quicken Loans" to "Rocket Mortgage".
62 | - Updates RockLib.Configuration to latest version, [2.5.3](https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration/CHANGELOG.md#253---2021-08-11).
63 | - Updates RockLib.Configuration.ObjectFactory to latest version, [1.6.9](https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration.ObjectFactory/CHANGELOG.md#169---2021-08-11).
64 |
65 | ## 2.0.5 - 2021-05-06
66 |
67 | #### Changed
68 |
69 | - Updates RockLib.Configuration and RockLib.Configuration.ObjectFactory packages to latest versions, which include SourceLink.
70 |
71 | ## 2.0.4 - 2021-04-22
72 |
73 | #### Added
74 |
75 | - Adds SourceLink to nuget package.
76 |
77 | ----
78 |
79 | **Note:** Release notes in the above format are not available for earlier versions of
80 | RockLib.Secrets. What follows below are the original release notes.
81 |
82 | ----
83 |
84 | ## 2.0.3
85 |
86 | Adds net5.0 target.
87 |
88 | ## 2.0.2
89 |
90 | Adds icon to project and nuget package.
91 |
92 | ## 2.0.1
93 |
94 | Updates to align with nuget conventions.
95 |
96 | ## 2.0.0
97 |
98 | - Made configuration extension methods, sources, & providers more idiomatic.
99 | - Added periodic reloading of secrets.
100 | - Added exception handler for when `ISecret.GetValue()` throws an exception.
101 | - Removed the `ISecretsProvider` interface.
102 | - Renamed `ISecret.Key` to `ConfigurationKey`.
103 | - Updated configuration. Instead of the `RockLib_Secrets` / `RockLib.Secrets` composite section defining an `ISecretsProvider`, the section defines one or more instances of `ISecret`.
104 |
105 | ## 1.0.2
106 |
107 | The parameterless overload of the AddRockLibSecrets extension method throws an exception if the configuration does not define a secrets provider in the RockLib_Secrets / RockLib.Secrets composite configuration section or if a secrets provider cannot be created due to invalid configuration.
108 |
109 | ## 1.0.1
110 |
111 | - Adds support for rocklib_secrets config section.
112 | - Adds ConfigSection attribute for the Rockifier tool.
113 |
114 | ## 1.0.0
115 |
116 | Initial release.
117 |
--------------------------------------------------------------------------------
/RockLib.Secrets/ConfigurationBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using System;
3 |
4 | namespace RockLib.Secrets
5 | {
6 | ///
7 | /// Extension methods for adding .
8 | ///
9 | public static class ConfigurationBuilderExtensions
10 | {
11 | ///
12 | /// The key to the where the
13 | /// default default action to be invoked for SecretsConfigurationProvider when
14 | /// an error occurs while getting the value of a secret is stored.
15 | ///
16 | public const string SecretExceptionHandlerKey = "RockLib.SecretExceptionHandler";
17 |
18 | ///
19 | /// Adds a secrets configuration source to the , returning
20 | /// an used to define the source's secrets.
21 | ///
22 | /// The to add to.
23 | ///
24 | /// An used to define the source's secrets.
25 | ///
26 | public static ISecretsConfigurationBuilder AddRockLibSecrets(this IConfigurationBuilder builder) =>
27 | builder.AddRockLibSecrets(null);
28 |
29 | ///
30 | /// Adds a secrets configuration source to the , returning
31 | /// an used to define the source's secrets.
32 | ///
33 | /// The to add to.
34 | ///
35 | /// Configures the secrets configuration source. Can be .
36 | ///
37 | ///
38 | /// An used to define the source's secrets.
39 | ///
40 | ///
41 | /// Thrown if is null.
42 | ///
43 | public static ISecretsConfigurationBuilder AddRockLibSecrets(this IConfigurationBuilder builder,
44 | Action? configureSource)
45 | {
46 | #if NET6_0_OR_GREATER
47 | ArgumentNullException.ThrowIfNull(builder);
48 | #else
49 | if (builder is null)
50 | {
51 | throw new ArgumentNullException(nameof(builder));
52 | }
53 | #endif
54 |
55 | var source = new SecretsConfigurationSource();
56 | configureSource?.Invoke(source);
57 | builder.Add(source);
58 | return new SecretsConfigurationBuilder(source);
59 | }
60 |
61 | ///
62 | /// Sets a default action to be invoked for SecretsConfigurationProvider when an error occurs while getting
63 | /// the value of a secret.
64 | ///
65 | /// The to add to.
66 | /// The action to be invoked for SecretsConfigurationProvider.
67 | /// The .
68 | ///
69 | /// Thrown if or is null.
70 | ///
71 | public static IConfigurationBuilder SetSecretExceptionHandler(this IConfigurationBuilder builder,
72 | Action onSecretException)
73 | {
74 | #if NET6_0_OR_GREATER
75 | ArgumentNullException.ThrowIfNull(builder);
76 | ArgumentNullException.ThrowIfNull(onSecretException);
77 | #else
78 | if (builder is null)
79 | {
80 | throw new ArgumentNullException(nameof(builder));
81 | }
82 |
83 | if(onSecretException is null)
84 | {
85 | throw new ArgumentNullException(nameof(onSecretException));
86 | }
87 | #endif
88 |
89 | builder.Properties[SecretExceptionHandlerKey] = onSecretException;
90 | return builder;
91 | }
92 |
93 | ///
94 | /// Gets the default action to be invoked for SecretsConfigurationProvider when an error occurs while
95 | /// getting the value of a secret.
96 | ///
97 | /// The .
98 | ///
99 | /// The default action to be invoked for SecretsConfigurationProvider when an error occurs while
100 | /// getting the value of a secret, or null otherwise.
101 | ///
102 | ///
103 | /// Thrown if is null.
104 | ///
105 | public static Action? GetSecretExceptionHandler(this IConfigurationBuilder builder)
106 | {
107 | #if NET6_0_OR_GREATER
108 | ArgumentNullException.ThrowIfNull(builder);
109 | #else
110 | if (builder is null)
111 | {
112 | throw new ArgumentNullException(nameof(builder));
113 | }
114 | #endif
115 |
116 | if (builder.Properties.TryGetValue(SecretExceptionHandlerKey, out var value) && value is Action exceptionHandler)
117 | {
118 | return exceptionHandler;
119 | }
120 |
121 | return null;
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/RockLib.Secrets/ISecret.cs:
--------------------------------------------------------------------------------
1 | namespace RockLib.Secrets
2 | {
3 | ///
4 | /// Defines a secret.
5 | ///
6 | public interface ISecret
7 | {
8 | ///
9 | /// Gets the configuration key for the secret.
10 | ///
11 | string ConfigurationKey { get; }
12 |
13 | ///
14 | /// Gets the value of the secret.
15 | ///
16 | /// The secret value.
17 | string GetValue();
18 | }
19 | }
--------------------------------------------------------------------------------
/RockLib.Secrets/ISecretsConfigurationBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace RockLib.Secrets
2 | {
3 | ///
4 | /// Defines a builder for adding secrets.
5 | ///
6 | public interface ISecretsConfigurationBuilder
7 | {
8 | ///
9 | /// Adds a secret to the builder.
10 | ///
11 | /// The .
12 | /// The .
13 | ISecretsConfigurationBuilder AddSecret(ISecret secret);
14 | }
15 | }
--------------------------------------------------------------------------------
/RockLib.Secrets/RockLib.Secrets.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Embedded
4 | A configuration provider for Microsoft.Extensions.Configuration that is backed by runtime secrets.
5 | True
6 | True
7 | icon.png
8 | RockLib.Secrets
9 | LICENSE.md
10 | https://github.com/RockLib/RockLib.Secrets
11 | A changelog is available at https://github.com/RockLib/RockLib.Secrets/blob/main/RockLib.Secrets/CHANGELOG.md.
12 | false
13 | rocklib secrets configuration
14 | 6.0.0
15 | True
16 | 6.0.0
17 |
18 |
19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml
20 |
21 |
22 | true
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/RockLib.Secrets/RockLib.Secrets.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29926.136
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets", "RockLib.Secrets.csproj", "{6E303E27-044B-420D-AD41-D07959C9E32F}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Secrets.Tests", "..\Tests\RockLib.Secrets.Tests\RockLib.Secrets.Tests.csproj", "{AD3162D0-B27E-466C-B0A4-995FAC4B68EC}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {6E303E27-044B-420D-AD41-D07959C9E32F}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {AD3162D0-B27E-466C-B0A4-995FAC4B68EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {AD3162D0-B27E-466C-B0A4-995FAC4B68EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {AD3162D0-B27E-466C-B0A4-995FAC4B68EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {AD3162D0-B27E-466C-B0A4-995FAC4B68EC}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {A43A5F42-B927-48DA-9BC9-D6123CFE9BDC}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/RockLib.Secrets/SecretExceptionContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RockLib.Secrets
4 | {
5 | ///
6 | /// Contains information about a secret exception.
7 | ///
8 | public class SecretExceptionContext
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | ///
14 | /// The that invoked the
15 | /// method that caused the exception.
16 | ///
17 | /// The that caused the exception.
18 | /// The exception that occurred in .
19 | ///
20 | /// Thrown if or or is null.
21 | ///
22 | public SecretExceptionContext(SecretsConfigurationProvider provider, ISecret secret, Exception exception)
23 | {
24 | Provider = provider ?? throw new ArgumentNullException(nameof(provider));
25 | Secret = secret ?? throw new ArgumentNullException(nameof(secret));
26 | Exception = exception ?? throw new ArgumentNullException(nameof(exception));
27 | }
28 |
29 | ///
30 | /// The that invoked the
31 | /// method that caused the exception.
32 | ///
33 | public SecretsConfigurationProvider Provider { get; }
34 |
35 | ///
36 | /// The that caused the exception.
37 | ///
38 | public ISecret Secret { get; }
39 |
40 | ///
41 | /// The exception that occurred in .
42 | ///
43 | public Exception Exception { get; }
44 | }
45 | }
--------------------------------------------------------------------------------
/RockLib.Secrets/SecretsConfigurationBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RockLib.Secrets
4 | {
5 | ///
6 | /// A builder object that adds secrets to a secrets source.
7 | ///
8 | public class SecretsConfigurationBuilder : ISecretsConfigurationBuilder
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The secrets configuration source.
14 | ///
15 | /// Thrown if is null.
16 | ///
17 | public SecretsConfigurationBuilder(SecretsConfigurationSource source)
18 | {
19 | Source = source ?? throw new ArgumentNullException(nameof(source));
20 | }
21 |
22 | ///
23 | /// The source backing the builder.
24 | ///
25 | public SecretsConfigurationSource Source { get; }
26 |
27 | ///
28 | /// Adds a secret to the secrets source.
29 | ///
30 | /// The .
31 | /// The .
32 | ///
33 | /// Thrown if is null.
34 | ///
35 | public ISecretsConfigurationBuilder AddSecret(ISecret secret)
36 | {
37 | Source.Secrets.Add(secret ?? throw new ArgumentNullException(nameof(secret)));
38 | return this;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/RockLib.Secrets/SecretsConfigurationProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Linq;
6 | using System.Threading;
7 |
8 | namespace RockLib.Secrets
9 | {
10 | ///
11 | /// Implementation of backed by a collection of secrets.
12 | ///
13 | public class SecretsConfigurationProvider : ConfigurationProvider, IDisposable
14 | {
15 | private readonly Timer _timer;
16 | private bool _disposed;
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The source settings.
22 | ///
23 | /// Thrown if is null
24 | ///
25 | ///
26 | /// Thrown if the secrets collection has any unexpected issues.
27 | ///
28 | public SecretsConfigurationProvider(SecretsConfigurationSource source)
29 | {
30 | Source = source ?? throw new ArgumentNullException(nameof(source));
31 | Secrets = new ReadOnlyCollection(source.Secrets.ToArray());
32 | _timer = new Timer(_ => Load());
33 |
34 | if (!Secrets.Any())
35 | {
36 | throw new ArgumentException("The SecretsConfigurationSource must contain at least one secret.", nameof(source));
37 | }
38 | if (Secrets.Any(s => s is null))
39 | {
40 | throw new ArgumentException("The SecretsConfigurationSource cannot contain any null secrets.", nameof(source));
41 | }
42 | if (Secrets.Any(s => s.ConfigurationKey is null))
43 | {
44 | throw new ArgumentException("The SecretsConfigurationSource cannot contain any secrets with a null Key.", nameof(source));
45 | }
46 | if (Secrets.Select(s => s.ConfigurationKey).Distinct(StringComparer.OrdinalIgnoreCase).Count() != Secrets.Count)
47 | {
48 | throw new ArgumentException("The SecretsConfigurationSource cannot contain any secrets with duplicate Keys.", nameof(source));
49 | }
50 | }
51 |
52 | ///
53 | /// The source settings for this provider.
54 | ///
55 | public SecretsConfigurationSource Source { get; }
56 |
57 | ///
58 | /// The secrets that define this provider.
59 | ///
60 | public IReadOnlyList Secrets { get; }
61 |
62 | ///
63 | /// Loads data from .
64 | ///
65 | public override void Load()
66 | {
67 | try
68 | {
69 | var secrets = Secrets.Select(secret =>
70 | {
71 | try
72 | {
73 | return new { secret.ConfigurationKey, Value = secret.GetValue() };
74 | }
75 | // Would love to catch a specific exception, but...
76 | // we don't know what the implementation of ISecret.GetValue()
77 | // will do here, and this code has already been published, so...
78 | // we have to keep this as a general Exception.
79 | #pragma warning disable CA1031 // Do not catch general exception types
80 | catch (Exception ex)
81 | #pragma warning restore CA1031 // Do not catch general exception types
82 | {
83 | Source.OnSecretException?.Invoke(new SecretExceptionContext(this, secret, ex));
84 | return new { secret.ConfigurationKey, Value = (string)null! };
85 | }
86 | });
87 |
88 | var secretChanged = false;
89 |
90 | if (Data.Count == 0)
91 | {
92 | foreach (var secret in secrets)
93 | {
94 | Data.Add(secret.ConfigurationKey, secret.Value);
95 | }
96 | }
97 | else
98 | {
99 | foreach (var secret in secrets)
100 | {
101 | if (secret.Value is not null && Data[secret.ConfigurationKey] != secret.Value)
102 | {
103 | secretChanged = true;
104 | Data[secret.ConfigurationKey] = secret.Value;
105 | }
106 | }
107 | }
108 |
109 | if (secretChanged)
110 | {
111 | OnReload();
112 | }
113 | }
114 | finally
115 | {
116 | _timer.Change(Source.ReloadMilliseconds, Timeout.Infinite);
117 | }
118 | }
119 |
120 | ///
121 | /// Disposes the unmanaged resources.
122 | ///
123 | ~SecretsConfigurationProvider()
124 | {
125 | Dispose(false);
126 | }
127 |
128 | ///
129 | /// Disposes the object.
130 | ///
131 | public void Dispose()
132 | {
133 | Dispose(true);
134 | GC.SuppressFinalize(this);
135 | }
136 |
137 | ///
138 | /// Disposes the object
139 | ///
140 | /// Specifies if this is a managed or unmanaged disposal
141 | protected virtual void Dispose(bool dispose)
142 | {
143 | if(!_disposed)
144 | {
145 | if(dispose)
146 | {
147 | _timer.Dispose();
148 | }
149 | }
150 | _disposed = true;
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/RockLib.Secrets/SecretsConfigurationSource.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using RockLib.Configuration;
3 | using RockLib.Configuration.ObjectFactory;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 |
9 | namespace RockLib.Secrets
10 | {
11 | ///
12 | /// Represents a source of secrets as an .
13 | ///
14 | public class SecretsConfigurationSource : IConfigurationSource
15 | {
16 | /// The default value of the property, 5 minutes.
17 | public const int DefaultReloadMilliseconds = 5 * 60 * 1000;
18 |
19 | private int _reloadMilliseconds = DefaultReloadMilliseconds;
20 | private bool _alreadyBuilt;
21 |
22 | ///
23 | /// Gets the list of
24 | ///
25 | public IList Secrets { get; } = new List();
26 |
27 | ///
28 | /// Will be called if an uncaught exception occurs when calling
29 | /// from the method.
30 | ///
31 | public Action? OnSecretException { get; set; }
32 |
33 | ///
34 | /// The number of milliseconds to wait before reloading secrets. Specify
35 | /// to disable reloading.
36 | ///
37 | public int ReloadMilliseconds
38 | {
39 | get => _reloadMilliseconds;
40 | set
41 | {
42 | if (value < Timeout.Infinite)
43 | {
44 | throw new ArgumentOutOfRangeException(nameof(value), "Must be either non-negative or -1.");
45 | }
46 | _reloadMilliseconds = value;
47 | }
48 | }
49 |
50 | ///
51 | /// Disables periodic reloading.
52 | ///
53 | public void DisableReload() => ReloadMilliseconds = Timeout.Infinite;
54 |
55 | ///
56 | /// Builds the for this source.
57 | ///
58 | /// The .
59 | /// An instance of .
60 | public IConfigurationProvider Build(IConfigurationBuilder builder)
61 | {
62 | #if NET6_0_OR_GREATER
63 | ArgumentNullException.ThrowIfNull(builder);
64 | #else
65 | if (builder is null)
66 | {
67 | throw new ArgumentNullException(nameof(builder));
68 | }
69 | #endif
70 |
71 | if (!_alreadyBuilt)
72 | {
73 | _alreadyBuilt = true;
74 | foreach (var secretFromConfiguration in CreateSecretsFromConfiguration(builder))
75 | {
76 | Secrets.Add(secretFromConfiguration);
77 | }
78 | }
79 |
80 | OnSecretException ??= builder.GetSecretExceptionHandler();
81 |
82 | return new SecretsConfigurationProvider(this);
83 | }
84 |
85 | private List CreateSecretsFromConfiguration(IConfigurationBuilder builder)
86 | {
87 | // Make a copy of the builder, excluding this SecretsConfigurationSource.
88 | // Otherwise there will be infinite recursion when building the builder.
89 | var builderCopy = new ConfigurationBuilder();
90 |
91 | foreach (var source in builder.Sources.Where(s => !ReferenceEquals(this, s)))
92 | {
93 | builderCopy.Add(source);
94 | }
95 |
96 | foreach (var property in builder.Properties)
97 | {
98 | builderCopy.Properties.Add(property.Key, property.Value);
99 | }
100 |
101 | var configuration = builderCopy.Build();
102 |
103 | return configuration.GetCompositeSection("RockLib_Secrets", "RockLib.Secrets")
104 | .Create>()!;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Aws.Tests/AssemblySettings.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Aws.Tests/AwsSecretTests.cs:
--------------------------------------------------------------------------------
1 | using Amazon;
2 | using Amazon.SecretsManager;
3 | using Amazon.SecretsManager.Model;
4 | using FluentAssertions;
5 | using Moq;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Text;
10 | using System.Threading;
11 | using Xunit;
12 |
13 | namespace RockLib.Secrets.Aws.Tests;
14 |
15 | public static class AwsSecretTests
16 | {
17 | [Fact]
18 | public static void Create()
19 | {
20 | var secretsManager = new Mock().Object;
21 |
22 | var secret = new AwsSecret("configurationKey", "secretId", "secretKey", secretsManager);
23 |
24 | secret.ConfigurationKey.Should().Be("configurationKey");
25 | secret.SecretId.Should().Be("secretId");
26 | secret.SecretKey.Should().Be("secretKey");
27 | secret.SecretsManager.Should().BeSameAs(secretsManager);
28 | }
29 |
30 | [Fact]
31 | public static void CreateWithNullSecretKey()
32 | {
33 | var secretsManager = new Mock().Object;
34 |
35 | var secret = new AwsSecret("configurationKey", "secretId", null, secretsManager);
36 |
37 | secret.ConfigurationKey.Should().Be("configurationKey");
38 | secret.SecretId.Should().Be("secretId");
39 | secret.SecretKey.Should().BeNull();
40 | secret.SecretsManager.Should().BeSameAs(secretsManager);
41 | }
42 |
43 | [Fact]
44 | public static void CreateWithNullManager()
45 | {
46 | var secret = new AwsSecret("configurationKey", "secretId", "secretKey", null);
47 |
48 | secret.ConfigurationKey.Should().Be("configurationKey");
49 | secret.SecretId.Should().Be("secretId");
50 | secret.SecretKey.Should().Be("secretKey");
51 | secret.SecretsManager.Should().BeNull();
52 | }
53 |
54 | [Fact]
55 | public static void CreateWithNoSecretKeyAndManager()
56 | {
57 | var secret = new AwsSecret("configurationKey", "secretId");
58 |
59 | secret.ConfigurationKey.Should().Be("configurationKey");
60 | secret.SecretId.Should().Be("secretId");
61 | secret.SecretKey.Should().BeNull();
62 | secret.SecretsManager.Should().BeNull();
63 | }
64 |
65 | [Fact]
66 | public static void CreateWithNullConfigurationKey()
67 | {
68 | var act = () => new AwsSecret(null!, "secretId", null, Mock.Of());
69 |
70 | act.Should().ThrowExactly().WithMessage("*configurationKey*");
71 | }
72 |
73 | [Fact]
74 | public static void CreateWithNullSecretId()
75 | {
76 | var act = () => new AwsSecret("configurationKey", null!, null, Mock.Of());
77 |
78 | act.Should().ThrowExactly().WithMessage("*secretId*");
79 | }
80 |
81 | [Fact]
82 | public static void GetValue()
83 | {
84 | var response = new GetSecretValueResponse
85 | {
86 | SecretString = "{'myAwsSecretKey':'secretValue'}"
87 | };
88 |
89 | var mockSecretsManager = new Mock();
90 | mockSecretsManager.Setup(m => m.GetSecretValueAsync(It.IsAny(), It.IsAny()))
91 | .ReturnsAsync(response);
92 |
93 | var secret = new AwsSecret("myConfigurationKey", "mySecretId", "myAwsSecretKey", mockSecretsManager.Object);
94 |
95 | var value = secret.GetValue();
96 |
97 | value.Should().Be("secretValue");
98 |
99 | mockSecretsManager.Verify(m => m.GetSecretValueAsync(
100 | It.Is(r => r.SecretId == "mySecretId"), It.IsAny()),
101 | Times.Once());
102 | }
103 |
104 | [Fact]
105 | public static void GetValueWhenSecretKeyIsNotSupplied()
106 | {
107 | var response = new GetSecretValueResponse
108 | {
109 | SecretString = "mySecretString"
110 | };
111 |
112 | var mockSecretsManager = new Mock();
113 | mockSecretsManager.Setup(m => m.GetSecretValueAsync(It.IsAny(), It.IsAny()))
114 | .ReturnsAsync(response);
115 |
116 | var secret = new AwsSecret("myConfigurationKey", "mySecretId", null, mockSecretsManager.Object);
117 |
118 | var value = secret.GetValue();
119 |
120 | value.Should().Be("mySecretString");
121 |
122 | mockSecretsManager.Verify(m => m.GetSecretValueAsync(
123 | It.Is(r => r.SecretId == "mySecretId"), It.IsAny()),
124 | Times.Once());
125 | }
126 |
127 | [Fact]
128 | public static void GetValueWhenSecretStringIsNull()
129 | {
130 | var buffer = Encoding.UTF8.GetBytes("Hello, world!");
131 |
132 | var response = new GetSecretValueResponse
133 | {
134 | SecretBinary = new MemoryStream(buffer)
135 | };
136 |
137 | var mockSecretsManager = new Mock();
138 | mockSecretsManager.Setup(m => m.GetSecretValueAsync(It.IsAny(), It.IsAny()))
139 | .ReturnsAsync(response);
140 |
141 | var secret = new AwsSecret("myConfigurationKey", "mySecretId", null, mockSecretsManager.Object);
142 |
143 | var value = secret.GetValue();
144 |
145 | value.Should().Be(Convert.ToBase64String(buffer));
146 |
147 | mockSecretsManager.Verify(m => m.GetSecretValueAsync(
148 | It.Is(r => r.SecretId == "mySecretId"), It.IsAny()),
149 | Times.Once());
150 | }
151 |
152 | [Fact]
153 | public static void GetValueWithNullResponseFromSecretsManager()
154 | {
155 | var mockSecretsManager = new Mock();
156 | mockSecretsManager.Setup(m => m.GetSecretValueAsync(It.IsAny(), It.IsAny()))
157 | .ReturnsAsync((GetSecretValueResponse)null!);
158 |
159 | var secret = new AwsSecret("myConfigurationKey", "mySecretId", "myAwsSecretKey", mockSecretsManager.Object);
160 |
161 | var act = () => secret.GetValue();
162 |
163 | act.Should().ThrowExactly().WithMessage("*Response was null.*");
164 | }
165 |
166 | [Fact]
167 | public static void GetValueWhenSecretStringDoesNotContainSecretKey()
168 | {
169 | var response = new GetSecretValueResponse
170 | {
171 | SecretString = "{}"
172 | };
173 |
174 | var mockSecretsManager = new Mock();
175 | mockSecretsManager.Setup(m => m.GetSecretValueAsync(It.IsAny(), It.IsAny()))
176 | .ReturnsAsync(response);
177 |
178 | var secret = new AwsSecret("myConfigurationKey", "mySecretId", "myAwsSecretKey", mockSecretsManager.Object);
179 |
180 | var act = () => secret.GetValue();
181 |
182 | act.Should().ThrowExactly().WithMessage("*Response did not contain item with the name 'myAwsSecretKey'.*");
183 | }
184 |
185 | [Fact]
186 | public static void GetValueWhenSecretStringandSecretBinaryAreNull()
187 | {
188 | var response = new GetSecretValueResponse();
189 |
190 | var mockSecretsManager = new Mock();
191 | mockSecretsManager.Setup(m => m.GetSecretValueAsync(It.IsAny(), It.IsAny()))
192 | .ReturnsAsync(response);
193 |
194 | var secret = new AwsSecret("myConfigurationKey", "mySecretId", "myAwsSecretKey", mockSecretsManager.Object);
195 |
196 | var act = () => secret.GetValue();
197 |
198 | act.Should().ThrowExactly().WithMessage("*Response did not contain a value for SecretString or SecretBinary.*");
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Aws.Tests/AwsSecretsConfigurationBuilderExtensionsTests .cs:
--------------------------------------------------------------------------------
1 | using Amazon.SecretsManager;
2 | using FluentAssertions;
3 | using Microsoft.Extensions.Configuration;
4 | using Moq;
5 | using System;
6 | using Xunit;
7 |
8 | namespace RockLib.Secrets.Aws.Tests;
9 |
10 | public static class AwsSecretsConfigurationBuilderExtensionsTests
11 | {
12 | [Fact]
13 | public static void AddAwsSecret()
14 | {
15 | ISecret secret = null!;
16 | var builderMock = new Mock();
17 | builderMock
18 | .Setup(bm => bm.AddSecret(It.IsAny()))
19 | .Callback(s => secret = s);
20 |
21 | var configurationKey = "configurationKey";
22 | var secretId = "secretId";
23 | var secretKey = "secretKey";
24 | var awsSecretsManager = new Mock().Object;
25 |
26 | builderMock.Object.AddAwsSecret(configurationKey, secretId, secretKey, awsSecretsManager);
27 |
28 | builderMock.Verify();
29 |
30 | var awsSecret = secret.Should().BeOfType().Subject;
31 | awsSecret.ConfigurationKey.Should().Be(configurationKey);
32 | awsSecret.SecretId.Should().Be(secretId);
33 | awsSecret.SecretKey.Should().Be(secretKey);
34 | awsSecret.SecretsManager.Should().BeSameAs(awsSecretsManager);
35 | }
36 |
37 | [Fact]
38 | public static void AddAwsSecretWithNullBuilder()
39 | {
40 | var configurationKey = "configurationKey";
41 | var secretId = "secretId";
42 | var secretKey = "secretKey";
43 | var awsSecretsManager = new Mock().Object;
44 |
45 | ISecretsConfigurationBuilder builder = null!;
46 |
47 | Action act = () => builder.AddAwsSecret(configurationKey, secretId, secretKey, awsSecretsManager);
48 |
49 | act.Should().ThrowExactly().WithMessage("*builder*");
50 | }
51 |
52 | [Fact]
53 | public static void AddAwsSecretsWithRockLibConfiguration()
54 | {
55 | var configurationBuilder = new ConfigurationBuilder();
56 | configurationBuilder.AddJsonFile("appsettings.json")
57 | .AddRockLibSecrets();
58 | var buildAction = () => configurationBuilder.Build();
59 | buildAction.Should().NotThrow();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Aws.Tests/FakeSecretsManager.cs:
--------------------------------------------------------------------------------
1 | using Amazon.Runtime;
2 | using Amazon.Runtime.Endpoints;
3 | using Amazon.SecretsManager;
4 | using Amazon.SecretsManager.Model;
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace RockLib.Secrets.Aws.Tests;
10 |
11 | // Defined purely for the appsettings.json file
12 | // for testing.
13 | #pragma warning disable CA1065
14 | public sealed class FakeSecretsManager
15 | : IAmazonSecretsManager
16 | {
17 | public ISecretsManagerPaginatorFactory Paginators => throw new NotImplementedException();
18 |
19 | public IClientConfig Config => throw new NotImplementedException();
20 |
21 | public Task CancelRotateSecretAsync(CancelRotateSecretRequest request, CancellationToken cancellationToken = default)
22 | {
23 | throw new NotImplementedException();
24 | }
25 |
26 | public CancelRotateSecretResponse CancelRotateSecret(CancelRotateSecretRequest request)
27 | {
28 | throw new NotImplementedException();
29 | }
30 |
31 | public Task CreateSecretAsync(CreateSecretRequest request, CancellationToken cancellationToken = default)
32 | {
33 | throw new NotImplementedException();
34 | }
35 |
36 | public CreateSecretResponse CreateSecret(CreateSecretRequest request)
37 | {
38 | throw new NotImplementedException();
39 | }
40 |
41 | public Task DeleteResourcePolicyAsync(DeleteResourcePolicyRequest request, CancellationToken cancellationToken = default)
42 | {
43 | throw new NotImplementedException();
44 | }
45 |
46 | public DeleteResourcePolicyResponse DeleteResourcePolicy(DeleteResourcePolicyRequest request)
47 | {
48 | throw new NotImplementedException();
49 | }
50 |
51 | public Task DeleteSecretAsync(DeleteSecretRequest request, CancellationToken cancellationToken = default)
52 | {
53 | throw new NotImplementedException();
54 | }
55 |
56 | public DeleteSecretResponse DeleteSecret(DeleteSecretRequest request)
57 | {
58 | throw new NotImplementedException();
59 | }
60 |
61 | public Task DescribeSecretAsync(DescribeSecretRequest request, CancellationToken cancellationToken = default)
62 | {
63 | throw new NotImplementedException();
64 | }
65 |
66 | public DescribeSecretResponse DescribeSecret(DescribeSecretRequest request)
67 | {
68 | throw new NotImplementedException();
69 | }
70 |
71 | public void Dispose()
72 | {
73 | throw new NotImplementedException();
74 | }
75 |
76 | public Task GetRandomPasswordAsync(GetRandomPasswordRequest request, CancellationToken cancellationToken = default)
77 | {
78 | throw new NotImplementedException();
79 | }
80 |
81 | public GetRandomPasswordResponse GetRandomPassword(GetRandomPasswordRequest request)
82 | {
83 | throw new NotImplementedException();
84 | }
85 |
86 | public Task GetResourcePolicyAsync(GetResourcePolicyRequest request, CancellationToken cancellationToken = default)
87 | {
88 | throw new NotImplementedException();
89 | }
90 |
91 | public GetResourcePolicyResponse GetResourcePolicy(GetResourcePolicyRequest request)
92 | {
93 | throw new NotImplementedException();
94 | }
95 |
96 | public Task GetSecretValueAsync(GetSecretValueRequest request, CancellationToken cancellationToken = default)
97 | {
98 | throw new NotImplementedException();
99 | }
100 |
101 | public GetSecretValueResponse GetSecretValue(GetSecretValueRequest request)
102 | {
103 | throw new NotImplementedException();
104 | }
105 |
106 | public Task ListSecretsAsync(ListSecretsRequest request, CancellationToken cancellationToken = default)
107 | {
108 | throw new NotImplementedException();
109 | }
110 |
111 | public ListSecretsResponse ListSecrets(ListSecretsRequest request)
112 | {
113 | throw new NotImplementedException();
114 | }
115 |
116 | public Task ListSecretVersionIdsAsync(ListSecretVersionIdsRequest request, CancellationToken cancellationToken = default)
117 | {
118 | throw new NotImplementedException();
119 | }
120 |
121 | public ListSecretVersionIdsResponse ListSecretVersionIds(ListSecretVersionIdsRequest request)
122 | {
123 | throw new NotImplementedException();
124 | }
125 |
126 | public Task PutResourcePolicyAsync(PutResourcePolicyRequest request, CancellationToken cancellationToken = default)
127 | {
128 | throw new NotImplementedException();
129 | }
130 |
131 | public PutResourcePolicyResponse PutResourcePolicy(PutResourcePolicyRequest request)
132 | {
133 | throw new NotImplementedException();
134 | }
135 |
136 | public Task PutSecretValueAsync(PutSecretValueRequest request, CancellationToken cancellationToken = default)
137 | {
138 | throw new NotImplementedException();
139 | }
140 |
141 | public PutSecretValueResponse PutSecretValue(PutSecretValueRequest request)
142 | {
143 | throw new NotImplementedException();
144 | }
145 |
146 | public Task RemoveRegionsFromReplicationAsync(RemoveRegionsFromReplicationRequest request, CancellationToken cancellationToken = default)
147 | {
148 | throw new NotImplementedException();
149 | }
150 |
151 | public RemoveRegionsFromReplicationResponse RemoveRegionsFromReplication(RemoveRegionsFromReplicationRequest request)
152 | {
153 | throw new NotImplementedException();
154 | }
155 |
156 | public Task ReplicateSecretToRegionsAsync(ReplicateSecretToRegionsRequest request, CancellationToken cancellationToken = default)
157 | {
158 | throw new NotImplementedException();
159 | }
160 |
161 | public ReplicateSecretToRegionsResponse ReplicateSecretToRegions(ReplicateSecretToRegionsRequest request)
162 | {
163 | throw new NotImplementedException();
164 | }
165 |
166 | public Task RestoreSecretAsync(RestoreSecretRequest request, CancellationToken cancellationToken = default)
167 | {
168 | throw new NotImplementedException();
169 | }
170 |
171 | public RestoreSecretResponse RestoreSecret(RestoreSecretRequest request)
172 | {
173 | throw new NotImplementedException();
174 | }
175 |
176 | public Task RotateSecretAsync(RotateSecretRequest request, CancellationToken cancellationToken = default)
177 | {
178 | throw new NotImplementedException();
179 | }
180 |
181 | public RotateSecretResponse RotateSecret(RotateSecretRequest request)
182 | {
183 | throw new NotImplementedException();
184 | }
185 |
186 | public Task StopReplicationToReplicaAsync(StopReplicationToReplicaRequest request, CancellationToken cancellationToken = default)
187 | {
188 | throw new NotImplementedException();
189 | }
190 |
191 | public StopReplicationToReplicaResponse StopReplicationToReplica(StopReplicationToReplicaRequest request)
192 | {
193 | throw new NotImplementedException();
194 | }
195 |
196 | public Task TagResourceAsync(TagResourceRequest request, CancellationToken cancellationToken = default)
197 | {
198 | throw new NotImplementedException();
199 | }
200 |
201 | public TagResourceResponse TagResource(TagResourceRequest request)
202 | {
203 | throw new NotImplementedException();
204 | }
205 |
206 | public Task UntagResourceAsync(UntagResourceRequest request, CancellationToken cancellationToken = default)
207 | {
208 | throw new NotImplementedException();
209 | }
210 |
211 | public UntagResourceResponse UntagResource(UntagResourceRequest request)
212 | {
213 | throw new NotImplementedException();
214 | }
215 |
216 | public Task UpdateSecretAsync(UpdateSecretRequest request, CancellationToken cancellationToken = default)
217 | {
218 | throw new NotImplementedException();
219 | }
220 |
221 | public UpdateSecretResponse UpdateSecret(UpdateSecretRequest request)
222 | {
223 | throw new NotImplementedException();
224 | }
225 |
226 | public Task UpdateSecretVersionStageAsync(UpdateSecretVersionStageRequest request, CancellationToken cancellationToken = default)
227 | {
228 | throw new NotImplementedException();
229 | }
230 |
231 | public UpdateSecretVersionStageResponse UpdateSecretVersionStage(UpdateSecretVersionStageRequest request)
232 | {
233 | throw new NotImplementedException();
234 | }
235 |
236 | public Task ValidateResourcePolicyAsync(ValidateResourcePolicyRequest request, CancellationToken cancellationToken = default)
237 | {
238 | throw new NotImplementedException();
239 | }
240 |
241 | public ValidateResourcePolicyResponse ValidateResourcePolicy(ValidateResourcePolicyRequest request)
242 | {
243 | throw new NotImplementedException();
244 | }
245 |
246 | public Task BatchGetSecretValueAsync(BatchGetSecretValueRequest request, CancellationToken cancellationToken = default)
247 | {
248 | throw new NotImplementedException();
249 | }
250 |
251 | public Endpoint DetermineServiceOperationEndpoint(AmazonWebServiceRequest request)
252 | {
253 | throw new NotImplementedException();
254 | }
255 |
256 | public BatchGetSecretValueResponse BatchGetSecretValue(BatchGetSecretValueRequest request)
257 | {
258 | throw new NotImplementedException();
259 | }
260 | }
261 | #pragma warning restore CA1065
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Aws.Tests/RockLib.Secrets.Aws.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 |
8 |
9 |
10 | PreserveNewest
11 | true
12 | PreserveNewest
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 | all
27 | runtime; build; native; contentfiles; analyzers; buildtransitive
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Aws.Tests/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "AllSettings": "",
3 | "NoSecretKey": "",
4 | "NoSecretsManager": "",
5 | "NoSecretKeyAndSecretsManager": "",
6 |
7 | "RockLib.Secrets": [
8 | {
9 | "Type": "RockLib.Secrets.Aws.AwsSecret, RockLib.Secrets.Aws",
10 | "Value": {
11 | "ConfigurationKey": "AllSettings",
12 | "SecretId": "RockLib.Example.Secret",
13 | "SecretKey": "MySecretKey",
14 | "SecretsManager": {
15 | "Type": "RockLib.Secrets.Aws.Tests.FakeSecretsManager, RockLib.Secrets.Aws.Tests"
16 | }
17 | }
18 | },
19 | {
20 | "Type": "RockLib.Secrets.Aws.AwsSecret, RockLib.Secrets.Aws",
21 | "Value": {
22 | "ConfigurationKey": "NoSecretKey",
23 | "SecretId": "RockLib.Example.Secret",
24 | "SecretsManager": {
25 | "Type": "RockLib.Secrets.Aws.Tests.FakeSecretsManager, RockLib.Secrets.Aws.Tests"
26 | }
27 | }
28 | },
29 | {
30 | "Type": "RockLib.Secrets.Aws.AwsSecret, RockLib.Secrets.Aws",
31 | "Value": {
32 | "ConfigurationKey": "NoSecretsManager",
33 | "SecretId": "RockLib.Example.Secret",
34 | "SecretKey": "MySecretKey"
35 | }
36 | },
37 | {
38 | "Type": "RockLib.Secrets.Aws.AwsSecret, RockLib.Secrets.Aws",
39 | "Value": {
40 | "ConfigurationKey": "NoSecretKeyAndSecretsManager",
41 | "SecretId": "RockLib.Example.Secret"
42 | }
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/AssemblySettings.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/ConfigurationBuilderExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.Extensions.Configuration;
3 | using System;
4 | using Xunit;
5 |
6 | namespace RockLib.Secrets.Tests
7 | {
8 | public static class ConfigurationBuilderExtensionsTests
9 | {
10 | [Fact]
11 | public static void AddRockLibSecrets()
12 | {
13 | var builder = new ConfigurationBuilder();
14 |
15 | builder.Sources.Should().BeEmpty();
16 |
17 | var secretsBuilder = builder.AddRockLibSecrets();
18 |
19 | builder.Sources.Should().ContainSingle();
20 | builder.Sources[0].Should().BeOfType();
21 |
22 | secretsBuilder.Should().BeOfType()
23 | .Which.Source.Should().BeSameAs(builder.Sources[0]);
24 | }
25 |
26 | [Fact]
27 | public static void AddRockLibSecretsWithNullBuilder()
28 | {
29 | IConfigurationBuilder builder = null!;
30 |
31 | var act = () => builder.AddRockLibSecrets();
32 |
33 | act.Should().ThrowExactly().WithMessage("*builder*");
34 | }
35 |
36 | [Fact]
37 | public static void AddRockLibSecretsWithConfiguration()
38 | {
39 | var builder = new ConfigurationBuilder();
40 |
41 | builder.Sources.Should().BeEmpty();
42 |
43 | var secret = MockSecret.Get("foo", "bar").Object;
44 |
45 | Action configureSource = source => source.Secrets.Add(secret);
46 |
47 | var secretsBuilder = builder.AddRockLibSecrets(configureSource);
48 |
49 | builder.Sources.Should().ContainSingle();
50 | builder.Sources[0].Should().BeOfType()
51 | .Which.Secrets.Should().ContainSingle(s => ReferenceEquals(s, secret));
52 |
53 | secretsBuilder.Should().BeOfType()
54 | .Which.Source.Should().BeSameAs(builder.Sources[0]);
55 | }
56 |
57 | [Fact]
58 | public static void AddRockLibSecretsWithSourceAndNullBuilder()
59 | {
60 | IConfigurationBuilder builder = null!;
61 |
62 | Action act = () => builder.AddRockLibSecrets(source => { });
63 |
64 | act.Should().ThrowExactly().WithMessage("*builder*");
65 | }
66 |
67 | [Fact]
68 | public static void SetSecretExceptionHandlerMethod()
69 | {
70 | Action onSecretException = context => { };
71 |
72 | var builder = new ConfigurationBuilder();
73 |
74 | builder.SetSecretExceptionHandler(onSecretException);
75 |
76 | builder.Properties[ConfigurationBuilderExtensions.SecretExceptionHandlerKey].Should().BeSameAs(onSecretException);
77 | }
78 |
79 | [Fact]
80 | public static void SetSecretExceptionHandlerMethodWithNullBuilder()
81 | {
82 | IConfigurationBuilder builder = null!;
83 | Action onSecretException = context => { };
84 |
85 | Action act = () => builder.SetSecretExceptionHandler(onSecretException);
86 |
87 | act.Should().ThrowExactly().WithMessage("*builder*");
88 | }
89 |
90 | [Fact]
91 | public static void SetSecretExceptionHandlerMethodWithNullHandler()
92 | {
93 | var builder = new ConfigurationBuilder();
94 |
95 | Action act = () => builder.SetSecretExceptionHandler(null!);
96 |
97 | act.Should().ThrowExactly().WithMessage("*onSecretException*");
98 | }
99 |
100 | [Fact]
101 | public static void GetSecretExceptionHandlerMethod()
102 | {
103 | var builder = new ConfigurationBuilder();
104 | Action onSecretException = context => { };
105 |
106 | builder.Properties.Add(ConfigurationBuilderExtensions.SecretExceptionHandlerKey, onSecretException);
107 |
108 | var secretsProvider = builder.GetSecretExceptionHandler();
109 |
110 | secretsProvider.Should().BeSameAs(onSecretException);
111 | }
112 |
113 | [Fact]
114 | public static void GetSecretExceptionHandlerMethodWithNoHandler()
115 | {
116 | var builder = new ConfigurationBuilder();
117 |
118 | var onSecretException = builder.GetSecretExceptionHandler();
119 |
120 | onSecretException.Should().BeNull();
121 | }
122 |
123 | [Fact]
124 | public static void GetSecretExceptionHandlerMethodWithNullBuilder()
125 | {
126 | IConfigurationBuilder builder = null!;
127 |
128 | Action act = () => builder.GetSecretExceptionHandler();
129 |
130 | act.Should().ThrowExactly().WithMessage("*builder*");
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/MockSecret.cs:
--------------------------------------------------------------------------------
1 | using Moq;
2 | using System;
3 |
4 | namespace RockLib.Secrets.Tests
5 | {
6 | internal static class MockSecret
7 | {
8 | ///
9 | /// Gets a mock secret that returns the specified value.
10 | ///
11 | public static Mock Get(string key, string value)
12 | {
13 | var mockSecret = new Mock();
14 | mockSecret.Setup(m => m.ConfigurationKey).Returns(key);
15 | mockSecret.Setup(m => m.GetValue()).Returns(value);
16 | return mockSecret;
17 | }
18 |
19 | ///
20 | /// Gets a mock secret that throws the specified exception.
21 | ///
22 | public static Mock Get(string key, Exception exception)
23 | {
24 | var mockSecret = new Mock();
25 | mockSecret.Setup(m => m.ConfigurationKey).Returns(key);
26 | mockSecret.Setup(m => m.GetValue()).Throws(exception);
27 | return mockSecret;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/RockLib.Secrets.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/SecretsConfigurationBuilderTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using Xunit;
4 |
5 | namespace RockLib.Secrets.Tests
6 | {
7 | public static class SecretsConfigurationBuilderTests
8 | {
9 | [Fact]
10 | public static void Create()
11 | {
12 | var source = new SecretsConfigurationSource();
13 |
14 | var builder = new SecretsConfigurationBuilder(source);
15 |
16 | builder.Source.Should().BeSameAs(source);
17 | }
18 |
19 | [Fact]
20 | public static void ConstructorWithNullSource()
21 | {
22 | var act = () => new SecretsConfigurationBuilder(null!);
23 |
24 | act.Should().ThrowExactly().WithMessage("*source*");
25 | }
26 |
27 | [Fact]
28 | public static void AddSecretMethod()
29 | {
30 | var source = new SecretsConfigurationSource();
31 |
32 | var builder = new SecretsConfigurationBuilder(source);
33 |
34 | var secret = MockSecret.Get("foo", "bar").Object;
35 |
36 | builder.AddSecret(secret);
37 |
38 | source.Secrets.Should().ContainSingle()
39 | .Which.Should().BeSameAs(secret);
40 | }
41 |
42 | [Fact]
43 | public static void AddSecretMethodWithNullSecret()
44 | {
45 | var source = new SecretsConfigurationSource();
46 |
47 | var builder = new SecretsConfigurationBuilder(source);
48 |
49 | var act = () => builder.AddSecret(null!);
50 |
51 | act.Should().ThrowExactly().WithMessage("*secret*");
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/SecretsConfigurationProviderTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using Xunit;
6 |
7 | namespace RockLib.Secrets.Tests
8 | {
9 | public static class SecretsConfigurationProviderTests
10 | {
11 | [Fact]
12 | public static void Create()
13 | {
14 | var secret1 = MockSecret.Get("key1", "value1").Object;
15 | var secret2 = MockSecret.Get("key2", "value2").Object;
16 |
17 | var source = new SecretsConfigurationSource
18 | {
19 | Secrets = { secret1, secret2 }
20 | };
21 |
22 | using var provider = new SecretsConfigurationProvider(source);
23 |
24 | provider.Source.Should().BeSameAs(source);
25 |
26 | provider.Secrets.Should().NotBeSameAs(source.Secrets);
27 | provider.Secrets.Should().BeEquivalentTo(source.Secrets);
28 | }
29 |
30 | [Fact]
31 | public static void CreateWithNullSource()
32 | {
33 | var act = () => new SecretsConfigurationProvider(null!);
34 |
35 | act.Should().ThrowExactly().WithMessage("*source*");
36 | }
37 |
38 | [Fact]
39 | public static void CreateWithNoSecrets()
40 | {
41 | var source = new SecretsConfigurationSource();
42 |
43 | var act = () => new SecretsConfigurationProvider(source);
44 |
45 | act.Should().ThrowExactly().WithMessage("The SecretsConfigurationSource must contain at least one secret.*source*");
46 | }
47 |
48 | [Fact]
49 | public static void CreateWithNullValueInSecrets()
50 | {
51 | var secret1 = MockSecret.Get("key1", "value1").Object;
52 | ISecret secret2 = null!;
53 |
54 | var source = new SecretsConfigurationSource
55 | {
56 | Secrets = { secret1, secret2 }
57 | };
58 |
59 | var act = () => new SecretsConfigurationProvider(source);
60 |
61 | act.Should().ThrowExactly().WithMessage("The SecretsConfigurationSource cannot contain any null secrets.*source*");
62 | }
63 |
64 | [Fact]
65 | public static void CreateWithNullKeyInSecrets()
66 | {
67 | var secret1 = MockSecret.Get("key1", "value1").Object;
68 | var secret2 = MockSecret.Get(null!, "value2").Object;
69 |
70 | var source = new SecretsConfigurationSource
71 | {
72 | Secrets = { secret1, secret2 }
73 | };
74 |
75 | var act = () => new SecretsConfigurationProvider(source);
76 |
77 | act.Should().ThrowExactly().WithMessage("The SecretsConfigurationSource cannot contain any secrets with a null Key.*source*");
78 | }
79 |
80 | [Fact]
81 | public static void CreateWithDuplicateKeysInSecrets()
82 | {
83 | var secret1 = MockSecret.Get("key1", "value1").Object;
84 | var secret2 = MockSecret.Get("key1", "value2").Object;
85 |
86 | var source = new SecretsConfigurationSource
87 | {
88 | Secrets = { secret1, secret2 }
89 | };
90 |
91 | var act = () => new SecretsConfigurationProvider(source);
92 |
93 | act.Should().ThrowExactly().WithMessage("The SecretsConfigurationSource cannot contain any secrets with duplicate Keys.*source*");
94 | }
95 |
96 | [Fact]
97 | public static void LoadMethod()
98 | {
99 | var secret1 = MockSecret.Get("foo", "abc").Object;
100 | var secret2 = MockSecret.Get("bar", (string)null!).Object;
101 |
102 | var source = new SecretsConfigurationSource
103 | {
104 | Secrets = { secret1, secret2 },
105 | ReloadMilliseconds = Timeout.Infinite
106 | };
107 |
108 | using var provider = new SecretsConfigurationProvider(source);
109 |
110 | provider.Load();
111 |
112 | provider.TryGet("foo", out var fooValue).Should().BeTrue();
113 | fooValue.Should().Be("abc");
114 |
115 | provider.TryGet("bar", out var barValue).Should().BeTrue();
116 | barValue.Should().BeNull();
117 | }
118 |
119 | [Fact]
120 | public static void LoadMethodWhenGetValueThrowsException()
121 | {
122 | var exception = new ArgumentNullException();
123 |
124 | var mockSecret1 = MockSecret.Get("foo", "abc");
125 | var mockSecret2 = MockSecret.Get("bar", exception);
126 |
127 | var caughtExceptions = new List();
128 |
129 | void OnSecretException(SecretExceptionContext context)
130 | {
131 | caughtExceptions.Add(context.Exception);
132 | }
133 |
134 | var source = new SecretsConfigurationSource
135 | {
136 | Secrets = { mockSecret1.Object, mockSecret2.Object },
137 | ReloadMilliseconds = Timeout.Infinite,
138 | OnSecretException = OnSecretException
139 | };
140 |
141 | using var provider = new SecretsConfigurationProvider(source);
142 |
143 | provider.Load();
144 |
145 | provider.TryGet("foo", out var fooValue).Should().BeTrue();
146 | fooValue.Should().Be("abc");
147 |
148 | provider.TryGet("bar", out var barValue).Should().BeTrue();
149 | barValue.Should().BeNull();
150 |
151 | caughtExceptions.Should().HaveCount(1);
152 | caughtExceptions[0].Should().BeSameAs(exception);
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/Tests/RockLib.Secrets.Tests/SecretsConfigurationSourceTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.Extensions.Configuration;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading;
6 | using Xunit;
7 |
8 | namespace RockLib.Secrets.Tests
9 | {
10 | public class SecretsConfigurationSourceTests
11 | {
12 | [Fact]
13 | public void DisableReloadMethod()
14 | {
15 | var source = new SecretsConfigurationSource
16 | {
17 | ReloadMilliseconds = 1000
18 | };
19 |
20 | source.DisableReload();
21 |
22 | source.ReloadMilliseconds.Should().Be(Timeout.Infinite);
23 | }
24 |
25 | [Fact]
26 | public void BuildMethod()
27 | {
28 | var secret1 = MockSecret.Get("key1", "value1").Object;
29 | var secret2 = MockSecret.Get("key2", "value2").Object;
30 |
31 | var source = new SecretsConfigurationSource
32 | {
33 | Secrets = { secret1, secret2 }
34 | };
35 |
36 | var builder = new ConfigurationBuilder()
37 | .AddInMemoryCollection(new Dictionary
38 | {
39 | ["RockLib.Secrets:Type"] = typeof(CustomSecret).AssemblyQualifiedName!
40 | });
41 |
42 | var provider = (SecretsConfigurationProvider)source.Build(builder);
43 |
44 | provider.Source.Should().BeSameAs(source);
45 |
46 | provider.Secrets.Should().NotBeSameAs(source.Secrets);
47 | provider.Secrets.Should().BeEquivalentTo(source.Secrets);
48 |
49 | provider.Secrets[0].Should().BeSameAs(secret1);
50 | provider.Secrets[1].Should().BeSameAs(secret2);
51 | provider.Secrets[2].Should().BeOfType();
52 | }
53 |
54 | [Fact]
55 | public void BuildMethodWhenOnSecretExceptionIsNull()
56 | {
57 | Action onSecretException = context => { };
58 |
59 | var source = new SecretsConfigurationSource
60 | {
61 | Secrets = { MockSecret.Get("key", "value").Object }
62 | };
63 |
64 | var builder = new ConfigurationBuilder();
65 | builder.SetSecretExceptionHandler(onSecretException);
66 |
67 | var provider = source.Build(builder);
68 |
69 | var secretsConfigurationProvider = provider.Should().BeOfType().Subject;
70 | secretsConfigurationProvider.Source.Should().BeSameAs(source);
71 | source.OnSecretException.Should().BeSameAs(onSecretException);
72 | }
73 |
74 | [Fact]
75 | public void BuildMethodAddingSecretsOnFirstCall()
76 | {
77 | var secret = MockSecret.Get("key", "value").Object;
78 |
79 | var source = new SecretsConfigurationSource
80 | {
81 | Secrets = { secret }
82 | };
83 |
84 | var builder = new ConfigurationBuilder()
85 | .AddInMemoryCollection(new Dictionary
86 | {
87 | ["RockLib.Secrets:Type"] = typeof(CustomSecret).AssemblyQualifiedName!
88 | });
89 |
90 | // Before building, source only contains the single secret that was added directly to it.
91 | source.Secrets.Should().ContainSingle(s => ReferenceEquals(s, secret));
92 |
93 | source.Build(builder);
94 |
95 | // After building the first time, the secret defined in configuration has been added to the source.
96 | source.Secrets.Should().HaveCount(2);
97 | source.Secrets[0].Should().BeSameAs(secret);
98 | source.Secrets[1].Should().BeOfType();
99 |
100 | source.Build(builder);
101 |
102 | // After building a second time, source hasn't changed.
103 | source.Secrets.Should().HaveCount(2);
104 | source.Secrets[0].Should().BeSameAs(secret);
105 | source.Secrets[1].Should().BeOfType();
106 | }
107 |
108 | #pragma warning disable CA1812
109 | private sealed class CustomSecret : ISecret
110 | #pragma warning restore CA1812
111 | {
112 | public string ConfigurationKey => "CustomSecret.Key";
113 |
114 | public string GetValue() => "CustomSecret.GetValue()";
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/docs/AwsSecret.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 5
3 | sidebar_label: 'Configure AWS secrets'
4 | ---
5 |
6 | # How to configure AWS secrets
7 |
8 | Add AWS secrets to the `ISecretsConfigurationBuilder` by calling the `AddAwsSecret` method, supplying the following parameters:
9 | - `configurationKey` (required)
10 | - The configuration key for the secret.
11 | - `secretId` (required)
12 | - The Amazon Resource Name (ARN) or the friendly name of the secret.
13 | - `secretKey` (optional)
14 | - The key of the secret in AWS.
15 | - `secretsManager` (optional)
16 | - The client object used for routing calls to AWS. Typically, this is an instance of `AmazonSecretsManagerClient`.
17 |
18 | ```csharp
19 | var builder = new ConfigurationBuilder();
20 | builder.AddRockLibSecrets()
21 | .AddAwsSecret("MyConnectionString", "MyApp", "ConnectionString", new AmazonSecretsManagerClient())
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/Configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | sidebar_label: 'Define secrets in configuration'
4 | ---
5 |
6 | # How to define secrets in configuration
7 |
8 | Any secrets that are defined in the "RockLib.Secrets" section of the *other* sources of a configuration builder are added to the `SecretsConfigurationSource`.
9 |
10 | For example, if a configuration builder has JSON file and RockLib secrets sources added:
11 |
12 | ```csharp
13 | IConfigurationBuilder builder = new ConfigurationBuilder;
14 |
15 | builder.AddJsonFile("appsettings.json");
16 |
17 | // Note: No secrets are added directly to the builder in this example, but if
18 | // any had been, they would also be available from the builder's configuration.
19 | builder.AddRockLibSecrets();
20 | ```
21 |
22 | And the `appsettings.json` file defines one or more secrets in its "RockLib.Secrets" section, like this:
23 |
24 | ```json
25 | {
26 | "RockLib.Secrets": [
27 | {
28 | "Type": "ExampleApp.MyCustomSecret, ExampleApp",
29 | "Value": {
30 | "ConfigurationKey": "MyConnectionString"
31 | }
32 | }
33 | ]
34 | }
35 | ```
36 |
37 | Then when the builder is built, those secrets will be available from the returned `IConfiguration` by the value of their `ConfigurationKey` property.
38 |
39 | ```csharp
40 | IConfiguration config = builder.Build();
41 |
42 | string myConnectionString = config["MyConnectionString"];
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/ConfigurationBuilder.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | sidebar_label: 'Add secrets to configuration builder'
4 | ---
5 |
6 | # How to add secrets directly to a configuration builder
7 |
8 | To add secrets to an `IConfigurationBuilder`, call the `AddRockLibSecrets` extension method on the builder, optionally passing an `Action` for [configuring the source](#secretsconfigurationsource).
9 |
10 | ```csharp
11 | IConfigurationBuilder builder = new ConfigurationBuilder();
12 |
13 | builder.AddRockLibSecrets(source =>
14 | {
15 | // TODO: configure source
16 | });
17 | ```
18 |
19 | This extension method returns an `ISecretsConfigurationBuilder`, which has a `AddSecret` method for adding secrets.
20 |
21 | ```csharp
22 | IConfigurationBuilder builder = new ConfigurationBuilder();
23 |
24 | ISecretsConfigurationBuilder secretsBuilder = builder.AddRockLibSecrets();
25 |
26 | secretsBuilder.AddSecret(new MyCustomSecret(configurationKey: "MyConnectionString"));
27 | ```
28 |
29 | Implementations of `ISecret`, such as `AwsSecret`, have extension methods for simplifying the adding of a secret:
30 |
31 | ```csharp
32 | IConfigurationBuilder builder = new ConfigurationBuilder();
33 |
34 | ISecretsConfigurationBuilder secretsBuilder = builder.AddRockLibSecrets();
35 |
36 | secretsBuilder.AddAwsSecret("MyConnectionString", "MySecretId", "MySecretKey");
37 | ```
38 |
39 | ---
40 |
41 | ## SecretsConfigurationSource
42 |
43 | The `SecretsConfigurationSource` defines several options:
44 |
45 | - `Secrets`
46 | - This property defines the secrets that the source provides.
47 | - The `ConfigurationBuilder` class adds to this property when its `AddSecret` method is called.
48 | - `OnSecretException`
49 | - This callback is invoked whenever the `GetValue()` method of an `ISecret` throws an exception.
50 | - `ReloadMilliseconds`
51 | - This property defines how often the configuration provider reloads its data. Specify `Timeout.Infinite` or call the `DisableReload()` method to disable reloading.
52 |
--------------------------------------------------------------------------------
/docs/GettingStarted.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Getting Started
6 |
7 | In this tutorial, we will be building a console application that prints out the value of secrets read from configuration, by reversing the secret text.
8 |
9 | ---
10 |
11 | Create a .NET Core console application named "SecretsTutorial".
12 |
13 | ---
14 |
15 | Add a nuget reference for "RockLib.Secrets" to the project.
16 |
17 | ---
18 |
19 | Add a class named 'ReversedSecret.cs' to the project. Replace the default code with the following:
20 |
21 | ```csharp
22 | using RockLib.Secrets;
23 | using System.Linq;
24 |
25 | namespace SecretsTutorial
26 | {
27 | public class ReversedSecret : ISecret
28 | {
29 | private readonly string _secret;
30 |
31 | public ReversedSecret(string configurationKey, string secret)
32 | {
33 | ConfigurationKey = configurationKey;
34 | _secret = secret;
35 | }
36 |
37 | public string ConfigurationKey { get; }
38 |
39 | public string GetValue()
40 | {
41 | return new string(_secret.ToCharArray().Reverse().ToArray());
42 | }
43 | }
44 | }
45 | ```
46 | ---
47 |
48 | Add a new JSON file to the project named 'appsettings.json'. Set its 'Copy to Output Directory' setting to 'Copy if newer'. Add the following configuration:
49 |
50 | ```json
51 | {
52 | "FirstSecretKey": "the first secret has not been applied yet",
53 | "SecondSecretKey": "the second secret has not been applied yet",
54 | "ThirdSecretKey": "the third secret has not been applied yet",
55 |
56 | "RockLib.Secrets": [
57 | {
58 | "Type": "SecretsTutorial.ReversedSecret, SecretsTutorial",
59 | "Value": {
60 | "ConfigurationKey": "FirstSecretKey",
61 | "Secret": "terces tsrif eht si sihT"
62 | }
63 | },
64 | {
65 | "Type": "SecretsTutorial.ReversedSecret, SecretsTutorial",
66 | "Value": {
67 | "ConfigurationKey": "SecondSecretKey",
68 | "Secret": "terces dnoces eht si sihT"
69 | }
70 | },
71 | {
72 | "Type": "SecretsTutorial.ReversedSecret, SecretsTutorial",
73 | "Value": {
74 | "ConfigurationKey": "ThirdSecretKey",
75 | "Secret": "terces driht eht si sihT"
76 | }
77 | }
78 | ]
79 | }
80 | ```
81 |
82 | This configuration will set up three secrets that can be accessed through config via the following keys, "FirstSecretKey", "SecondSecretKey", or "ThirdSecretKey".
83 |
84 | ---
85 |
86 | Edit the `Program.cs` file as follows:
87 |
88 | ```csharp
89 | using Microsoft.Extensions.Configuration;
90 | using RockLib.Secrets;
91 | using System;
92 | using System.Diagnostics;
93 |
94 | namespace SecretsTutorial
95 | {
96 | public class Program
97 | {
98 | public static void Main(string[] args)
99 | {
100 | var configBuilder = new ConfigurationBuilder();
101 |
102 | configBuilder.AddJsonFile("appsettings.json");
103 | configBuilder.AddRockLibSecrets();
104 |
105 | var config = configBuilder.Build();
106 |
107 | Console.WriteLine("First Secret: " + config["FirstSecretKey"]);
108 | Console.WriteLine("Second Secret: " + config["SecondSecretKey"]);
109 | Console.WriteLine("Third Secret: " + config["ThirdSecretKey"]);
110 |
111 | Debugger.Break();
112 | }
113 | }
114 | }
115 | ```
116 |
117 | ---
118 |
119 | Start the application. It should print out the reversed secrets, like below:
120 |
121 | ```powershell
122 | First Secret: This is the first secret
123 | Second Secret: This is the second secret
124 | Third Secret: This is the third secret
125 | ```
126 |
--------------------------------------------------------------------------------
/docs/Usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | sidebar_label: 'Use secrets from RockLib.Secrets'
4 | ---
5 |
6 | # How to use the secrets from RockLib.Secrets
7 |
8 | *There's not much to do.*
9 |
10 | ## From a .NET Core app with DI
11 |
12 | For .NET Core applications that use an `IHostBuilder` or `IWebHostBuilder` and call the `ConfigureAppConfiguration` and `AddRockLibSecrets` extension methods, the `IConfiguration` that is DI registered contains the values of the provider's secrets. Such applications have a Program.cs similar to this:
13 |
14 | ```csharp
15 | Host.CreateDefaultBuilder(args)
16 | .ConfigureAppConfiguration(builder => builder.AddRockLibSecrets());
17 | ```
18 |
19 | Controllers and servivces can access secret values through configuration like this:
20 |
21 | ```csharp
22 | public class MyService
23 | {
24 | private readonly IConfiguration _config;
25 |
26 | public MyService(IConfiguration config)
27 | {
28 | _config = config;
29 | }
30 |
31 | public void DoSomething()
32 | {
33 | string connectionString = _config["MyConnectionString"];
34 | }
35 | }
36 | ```
37 |
38 | ## From a built IConfigurationBuilder
39 |
40 | When building an `IConfiguration` or `IConfigurationRoot` directly, secrets can be accessed by their key:
41 |
42 | ```csharp
43 | IConfigurationBuilder configBuilder = new ConfigurationBuilder();
44 |
45 | configBuilder.AddRockLibSecrets()
46 | .AddSecret(new MyCustomSecret(configurationKey: "MyConnectionString"));
47 |
48 | IConfiguration config = configBuilder.Build();
49 |
50 | // Variable will contain whatever MyCustomSecret.GetValue() returned.
51 | string myConnectionString = config["MyConnectionString"];
52 | ```
53 |
54 | ## From Config.Root
55 |
56 | The `Config.Root` property from the RockLib.Configuration library automatically adds the secrets defined in its default configuration. See [defining secrets in configuration](Configuration.md) for more details.
57 |
58 | ```json
59 | {
60 | "MyConnectionString": "the connection string secret has not been applied yet",
61 |
62 | "RockLib.Secrets": {
63 | "Type": "ExampleApp.MyCustomSecret, ExampleApp",
64 | "Value": {
65 | "ConfigurationKey": "MyConnectionString"
66 | }
67 | }
68 | }
69 | ```
70 |
71 | Having the above `appsettings.json` file would allow access to the secrets like this:
72 |
73 | ```csharp
74 | using RockLib.Configuration;
75 | . . .
76 | string myConnectionString = Config.Root["MyConnectionString"];
77 | ```
78 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RockLib/RockLib.Secrets/9a920dd0ac2402cf11c55f99187bce8820a2d7cf/icon.png
--------------------------------------------------------------------------------