Skip to main content
Version: Next

Introducing secret store

As an alternative to the usage of placing secrets into Microsoft's IConfiguration instance in your application, Arcus Security provides a alternative concept called 'secret store'.

We provide an approach similar to how IConfiguration is built, but with a focus on secrets. You can register one or more 'secret providers' where you want your application secrets to be retrieved from.

โšก Caching

We provide caching so the secret providers will not be called upon every secret retrieval. This helps you avoiding hitting service limitations.

๐Ÿ”Œ Plug & play

We support using multiple secret providers and combinations of secret providers so that with a single secret retrieval, you can query multiple secret providers (also multiple Azure Key Vaults).


๐Ÿ›ก๏ธ Security by design

Vulnerabilities are introduced when secrets are seen as data (logs or when expired secrets don't get transient handling upon retrieval). The secret store provides a single place to retrieve secrets instead of scattering the integration across the application.

๐Ÿงฉ Extensibility

Arcus secret store is highly extensible and can be extended with your own custom secret providers.

Register the secret storeโ€‹

The secret store is configured during the initial application build-up with one ore more secret providers:

using Arcus.Security;

var builder = Host.CreateDefaultBuilder(args);

// #1 Via the host builder.
builder.ConfigureSecretStore((config, store) =>
{
store.AddEnvironmentVariables(...);
});

builder.ConfigureServices(services =>
{
// #2 Via the services
services.AddSecretStore(store =>
{
store.AddAzureKeyVault(...);
});
});

The secret store is directly available via the ISecretStore interface which provides multiple secret retrieval options. Upon retrieval, the previously registered secret providers are one-by-one (in the order as they are registered) queried. A failure is returned when no secret is found.

using Arcus.Security;

public class MySqlService
{
private readonly ISecretStore _store;

public MySqlService(ISecretStore store)
{
_store = store;
}

public async Task ConnectAsync()
{
// #1 synchronously
SecretResult result = _store.GetSecret("Contoso_Sql_ConnectionString");
string secretValue = result.Value;

// #2 asynchronously
SecretResult result = await _store.GetSecretAsync("Contoso_Sql_ConnectionString");
if (!result.IsSuccess)
{
SecretFailure failure = result.Failure; // The type of failure that occurred.
Exception cause = result.FailureCause; // When the failure was caused by an exception.
}
}
}

Customization the secret storeโ€‹

โšก Secret caching

To limit possible throttling during external secrets retrieval, the secret store can be customized to cache secrets for a period of time.

builder.ConfigureServices(services =>
{
services.AddSecretStore(store =>
{
store.UseCaching(TimeSpan.FromMinutes(1));
})
});

All secrets are cached by default, but this can be ignored on a by-secret basis.

using Arcus.Security;

ISecretStore store = ...
SecretResult result = store.GetSecret("Contoso_Sql_ConnectionString", options =>
{
options.UseCache = false;
});

Invalidating cached secrets when secrets are externally rotated also happens on the secret store itself.

using Arcus.Security;

ISecretStore store = ...
await store.Cache.InvalidateSecretAsync("Contoso_Sql_ConnectionString");

๐Ÿ”„ Secret mapping

In some cases, you want the application secret name to be something different than the actual name that gets stored externally. Environment variables, for example might be always upper case, but this knowledge might not be something that you want to expose directly to your application -- especially if you want to override certain secrets during development.

All available secret secret providers can be given a custom secret name mapping upon registration. This mapping happens just before actually retrieving the secret (and independently from secret caching).

builder.ConfigureServices(services =>
{
services.AddSecretStore(store =>
{
store.AddEnvironmentVariables(..., options =>
{
options.MapSecretName((string name) => name.ToUpper());
});
})
});

๐Ÿท๏ธ Naming secret providers

All available secret providers can be given a custom name upon registration to identify themselves.

builder.ConfigureServices(services =>
{
services.AddAzureKeyVault(..., options =>
{
options.ProviderName = "AdminSecrets";
});
});

This name can later be used to retrieve back a secret provider in the secret store. This can be useful when you want to run a specific action that is only available on a single secret provider (ex. secret versions).

using Arcus.Security;

ISecretStore store = ...
var provider = store.GetProvider<KeyVaultSecretProvider>("AdminSecrets");
grouping secret providers

Multiple secret providers can be registered with the same name. Upon retrieval, the matching secret providers are returned as a 'subset'.

using Arcus.Security;

ISecretProvider provider = store.GetProvider("AdminSecrets");

Built-in secret providersโ€‹

The following secret providers are always available in your application, no matter which secret provider NuGet package you install.

Include the application configuration (IConfiguration) as a secret provider in the secret store. This is useful for development, but SHOULD NOT be used in production.

builder.ConfigureSecretStore((IConfiguration config, store) =>
{
#if DEBUG
store.AddConfiguration(config);
#endif
});