19

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?

Joe Schmoe
  • 1,574
  • 4
  • 23
  • 48
  • No familiar with the API, but could you use https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.configurationextensions.addusersecrets?view=dotnet-plat-ext-1.1#Microsoft_Extensions_Configuration_ConfigurationExtensions_AddUserSecrets_Microsoft_Extensions_Configuration_IConfigurationBuilder_System_String_ ? – Matthew Feb 27 '20 at 16:21
  • You can use separate `secrets` files for Debug, Release etc – Panagiotis Kanavos Feb 27 '20 at 16:51
  • @PanagiotisKanavos what's the trick to specify different secrets per configuration? The `--configuration` (`-c`) parameter doesn't seem to do anything for `dotnet user-secrets` commands. I've tried with `dotnet --version` 5.0.403. – Bryan Knox Nov 11 '21 at 18:57
  • 1
    @BryanKnox I added that to the question – Panagiotis Kanavos Nov 12 '21 at 10:17

5 Answers5

16

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
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thanks @PanagiotisKanavos! Adding the info and examples for the `` elements with the `Condition` attributes in the project file really helps. – Bryan Knox Nov 14 '21 at 00:23
12

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.

Jan Vargovsky
  • 183
  • 2
  • 8
2

It is possible to use a different secrets file for a different environment. Secrets are located in %APPDATA%\Microsoft\UserSecrets

You can create a new folder MyProject-MyCustomEnvironment inside the UserSecrets folder, and put in there the desired secrets.json for the new environment. The secrets file must be named exactly secrets.json. MyProject-MyCustomEnvironment will be the secrets id.

Then, in Program.cs, we just need to pass a different secrets id depending on the environment:

var builder = WebApplication.CreateBuilder();
            
if (builder.Environment.IsEnvironment("MyCustomEnvironment"))
{
    builder.Configuration.AddUserSecrets("MyProject-MyCustomEnvironment");
}

When the environment is Development, the standard secrets file is added by default. Its folder name matches the secrets id defined in UserSecretsId element of the project csproj file.

Marcello
  • 21
  • 3
0

The Secret Manager (https://learn.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://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-3.1

Bryan Lewis
  • 5,629
  • 4
  • 39
  • 45
  • That's not what the OP asked. The question is if it's possible to pick different "secrets" files on the dev machine simply by changing the environment variable. – Panagiotis Kanavos Feb 27 '20 at 16:43
  • In rereading it, perhaps I did misunderstand. If OP has these multiple environments all on the same machine and wants to use the development Secrets Manager for all of them, then perhaps my answer is not relevant (although you can use Key Vault locally if you want). – Bryan Lewis Feb 27 '20 at 16:50
  • @BryanLewis yes, you misunderstood me. I am aware of Key Vault. I also used Windows Credential Manager for the same purpose. – Joe Schmoe Feb 27 '20 at 21:05
  • Up voted because this is an important point. The user secrets.json file is only merged with appsettings.json when IsDevelopment = true. So you can flip between appsettings in the IDE by setting the environment, but secrets is fixed to development only. – Yogi Nov 02 '20 at 17:03
  • You forgot about regions. We can have it multiple and separate secret per region still in dev environment. – SerG Jan 12 '22 at 17:50
0

There is no "out of the box" way to use different secrets per environment. However you can use the options pattern to bind your configs depending on environment:

Create a secrets.json with your environments at the top level:

{
  "Local": {
    "Secret1": "local secret",
    "ConnectionStrings": {
      "DB": "LocalDBConnectionstring"
    }
  },

  "Development": {
    "Secret1": "dev secret",
    "ConnectionStrings": {
      "DB": "DevDBConnectionstring"
    }
  }
}

Create classes to map your secret configurations:

public class SecretConfigurationEnvironment
{
    public string secret1 { get; set; }
    public ConnectionStringsConfig ConnectionStrings { get; set; }
}

public class ConnectionStringsConfig
{
    public string DB { get; set; }
}

Map the secrets to your classes in your program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<SecretConfigurationEnvironment>(
    builder.Configuration.GetSection(builder.Environment.EnvironmentName));

Use your configs with dependencyInjection whereever you need them:

public class MyClass {
   private readonly IOptions<SecretConfigurationEnvironment> _secretConfig;

   public MyClass(IOptions<SecretConfigurationEnvironment> secretConfig) {
      _secretConfig = secretConfig
   }

   public void MyMethod() {
      var envDBConnectionString = _secretConfig.value.Connectionstrings.DB
   }
}

Now your configs will change depending on your environment and you can still use all your configs stored in the appsettings.environment.json files (also with options if you like)

That being said, here is the usual disclaimer: Don't use secrets.json for staging or production secrets.

The code i provided is for .net 6, wep-api. Older versions or different project-types are very similar though and can be found at the microsoft documentation.

Tombalabomba
  • 181
  • 1
  • 7