diff --git a/docs/versioned_docs/version-v1.8.0/02-Guides/add-secret-store-with-keyvault-integration.md b/docs/versioned_docs/version-v1.8.0/02-Guides/add-secret-store-with-keyvault-integration.md new file mode 100644 index 00000000..e805786a --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/02-Guides/add-secret-store-with-keyvault-integration.md @@ -0,0 +1,139 @@ +--- +title: "Add secret store with Azure Key Vault integration" +layout: default +--- + +# Add secret store with Azure Key Vault integration +The Arcus secret store is a alternative on the general usage of storing secrets in the application configuration (`IConfiguration`). It is important in application development to differentiate between configuration data and sensitive information like secrets. The Arcus secret store is extremely flexible and can be extended to support several secret providers to retrieve its secrets - both built-in as well as custom. + +The secret store can be added during any part of de application lifetime. Once added, you can benefit from the safe and convenient way of secret retrieval that is the Arcus secret store. + +This user guide will cover how the Arcus secret store can be added to an existing API application in order to retrieve secrets from Azure Key Vault. + +## Terminology +To fully understand the power of the secret store, some terminology has to be understood: +- **Secret**: piece of sensitive information like access keys or connection strings; anything that is private and cannot be made public +- **Secret store**: central place where the application retrieve its secrets. +- **Secret provider**: implementation that provides secrets to the secret store (ex.: Azure Key Vault secret provider), many secret providers can be configured within a secret store. + +## Sample application +In this user guide, a fictive API application will be used to add the secret store to. We will be working with two major parts. + +The initial place where the application will be started: +```csharp +public class Program +{ + public static void Main(string[] args) + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddJsonFile("appsettings.json"); + + WebApplication app = builder.Build(); + app.UseRouting(); + app.Run(); + } +} +``` + +And the API controller where a secret is being used: +```csharp +[ApiController] +public class OrderController : ControllerBase +{ + private readonly IConfiguration _configuration; + + public OrderController(IConfiguration configuration) + { + _configuration = configuration; + } + + [HttpPost] + public async Task Post([FromBody] Order order) + { + string connectionString = _configuration["Azure:ServiceBus:ConnectionString"]; + + // Post Order to Azure Service Bus... + } +} +``` + +> Note that in this example, we use the application configuration to retrieve the Azure Service Bus connection string to post the incoming order to Azure Service Bus and not the Arcus secret store. + +## Use Arcus secret store +For us to move away from the application configuration, we need to make use of the Arcus secret store. The following step instructions will guide you in this process: + +### 1. Install Arcus security +For this example, we will be using Azure Key Vault as our single secret provider in the secret store, so we can install this directly: +```shell +PM > Install-Package Arcus.Security.Providers.AzureKeyVault +``` + +> Note that this package depends on the `Arcus.Security.Core` package, where the secret store exists. + +### 2. Add Arcus secret store to application +Once the package is installed, add the secret store via extensions to the API application: +* 2.1 Use the `.ConfigureSecretStore` to setup the secret store with necessary secret providers +* 2.2 Use the the `.AddAzureKeyVaultWithManagedIdentity` to add the Azure Key Vault secret provider to the secret store + +```csharp +using Arcus.Security.Core.Caching.Configuration; + +public class Program +{ + public static void Main(string[] args) + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddJsonFile("appsettings.json"); + + builder.Host.ConfigureSecretStore((configuration, stores) => + { + string vaultUri = configuration["Azure:KeyVault:VaultUri"]; + stores.AddAzureKeyVaultWithManagedIdentity(vaultUri, CacheConfiguration.Default); + }); + + WebApplication app = builder.Build(); + app.UseRouting(); + app.Run(); + } +} +``` + +> Note that during the configuration of the secret store, you will be able to access the application configuration; in this case it is used to retrieve the URI where the Azure Key Vault is located. + +### 3. Inject the Arcus secret store in application +Now that the Arcus secret store is added and configured to the application, the application can use it to retrieve it secrets. The Arcus secret store is accessible throughout the application via the `ISecretProvider` interface - combining all the configured secret providers. + +In the `OrderController`, inject the `ISecretProvider` interface via the constructor. The `ISecretProvider` will allow you to retrieve the Azure Service Bus connection string from the secret store. +```csharp +using Arcus.Security.Core; + +[ApiController] +public class OrderController : ControllerBase +{ + private readonly ISecretProvider _secretProvider; + + public OrderController(ISecretProvider secretProvider) + { + _secretProvider = secretProvider; + } + + [HttpPost] + public async Task Post([FromBody] Order order) + { + string connectionString = await _secretProvider.GetRawSecretAsync("Azure_ServiceBus_ConnectionString"); + + // Post Order to Azure Service Bus... + } +} +``` + +## Conclusion +In this user guide, you've seen how the Arcus secret store can be added to an existing application to retrieve secrets. The Arcus secret store is a very wide topic and can be configured with many different options. See [this documentation page](../03-Features/secret-store/index.md) to learn more about the Arcus secret store. + +## Further reading +- [Arcus secret store documentation](../03-Features/secret-store/index.md) + - [Azure Key Vault secret provider](../03-Features/secret-store/provider/key-vault.md) + - [Create your own secret provider](../03-Features/secret-store/create-new-secret-provider.md) +- [Introducing Arcus secret store](https://www.codit.eu/blog/introducing-secret-store-net-core/) +- [The power of the Arcus secret store](https://www.codit.eu/blog/the-power-of-the-arcus-secret-store/) +- [Role-based authorization by low-level customization of the Arcus secret store](https://www.codit.eu/blog/role-based-authorization-low-level-customization-arcus-secret-store/) \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/_category_.yml b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/_category_.yml new file mode 100644 index 00000000..63eb56a0 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/_category_.yml @@ -0,0 +1 @@ +label: 'Secret store' \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/azure-functions.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/azure-functions.md new file mode 100644 index 00000000..575e84d5 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/azure-functions.md @@ -0,0 +1,110 @@ +--- +title: "Azure Functions support" +layout: default +--- + +# Using secret store within Azure Functions +This separate documentation section explains how the Arcus secret store can be used within Azure Functions environments (both in-process and isolated). + +## Using secret store within in-process Azure Functions +To more easily configure the secret store, we provided a dedicated package that builds on top of the `IFunctionsHostBuilder`: + +## Installation +For this feature, the following package needs to be installed: + +```shell +PM > Install-Package Arcus.Security.AzureFunctions +``` + +### Usage +The secret stores are configured during the initial application build-up in the `Startup.cs`: +```csharp +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +[assembly: FunctionsStartup(typeof(Startup))] + +namespace MyHttpAzureFunction +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + builder.ConfigureSecretStore((FunctionsHostBuilderContext context, IConfiguration config, SecretStoreBuilder stores) => + { + var keyVaultName = config["KeyVault_Name"]; + stores.AddEnvironmentVariables() + .AddAzureKeyVaultWithManagedIdentity($"https://{keyVaultName}.vault.azure.net"); + }) + } + } +} +``` + +Once the secret providers are defined, the `ISecretProvider` can be used as any other registered service: +```csharp +using Arcus.Security.Core; + +namespace Application +{ + public class MyHttpTrigger + { + public MyHttpTrigger(ISecretProvider secretProvider) + { + } + + [FunctionName("MyHttpTrigger")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + return new OkObjectResult("Response from function with injected dependencies."); + } + } +} +``` + +## Using secret store within isolated Azure Functions +Since isolated Azure Functions are built with the default `HostBuilder`, the general secret store packages can be used in this environment. No need to install the dedicated `Arcus.Security.AzureFunctions` package. + +### Usage +Using the available extensions on the `HostBuilder` or `IServiceCollection`, the secret store can be added, just like a Web API or console application. + +```csharp +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + + }) + .ConfigureSecretStore((context, config, stores) => + { + builder.AddEnvironmentVariables() + .AddAzureKeyVaultWithManagedIdentity($"https://{keyVaultName}.vault.azure.net"); + }) + .Build(); +``` + +Once the secret providers are defined, the `ISecretProvider` can be used as any other registered service: +```csharp +using Arcus.Security.Core; + +namespace Application +{ + public class MyHttpTrigger + { + public MyHttpTrigger(ISecretProvider secretProvider) + { + } + + [Function("MyHttpTrigger")] + public HttpResponseData Run( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequestData req, + ILogger log) + { + var response = req.CreateResponse(HttpStatusCode.OK); + return response; + } + } +} +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/create-new-secret-provider.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/create-new-secret-provider.md new file mode 100644 index 00000000..175be2b5 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/create-new-secret-provider.md @@ -0,0 +1,295 @@ +--- +title: "Create custom secret provider" +layout: default +--- + +# Create a new secret provider +The Arcus secret store allows custom secret provider implementations if you want to retrieve secrets from a location that is not built-in. +This section describes how you develop, configure and finally register your custom secret provider implementation into the Arcus secret store. + +## Prerequisites +The secret providers are configured during the initial application build-up: + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + builder.AddEnvironmentVariables(); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` + +This section describes how a new secret store source can be added to the pipeline. + +## Developing a secret provider +1. Install the NuGet package `Arcus.Security.Core`. +2. Implement your own implementation of the `ISecretProvider` + ```csharp + using Arcus.Security.Core; + using Microsoft.Win32; + + namespace Application.Security.CustomProviders + { + public class RegistrySecretProvider : ISecretProvider + { + public Task GetRawSecretAsync(string secretName) + { + object value = Registry.LocalMachine.GetValue(secretName); + return Task.FromResult(value?.ToString()); + } + + public async Task GetSecretAsync(string secretName) + { + string secretValue = await GetRawSecretAsync(secretName); + return new Secret(secretValue); + } + } + } + ``` +3. Optionally, you can provide an extension for a consumer-friendly way to add the provider. + ```csharp + namespace Microsoft.Extensions.Hosting + { + public static class SecretStoreBuilderExtensions + { + public static SecretStoreBuilder AddRegistry(this SecretStoreBuilder builder) + { + var provider = new RegistrySecretProvider(); + return builder.AddProvider(provider); + } + } + } + ``` + And in the `Startup.cs`: + ```csharp + .ConfigureSecretStore((context, config, builder) => + { + builder.AddRegistry(); + }) + ``` + + Or, you can use your provider directly. + ```csharp + .ConfigureSecretStore((context, config, builder) => + { + var provider = new RegistrySecretProvider(); + builder.AddProvider(provider); + }) + ``` +4. Now, the secret source is available in the resulting `ISecretProvider` registered in the dependency injection container. + ```csharp + using Arcus.Security.Core; + + namespace Application.Controllers + { + [ApiController] + public class OrderController : ControllerBase + { + public class OrderController(ISecretProvider secretProvider) + { + } + } + } + ``` + +### Adding dependency services to your secret provider +When your secret provider requires additional services, configured in the dependency container, you can choose to pick an method overload that provides access to the `IServiceProvider`: + +The example below shows how an `ILogger` instance is passed to the secret provider. +```csharp +using System; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.Hosting +{ + public static class SecretStoreBuilderExtensions + { + public static SecretStoreBuilder AddRegistry(this SecretStoreBuilder builder) + { + return builder.AddProvider((IServiceProvider serviceProvider) => + { + var logger = serviceProvider.GetRequiredService>(); + return new RegistrySecretProvider(logger); + }); + } + } +} +``` + +### Adding caching to your secret provider +When your secret provider requires caching, you can wrap the provider in a `CachedSecretProvider` at registration: +```csharp +using Arcus.Security.Core.Caching; + +namespace Microsoft.Extensions.Hosting +{ + public static class SecretStoreBuilderExtensions + { + public static SecretStoreBuilder AddCachedRegistry(this SecretStoreBuilder builder) + { + var provider = new RegistrySecretProvider(); + var configuration = new CacheConfiguration(TimeSpan.FromSeconds(5)); + + return builder.AddProvider(new CachedSecretProvider(provider, configuration)); + } + } +} +``` + +When accessing the provider in the application, you can use the `ICachedSecretProvider` to have access to the cache-specific methods. +```csharp +using Arcus.Security.Core.Caching; + +namespace Application.Controllers +{ + [ApiController] + public class OrderController : ControllerBase + { + public class OrderController(ICachedSecretProvider secretProvider) + { + } + } +} +``` + +### Adding secret versions to your secret provider +When your secret storage location supports versioned secrets, you could consider adapting your secret provider to support these. +For more information on how you can use multiple versions of a secret in your application, see [this dedicated page](./versioned-secret-provider.md). + +Implement from `IVersionedSecretProvider` instead of `ISecretProvider` to allow the secret store to pick that your secret provider supports secret versions. +The following example shows how the registry secret provider only supports two versions of a secret: +```csharp +using Arcus.Security.Core; +using Microsoft.Win32; + +public class RegistrySecretProvider : IVersionedSecretProvider +{ + // Also implement the general `ISecretProvider` methods... + + public Task> GetRawSecretsAsync(string secretName, int amountOfVersions) + { + if (amountOfVersions >= 2) + { + object valueV1 = Registry.LocalMachine.GetValue("v1\\" + secretName); + object valueV2 = Registry.LocalMachine.GetValue("v2\\" + secretName); + + return Task.FromResult(new[] { valueV1, valueV2 }); + } + + object valueV1 = Registry.LocalMachine.GetValue("v1\\" + secretName); + return Task.FromResult(new[] { valueV1 }); + } + + public async Task> GetSecretAsync(string secretName, int amountOfVersions) + { + string secretValue = await GetRawSecretAsync(secretName); + return new Secret(secretValue); + } +} +``` + +The `amountOfVersions` can be configured via the secret provider options (`.AddVersionedSecret`). +Each secret provider registration has the ability to register a amount of secret versions for secret name, that amount is passed to your implementation. For more information, see [this dedicated page](./versioned-secret-provider.md). + +> 💡 Note that versioned secrets can be combined with caching. The set of secrets will be cached, just like a single secret. + +### Adding secret name mutation before looking up secret +When you want secret names 'changed' or 'mutated' before they go through your secret provider (ex. changing `Arcus.Secret` to `ARCUS_SECRET`); +you can pass along a custom mutation function during the registration: + +```csharp +namespace Microsoft.Extensions.Hosting +{ + public static class SecretStoreBuilderExtensions + { + public static SecretStoreBuilder AddRegistry(this SecretStoreBuilder builder) + { + var secretProvider = new RegistrySecretProvider(); + + return builder.AddProvider(secretProvider, options => options.MutateSecretName = secretName => secretName.Replace(".", "_").ToUpper()); + } + } +} +``` + +Or allow users to specify this: + +```csharp +namespace Microsoft.Extensions.Hosting +{ + public static class SecretStoreBuilderExtensions + { + public static SecretStoreBuilder AddRegistry( + this SecretStoreBuilder builder, + Func mutateSecretName = null) + { + var secretProvider = new RegistrySecretProvider(); + + return builder.AddProvider(secretProvider, mutateSecretName); + } + } +} +``` + +So they can provide a custom mutation: + +```csharp +.ConfigureSecretStore((config, stores) => +{ + stores.AddRegistry(secretName => secretName.Replace(".", "_").ToUpper()); +}) +``` + +### Adding critical exceptions +When implementing your own `ISecretProvider`, you may come across situations where you want to throw an critical exception (for example: authentication, authorization failures...) +and that this critical exception is eventually thrown by the secret store when you're looking up secrets. + +When the authentication (for example) only happens when your secret provider _actually_ looks for secrets, then you may want to benefit from this feature. +If you don't provide any critical exceptions yourself, the exception may only be logged and you may end up with only a `SecretNotFoundException`. + +Adding these critical exception can be done during the registration of your secret provider: + +```csharp +using System.Net; +using System.Security.Authentication; +using Microsoft.Rest; + +namespace Microsoft.Extensions.Hosting +{ + public static class SecretStoreBuilderExtensions + { + public static SecretStoreBuilder AddHttpVault(this SecretStoreBuilder builder, string endpoint) + { + // Make sure that ALL exceptions of this type is considered critical. + builder.AddCriticalException(); + + // Make sure that only exceptions of this type where the given filter succeeds is considered critical. + builder.AddCriticalException(exception => + { + return exception.Response.HttpStatusCode == HttpStatusCode.Forbidden; + }); + + return builder.AddProvider(new HttpVaultSecretProvider(endpoint)); + } + } +} +``` + +> Note that when multiple secret providers in the secret store are throwing critical exceptions upon retrieving a secret, then these critical exceptions will be wrapped inside a `AggregateException`. +> In the other case the single critical exception is being thrown. + +## Contribute your secret provider +We are open for contributions and are more than happy to receive pull requests with new secret providers! \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/index.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/index.md new file mode 100644 index 00000000..28149381 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/index.md @@ -0,0 +1,165 @@ +--- +title: "What is it?" +layout: default +slug: /features/secret-store +sidebar_position: 1 +--- + +# Using secret store +As alternative to the usage of placing secrets into an `IConfiguration` instance in your application, the `Arcus.Security.Core` package 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 pick and choose the secret providers you want to use and we'll get the job done! + +Once register, you can fetch all secrets by using `ISecretProvider` which will get secrets from all the different registered secret providers. + +> :bulb: See [this section](./azure-functions.md) if you want to use the secret store functionality within Azure Functions. + +![Arcus secret store integration example](/img/arcus-secret-store-diagram.png) + +## Why would I use it? +Why would you use our Arcus secret store instead of just using the Azure SDK directly to access Azure Key Vault secrets? + +The Arcus secret store has some advantages over using the Azure SDK or configuration directly: + +**✔ Caching** +* We provide caching so the secret providers will not be called upon every secret retrieval. This helps you avoiding hitting service limitations and we provide [asynchronous cache invalidation](https://background-jobs.arcus-azure.net/features/security/auto-invalidate-secrets). + +**✔ Plug & play** +* We support using multiple and combinations of secret providers so with a single secret retrieval can query multiple secret providers (also multiple Azure Key Vaults). + +**✔ Design for security** +* While using configuration for storing secrets can be good for development it is not a safe approach. With the secret store, we provide a single place to retrieve secrets instead of scattering the integration across the application. +* Separating configuration data and sensitive secrets is key in developing secure projects. Vulnerabilities gets introduced when secrets are seen as data and are included in logs, for example. Or when expired secrets doesn't get transient handling upon retrieval. + +**✔ Extensibility** +* Arcus secret store is highly extensible and can be extended with [your own custom secret providers](./create-new-secret-provider.md), [in-memory secret providers for testing](https://github.com/arcus-azure/arcus.testing/blob/master/docs/v0.3/features/inmemory-secret-provider.md)... + +## Built-in secret providers +Several built in secret providers available in the package. + +* [Configuration](./provider/configuration.md) +* [Environment variables](./provider/environment-variables.md) + +And several additional providers in separate packages. + +* [Azure Key Vault](./provider/key-vault.md) +* [Command line](./provider/cmd-line.md) +* [Docker secrets](./provider/docker-secrets.md) +* [HashiCorp](./provider/hashicorp-vault.md) +* [User Secrets](./provider/user-secrets.md) + +If you require an additional secret providers that aren't available here, please [this document](./create-new-secret-provider.md) that describes how you can create your own secret provider. + +## Additional features +Lists all the additional functions of the secret store. + +* [Create a custom secret provider](./create-new-secret-provider.md) +* [Retrieve a specific secret provider](./named-secret-providers.md) + +## Installation +For this feature, the following package needs to be installed: + +```shell +PM > Install-Package Arcus.Security.Core +``` + +## Usage +The secret stores are configured during the initial application build-up in the `Program.cs`: + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, config) => + { + config.AddJsonFile("appsettings.json") + .AddJsonFile("appsettings.Development.json"); + }) + .ConfigureSecretStore((context, config, builder) => + { +#if DEBUG + builder.AddConfiguration(config); +#endif + var keyVaultName = config["KeyVault_Name"]; + builder.AddEnvironmentVariables() + .AddAzureKeyVaultWithManagedIdentity($"https://{keyVaultName}.vault.azure.net"); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` + +Once the secret providers are defined, the `ISecretProvider` can be used as any other registered service: + +```csharp +using Arcus.Security.Core; + +namespace Application.Controllers +{ + [ApiController] + public class HealthController : ControllerBase + { + public HealthController(ISecretProvider secretProvider) + { + } + } +} +``` + +### Configuring secret store without .NET host builder +The secret store is also available directly on the `IServiceCollection` for applications that run without a .NET hosting context but still want to make use of the Arcus secret store. + +Just like you would register the secret store on the `HostBuilder`, you can use the `.AddSecretStore` extension method to register the secret store: + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + IConfiguration configuration = + new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + services.AddSecretStore(stores => + { + stores.AddEnvironmentVariables(); + + #if DEBUG + stores.AddConfiguration(configuration); + #endif + + var keyVaultName = configuration["KeyVault_Name"]; + stores.AddAzureKeyVaultWithManagedServiceIdentity($"https://{keyVaultName}.vault.azure.net"); + }); +} +``` + +When your application wants to access a secret, all it has to do is use `ISecretProvider` which will give you access to all the registered secret providers. + +## Using secret store within Azure Functions +See [this page](./azure-functions.md) how the secret store can be used within Azure Functions. + +## Secret store configuration +The secret store as additional configuration that controls the behavior of the store. +See below the available features so you can setup your secret store for your needs. + +### Include security auditing +The secret store has the ability to audit each secret retrieval so malicious activity can be spotted more easily. +This functionality is available in both the regular .NET Core as Azure Functions environment. + +```csharp +.ConfigureSecretStore((config, stores) => +{ + // Will log an security event for each retrieved secret, including the secret name and the provider that has tried to retrieve the secret. + // Default: `false` + stores.WithAuditing(options => options.EmitSecurityEvents = true); +}) +``` + diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/named-secret-providers.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/named-secret-providers.md new file mode 100644 index 00000000..57fd8cf2 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/named-secret-providers.md @@ -0,0 +1,140 @@ +--- +title: "Get secret provider by name" +layout: default +--- + +# Retrieve a specific secret provider from the secret store + +The default workings of the secret store, is that a set of secret providers are registered and the consumer gets access to all of secrets provided by using `ISecretProvider`. + +In some cases, you may want to retrieve a specific secret provider or a subset of secret providers from the store because that secret provider has some functionality that the other providers don't have. + +In those cases, you can register your secret provider(s) with a name so that, in a later stage, you can retrieve your named provider(s). + +## Registering a named secret provider + +Lets consider that you want explicitly use the built-in environment variables secret provider. +First, the secret provider has to be registered with a unique name. + +```csharp +.ConfigureSecretStore((config, stores) => +{ + stores.AddEnvironmentVariables(..., name: "environment-variables"); +}) +``` + +## Retrieving a named secret provider + +Now that the named environment variables secret provider is registered, we are able to retrieve this provider in our application. + +Instead of injecting `ISecretProvider` in your application to access secrets, we'll inject `ISecretStore` interface to retrieve named secret providers. + +```csharp +using Arcus.Security.Core; +using Arcus.Security.Core.Caching; +using Arcus.Security.Core.Providers; + +namespace Application +{ + [ApiController] + public class OrderController : ControllerBase + { + public class OrderController(ISecretStore secretStore) + { + // Gets the `ISecretProvider` with the matched name (with either using the `ISecretProvider` as return type or your own generic type). + // ⚠ The name of the registered secret providers should be unique when retrieving the concrete secret provider; + // otherwise, an exception will be thrown when you try to access the `GetProvider<>` or `GetCachedProvider<>`. + var secretProvider = secretStore.GetProvider("environment-variables"); + + // Gets the `ICachedSecretProvider` with the matched name (with either using the `ICachedSecretProvider` as return type or your own generic type). + // Mark that this only works when the secret provider was registered as a cached secret provider. + ICachedSecretProvider cachedSecretProvider = secretStore.GetCachedProvider("your-cached-secret-provider"); + } + } +} +``` + +## Retrieving a subset of named secret providers + +At some times, you may want to retrieve a subset of secret providers. This is especially useful when you want to control the external secret providers based application-specific settings. + +Let's consider this secret store setup: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +public static class Program +{ + public static void Main(string[] args) + { + return CreateDefaultBuilder(args).Build().Run(); + } + + private static IHostBuilder CreateDefaultBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration(builder => builder.AddCommandLine(args)) + .ConfigureSecretStore((config, stores) => + { + stores.AddEnvironmentVariables(); + + stores.AddAzureKeyVaultWithManagedIdentity("https://admin.vault.azure.net"); + + stores.AddAzureKeyVaultWIthManagedIdentity("https://user.vault.azure.net"); + }); + } +} +``` + +Imagine that you actually want some parts of the application to only have access to the Azure Key Vault `admin` plus the environment variables, and other parts only the Azure Key Vault `user` plus the environment variables. +This can be used for authorization restrictions, performance-wise to limit the external calls... + +This problem can also be fixed by adding the same name to the required subset. Let's use "Admin Secrets" and "User Secrets" as our names: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +public static class Program +{ + public static void Main(string[] args) + { + return CreateDefaultBuilder(args).Build().Run(); + } + + private static IHostBuilder CreateDefaultBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration(builder => builder.AddCommandLine(args)) + .ConfigureSecretStore((config, stores) => + { + stores.AddEnvironmentVariables(configureOptions: options => options.Name = "Admin Secrets") + .AddAzureKeyVaultWithManagedIdentity("https://admin.vault.azure.net", configureOptions: options => options.Name = "Admin Secrets"); + + stores.AddEnvironmentVariables(configureOptions: options => options.Name = "User Secrets") + .AddAzureKeyVaultWIthManagedIdentity("https://user.vault.azure.net", configureOptions: options => options.Name = "User Secrets"); + }); + } +} +``` + +Within the application, you can now use either subset of the secret store by calling the correct configured name: + +```csharp +using Arcus.Security.Core; + +namespace Application +{ + [ApiController] + public class OrderController : ControllerBase + { + public class OrderController(ISecretStore secretStore) + { + // Combines the environment variables + Azure Key Vault 'admin' + ISecretProvider adminSecretProvider = secretStore.GetProvider("Admin Secrets"); + + // Combines the environment variables + Azure Key Vault 'user' + ISecretProvider userSecretProvider = secretStore.GetProvider("User Secrets"); + } + } +} +``` diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/_category_.yml b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/_category_.yml new file mode 100644 index 00000000..30c9797e --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/_category_.yml @@ -0,0 +1 @@ +label: 'Secret providers' \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/cmd-line.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/cmd-line.md new file mode 100644 index 00000000..5025a855 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/cmd-line.md @@ -0,0 +1,49 @@ +--- +title: "Command line secret provider" +layout: default +--- + +# Command line secret provider +The command line secret provider transforms all your command line arguments in application secrets. + +## Installation +Adding command line arguments into the secret store requires following package: + +```shell +PM > Install-Package Arcus.Security.Providers.CommandLine +``` + +## Configuration +After installing the package, the additional extensions becomes available when building the secret store. + +```csharp +using Arcus.Security.Core; +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + // Uses the passed-in command line arguments as secrets in the secret store. + builder.AddCommandLine(args); + + // Uses the passed-in command lien arguments, using underscores and capitals for secret name structure. + // Example - When looking up Queue.Name it will be changed to ARCUS_QUEUE_NAME. + builder.AddCommandLine(args, mutateSecretName: secretName => secretName.Replace(".", "_").ToUppder()); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddCommandLine(args, name: "CommandLine"); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/configuration.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/configuration.md new file mode 100644 index 00000000..5d8e4627 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/configuration.md @@ -0,0 +1,52 @@ +--- +title: "Configuration secret provider" +layout: default +--- + +# Configuration secret provider +Configuration secret provider brings you all registered configuration providers of .NET Core by using `IConfiguration` to your application. + +> :warning: When using configuration secret provider, it will look for secrets in all configuration sources which is not secure. This provider should only be used for development. + +## Installation +The configuration secret provider is built-in as part of the package [Arcus.Security.Core](https://www.nuget.org/packages/Arcus.Security.Core). + +## Configuration + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, config) => + { + config.AddJsonFile("appsettings.json") + .AddJsonFile("appsettings.Development.json"); + }) + .ConfigureSecretStore((HostBuilderContext context, IConfiguration config, SecretStoreBuilder builder) => + { +#if DEBUG + // Uses the built `IConfiguration` as a secret provider. + builder.AddConfiguration(config); + + // Uses the built `IConfiguration` as secret provider, using `:` instead of `.` when looking up secrets. + // Example - When looking up `Queue.Name` it will be changed to `queue:name`. + builder.AddConfiguration(config, mutateSecretName: secretName => secretName.Replace(".", ":").ToLower()); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddConfiguration(..., name: "Configuration"); +#endif + }); + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/docker-secrets.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/docker-secrets.md new file mode 100644 index 00000000..49bcb205 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/docker-secrets.md @@ -0,0 +1,95 @@ +--- +title: "Docker Secrets secret provider" +layout: default +--- + +# Docker Secrets secret provider +This provider allows you to work with Docker secrets. When using Docker secrets in Docker Swarm, the secrets are injected in the Docker container as files. +The Docker secrets secret provider provides access to those secrets via the secret store. + +This secret provider offers functionality which is equivalent to the _KeyPerFile_ Configuration Provider, but instead of adding the secrets to the Configuration, this secret provider allows access to the Docker Secrets via the _ISecretProvider_ interface. + +## Installation +Adding secrets from the User Secrets manager into the secret store requires following package: + +```shell +PM > Install-Package Arcus.Security.Providers.DockerSecrets +``` + +## Configuration +After installing the package, the additional extensions becomes available when building the secret store. + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + // Adds the secrets that exist in the "/run/secrets" directory to the ISecretStore + // Docker secrets are by default mounted into the /run/secrets directory + // when using Linux containers on Docker Swarm. + builder.AddDockerSecrets(directoryPath: "/run/secrets"); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` + +## Retrieving secrets + +Suppose you have the following docker-compose file: + +```yaml +version: '3.8' +services: + person-api: + image: person-api:latest + ports: + - 5555:80 + secrets: + - ConnectionStrings__PersonDatabase + +secrets: + ConnectionStrings__PersonDatabase: + external: true +``` + +After adding the Docker Secrets secret provider to the secret store, the Docker secrets can simply be retrieved by calling the appropriate methods on the `ISecretProvider`: + +```csharp +using Arcus.Security.Core; + +namespace Application.Controllers +{ + public class PersonController + { + private readonly ISecretProvider _secrets; + + public PersonController(ISecretProvider secrets) + { + _secrets = secrets; + } + + [HttpGet] + public async Task GetPerson(Guid personId) + { + string connectionString = await _secrets.GetRawSecretAsync("ConnectionStrings:PersonDatabase") + + using (var connection = new SqlDbConnection(connectionString)) + { + var person = new PersonRepository(connection).GetPersonById(personId); + return Ok(new { Id = person.Id, Name = person.Name }); + } + } + } +``` + diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/environment-variables.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/environment-variables.md new file mode 100644 index 00000000..c3c1d3b1 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/environment-variables.md @@ -0,0 +1,50 @@ +--- +title: "Environment variables secret provider" +layout: default +--- + +# Environment variables secret provider +Environment variable secret provider brings environment variables as secrets to your application. + +## Installation +The environment variable secret provider is built-in as part of the package [Arcus.Security.Core](https://www.nuget.org/packages/Arcus.Security.Core). + +## Configuration +The secret provider is available as an extension. + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + // Uses the environment variables from the environment block associated with the current process. + builder.AddEnvironmentVariables(); + + // Uses the environment variables stored or retrieved from the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment key in the Windows operating system registry. + builder.AddEnvironmentVariables(EnvironmentVariableTarget.Machine); + + // Uses the environment variables starting with 'ARCUS_' from the environment block associated with the current process. + builder.AddEnvironmentVariables(prefix: "ARCUS_"); + + // Uses the environment variables, using underscores and capitals for secret name structure. + // Example - When looking up Queue.Name it will be changed to ARCUS_QUEUE_NAME. + builder.AddEnvironmentVariables(mutateSecretName: name => $"ARCUS_{name.Replace(".", "_").ToUpper()}"); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddEnvironmentVariables(..., name: "Configuration"); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/hashicorp-vault.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/hashicorp-vault.md new file mode 100644 index 00000000..05a09a54 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/hashicorp-vault.md @@ -0,0 +1,242 @@ +--- +title: "HashiCorp Vault secret provider" +layout: default +--- + +# HashiCorp Vault secret provider +HashiCorp Vault secret provider brings secrets from the KeyValue secret engine to your application. + +## Installation +Adding secrets from HashiCorp Vault into the secret store requires following package: + +```shell +PM > Install-Package Arcus.Security.Providers.HashiCorp +``` + +## Configuration +After installing the package, the additional extensions becomes available when building the secret store. + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + // Adding the HashiCorp Vault secret provider with the built-in overloads. + // ======================================================================= + + // UserPass authentication built-in overload: + // ------------------------------------------ + builder.AddHashiCorpVaultWithUserPass( + // URI where the HashiCorp Vault is running. + vaultServerUriWithPort: "https://uri.to.your.running.vault:5200", + // Username/Password combination to authenticate with the vault. + username: "admin", + password: "s3cr3t", + // Path where the secrets are stored in the KeyValue secret engine. + secretPath: "my-secrets" + ); + + // Following defaults can be overridden: + + // Mount point of UserPass authentication (default: userpass). + builder.AddHashiCorpVaultWithUserPass(..., options => options.UserPassMountPoint: "myuserpass"); + + // Version of the KeyValue secret engine (default: V2). + builder.AddHashiCorpVaultWithUserPass(..., options => options.KeyValueVersion: VaultKeyValueSecretEngineVersion.V1); + + // Mount point of KeyValue secret engine (default: kv-v2). + builder.AddHashiCorpVaultWithUserPass(..., options => options.KeyValueMountPoint: "secret"); + + // Adding the HashiCorp Vault secret provider with UserPass authentication, using `-` instead of `:` when looking up secrets. + // Example - When looking up `Foo:Bar` it will be changed to `Foo-Bar`. + builder.AddHashiCorpVaultWithUserPass(..., mutateSecretName: secretName => secretName.Replace(":", "-")); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddHashiCorpVault(..., name: "HashiCorp"); + + // Kubernetes authentication built-in overload: + // -------------------------------------------- + builder.AddHashiCorpVaultWithKubernetes( + // URI where the HashiCorp Vault is running. + vaultServerUriWithPort: "https://uri.to.your.running.vault:5200", + // Role name of the Kubernetes service account. + roleName: "admin", + // JSON web token (JWT) of the Kubernetes service account, + jwt: "ey.xxx.xxx", + // Path where the secrets are stored in the KeyValue secret engine. + secretPath: "my-secrets" + ); + + // Mount point of Kubernetes authentication (default: kubernetes). + builder.AddHashiCorpVaultWithKubernetes(..., options => options.KubernetesMountPoint: "mykubernetes"); + + // Version of the KeyValue secret engine (default: V2). + builder.AddHashiCorpVaultWithKubernetes(..., options => options.KeyValueVersion: VaultKeyValueSecretEngineVersion.V1); + + // Mount point of KeyValue secret engine (default: kv-v2). + builder.AddHashiCorpVaultWithKubernetes(..., options => options.KeyValueMountPoint: "secret"); + + // Adding the HashiCorp Vault secret provider with Kubernetes authentication, using `-` instead of `:` when looking up secrets. + // Example - When looking up `Foo:Bar` it will be changed to `Foo-Bar`. + builder.AddHashiCorpVaultWithKubernetes(..., mutateSecretName: secretName => secretName.Replace(":", "-")); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddHashiCorpVault(..., name: "HashiCorp"); + + // Custom settings overload for when using the [VaultSharp](https://github.com/rajanadar/VaultSharp) settings directly: + // -------------------------------------------------------------------------------------------------------------------- + var tokenAuthentication = new TokenAuthMethodInfo("token"); + var settings = VaultClientSettings("http://uri.to.your.running.vault.5200", tokenAuthentication); + builder.AddHashiCorpVault( + settings, + // Path where the secrets are stored in the KeyValue secret engine. + secretPath: "my-secrets"); + + // Version of the KeyValue secret engine (default: V2). + builder.AddHashiCorpVault(..., options => options.KeyValueVersion: VaultKeyValueSecretEngineVersion.V1); + + // Mount point of KeyValue secret engine (default: kv-v2). + builder.AddHashiCorpVault(..., options => options.KeyValueMountPoint: "secret"); + + // Adding the HashiCorp Vault secret provider, using `-` instead of `:` when looking up secrets. + // Example - When looking up `Foo:Bar` it will be changed to `Foo-Bar`. + builder.AddHashiCorpVault(..., mutateSecretName: secretName => secretName.Replace(":", "-")); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddHashiCorpVault(..., name: "HashiCorp"); + + // Additional settings: + // ------------------- + + // Tracking the HashiCorp Vault dependency which works well together with Application Insights (default: `false`). + // See https://observability.arcus-azure.net/features/writing-different-telemetry-types#measuring-custom-dependencies for more information. + builder.AddHashiCorpVault(..., options => options.TrackDependency = true); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` + +### Custom implementation +We allow custom implementations of the HashiCorp Vault secret provider. +This can come in handy when you want to perform additional actions during the secret retrieval or want to extend the available HashiCorp Vault authentication options. + +In this example we'll add retry functionality, using [Polly](https://github.com/App-vNext/Polly), to the secret provider. +First, you'll have to implement the `HashiCorpSecretProvider`. + +```csharp +using Polly; +using Polly.Retry; + +public class RetryableHashiCorpSecretProvider : HashiCorpSecretProvider +{ + private readonly AsyncRetryPolicy _retryPolicy; + + public RetryableHashiCorpSecretProvider( + VaultClientSettings settings, + string secretPath, + HashiCorpVaultOptions options, + AsyncRetryPolicy retryPolicy, + ILogger logger) + : base(settings, secretPath, options, logger) + { + _retryPolicy = rectryPolicy; + } + + public override async Task GetSecretAsync(string secretName) + { + await _retryPolicy.ExecuteAsync(async () => + { + await base.GetSecretAsync(secretName); + }); + } + + public override async Task GetRawSecretAsync(string secretName) + { + await _retryPolicy.ExecuteAsync(async () => + { + await base.GetRawSecretAsync(secretName); + }); + } +} +``` + +As you can see, we allow both secret retrieval methods to be overridden so we can prepend our retry functionality. +To use this within the secret store, you can use the available method extension that allows you to provide your custom implementation type: + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, stores) => + { + stores.AddHashiCorp((IServiceProvider serviceProvider) => + { + // UserPass authentication options. + var options = new HashiCorpVaultUserPassOptions(); + var authenticationMethod = new UserPassAuthMethodInfo(options.UserPassMountPoint, "admin"", "P@ssw0rd"); + var settings = new VaultClientSettings("https://uri.to.your.running.vault:5200", authenticationMethod); + var logger = serviceProvider.GetService>(); + + // Retryable options. + var retryPolicy = + Policy.Handle(exceptionPredicate) + .WaitAndRetryAsync(5, attempt => TimeSpan.FromSeconds(1)); + + return new RetryableHashiCorpSecretProvider(settings, "my-secrets-path", options, retryPolicy, logger); + }); + }); + } +} +``` +That's all it takes to use your custom implementation. + +We also recommend to use a custom extension on the secret store builder to make this more user-friendly. + +```csharp +public static class SecretStoreBuilderExtensions +{ + public static SecretStoreBuilder AddRetryableHashiCorpWithUserPass( + this SecretStoreBuilder builder, + string vaultServerUriWithPort, + string username, + string password, + string secretPath, + AsyncRetryPolicy retryPolicy, + Action configureOptions) + { + builder.AddProvider((IServiceProvider serviceProvider) => + { + var options = new HashiCorpVaultUserPassOptions(); + var authenticationMethod = new UserPassAuthMethodInfo(options.UserPassMountPoint, username, password); + var settings = new VaultClientSettings(vaultServerUriWithPort, authenticationMethod); + var logger = serviceProvider.GetService>(); + + return new RetryableHashiCorpSecretProvider(settings, secretPath, options, retryPolicy, logger); + }); + } +} +``` + diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/key-vault.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/key-vault.md new file mode 100644 index 00000000..9677b547 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/key-vault.md @@ -0,0 +1,78 @@ +--- +title: "Azure Key Vault secret provider" +layout: default +--- + +# Azure Key Vault secret provider +Azure Key Vault secret provider brings secrets from Azure Key Vault to your application. + +## Installation +Adding secrets from Azure Key Vault into the secret store requires following package: + +```shell +PM > Install-Package Arcus.Security.Providers.AzureKeyVault +``` + +## Configuration +After installing the package, the additional extensions becomes available when building the secret store. + +[Managed Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) authentication is the recommended approach to interact with Azure Key Vault. + +See [Service-to-service authentication to Azure Key Vault using .NET - Connection String Support](https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication#connection-string-support) for supported connection strings and [National clouds - Azure AD authentication endpoints](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud#azure-ad-authentication-endpoints) for valid azure AD instances. + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + // Adding the Azure Key Vault secret provider with the built-in overloads + // `keyVaultUri`: the URI where the Azure Key Vault is located. + builder.AddAzureKeyVaultWithManagedIdentity(keyVaultUri); + + // Several other built-in overloads are available too: + // `AddAzureKeyVaultWithServicePrincipal` + // `AddAzureKeyVaultWithCertificate` + + // Or, alternatively using the fully customizable approach. + // `clientId`: The client id to authenticate for a user assigned managed identity. + // More information on user assigned managed identities can be found here: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview#how-a-user-assigned-managed-identity-works-with-an-azure-vm + var vaultAuthentication = new ChainedTokenCredential(new ManagedIdentityCredential(clientId), new EnvironmentCredential()); + var vaultConfiguration = new KeyVaultConfiguration(keyVaultUri); + + builder.AddAzureKeyVault(vaultAuthentication, vaultConfiguration); + + // Adding a default cached variant of the Azure Key Vault provider (default: 5 min caching). + builder.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, cacheConfiguration: CacheConfiguration.Default); + + // Assign a configurable cached variant of the Azure Key Vault provider. + var cacheConfiguration = new CacheConfiguration(TimeSpan.FromMinutes(1)); + builder.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, cacheConfiguration); + + // Tracking the Azure Key Vault dependency which works well together with Application Insights (default: `false`). + // See https://observability.arcus-azure.net/features/writing-different-telemetry-types#measuring-custom-dependencies for more information. + builder.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, configureOptions: options => options.TrackDependency = true); + + // Adding the Azure Key Vault secret provider, using `-` instead of `:` when looking up secrets. + // Example - When looking up `ServicePrincipal:ClientKey` it will be changed to `ServicePrincipal-ClientKey`. + builder.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, mutateSecretName: secretName => secretName.Replace(":", "-")); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddAzureKeyVaultWithManagedIdentity(..., name: "AzureKeyVault.ManagedIdentity"); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` + diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/user-secrets.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/user-secrets.md new file mode 100644 index 00000000..a188295e --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/provider/user-secrets.md @@ -0,0 +1,57 @@ +--- +title: "User Secrets secret provider" +layout: default +--- + +# User Secrets manager secret provider +User Secrets secret provider brings local secrets during development to your application. + +> :warning: When using User Secrets secret provider, it will look for secrets on the local disk which is not secure. This provider should only be used for development. + +## Installation +Adding secrets from the User Secrets manager into the secret store requires following package: + +```shell +PM > Install-Package Arcus.Security.Providers.UserSecrets +``` + +## Configuration +After installing the package, the additional extensions becomes available when building the secret store. + +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureSecretStore((context, config, builder) => + { + // Adds the user secrets secret source with specified user secrets ID. + // A user secrets ID is a unique value used to store and identify a collection of secrets. + + // `Program`: The type from the assembly to search for an instance of `UserSecretsIdAttribute`. + builder.AddUserSecrets(); + + // The user secrets ID which gets provided directly without looking up the `UserSecretsIdAttribute` in the assembly. + builder.AddUserSecrets("bee01c693fe44766b1f3ef1e1f1f7883"); + + // The user secrets ID, using lower case transformation before looking up secrets. + // Example - When looking up `Client.ID` it will be changed to `client.id`. + builder.AddUserSecrets(mutateSecretName: secretName => secretName.ToLower()); + + // Providing an unique name to this secret provider so it can be looked up later. + // See: "Retrieve a specific secret provider from the secret store" + builder.AddUserSecrets(..., name: "UserSecrets"); + }) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + } +} +``` + diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/versioned-secret-provider.md b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/versioned-secret-provider.md new file mode 100644 index 00000000..569b414a --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secret-store/versioned-secret-provider.md @@ -0,0 +1,69 @@ +--- +title: "Secret versions" +layout: default +--- + +# Adding secret versions to secret store +The basic functionality of the Arcus secret store is one-way secret retrieval, meaning that a single secret matches a single secret name. +In production scenario's, multiple versions of a secrets are possible especially in key rotation scenario's where you would want to support the 'older' version until each system uses the new one. + +In those scenario's, the secret provider implementations registered in the secret store should be able to return many secret values based on a secret name. +Currently, only the Azure Key Vault secret provider is adapted to use many secret versions, but nothing stops you from implementing your own versioned secret provider. + +## Example: Azure Key Vault secret versions +The following example will show you how two secret versions are taken into account when retrieving a secret from the secret store. +Any secret retrievals for `MySecret` will make sure that the Azure Key Vault implementation takes the latest two secret versions. + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + Host.CreateDefaultBuilder() + .ConfigureSecretStore((configuration, stores) => + { + stores.AddAzureKeyVaultWithManagedIdentity(..., (SecretProviderOptions options) => + { + options.AddVersionedSecret("MySecret", allowedVersions: 2); + }); + }) + .Build() + .Run(); + } +} +``` + +This secret version registration also poses the question of how these two versions of the secret can be used, since the basic secret store interface only provides single secrets. +Additional extensions are added on the secret store that allows you to interact with the available secret versions in your application: +```csharp +using Arcus.Security.Core; + +[ApiController] +[Route("/api/v1/order")] +public class OrderController : ControllerBase +{ + private readonly ISecretProvider _secretProvider; + + public OrderController(ISecretProvider secretProvider) + { + _secretProvider = secretProvider; + } + + [HttpPost] + public async Task Post([FromBody] Order order) + { + // Get all secrets available. + IEnumeration secrets = await _secretProvider.GetSecretsAsync("MySecret"); + + // Get all secret values available. + IEnumeration secretValues = await _secretProvider.GetRawSecretsAsync("MySecret"); + } +} +``` + +> Note that since we only allowed 2 secret versions in the registration, the Azure Key Vault will only return two secrets. If there more registered versions allowed than available on Azure Key Vault, then the maximum amount available will return + +> 💡 Note that versioned secrets can be combined with caching. The set of secrets will be cached, just like a single secret. diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secrets/_category_.yml b/docs/versioned_docs/version-v1.8.0/03-Features/secrets/_category_.yml new file mode 100644 index 00000000..2451dcec --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secrets/_category_.yml @@ -0,0 +1 @@ +label: 'Interacting with secrets' \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secrets/consume-from-key-vault.md b/docs/versioned_docs/version-v1.8.0/03-Features/secrets/consume-from-key-vault.md new file mode 100644 index 00000000..5a69ecfd --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secrets/consume-from-key-vault.md @@ -0,0 +1,58 @@ +--- +title: "Consuming Azure Key Vault secrets" +layout: default +--- + +# Consuming Azure Key Vault secrets + +## Store Azure Key vault secrets +The [Azure Key vault secret provider](../secret-store/provider/key-vault.md) provides the capability to also store secrets. This functionality is only available on the secret provider itself and not on the entire secret store. + +Following steps guide you to store an Azure Key Vault secret via the Azure Key vault secret provider. +1. Register the Azure Key Vault secret provider as a [named secret provider](../secret-store/named-secret-providers.md). + ```csharp + stores.AddAzureKeyVaultWithManagedIdentity(..., name: "AzureKeyVault.ManagedIdentity"); + ``` +2. Retrieve the Azure Key Vault secret provider from the `ISecretStore` (see the [named secret provider docs](../secret-store/named-secret-providers.md) for info) + ```csharp + ISecretStore secretStore = ... + var secretProvider = secretStore.GetProvider("AzureKeyVault.ManagedIdentity); + ``` +3. Store the secret by calling the `StoreSecretAsync` method. + ```csharp + KeyVaultSecretProvider secretProvider = ... + await secretProvider.StoreSecretAsync("MySecret", "P@ssw0rd!); + ``` + +## Open for extension +You can easily extend the Key Vault provider by overriding the `GetSecret*Async` methods on the it. + +This useful to provide additional logging, for example, during the retrieval of the secrets. + +```csharp +using Microsoft.Extensions.Logging; +using Arcus.Security.Core; +using Arcus.Security.Providers.AzureKeyVault; + +public class LoggedKeyVaultSecretProvider : KeyVaultSecretProvider +{ + private readonly ILogger _logger; + + public LoggedKeyVaultSecretProvider(ILogger logger) + { + _logger = logger; + } + + public override async Task GetSecretAsync(string secretName) + { + using (var measurement = DependencyMeasurement.Start()) + { + Secret secret = await base.GetSecretAsync(secretName); + _logger.LogDependency("Azure Key Vault", "Secret", isSuccessful: true, startTime: measurement.StartTime, duration: measurement.Elapsed); + } + + return secret; + } +} +``` + diff --git a/docs/versioned_docs/version-v1.8.0/03-Features/secrets/general.md b/docs/versioned_docs/version-v1.8.0/03-Features/secrets/general.md new file mode 100644 index 00000000..25d7cd28 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/03-Features/secrets/general.md @@ -0,0 +1,71 @@ +--- +title: "Consuming Secrets" +layout: default +--- + +# Consuming secrets +Every provider implements `ISecretProvider` which makes it easy to use a consistent flow, regardless of the provider. + +Secrets can be easily retrieved as follows: + +```csharp +Secret secret = await secretProvider.GetSecretAsync("EventGrid-AuthKey"); + +string secretValue = secret.Value; +string secretVersion = secret.Version; +DateTimeOffset? expirationDate = secret.Expires; +``` + +# Raw secrets +In some scenarios you'd like to just get the secret value directly without any metadata. +This is possible by calling the `...Raw...` variants on the `ISecretProvider` implementations. + +```csharp +string secretValue = await secretProvider.GetRawSecretAsync("EventGrid-AuthKey"); +``` + +# Caching Secrets +Some secret providers recommend to cache secrets for a while to avoid hitting the service limitations. + +We provide a `CachedSecretProvider` which allows the secrets to be cached in memory for a certain amount of time. + +```csharp +var cachedSecretProvider = new CachedSecretProvider(secretProvider); +Secret secret = await cachedSecretProvider.GetSecretAsync("EventGrid-AuthKey"); +``` + +If you prefer a more fluent approach you can also use our `WithCaching` extension. + +```csharp +var cachedSecretProvider = new KeyVaultSecretProvider(vaultAuthentication, vaultConfiguration) + .WithCaching(); +Secret secret = await cachedSecretProvider.GetSecretAsync("EventGrid-AuthKey"); +``` + +## Configuring the cache +By default, retrieved secrets are cached for **5 minutes**, but you can configure this yourself. + +```csharp +var cacheConfiguration = new CacheConfiguration(TimeSpan.FromMinutes(10)); // Optional: Default is 5 min +var cachedSecretProvider = new CachedSecretProvider(secretProvider, cacheConfiguration); +Secret secret = await cachedSecretProvider.GetSecretAsync("EventGrid-AuthKey"); +``` + +## Bypassing cached secrets +In some scenarios you'd like to skip the cache and retrieve the secret by looking it up in the secret-store, instead of retrieving it from the cache. + +This is important because in certain scenarios your secrets can be rolled and thus you will be revoked access. + +```csharp +Secret secret = await cachedSecretProvider.GetSecretAsync("EventGrid-AuthKey", ignoreCache: true); +``` + +## Invalidates a secret from the cache +In some scenarios you'd like to remove a cache entry so that the secret will be retrieved from the provider when a new lookup will be done. + +After a hard refresh you can use the latest secret again and proceed your work. This is useful for scenario's where the secret is updated and you need to tell the cache somehow. + +```csharp +await cachedSecretProvider.InvalidateSecretAsync("EventGrid-AuthKey"); +``` + diff --git a/docs/versioned_docs/version-v1.8.0/index.md b/docs/versioned_docs/version-v1.8.0/index.md new file mode 100644 index 00000000..a745bb99 --- /dev/null +++ b/docs/versioned_docs/version-v1.8.0/index.md @@ -0,0 +1,37 @@ +--- +title: "Arcus - Security" +layout: default +slug: / +sidebar_label: Welcome +sidebar_position: 1 +--- + +# Introduction + +Arcus Security allows you to work easily with secrets. Instead of retrieving sensitive information from your application's configuration, Arcus Security allows you to retrieve secrets from a configured **Secret Store**. The secret store supports multiple secret providers to get its secrets from, like Azure Key Vault, HashiCorp, etc. and allows you to write your own secret provider. + +Additionally, Arcus Security makes sure that retrieved secrets are cached for a while so to avoid multiple calls to the backing secret provider, which prevents throttling. + +![Arcus secret store integration example](/img/arcus-secret-store-diagram.png) + +# Guidance +* [Add Arcus secret store with Azure Key vault integration](02-Guides/add-secret-store-with-keyvault-integration.md) + +# Installation + +We provide a NuGet package per provider and area. + +Here is how you install all Arcus Security packages +```shell +PM > Install-Package Arcus.Security.All +``` + +Here is how you consume secrets for Azure Key Vault: +```shell +PM > Install-Package Arcus.Security.Providers.AzureKeyVault +``` + +# License +This is licensed under The MIT License (MIT). Which means that you can use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the web application. But you always need to state that Codit is the original author of this web application. + +*[Full license here](https://github.com/arcus-azure/arcus.security/blob/master/LICENSE)* diff --git a/docs/versioned_sidebars/version-v1.8.0-sidebars.json b/docs/versioned_sidebars/version-v1.8.0-sidebars.json new file mode 100644 index 00000000..c4b5ba99 --- /dev/null +++ b/docs/versioned_sidebars/version-v1.8.0-sidebars.json @@ -0,0 +1,8 @@ +{ + "version-v1.8.0/tutorialSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/docs/versions.json b/docs/versions.json index f5c7bf94..4074e759 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,4 +1,5 @@ [ + "v1.8.0", "v1.7.0", "v1.6.0", "v1.5.0",