Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does .Net Core support User Secrets per environment?

Let's say I have connection string for Development environment specified in appsettings.Development.json and connection string for the Staging environment specified in appsettings.Staging.json

All I need to do to switch between Development and Staging is to navigate to Visual Studio Debug tab in project properties and change the value for ASPNETCORE_ENVIRONMENT environment variable.

Now, of course I don't want to have connection string in appsettings.*.json for security reasons. So I move it to User Secrets.

Problem is - it seems there is just one secrets.json file that is used by all the environments. There are no secrets.Development.json or secrets.Staging.json. This means after I switch from Development to Staging environment via Visual Studio Debug tab I then also need to change connection strings manually in secrets.json which kind of defeats the purpose of having built-in support for the environments.

Is this correct that User Secrets are not supported on per-environment basis? If so - is there another approach that would avoid having to modify Secret connection string manually when switching environments?

like image 864
Joe Schmoe Avatar asked Feb 27 '20 16:02

Joe Schmoe


People also ask

What is user secrets in .NET Core?

The user secrets configuration provider registers the appropriate configuration source with the . NET Configuration API. ASP.NET Core web apps created with dotnet new or Visual Studio generate the following code: C# Copy.

Where are .NET user secrets stored?

In a Windows machine, they are stored in the %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets. json file. In a Linux/macOS machine, they are stored in the ~/. microsoft/usersecrets/<user_secrets_id>/secrets.

Which environment values are Bydefault supported by .NET Core?

ASP.NET Core uses an environment variable called ASPNETCORE_ENVIRONMENT to indicate the runtime environment. The value of this variable can be anything as per your need but typically it can be Development, Staging, or Production. The value is case insensitive in Windows and Mac OS but it is case sensitive on Linux.

How do I use user secrets in dotnet?

Creating User Secrets via Visual Studio By far the easiest way to use User Secrets is via Visual Studio. Right click your entry project and select “Manage User Secrets”. Visual Studio will then work out the rest, installing any packages you require and setting up the secrets file! Easy!


3 Answers

If you check the tool's parameters with dotnet user-secrets --help you'll see you can specify different secrets per configuration (Debug, Release, any other you want) but not per environment. Which is not a bad decision if you think about it.

The ASPNETCORE_ENVIRONMENT environment variable is meant to tell your application whether the current machine or container is a Development, Production or other environment, so it can pick the appropriate settings file. This environment variable isn't expected to change from one application execution to the next. Even when using containers, the environment variables are passed from the host to the container and aren't expected to change during the container's lifetime.

The secrets files are supposed to be per machine, for development purposes, so there's no need to keep separate files per environment. It makes much more sense to use separate files for configuration, allowing developers to simply change from Dev to Release or Testing or any other custom configuration they may have.

Specifying secrets per configuration

The dotnet user-secrets tool works by reading the UserSecretsId value from the project file and storing the secrets in a JSON file with the same name, eg c952ecfc-344e-43e1-bb67-1ac05973d6c6.json. It's possible to store a UserSecretsId for each configuration.

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <UserSecretsId>c952ecfc-344e-43e1-bb67-1ac05973d6c6</UserSecretsId>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <UserSecretsId>7D104000-2230-4EDE-8AE6-63BDDA0BD0C5</UserSecretsId>
</PropertyGroup>

When the -c parameter is used to specify a configuration, the user-secrets tool will read the UserSecretsId value from the corresponding section and use it to store or read secrets.

The dotnet user-secrets init command doesn't recognize the -c parameter, so the csproj file needs to be modified directly.

Once that's done, one can set and read secrets by specifying the configuration, eg :

❯ dotnet user-secrets set -c Debug Key1 Value1
Successfully saved Key1 = Value1 to the secret store.

❯ dotnet user-secrets set -c Release Key1 Value2
Successfully saved Key1 = Value2 to the secret store.

❯ dotnet user-secrets list -c Debug
Key1 = Value1

❯ dotnet user-secrets list -c Release
Key1 = Value2
like image 83
Panagiotis Kanavos Avatar answered Nov 01 '22 05:11

Panagiotis Kanavos


I also needed this and I think I've come up with an elegant solution.

secrets.json file is shared among all environments you are using, what you can do is add the environment parent to each node in the file and then do the little trick (last 2 code fragments).

Suppose you have a configuration, e.g. in appsettings.json or appsettings.{environment}.json:

{
  "Key1": "value1",
  "Secret1": "<set yourself>"
}

and then you have the secret part in secrets.json:

{
  "Secret1": "my secret value"
}

you can get and bind the whole section easily:

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddJsonFile($"appsettings.{environment}.json")
    .AddUserSecrets<Program>()
    .Build();
var myConfiguration = configuration.Get<MyConfiguration>();

Now comes the problem, I want to have multiple environments in the secrets.json. I personally used to have secrets for all environments I needed and just comment/uncomment what I wanted, however, it's manual work. So I will prefix them with environment name instead.

{
  "Development:Secret1": "my secret development value",
  "Staging:Secret1": "my secret staging value"
}

You have to load the environment-specific configuration(s) from the IConfiguration instance and override the existing myConfiguration values using:

configuration.GetSection(environment).Bind(myConfiguration);
\\ or
configurationRoot.Bind(environment, configuration);

And that's it.

If you run it using environment="Development", you will have "my secret development value" loaded. If you run it using environment="Staging", you will have "my secret staging value" loaded.

Additional details

The double dot character (:) acts as a new section, so if you write

{
  "Development:Secret1": "my secret development value",
  "Staging:Secret1": "my secret staging value"
}

it's the same as

{
  "Development":
  {
    "Secret1": "my secret development value"
  },
  "Staging":
  {
    "Secret1": "my secret staging value"
  }
}

the trick is about loading just the environment-specific part and binding it to the myConfiguration instance.

I must mention that no matter what environment you are using, all the secrets are actually loaded in the memory.

like image 45
Jan Vargovsky Avatar answered Nov 01 '22 05:11

Jan Vargovsky


The Secret Manager (https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1) is designed strictly for development, not any other stage (environment), since it is inherently insecure (local dev secrets are not encrypted). See the warning on the page linked. So there is no need to have per environment secrets storage vis-a-vis that tool. For other environments (staging, prod, etc), Microsoft would likely steer you toward their secure secrets storage service -- Key Vault. You can use the Secret Manager for dev secrets and then store the other environments in Key Vault. I have done this in many Asp.Net Core apps and it works well. For Key Vault info, see this: https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-3.1

like image 34
Bryan Lewis Avatar answered Nov 01 '22 06:11

Bryan Lewis