This post will be the first in a two part blog series aimed at helping you, the .NET developer, to keep your code free of secrets. In days gone by developers would just add their secret api key or database credentials into the web config files and away they go. The problem with that is of course is that it’s a security nightmare. Passwords should really only be *known* on an ‘as needed’ basis
SAP Data Services, with which I have more than a passing acquaintance, gets around this by exposing all of the connectivity data via a web portal meaning that in practise… software developers need actually never know the password of the data to which they connect. It can instead all be maintained by Database Administrators, in practise of course that almost never happens. Its still the case though that the ‘unencrypted’ password is never exposed to the user once entered. The same cannot be said of your average web config. You could of course encrypt your configuration files but that still generally keeps the whole process firmly in the realm of developers, who in a large secure organisation don’t *need* to have access to the database. This blog will instead give you a solution whereby you can keep this secret data external to your application at all times. This reduces the surface area of any attack vectors on your organisation via the following:-
- Your public facing source code repository contains no secrets.
- Your public facing web server contains no secrets.
- Your development team need be exposed to no production secrets.
- Development team instead work on off line test data. It goes without saying that there should be zero security correlation with the production system and that the resultant data should be obfuscated if required
This blog will thus be split into two parts, the remainder of this part (Part 1) will be dedicated to removing the secrets from your development code. Part 2 will be concerned with how to deal with these secrets in your production code. The approaches that I have taken will require that there be no code changes between the two, code executed to obtain secrets in the development environment will execute identically in the production environment. Thats not to say that there won’t be set up code that *does* behave differently in those two environments. On with the show.
I’m sure that by now you’ve likely realised that the first approach for dealing with this secret free code is in fact the ‘User Secrets’ functionality included with .NET core. User Secrets is a mechanism which has to be initiated and will allow you to store some configuration data in an ‘Off Project’ location. The data will be stored in a standard JSON configuration file and will not be encrypted. “Not Encrypted” I hear you say… “Yes, Not Encrypted”. This should not be a problem as at this juncture we are talking about development data.
- Development data should contain no sensitive data
- In the development environment you should be connecting to development only database servers, or at worst using integrated security so that your security is controlled by your DBA. Personally in a development environment I would always use integrated security.
- API keys will likely be test api keys
- Configuration will be stored in an off project location. If a hacker has *this * much access to a machine you need to be more worried about this breach than with regard to a few development sets of credentials
So, as you see not encrypted should not really not be a problem. Lets get started with this, in this instance I have a working .NET Core MVC web application that I want to leverage the secret stores for housing my database connection string.
User Secrets need to be initiated in any given project (it is project, not solution based). So we head over to the Package Manager Console window and initialise it from here using the following syntax:-
dotnet user-secrets init --project projectName
Note that the projectName parameter should be replaced with your actual project name. In addition this is only required in a solution where you have multiple projects. Personally I’m a bit of a purist and I always separate my solution into UI/Service/Data/Model layers using separate projects. Here is the code utilised in my ‘Greenfingers’ project
As you can see a new UserSecretsID guid is created. Lets take a look at how this is used. If we take a look at our project file (XML view) you will see that the following fragment has been inserted. As you can see this guid matches UserSecretsID that was generated above.
In addition if we look at our file system you will also note the following:-
This standard JSON configuration file will be where our unencrypted secrets will be kept. At the moment, as your’d expect its just an empty file. Lets try adding our secret to the file. There are a couple of ways you can do this, you can interact directly with the file using your favourite text editor (for that’s Sublime of course) or alternatively we can use the user secrets app itself. Lets try the latter. We set a variable by using the following syntax
dotnet user-secrets set "PropertyName" "PropertyValue" --project ProjectName
Thus below I am setting the “ConnectionStrings:TrustedUser” to point at my database.
Lets read that value back and see how it was saved. We can’t read individually but we can list the entire contents of the file like so.
dotnet user-secrets list --project ProjectName
As you can see the item we saved appears in our list. Lets take a look directly inside our file now
Thats a little odd. To be honest the : separator is supposed to be a JSON object separator and so I would have expected the format of the document to be more….. JSONish. I thus directly edited the document to this.
I then saved the document and then re-ran the list statement and it of course returned exactly the same correct data.
Sadly it seems that there are formatting issues with the tool, it doesn’t effect the operations but it does, however, affect the readability. For me this is enough to outright prevent me from using it. Should you wish to continue using the tool you can also remove items using the following syntax
dotnet user-secrets remove "PropertyName" --project projectName
Once you have your settings applied within your secrets file (however you wish to format them!) plugging all of this functionality in is ….. not actually required. The DefaultBuilder actually supports User Secrets out of the box. This is the default out of the box Program.cs file for a .net core MVC web project.
If we take a look at the Microsoft documentation for this Host.CreateDefaultBuilder you’ll see the following highlighted fragment which tells you just this.
All that thus remains in this example is to write the code that consumes this configuration data. In our situation we need to it to prime our database context when we are setting up our service and nothing more. So its really a very simple one line change. Remember this one line will function exactly the same whether we are in Development where we will be extracting our data from the User Secrets file or whether we are in Production which is the subject of the next blog in this series
Next Up, Keeping Secrets in the Production environment….