How many times you almost pushed some sensitive data to public repo? I have to admit, it’s one of my nightmares since I heard about crawlers occuping github and searching for credit cards data, API credentials and so on.
But it looks like this problem is over, at least for .Net Core developers. And it is because of one simple tool provided by Microsoft. It’s name is…
Secret Manager
You may have heard about it earlier and how it’s managed through console, so let me introduce another approach – managing secrets with Visual Studio interface.
Here is a description how to install Secret Manager. When you do it, we can start playing.
In Visual Studio (in my case – 2015) click right mouse button on the Web project.
An empty file secrets.json appears and we type there sensitive settings like connection string and APIs credentials. In my case it looks like this:
But how, where?
If you haven’t heard about Microsoft’s secrets before, you may be curious where this magical secrets.json file is saved. Especially, when you remember that it so smartly avoids sending itself to your repo. So here we go – path to a secrets.json file on the below screen.
As you can see, the file is saved locally so you don’t have to add additional rule to the .gitignore file. Your secrets will stay on your computer. It is worth mentioning that these data are saved in plain text so if someone has access to your machine and wants to steal them, he still can do it – keep it in mind.
References
To avoid problems, I recommend installing (or checking version, if they are installed) the below packages from NuGet in the project that reads configuration (I do it in separate class library project).
Microsoft.Extensions.Configuration (v.1.1.0) Microsoft.Extensions.Configuration.Abstractions (v. 1.1.0) Microsoft.Extensions.Configuration.EnvironmentVariables (v.1.0.0) Microsoft.Extensions.Configuration.FileExtensions (v.1.1.0) Microsoft.Extensions.Configuration.Json (v.1.1.0) Microsoft.Extensions.Configuration.UserSecrets (v.1.1.0)
Reading Secrets in your code
Ok, we saved our sensitive data and want to read them in the code. So what we do?
I, for example, created a helper class that will read configuration from the following sources (the order is important!) :
- appsettings.json file
- appsettings.{environmentName}.json – which means that I can have a few appsettings files, one for each environment
- environment variables – these are the variables stored in OS. Unfortunately, they are not encrypted but it’s safer option than json or xml file in the project folder, anyway
- user secrets – 🙂
The code looks like this (it’s similar to this post but notice the difference in line 16):
private static IConfigurationRoot BuildConfiguration(string path, string environmentName = null, bool addUserSecrets = false) { var builder = new ConfigurationBuilder() .SetBasePath(path) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); if (!String.IsNullOrWhiteSpace(environmentName)) { builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true); } builder = builder.AddEnvironmentVariables(); if (addUserSecrets) { builder.AddUserSecrets<ConfigurationHelper>(); } return builder.Build(); }
As you can see – we just read one by one the sources I listed above. If some source exists, its values overrides the ones read before.
Errors, errors everywhere!
Although you may install all the above NuGet packages, you still may get some strange errors, like the ones below.
Could not load file or assembly 'Microsoft.Extensions.Configuration.Json, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
…Or any similar error saying that some assembly cannot be load, although it is imported from NuGet.
Solution: If you read secrets in some class library project, check if your lib is referenced in any other project. Probably it is, so in this second project check if NuGet package from the error message is in the same version. If not, change package version so they match.
Could not find 'UserSecretsIdAttribute' on assembly 'ReMaster.Utilities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Solution: I’m not sure, if it is a good solution, but it works :). You have to add the following line just before the body of the class that reads secrets. In my case:
[assembly: UserSecretsId("aspnet-ReMaster.Web-20170309024542")] namespace ReMaster.Utilities.Tools { public class ConfigurationHelper { ….} }
If you don’t know where this green part comes from, just go to project.json of the web project and at the end of the file you will find something like this:
"userSecretsId": "aspnet-ReMaster.Web-20170309024542"
As you can see, this is the same string.
What if I run the project and I don’t have Secrets Manager?
It’s ok! You just change the configuration in appsettings.json file and it will work just fine! But there is one important rule – you have to have a copy of secrets settings in your appsettings.json. It’s great, if sensitive data in appsettings.json are some meaningful value like ‘CHANGE IT HERE, STUPID’. If you obey this rule, every new person in your team will be able to run a project with no need of installing anything.