- Separate config/secrets from source code, your configuration is sensitive, configuration strings may contain passwords or API keys or other secrets. Having this information exposed may leave your system vulnerable. You want to avoid storing any of the data in source code as your source code will most likely end up in a repo on GitHub or a similar place. Even though it's a private repo it may be exposed. Better to store this elsewhere.
- Values are different in different environments, the values you use for DEV, Staging, and Prod are hopefully different when it comes to connecting to a database or API. You need to acknowledge what's different so you can separate this out as configuration that needs to be replaced per environment.
- Operating systems are different, you might think that it's enough to make all secrets into environment variables, and you are done. However, you might have so much configuration that you need to organize it in a hierarchy like so "api:<apitype>:<apikey>". One problem though, isn't supported on all OSs but other characters are like ___. The point being is that you want an abstraction layer to organize your secrets/config.
References
Secret manager tool
- The tool is great for local dev, The secret manager tool is great for local development but that's where it should stay.
- Environment variables are not safe, your machine might be compromised and Environment Variables are plain text and not encrypted. So even though it's tempting to rely on Environment Variables and store those in AppSetting in Azure you want to look into more safe ways of handling secrets like
~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json
DEMO manage secrets
- Initialization, this is how you generate an id and creates a file that will contain your secrets
- Setting a value, this is about setting a value, either from a terminal or from code.
- Accessing a value, this can be done from both terminal and code.
- Removing a value, it's good to know how to remove a value when you no longer need it.
Initialization
1) First, create a .NET Core Web API project by typing the following command in the terminal:
dotnet new webapi -o api --no-https
cd api
2) Type the following command:
dotnet user-secrets init
The terminal should give you an output like so:
Set UserSecretsId to '<secret-id>' for MSBuild project '/path/to/your/project/project.csproj'.
3) Open up your project file that ends in .csproj.
<UserSecretsId>secret-id</UserSecretsId>
Setting a value
dotnet user-secrets list
No secrets configured for this application.
1) In the terminal type the following:
dotnet user-secrets set "ApiKey" "12345"
2) List the content of the secret store again:
ApiKey = 12345
ProductsUrl
ProductsApiKey
3) Type the following command to create *grouped* secrets:
dotnet user-secrets set "Products:Url" "http://path/to/product/url"
dotnet user-secrets set "Products:ApiKey" "123abc"
You should get the following output:
Successfully saved Products:Url = http://path/to/product/url to the secret store.
Successfully saved Products:ApiKey = 123abc to the secret store.
4) List the content of your secret:
Products:Url = http://path/to/product/url
Products:ApiKey = 123abc
ApiKey = 12345
Accessing a value
1) Open up `Startup.cs` and note how the constructor already inject it like so:
public Startup(IConfiguration configuration) {}
2) Locate ConfigureServices() method and add the following code to retrieve and display the secret:
public void ConfigureServices(IServiceCollection services)
{
ApiKey = Configuration["Products:Url"];
Console.WriteLine(ApiKey);
services.AddControllers();
}
dotnet build && dotnet run
http://path/to/product/url
We are about to register a singleton, this is something we can use and inject it into a service or controller. The singleton will contain the API keys or other secrets we might need to access.
4) Create a file AppConfiguration.cs and give it the following content:
namespace webapi_secret
{
public class AppConfiguration
{
public string ApiKey { get; set; }
public Product Product { get; set; }
}
}v
5) Go back to Startup.cs, locate the ConfigureServices() method, and add the following lines:
var config = new AppConfiguration();
config.ApiKey = Configuration["Products:Url"];
services.AddSingleton<AppConfiguration>(config);
using Microsoft.AspNetCore.Mvc;
namespace webapi_secret.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
AppConfiguration _config;
public ProductsController(AppConfiguration config)
{
this._config = config;
}
[HttpGet]
public string Get()
{
return this._config.ApiKey;
}
}
}
Note how we inject AppConfiguration in the constructor and assign the instance this._config = config;. Note also how we construct a method Get() and return config key:
[HttpGet]
public string Get()
{
return this._config.ApiKey;
}
7) Build and run this with the following command:
dotnet build && dotnet run
😎 Navigate to https://localhost:5001/products
This should print out the ApiKey.
Create a singleton HTTP service with the configuration
1) Create a file HttpService.cs and give it the following content:
namespace webapi_secret
{
public class HttpService
{
private string _apiKey;
public HttpService(string apiKey)
{
this._apiKey = apiKey;
}
public string ApiKey { get { return _apiKey; } }
}
}
2) Go to the file Startup.cs and locate the ConfigureService() method and the following line:
services.AddSingleton<HttpService>(new HttpService(Configuration["ApiKey"]));
Dealing with namespaces
1) Create a file ProductConfiguration.cs and give it the following content:
public class ProductConfiguration
{
public string Url { get; set; }
public string ApiKey { get; set; }
}
2) Go to Startup.cs and add the following code to ConfigureServices():
var productConfig = Configuration.GetSection("Products")
.Get<ProductConfiguration>();
services.AddSingleton<ProductConfiguration>(productConfig);
using Microsoft.AspNetCore.Mvc;
namespace webapi_secret.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
AppConfiguration _config;
ProductConfiguration _productConfig;
public ProductsController(AppConfiguration config, ProductConfiguration productConfiguration)
{
this._config = config;
this._productConfig = productConfiguration;
}
[HttpGet]
public string Get()
{
// return this._config.ApiKey;
return this._productConfig.ApiKey;
}
}
}
Removing a value
dotnet user-secrets remove "ApiKey"
dotnet user-secret clear
Summary
- Adding secrets
- Reading secrets from command-line and from code
- Configure DI instances to be populated by secrets
- Remove secrets