How to Prevent App Secrets From Ending up in Source Control for DotNet

How to Prevent App Secrets From Ending up in Source Control for DotNet

Keeping secrets out of your git repositories is one of those lessons that people don't take seriously until it's too late. All the stories from people who learned this the hard way involve someone who hacked their cloud account and racked up charges anywhere from a few thousand to $300k.

Should I Use Environment Variables

The answer in most languages is to use environment variables. Docker supports injecting environment variables. Azure supports injecting environment variables for App Services. The launchSettings.json even has an area for injecting environment variables.

So the answer is Environment Variables, right?

Secrets Manager

Microsoft actually recommends the use of Secret Manager. It's kind of like a mini azure key vault that you run locally to store your secrets and it's very easy to setup locally.

However, I find that this approach doesn't really work for me. Because it bundles ALL secrets together. Of course, you could namespace your keys, and maybe that is the correct solution, but what if there was another way?

AppSettings or launchSettings.json

We have AppSettings that allows us to store non secret environment specific variables and configurations. And then, we also have the launchSettings.json, which gives us the power of different profiles, setting environment variables to override AppSettings, etc.

Many people use an AppSettings.DevelopmentLocal.json and gitignore that file. But that means I can only store my local environments variables. If I need to troubleshoot something in another env it can become cumbersome.

In the beginning, people were storing their secrets in launchSettings.json and adding that file to the git ignore file. This allowed users to set their own profiles and keep their secrets close to the code without worrying about committing them up to a repo. But then the mindset changed, and it's been recommended to keep the launchSettings.json in your repo.

Reasons for making this change:

Ignoring launchSettings.json does not make much sense. Now .NET CLI even considers this file when running with dotnet run, as you can read here.

This settings will be useful if shared among project members, so it should be commited to the repo.

how I reacted to that stack overflow

This means that someone can check that box to make me launch a browser every time I build and that gets committed to the repo. This also begs the question of "why have AppSettings AND launchSettings.json?"

Another Way

I said there was another way, and there is. It involves a bit of git trickery though.

I propose committing your base launchSettings.json to the repository. This file rarely really changes, since most configs can be put into the AppSettings. This allows us to have the file in the repo and it can still be read by the dotnet run command.

Then we tell git to ignore any local changes to this file.

git update-index --skip-worktree Properties/launchSettings.json

this will now allow us to put all of our secrets into the launchSettings.json as profiles for the environments we want.

In the rare instance where you need to actually make a shared update to this file, you can turn it off. First make a backup of your file, and be very careful to not commit any of those secrets you had in there.

git update-index --no-skip-worktree Properties/launchSettings.json

Using this command, you'll be able to track changes to the launchSettings.json again.

Conclusion

Is it a bit hacky? Yes. Do you have to follow this method? No. You do you. But this is the most flexible solution for me. It allows me to keep my secrets close to the code I'm actually working with, and allows me to have profiles for local, dev, and QA as in case I need to troubleshoot the various phases.

Think I'm a moron and have a better way? Leave me a comment. I love hate mail.