From 193f8f87f1a779056ac92e919188c0f758c142f1 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Wed, 5 Jul 2023 12:57:07 +0200 Subject: [PATCH 01/16] feat: add dapr secret provider --- build/ci-build.yml | 5 - build/nuget-release.yml | 5 - build/templates/download-dapr.yml | 15 + build/templates/run-integration-tests.yml | 9 + .../provider/dapr-secret-store.md | 154 ++++ .../Arcus.Security.Providers.Dapr.csproj | 34 + .../DaprSecretProvider.cs | 137 ++++ .../DaprSecretProviderOptions.cs | 125 ++++ .../SecretStoreBuilderExtensions.cs | 98 +++ .../Arcus.Security.Tests.Integration.csproj | 15 +- .../Dapr/DaprSecretProviderTests.cs | 147 ++++ .../Dapr/Hosting/DaprSidecarFixture.cs | 256 +++++++ .../Dapr/Hosting/DaprSidecarOptions.cs | 90 +++ .../Dapr/Hosting/TestConfigExtensions.cs | 69 ++ .../az-keyvault-secret-store.yaml | 17 + .../Dapr/Resources/DaprSecretStore.cs | 32 + .../MultiValuedLocalDaprSecretProvider.cs | 44 ++ .../Resources/Local/local-secret-store.yaml | 15 + .../SecretStoreBuilderExtensionsTests.cs | 4 +- .../IntegrationTest.cs | 10 +- .../KeyVault/Configuration/KeyVaultConfig.cs | 81 ++ .../KeyVaultCachedSecretProviderTests.cs | 27 +- .../KeyVault/KeyVaultSecretProviderTests.cs | 12 +- .../SecretStoreBuilderExtensionsTests.cs | 695 ++++++------------ .../appsettings.json | 7 +- .../Arcus.Security.Tests.Unit.csproj | 1 + .../Dapr/DaprSecretProviderOptionsTests.cs | 90 +++ .../Dapr/DaprSecretProviderTests.cs | 23 + .../Extensions/SecretStoreExtensionsTests.cs | 32 + src/Arcus.Security.sln | 7 + 30 files changed, 1769 insertions(+), 487 deletions(-) create mode 100644 build/templates/download-dapr.yml create mode 100644 docs/preview/03-Features/secret-store/provider/dapr-secret-store.md create mode 100644 src/Arcus.Security.Providers.Dapr/Arcus.Security.Providers.Dapr.csproj create mode 100644 src/Arcus.Security.Providers.Dapr/DaprSecretProvider.cs create mode 100644 src/Arcus.Security.Providers.Dapr/DaprSecretProviderOptions.cs create mode 100644 src/Arcus.Security.Providers.Dapr/Extensions/SecretStoreBuilderExtensions.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Resources/AzureKeyVault/az-keyvault-secret-store.yaml create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/MultiValuedLocalDaprSecretProvider.cs create mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/local-secret-store.yaml create mode 100644 src/Arcus.Security.Tests.Integration/KeyVault/Configuration/KeyVaultConfig.cs create mode 100644 src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs create mode 100644 src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs create mode 100644 src/Arcus.Security.Tests.Unit/Dapr/Extensions/SecretStoreExtensionsTests.cs diff --git a/build/ci-build.yml b/build/ci-build.yml index 7340ae05..62ce4941 100644 --- a/build/ci-build.yml +++ b/build/ci-build.yml @@ -104,11 +104,6 @@ stages: inputs: artifact: 'Build' path: '$(Build.SourcesDirectory)' - - template: 'templates/download-hashicorp-vault.yml' - parameters: - targetFolder: '$(Build.SourcesDirectory)' - version: $(HashiCorp.Vault.Version) - vaultBinVariableName: 'Arcus.HashiCorp.VaultBin' - template: templates/run-integration-tests.yml parameters: dockerProjectName: '$(Project).Tests.Runtimes.AzureFunctions' diff --git a/build/nuget-release.yml b/build/nuget-release.yml index 7bd21bec..a29f51c5 100644 --- a/build/nuget-release.yml +++ b/build/nuget-release.yml @@ -90,11 +90,6 @@ stages: inputs: artifact: 'Build' path: '$(Build.SourcesDirectory)' - - template: 'templates/download-hashicorp-vault.yml' - parameters: - targetFolder: '$(Build.SourcesDirectory)' - version: $(HashiCorp.Vault.Version) - vaultBinVariableName: 'Arcus.HashiCorp.VaultBin' - template: templates/run-integration-tests.yml parameters: dockerProjectName: '$(Project).Tests.Runtimes.AzureFunctions' diff --git a/build/templates/download-dapr.yml b/build/templates/download-dapr.yml new file mode 100644 index 00000000..f9fbe7ff --- /dev/null +++ b/build/templates/download-dapr.yml @@ -0,0 +1,15 @@ +parameters: + - name: targetFolder + type: string + default: '$(Build.SourcesDirectory)' + - name: daprBinVariableName + type: string + default: 'Arcus.Dapr.DaprBin' + +steps: + - bash: | + wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash + dapr -h + echo "##vso[task.setvariable variable=Arcus.Dapr.DaprBin]/usr/local/bin" + workingDirectory: ${{ parameters.targetFolder }} + displayName: 'Download Dapr' \ No newline at end of file diff --git a/build/templates/run-integration-tests.yml b/build/templates/run-integration-tests.yml index 398523b5..ff7c59c2 100644 --- a/build/templates/run-integration-tests.yml +++ b/build/templates/run-integration-tests.yml @@ -28,6 +28,15 @@ steps: imageName: '${{ parameters.dockerProjectName }}:$(Build.BuildId)' containerName: '${{ parameters.dockerProjectName }}' ports: '$(Arcus.AzureFunctions.HttpPort):80' + - template: 'templates/download-dapr.yml' + parameters: + targetFolder: '$(Build.SourcesDirectory)' + daprBinVariableName: 'Arcus.Dapr.DaprBin' + - template: 'templates/download-hashicorp-vault.yml' + parameters: + targetFolder: '$(Build.SourcesDirectory)' + version: $(HashiCorp.Vault.Version) + vaultBinVariableName: 'Arcus.HashiCorp.VaultBin' - template: test/run-integration-tests.yml@templates parameters: dotnetSdkVersion: '$(DotNet.Sdk.Version)' diff --git a/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md b/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md new file mode 100644 index 00000000..75162b6b --- /dev/null +++ b/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md @@ -0,0 +1,154 @@ +--- +title: "Dapr secret provider" +layout: default +--- + +# Dapr secret provider +Dapr secret provider brings secrets from the Dapr secret store to your application. Dapr is commonly used in Kubernetes environments where there is usually not the same network capabilities as other application environments. +By using this secret provider, you still benefit from all the Arcus secret store features, while still using Dapr as your external secret source. + +⛔ Does not support [synchronous secret retrieval](../../secrets/general.md). + +## Installation +Adding secrets from Dapr into the secret store requires following package: + +```shell +PM > Install-Package Arcus.Security.Providers.Dapr +``` + +## 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 Dapr secret provider with the built-in overloads. + builder.AddDaprSecretStore( + // Name of the secret store where Dapr gets its secrets. + secretStore: "mycustomsecretstore", + // Following defaults can be overridden: + configureOptions: options => + { + // The URI endpoint to use for gRPC calls to the Dapr runtime. + // The default value will be http://127.0.0.1:DAPR_GRPC_PORT where DAPR_GRPC_PORT represents the value of the DAPR_GRPC_PORT environment variable. + options.GrpcEndpoint = "http://127.0.0.1:5001/"; + + // The URI endpoint to use for HTTP calls to the Dapr runtime. + // The default value will be http://127.0.0.1:DAPR_HTTP_PORT where DAPR_HTTP_PORT represents the value of the DAPR_HTTP_PORT environment variable. + options.HttpEndpoint = "http://127.0.0.1:5002"; + + // The API token on every request to the Dapr runtime (added to the request's headers). + options.DaprApiToken = "my-api-key"; + + // Tracking the Dapr secret store 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. + options.TrackDependency = true; + + // Additional metadata entry which will be sent to the Dapr secret store on every request. + options.AddMetadata("my-dapr-key", "my-dapr-value"); + }); + }); + } +} +``` + +### Custom implementation +We allow custom implementations of the Dapr secret provider. +This can come in handy when you want to perform additional actions during the secret retrieval. + +**Example** +In this example we'll create a custom implementation for the local Dapr secret store that allows multi-valued secrets. +First, we'll implement the `DaprSecretProvider`: + +```csharp +using Arcus.Security.Providers.Dapr; + +public class MultiValuedLocalDaprSecretProvider : DaprSecretProvider +{ + public MultiValuedLocalDaprSecretProvider( + string secretStore, + DaprSecretProviderOptions options, + ILogger logger) : base(secretStore, options, logger) + { + } +} +``` + +👀 Notice that we require to take in the name of the Dapr secret store and the additional user-defined options which can be configured during the registration of the secret provider. + +To control how Dapr secrets be retrieved, we need to implement the `DetermineDaprSecretName` method which takes in the secret name like it comes into the secret provider, and implement the multi-valued implementation: + +```csharp +using Arcus.Security.Providers.Dapr; + +public class MultiValuedLocalDaprSecretProvider : DaprSecretProvider +{ + // Constructor truncated... + + /// + /// Determine the Dapr secret key and section based on the user passed-in . + /// + /// + /// The key of the secret in the Dapr secret store can be the same as the section for single-valued Dapr secrets, but is different in multi-valued Dapr secrets. + /// Therefore, make sure to split the into the required (key, section) pair for your use-case. + /// + /// The user passed-in secret which gets translated to a Dapr secret key and section. + protected override (string daprSecretKey, string daprSecretSection) DetermineDaprSecretName(string secretName) + { + const string nestedSeparator = ":"; + + string[] subKeys = secretName.Split(nestedSeparator, StringSplitOptions.RemoveEmptyEntries); + if (subKeys.Length >= 2) + { + string remaining = string.Join(nestedSeparator, subKeys.Skip(1)); + return (subKeys[0], remaining); + } + + return (secretName, secretName); + } +} +``` + +> 💡 Dapr allows for multi-valued secrets for the local Dapr secret store. This means that while single-valued secrets have the same 'key' as 'section' in the returned dictionary, multi-valued secrets are retrieved differently. For more information on the Dapr .NET SDK, see [their official documentation](https://docs.dapr.io/developing-applications/sdks/dotnet/). + +Such a custom implementation can easily be registered with a dedicated extension on 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((config, context, stores) => + { + stores.AddDaprSecretStore( + (IServiceProvider provider, DaprSecretProviderOptions options) => + { + var logger = provider.GetService>(); + return new MultiValuedLocalDaprSecretProvider("mycustomsecretstore", options, logger); + }, + (DaprSecretProviderOptions options) => + { + // Configure additional options which can be passed in the implementation factory function of the custom implementation. + }); + }); + } +} +``` \ No newline at end of file diff --git a/src/Arcus.Security.Providers.Dapr/Arcus.Security.Providers.Dapr.csproj b/src/Arcus.Security.Providers.Dapr/Arcus.Security.Providers.Dapr.csproj new file mode 100644 index 00000000..42aec4b5 --- /dev/null +++ b/src/Arcus.Security.Providers.Dapr/Arcus.Security.Providers.Dapr.csproj @@ -0,0 +1,34 @@ + + + + net6.0 + Arcus + Provides support for Dapr Secrets with Arcus Secret Store + Copyright (c) Arcus + https://security.arcus-azure.net/ + https://github.com/arcus-azure/arcus.security + LICENSE + icon.png + Git + README.md + Kubernetes;Secrets;Dapr + true + true + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Arcus.Security.Providers.Dapr/DaprSecretProvider.cs b/src/Arcus.Security.Providers.Dapr/DaprSecretProvider.cs new file mode 100644 index 00000000..cc4782e0 --- /dev/null +++ b/src/Arcus.Security.Providers.Dapr/DaprSecretProvider.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Arcus.Observability.Telemetry.Core; +using Arcus.Security.Core; +using Dapr.Client; +using GuardNet; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Arcus.Security.Providers.Dapr +{ + /// + /// Represents an retrieving secrets from the Dapr secret store. + /// + public class DaprSecretProvider : ISecretProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the Dapr secret store from which the secrets should be retrieved from. + /// The optional set of options to manipulate the basic behavior of how the secrets should be retrieved. + /// The logger instance to write diagnostic trace messages during the retrieval of the Dapr secrets. + /// Thrown when the is blank. + public DaprSecretProvider(string secretStore, DaprSecretProviderOptions options, ILogger logger) + { + Guard.NotNullOrWhitespace(secretStore, nameof(secretStore)); + + SecretStore = secretStore; + Options = options ?? new DaprSecretProviderOptions(); + Logger = logger ?? NullLogger.Instance; + } + + /// + /// Gets the name of the Dapr secret store for which this secret provider is configured. + /// + protected string SecretStore { get; } + + /// + /// Gets the optional set of configured options to manipulated the basic behavior of how the secrets should be retrieved. + /// + /// + /// Options set when configuring this secret provider in the secret store. + /// + protected DaprSecretProviderOptions Options { get; } + + + /// + /// Gets the logger instance to write diagnostic trace messages during the Dapr secret retrieval. + /// + protected ILogger Logger { get; } + + /// + /// Retrieves the secret value, based on the given name. + /// + /// The name of the secret key. + /// Returns a that contains the secret key. + /// The must not be empty. + /// The must not be null. + /// The secret was not found, using the given name. + public async Task GetSecretAsync(string secretName) + { + Guard.NotNullOrWhitespace(secretName, nameof(secretName)); + + string secretValue = await GetRawSecretAsync(secretName); + return new Secret(secretValue); + } + + /// + /// Retrieves the secret value, based on the given name. + /// + /// The name of the secret key. + /// Returns the secret key. + /// The must not be empty. + /// The must not be null. + /// The secret was not found, using the given name. + public async Task GetRawSecretAsync(string secretName) + { + Guard.NotNullOrWhitespace(secretName, nameof(secretName)); + + Logger.LogTrace("Getting a secret '{SecretName}' from Dapr secret store '{StoreName}'...", secretName, SecretStore); + + (string daprSecretName, string daprSecretSection) = DetermineDaprSecretName(secretName); + string secretValue = await GetDaprSecretValueAsync(daprSecretName, daprSecretSection); + + Logger.LogTrace("Got secret '{SecretName}' from from Dapr secret store '{StoreName}'", secretName, SecretStore); + return secretValue; + } + + /// + /// Determine the Dapr secret key and section based on the user passed-in . + /// + /// + /// The key of the secret in the Dapr secret store can be the same as the section for single-valued Dapr secrets, but is different in multi-valued Dapr secrets. + /// Therefore, make sure to split the into the required (key, section) pair for your use-case. + /// + /// The user passed-in secret which gets translated to a Dapr secret key and section. + protected virtual (string daprSecretKey, string daprSecretSection) DetermineDaprSecretName(string secretName) + { + return (secretName, secretName); + } + + private async Task GetDaprSecretValueAsync(string daprSecretName, string daprSecretSection) + { + Guard.NotNullOrWhitespace(daprSecretName, nameof(daprSecretName)); + Guard.NotNullOrWhitespace(daprSecretSection, nameof(daprSecretSection)); + + using var measurement = DurationMeasurement.Start(); + bool isSuccessful = false; + + try + { + using DaprClient client = Options.CreateClient(); + Dictionary daprSecrets = await client.GetSecretAsync(SecretStore, daprSecretName); + + if (!daprSecrets.TryGetValue(daprSecretSection, out string secretValue)) + { + throw new SecretNotFoundException(daprSecretSection); + } + + isSuccessful = true; + return secretValue; + } + finally + { + if (Options.TrackDependency) + { + Logger.LogDependency("Dapr secret store", daprSecretName, isSuccessful, measurement, new Dictionary + { + ["SecretStore"] = SecretStore, + ["SecretSection"] = daprSecretSection + }); + } + } + } + } +} diff --git a/src/Arcus.Security.Providers.Dapr/DaprSecretProviderOptions.cs b/src/Arcus.Security.Providers.Dapr/DaprSecretProviderOptions.cs new file mode 100644 index 00000000..66230e4c --- /dev/null +++ b/src/Arcus.Security.Providers.Dapr/DaprSecretProviderOptions.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using Dapr.Client; +using GuardNet; + +namespace Arcus.Security.Providers.Dapr +{ + /// + /// Represents the available options for the . + /// + public class DaprSecretProviderOptions + { + private string _grpcEndpoint, _httpEndpoint, _daprApiToken; + + /// + /// Gets the optional metadata to be sent together with the Dapr runtime upon each secret retrieval. + /// + internal IDictionary Metadata { get; } = new Dictionary(); + + /// + /// Overrides the gRPC endpoint used by for communicating with the Dapr runtime. + /// + /// + /// The URI endpoint to use for gRPC calls to the Dapr runtime. The default value will be + /// http://127.0.0.1:DAPR_GRPC_PORT where DAPR_GRPC_PORT represents the value of the + /// DAPR_GRPC_PORT environment variable. + /// + /// Thrown when the is blank. + public string GrpcEndpoint + { + get => _grpcEndpoint; + set + { + Guard.NotNullOrWhitespace(value, nameof(value)); + _grpcEndpoint = value; + } + } + + /// + /// Overrides the HTTP endpoint used by for communicating with the Dapr runtime. + /// + /// + /// The URI endpoint to use for HTTP calls to the Dapr runtime. The default value will be + /// http://127.0.0.1:DAPR_HTTP_PORT where DAPR_HTTP_PORT represents the value of the + /// DAPR_HTTP_PORT environment variable. + /// + /// Thrown when the is blank. + public string HttpEndpoint + { + get => _httpEndpoint; + set + { + Guard.NotNullOrWhitespace(value, nameof(value)); + _httpEndpoint = value; + } + } + + /// + /// Adds a API token on every request to the Dapr runtime. + /// + /// Thrown when the is blank. + public string DaprApiToken + { + get => _daprApiToken; + set + { + Guard.NotNullOrWhitespace(value, nameof(value)); + _daprApiToken = value; + } + } + + /// + /// Gets or sets the flag to indicate whether or not the should track the Dapr dependency. + /// + public bool TrackDependency { get; set; } = false; + + /// + /// Adds an optional metadata entry which will be sent to the Dapr secret store. + /// + /// The unique metadata key. + /// The metadata value for the . + /// + /// Thrown when the or is blank, + /// or when there already exists a metadata entry for the same . + /// + public void AddMetadata(string key, string value) + { + Guard.NotNullOrWhitespace(key, nameof(key)); + Guard.NotNullOrWhitespace(value, nameof(value)); + + if (Metadata.ContainsKey(key)) + { + throw new ArgumentException( + $"Cannot add metadata entry because there already exists an entry with key '{key}'", nameof(key)); + } + + Metadata.Add(key, value); + } + + /// + /// Creates an based on the previously configured options. + /// + internal DaprClient CreateClient() + { + var builder = new DaprClientBuilder(); + + if (!string.IsNullOrWhiteSpace(_grpcEndpoint)) + { + builder.UseGrpcEndpoint(_grpcEndpoint); + } + + if (!string.IsNullOrWhiteSpace(_daprApiToken)) + { + builder.UseDaprApiToken(_daprApiToken); + } + + if (!string.IsNullOrWhiteSpace(_httpEndpoint)) + { + builder.UseHttpEndpoint(_httpEndpoint); + } + + return builder.Build(); + } + } +} diff --git a/src/Arcus.Security.Providers.Dapr/Extensions/SecretStoreBuilderExtensions.cs b/src/Arcus.Security.Providers.Dapr/Extensions/SecretStoreBuilderExtensions.cs new file mode 100644 index 00000000..3099adf9 --- /dev/null +++ b/src/Arcus.Security.Providers.Dapr/Extensions/SecretStoreBuilderExtensions.cs @@ -0,0 +1,98 @@ +using System; +using Arcus.Security.Providers.Dapr; +using GuardNet; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.Hosting +{ + /// + /// Extensions on the to add the secrets from the Dapr secret store. + /// + public static class SecretStoreBuilderExtensions + { + /// + /// Adds the secrets from the Dapr secret store. + /// + /// The builder instance to add the secret source to. + /// The name of the Dapr secret store to include in the secret store. + /// Thrown when the is null. + /// Thrown when the is blank. + public static SecretStoreBuilder AddDaprSecretStore( + this SecretStoreBuilder builder, + string secretStore) + { + Guard.NotNull(builder, nameof(builder)); + Guard.NotNullOrWhitespace(secretStore, nameof(secretStore)); + + return AddDaprSecretStore(builder, secretStore, configureOptions: null); + } + + /// + /// Adds the secrets from the Dapr secret store. + /// + /// The builder instance to add the secret source to. + /// The name of the Dapr secret store to include in the secret store. + /// The function to create an optional set of options to manipulate the basic behavior of how the secrets should be retrieved. + /// Thrown when the is null. + /// Thrown when the is blank. + public static SecretStoreBuilder AddDaprSecretStore( + this SecretStoreBuilder builder, + string secretStore, + Action configureOptions) + { + Guard.NotNull(builder, nameof(builder)); + Guard.NotNullOrWhitespace(secretStore, nameof(secretStore)); + + return AddDaprSecretStore(builder, (provider, options) => + { + var logger = provider.GetService>(); + return new DaprSecretProvider(secretStore, options, logger); + }, configureOptions); + } + + /// + /// Adds the secrets from the Dapr secret store. + /// + /// The custom implementation of the . + /// The builder instance to add the secret source to. + /// The function to create a custom instance of the . + /// Thrown when the is null. + public static SecretStoreBuilder AddDaprSecretStore( + this SecretStoreBuilder builder, + Func implementationFactory) + where TCustom : DaprSecretProvider + { + Guard.NotNull(builder, nameof(builder)); + Guard.NotNull(implementationFactory, nameof(implementationFactory)); + + return builder.AddProvider(implementationFactory, configureOptions: null); + } + + /// + /// Adds the secrets from the Dapr secret store. + /// + /// The custom implementation of the . + /// The builder instance to add the secret source to. + /// The function to create a custom instance of the . + /// The function to create an optional set of options to manipulate the basic behavior of how the secrets should be retrieved. + /// Thrown when the is null. + public static SecretStoreBuilder AddDaprSecretStore( + this SecretStoreBuilder builder, + Func implementationFactory, + Action configureOptions) + where TCustom : DaprSecretProvider + { + Guard.NotNull(builder, nameof(builder)); + Guard.NotNull(implementationFactory, nameof(implementationFactory)); + + var options = new DaprSecretProviderOptions(); + configureOptions?.Invoke(options); + + return builder.AddProvider( + provider => implementationFactory(provider, options), + configureOptions: null); + } + } +} diff --git a/src/Arcus.Security.Tests.Integration/Arcus.Security.Tests.Integration.csproj b/src/Arcus.Security.Tests.Integration/Arcus.Security.Tests.Integration.csproj index 28eeb0ce..3f4d3875 100644 --- a/src/Arcus.Security.Tests.Integration/Arcus.Security.Tests.Integration.csproj +++ b/src/Arcus.Security.Tests.Integration/Arcus.Security.Tests.Integration.csproj @@ -1,7 +1,7 @@  - net6.0;netcoreapp3.1 + net6.0 false CS0618 @@ -9,6 +9,7 @@ + @@ -17,12 +18,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -34,6 +37,16 @@ Always + + Always + + + Always + + + + + diff --git a/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs b/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs new file mode 100644 index 00000000..d4cae843 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Arcus.Security.Core; +using Arcus.Security.Providers.Dapr; +using Arcus.Security.Tests.Integration.Dapr.Hosting; +using Arcus.Security.Tests.Integration.Dapr.Resources.Local; +using Arcus.Security.Tests.Integration.KeyVault.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace Arcus.Security.Tests.Integration.Dapr +{ + public class DaprSecretProviderTests : IntegrationTest + { + /// + /// Initializes a new instance of the class. + /// + public DaprSecretProviderTests(ITestOutputHelper outputWriter) : base(outputWriter) + { + } + + private KeyVaultConfig KeyVault => Configuration.GetKeyVaultConfig(); + + [Fact] + public async Task Dapr_WithAzureKeyVault_Succeeds() + { + // Arrange + await using DaprSidecarFixture sideCar = await StartSideCarAsync(opt => + { + opt.LoadKeyVault(Configuration); + }); + + ISecretProvider provider = sideCar.GetSecretProvider(); + + Assert.NotNull(await provider.GetRawSecretAsync(KeyVault.SecretName)); + Assert.NotNull((await provider.GetSecretAsync(KeyVault.SecretName)).Value); + } + + [Fact] + public async Task Dapr_WithCustomMultiValuedLocal_Succeeds() + { + // Arrange + var secretStore = JObject.Parse(@"{ + ""redisPassword"": ""your redis password"", + ""connectionStrings"": { + ""mySql"": { + ""user"": ""your username"", + ""pass"": ""your password"" + } + } + }"); + + await using DaprSidecarFixture sideCar = await StartSideCarAsync(opt => + { + opt.LoadSecrets(secretStore); + }); + + ISecretProvider provider = sideCar.GetSecretProvider((serviceProvider, secretOptions, fixtureOptions) => + { + return new MultiValuedLocalDaprSecretProvider( + fixtureOptions.StoreName, + secretOptions, + serviceProvider.GetService>()); + }); + + // Act + string actual = await provider.GetRawSecretAsync("connectionStrings:mySql:pass"); + + // Assert + Assert.Equal("your password", actual); + } + + [Fact] + public async Task Dapr_WithKnownLocalSecretName_Succeeds() + { + // Arrange + var secretStore = JObject.Parse(@"{ + ""redisPassword"": ""your redis password"" + }"); + + await using DaprSidecarFixture sideCar = await StartSideCarAsync(opt => + { + opt.LoadSecrets(secretStore); + }); + + ISecretProvider provider = sideCar.GetSecretProvider(); + + // Act / Assert + string secretName = "redisPassword"; + string expected = "your redis password"; + Assert.Equal(expected, await provider.GetRawSecretAsync(secretName)); + Assert.Equal(expected, (await provider.GetSecretAsync(secretName)).Value); + } + + [Fact] + public async Task Dapr_WithUnknownLocalSecretName_Fails() + { + // Arrange + var secretStore = JObject.Parse(@"{ + ""redisPassword"": ""your redis password"" + }"); + + await using DaprSidecarFixture sideCar = await StartSideCarAsync(opt => + { + opt.LoadSecrets(secretStore); + }); + + ISecretProvider provider = sideCar.GetSecretProvider(); + + // Act / Assert + string secretName = "unknownPass"; + await Assert.ThrowsAsync(() => provider.GetSecretAsync(secretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(secretName)); + } + + [Theory] + [InlineData(false, 0)] + [InlineData(true, 1)] + public async Task Dapr_WithTrackDependency_Succeeds(bool trackDependency, int expectedDependencyTracked) + { + // Arrange + await using DaprSidecarFixture sideCar = await StartSideCarAsync(opt => + { + opt.LoadSecrets(JObject.Parse(@"{ ""myPass"": ""your password"" }")); + }); + + ISecretProvider provider = sideCar.GetSecretProvider(opt => opt.TrackDependency = trackDependency, SerilogLogger); + + // Act + string actual = await provider.GetRawSecretAsync("myPass"); + + // Assert + Assert.Equal("your password", actual); + Assert.Equal(expectedDependencyTracked, + InMemoryLogSink.CurrentLogEmits.Count(ev => ev.MessageTemplate.Text.Contains("Dependency"))); + } + + private async Task StartSideCarAsync(Action configureOptions) + { + return await DaprSidecarFixture.StartSideCarAsync(Configuration, Logger, configureOptions); + } + } +} diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs new file mode 100644 index 00000000..0f3d4c92 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Arcus.Security.Core; +using Arcus.Security.Providers.Dapr; +using Arcus.Security.Tests.Integration.Dapr.Resources; +using Arcus.Security.Tests.Integration.Fixture; +using Arcus.Testing.Logging; +using Dapr.Client; +using GuardNet; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json.Linq; +using Polly; +using Serilog; +using Xunit; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Arcus.Security.Tests.Integration.Dapr.Hosting +{ + /// + /// Represents the available teardown options for the . + /// + [Flags] + public enum TearDownOptions + { + /// + /// De-activate any additional teardown functionality. + /// + None = 0, + + /// + /// Logs the standard output console of the Dapr Sidecar service to the test output during teardown. + /// + LogDaprOutput = 1 + } + + /// + /// Represents a test fixture that runs the Dapr Sidecar as a temporary service. + /// + public sealed class DaprSidecarFixture : IAsyncDisposable + { + private const string DaprLocalSecretStore = "localsecretstore"; + + private readonly Process _process; + private readonly DaprSidecarOptions _options; + private readonly ILogger _logger; + private readonly ICollection _disposables = new Collection(); + + private DaprSidecarFixture(Process process, int port, DaprSidecarOptions options, ILogger logger) + { + Guard.NotNull(process, nameof(process)); + Guard.NotLessThan(port, 0, nameof(port)); + + _process = process; + _options = options; + _logger = logger ?? NullLogger.Instance; + + Endpoint = new Uri($"http://127.0.0.1:{port}/"); + } + + /// + /// Gets the GRPC endpoint where the Dapr Sidecar is hosted. + /// + public Uri Endpoint { get; } + + /// + /// Gets or sets the options to manipulate the teardown process of the test fixture. + /// + public TearDownOptions TearDownOptions { get; set; } = TearDownOptions.LogDaprOutput; + + /// + /// Starts a new as a temporary service with the provided user-defined options. + /// + /// The integration test configuration to retrieve the required hosting-related values. + /// The logger instance to write diagnostic trace messages during the startup and teardown of the test fixture. + /// The user-defined options to configure the Dapr Sidecar. + /// Thrown when the is null. + public static async Task StartSideCarAsync(TestConfig configuration, ILogger logger, Action configureOptions) + { + Guard.NotNull(configuration, nameof(configuration)); + logger ??= NullLogger.Instance; + int port = 60002; + + var options = new DaprSidecarOptions(); + configureOptions?.Invoke(options); + options.WriteSecretConfig(); + + Process process = CreateProcess(configuration, port, options); + var fixture = new DaprSidecarFixture(process, port, options, logger); + + await fixture.StartProcessAsync(); + return fixture; + } + + private static Process CreateProcess(TestConfig configuration, int port, DaprSidecarOptions options) + { + FileInfo daprExeFile = configuration.GetDaprInstallationFilePath(); + + string vaultArgs = String.Join(" ", + "run", + $"--resources-path {nameof(Dapr)}/Resources/{options.StoreType}", + $"--app-port 6002 --dapr-http-port 3601 --dapr-grpc-port {port}"); + + var startInfo = new ProcessStartInfo(daprExeFile.FullName, vaultArgs) + { + WorkingDirectory = Directory.GetCurrentDirectory(), + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true + }; + + return new Process { StartInfo = startInfo }; + } + + private async Task StartProcessAsync() + { + _logger.LogTrace("Starting Dapr Sidecar: {FileName} {Arguments}", _process.StartInfo.FileName, _process.StartInfo.Arguments); + + bool isStarted = _process.Start(); + if (!isStarted) + { + throw new InvalidOperationException( + "Cannot correctly start Dapr Sidecar process due to an unexpected failure, please make sure to check if the provided arguments are correct"); + } + + PolicyResult healthResult = + await Policy.TimeoutAsync(TimeSpan.FromSeconds(20)) + .WrapAsync(Policy.Handle() + .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(100))) + .ExecuteAndCaptureAsync(async () => + { + _logger.LogTrace("Checking Dapr Sidecar health..."); + + using var client = new DaprClientBuilder().UseGrpcEndpoint(Endpoint.OriginalString).Build(); + await client.GetMetadataAsync(); + }); + + if (healthResult.Outcome is OutcomeType.Failure) + { + _logger.LogError("Failed to correctly start Dapr Sidecar"); + await StopProcessAsync(); + + throw new TimeoutException( + "Could not correctly start Dapr Sidecar because the sidecar did not respond with a healthy response in the expected time frame", healthResult.FinalException); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); + _logger.LogTrace("Dapr Sidecar started at {Endpoint}!", Endpoint); + } + + /// + /// Gets a registered based on the current Dapr sidecar configuration. + /// + /// The function to configure additional values on the . + /// The optional logger to include while registering the secret provider. + public ISecretProvider GetSecretProvider( + Action configureProvider = null, + Serilog.ILogger logger = null) + { + return CreateSecretProvider(stores => stores.AddDaprSecretStore(_options.StoreName, options => + { + options.GrpcEndpoint = Endpoint.ToString(); + configureProvider?.Invoke(options); + }), logger); + } + + /// + /// Gets a registered based on the current Dapr sidecar configuration. + /// + /// The function to create a custom . + /// The function to configure additional values on the . + /// The optional logger to include while registering the secret provider. + public ISecretProvider GetSecretProvider( + Func implementationFactory, + Action configureProvider = null, + Serilog.ILogger logger = null) + where TCustom : DaprSecretProvider + { + return CreateSecretProvider(stores => + { + stores.AddDaprSecretStore( + (provider, opt) => implementationFactory(provider, opt, _options), + options => + { + options.GrpcEndpoint = Endpoint.ToString(); + configureProvider?.Invoke(options); + }); + }, logger); + } + + private ISecretProvider CreateSecretProvider( + Action configureSecretStore, + Serilog.ILogger logger) + { + var builder = new HostBuilder(); + if (logger != null) + { + builder.UseSerilog(logger); + } + + builder.ConfigureLogging(logging => + { + logging.SetMinimumLevel(LogLevel.Trace); + logging.AddProvider(new CustomLoggerProvider(_logger)); + }); + + builder.ConfigureSecretStore((config, stores) => + { + configureSecretStore(stores); + }); + + IHost host = builder.Build(); + _disposables.Add(host); + + IServiceProvider serviceProvider = host.Services; + return serviceProvider.GetRequiredService(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. + /// + /// A task that represents the asynchronous dispose operation. + public async ValueTask DisposeAsync() + { + await StopProcessAsync(); + Assert.All(_disposables, d => d.Dispose()); + } + + private async Task StopProcessAsync() + { + if (!_process.HasExited) + { + _process.Kill(entireProcessTree: true); + } + + if (TearDownOptions.HasFlag(TearDownOptions.LogDaprOutput)) + { + _logger.LogTrace("Dapr Sidecar read standard output..."); + string output = await _process.StandardOutput.ReadToEndAsync(); + _logger.LogDebug("Dapr Sidecar console: {Output}", output); + } + + _logger.LogTrace("Stop Dapr Sidecar at {Endpoint}", Endpoint); + _process.Dispose(); + } + } +} diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs new file mode 100644 index 00000000..70d46631 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; +using Arcus.Security.Tests.Integration.Fixture; +using Arcus.Security.Tests.Integration.KeyVault.Configuration; +using GuardNet; +using Newtonsoft.Json.Linq; + +namespace Arcus.Security.Tests.Integration.Dapr.Hosting +{ + public enum DaprStoreType + { + Local, AzureKeyVault + } + + /// + /// Represents the available user options for the . + /// + public class DaprSidecarOptions + { + private JObject _startObject; + private KeyVaultConfig _config; + + public DaprStoreType StoreType { get; private set; } + + public string StoreName + { + get + { + switch (StoreType) + { + case DaprStoreType.Local: return "localsecretstore"; + case DaprStoreType.AzureKeyVault: return "azurekeyvault"; + default: + throw new ArgumentOutOfRangeException(nameof(StoreType), StoreType, "Unknown Dapr store type"); + } + } + } + + public DaprSidecarOptions LoadKeyVault(TestConfig configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + + _config = configuration.GetKeyVaultConfig(); + StoreType = DaprStoreType.AzureKeyVault; + + return this; + } + + /// + /// Loads a JSON representing a local secret set of the Dapr secret store. + /// + /// The Dapr local secret store. + /// Thrown when the is null. + public DaprSidecarOptions LoadSecrets(JObject secretStore) + { + Guard.NotNull(secretStore, nameof(secretStore)); + + _startObject = secretStore; + StoreType = DaprStoreType.Local; + + return this; + } + + internal void WriteSecretConfig() + { + switch (StoreType) + { + case DaprStoreType.Local: + JObject json = _startObject ?? new JObject(); + string secretsPath = Path.Combine(Directory.GetCurrentDirectory(), "secrets.json"); + File.WriteAllText(secretsPath, json.ToString()); + break; + + case DaprStoreType.AzureKeyVault: + string storePath = Path.Combine(Directory.GetCurrentDirectory(), nameof(Dapr), "Resources", StoreType.ToString(), "az-keyvault-secret-store.yaml"); + string contents = File.ReadAllText(storePath); + + File.WriteAllText(storePath, + contents.Replace("[your_service_principal_tenant_id]", _config.Azure.TenantId) + .Replace("[your_service_principal_app_id]", _config.ServicePrincipal.ClientId) + .Replace("[your_keyvault_name]", _config.VaultName) + .Replace("[your_service_principal_app_secret]", _config.ServicePrincipal.ClientSecret)); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(StoreType), StoreType, "Unknown Dapr store type"); + } + } + } +} \ No newline at end of file diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs new file mode 100644 index 00000000..221fc1fd --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using GuardNet; +using Microsoft.Extensions.Configuration; + +// ReSharper disable once CheckNamespace +namespace Arcus.Security.Tests.Integration.Fixture +{ + /// + /// Extensions on the for easier access to the Dapr-related information. + /// + public static class TestConfigExtensions + { + /// + /// Gets the local file path of the Dapr installation on this system where the tests are run. + /// + /// The integration test configuration. + /// Thrown when the is null. + /// Thrown when there is no Dapr installation file path present in the application settings. + /// Thrown when a Dapr installation on the system where the tests are running cannot be found. + public static FileInfo GetDaprInstallationFilePath(this TestConfig configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + + var key = "Arcus:Dapr:DaprBin"; + string filePath = configuration.GetValue(key); + + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new KeyNotFoundException( + "Could not find the installation file path of the Dapr Sidecar in the local app settings" + + "please install the HashiCorp Vault on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + + $"and add the installation folder as configuration key '{key}' to your local app settings"); + } + + if (filePath.StartsWith("#{") && filePath.EndsWith("}#")) + { + throw new KeyNotFoundException( + $"Could not find the installation file path of the Dapr Sidecar in the local app settings because the appsettings token '{filePath}' is not yet replaced," + + "please install the HashiCorp Vault on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + + $"and add the installation folder as configuration key '{key}' to your local app settings"); + } + + FileInfo file; + try + { + file = new FileInfo(filePath); + } + catch (Exception exception) + { + throw new FileNotFoundException( + $"Could not find file path returned for key '{key}' because it doesn't point to valid Dapr Sidecar execution file, " + + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + + $"and add the installation folder as configuration key '{key}' to your local app settings", exception); + } + + if (!file.Exists || !file.Name.StartsWith("dapr")) + { + throw new FileNotFoundException( + $"Could not find file path returned for key '{key}' because it doesn't point to valid Dapr Sidecar execution file ('vault'), " + + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + + $"and add the installation folder as configuration key '{key}' to your local app settings"); + } + + return file; + } + } +} diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Resources/AzureKeyVault/az-keyvault-secret-store.yaml b/src/Arcus.Security.Tests.Integration/Dapr/Resources/AzureKeyVault/az-keyvault-secret-store.yaml new file mode 100644 index 00000000..b8f44109 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Resources/AzureKeyVault/az-keyvault-secret-store.yaml @@ -0,0 +1,17 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: azurekeyvault +spec: + type: secretstores.azure.keyvault + version: v1 + metadata: + - name: vaultName # Required + value: [your_keyvault_name] + # See authentication section below for all options + - name: azureTenantId + value: "[your_service_principal_tenant_id]" + - name: azureClientId + value: "[your_service_principal_app_id]" + - name: azureClientSecret + value: "[your_service_principal_app_secret]" diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs b/src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs new file mode 100644 index 00000000..9ad13a24 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs @@ -0,0 +1,32 @@ +namespace Arcus.Security.Tests.Integration.Dapr.Resources +{ + /// + /// Represents the schema of the YAML file of the Dapr local secret store. + /// + public class DaprSecretStore + { + public string ApiVersion { get; set; } + public string Kind { get; set; } + public DaprSecretStoreMetaData Metadata { get; set; } + public DaprSecretStoreSpec Spec { get; set; } + } + + public class DaprSecretStoreMetaData + { + public string Name { get; set; } + public string Namespace { get; set; } + } + + public class DaprSecretStoreSpec + { + public string Type { get; set; } + public string Version { get; set; } + public DaprSecretStoreSpecMetaData[] Metadata { get; set; } + } + + public class DaprSecretStoreSpecMetaData + { + public string Name { get; set; } + public object Value { get; set; } + } +} diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/MultiValuedLocalDaprSecretProvider.cs b/src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/MultiValuedLocalDaprSecretProvider.cs new file mode 100644 index 00000000..f31f1756 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/MultiValuedLocalDaprSecretProvider.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using Arcus.Security.Providers.Dapr; +using Microsoft.Extensions.Logging; + +namespace Arcus.Security.Tests.Integration.Dapr.Resources.Local +{ + /// + /// Represents a local variant of the that supports multi-valued secrets. + /// + public class MultiValuedLocalDaprSecretProvider : DaprSecretProvider + { + /// + public MultiValuedLocalDaprSecretProvider( + string secretStore, + DaprSecretProviderOptions options, + ILogger logger) + : base(secretStore, options, logger) + { + } + + /// + /// Determine the Dapr secret key and section based on the user passed-in . + /// + /// + /// The key of the secret in the Dapr secret store can be the same as the section for single-valued Dapr secrets, but is different in multi-valued Dapr secrets. + /// Therefore, make sure to split the into the required (key, section) pair for your use-case. + /// + /// The user passed-in secret which gets translated to a Dapr secret key and section. + protected override (string daprSecretKey, string daprSecretSection) DetermineDaprSecretName(string secretName) + { + const string nestedSeparator = ":"; + + string[] subKeys = secretName.Split(nestedSeparator, StringSplitOptions.RemoveEmptyEntries); + if (subKeys.Length >= 2) + { + string remaining = string.Join(nestedSeparator, subKeys.Skip(1)); + return (subKeys[0], remaining); + } + + return (secretName, secretName); + } + } +} diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/local-secret-store.yaml b/src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/local-secret-store.yaml new file mode 100644 index 00000000..320f2e89 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/Dapr/Resources/Local/local-secret-store.yaml @@ -0,0 +1,15 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: localsecretstore + namespace: default +spec: + type: secretstores.local.file + version: v1 + metadata: + - name: secretsFile + value: secrets.json + - name: nestedSeparator + value: ":" + - name: multiValued + value: true \ No newline at end of file diff --git a/src/Arcus.Security.Tests.Integration/HashiCorp/SecretStoreBuilderExtensionsTests.cs b/src/Arcus.Security.Tests.Integration/HashiCorp/SecretStoreBuilderExtensionsTests.cs index 1a2a0bf4..60a2ece1 100644 --- a/src/Arcus.Security.Tests.Integration/HashiCorp/SecretStoreBuilderExtensionsTests.cs +++ b/src/Arcus.Security.Tests.Integration/HashiCorp/SecretStoreBuilderExtensionsTests.cs @@ -52,7 +52,7 @@ public async Task AuthenticateWithInvalidUserPassPasswordKeyValue_GetSecret_Fail const string policyName = "my-policy"; var builder = new HostBuilder(); - builder.UseSerilog(Logger); + builder.UseSerilog(SerilogLogger); using (var server = await HashiCorpVaultTestServer.StartServerAsync(_config, _logger)) { @@ -104,7 +104,7 @@ public async Task AuthenticateWithUnauthorizedUserPassUserKeyValue_GetSecret_Fai const string policyName = "my-policy"; var builder = new HostBuilder(); - builder.UseSerilog(Logger); + builder.UseSerilog(SerilogLogger); using (var server = await HashiCorpVaultTestServer.StartServerAsync(_config, _logger)) { diff --git a/src/Arcus.Security.Tests.Integration/IntegrationTest.cs b/src/Arcus.Security.Tests.Integration/IntegrationTest.cs index 22202c39..0397b3ca 100644 --- a/src/Arcus.Security.Tests.Integration/IntegrationTest.cs +++ b/src/Arcus.Security.Tests.Integration/IntegrationTest.cs @@ -3,6 +3,7 @@ using Arcus.Testing.Logging.Extensions; using Arcus.Testing.Logging; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Serilog; using Serilog.Configuration; using Serilog.Core; @@ -15,7 +16,8 @@ public class IntegrationTest : IDisposable private bool _disposed; protected TestConfig Configuration { get; } - protected Logger Logger { get; } + protected Microsoft.Extensions.Logging.ILogger Logger { get; } + protected Logger SerilogLogger { get; } protected InMemoryLogSink InMemoryLogSink { get; } public IntegrationTest(ITestOutputHelper testOutput) @@ -25,11 +27,13 @@ public IntegrationTest(ITestOutputHelper testOutput) InMemoryLogSink = new InMemoryLogSink(); var configuration = new LoggerConfiguration() + .MinimumLevel.Verbose() .WriteTo.XunitTestLogging(testOutput) .WriteTo.Sink(InMemoryLogSink) .WriteTo.AzureApplicationInsights(Configuration.GetValue("Arcus:ApplicationInsights:InstrumentationKey")); - Logger = configuration.CreateLogger(); + Logger = new XunitTestLogger(testOutput); + SerilogLogger = configuration.CreateLogger(); } /// @@ -53,7 +57,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - Logger.Dispose(); + SerilogLogger.Dispose(); } } } \ No newline at end of file diff --git a/src/Arcus.Security.Tests.Integration/KeyVault/Configuration/KeyVaultConfig.cs b/src/Arcus.Security.Tests.Integration/KeyVault/Configuration/KeyVaultConfig.cs new file mode 100644 index 00000000..78958902 --- /dev/null +++ b/src/Arcus.Security.Tests.Integration/KeyVault/Configuration/KeyVaultConfig.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Arcus.Security.Tests.Integration.Fixture; +using Google.Api; +using Google.Protobuf.WellKnownTypes; + +namespace Arcus.Security.Tests.Integration.KeyVault.Configuration +{ + public static class TestConfigExtensions + { + public static KeyVaultConfig GetKeyVaultConfig(this TestConfig configuration) + { + return new KeyVaultConfig( + configuration.GetRequiredValue("Arcus:KeyVault:Uri"), + configuration.GetRequiredValue("Arcus:KeyVault:TestKeyName"), + new AzureEnvironmentConfig( + configuration.GetTenantId()), + new ServicePrincipalConfig( + configuration.GetServicePrincipalClientId(), + configuration.GetServicePrincipalClientSecret())); + } + } + + public class KeyVaultConfig + { + /// + /// Initializes a new instance of the class. + /// + public KeyVaultConfig( + string vaultUri, + string secretName, + AzureEnvironmentConfig environment, + ServicePrincipalConfig servicePrincipal) + { + VaultUri = vaultUri; + VaultName = new Uri(VaultUri).Host.Replace(".vault.azure.net", ""); + SecretName = secretName; + + Azure = environment; + ServicePrincipal = servicePrincipal; + } + + public string VaultName { get; } + public string VaultUri { get; } + public string SecretName { get; } + + public AzureEnvironmentConfig Azure { get; } + public ServicePrincipalConfig ServicePrincipal { get; } + } + + public class AzureEnvironmentConfig + { + /// + /// Initializes a new instance of the class. + /// + public AzureEnvironmentConfig(string tenantId) + { + TenantId = tenantId; + } + + public string TenantId { get; } + } + + public class ServicePrincipalConfig + { + /// + /// Initializes a new instance of the class. + /// + public ServicePrincipalConfig(string clientId, string clientSecret) + { + ClientId = clientId; + ClientSecret = clientSecret; + } + + public string ClientId { get; } + public string ClientSecret { get; } + } +} diff --git a/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultCachedSecretProviderTests.cs b/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultCachedSecretProviderTests.cs index e3fe5323..525fb56b 100644 --- a/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultCachedSecretProviderTests.cs +++ b/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultCachedSecretProviderTests.cs @@ -5,6 +5,7 @@ using Arcus.Security.Providers.AzureKeyVault.Authentication; using Arcus.Security.Providers.AzureKeyVault.Configuration; using Arcus.Security.Tests.Core.Fixture; +using Arcus.Security.Tests.Integration.KeyVault.Configuration; using Azure.Core; using Azure.Identity; using Azure.Security.KeyVault.Secrets; @@ -28,23 +29,23 @@ public KeyVaultCachedSecretProviderTests(ITestOutputHelper testOutput) : base(te public async Task KeyVaultSecretProvider_StoreSecret_Succeeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - + var keyVault = Configuration.GetKeyVaultConfig(); + string clientId = keyVault.ServicePrincipal.ClientId; + var secretName = $"Test-Secret-{Guid.NewGuid()}"; var secretValue = Guid.NewGuid().ToString(); - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, keyVault.Azure.TenantId)) using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, keyVault.ServicePrincipal.ClientSecret)) { - var tokenCredential = new ChainedTokenCredential(new ManagedIdentityCredential(clientId), new EnvironmentCredential()); - var keyVaultSecretProvider = new KeyVaultSecretProvider( - tokenCredential: tokenCredential, - vaultConfiguration: new KeyVaultConfiguration(keyVaultUri)); - var cachedSecretProvider = new KeyVaultCachedSecretProvider(keyVaultSecretProvider); + var tokenCredential = new ChainedTokenCredential( + new ManagedIdentityCredential(clientId), + new EnvironmentCredential()); + + var cachedSecretProvider = new KeyVaultCachedSecretProvider( + new KeyVaultSecretProvider( + tokenCredential, new KeyVaultConfiguration(keyVault.VaultUri))); try { @@ -62,7 +63,7 @@ public async Task KeyVaultSecretProvider_StoreSecret_Succeeds() } finally { - var client = new SecretClient(new Uri(keyVaultUri), tokenCredential); + var client = new SecretClient(new Uri(keyVault.VaultUri), tokenCredential); await client.StartDeleteSecretAsync(secretName); } } diff --git a/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultSecretProviderTests.cs b/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultSecretProviderTests.cs index 826b8f8e..9754535b 100644 --- a/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultSecretProviderTests.cs +++ b/src/Arcus.Security.Tests.Integration/KeyVault/KeyVaultSecretProviderTests.cs @@ -7,6 +7,7 @@ using Arcus.Security.Providers.AzureKeyVault.Authentication; using Arcus.Security.Providers.AzureKeyVault.Configuration; using Arcus.Security.Tests.Core.Fixture; +using Arcus.Security.Tests.Integration.KeyVault.Configuration; using Azure.Identity; using Azure.Security.KeyVault.Secrets; using Microsoft.Extensions.Configuration; @@ -27,11 +28,12 @@ public KeyVaultSecretProviderTests(ITestOutputHelper testOutput) : base(testOutp { } - private string TenantId => Configuration.GetTenantId(); - private string ClientId => Configuration.GetRequiredValue("Arcus:ServicePrincipal:ApplicationId"); - private string ClientSecret => Configuration.GetRequiredValue("Arcus:ServicePrincipal:AccessKey"); - private string TestSecretName => Configuration.GetRequiredValue("Arcus:KeyVault:TestKeyName"); - private string VaultUri => Configuration.GetRequiredValue("Arcus:KeyVault:Uri"); + private KeyVaultConfig KeyVault => Configuration.GetKeyVaultConfig(); + private string TenantId => KeyVault.Azure.TenantId; + private string ClientId => KeyVault.ServicePrincipal.ClientId; + private string ClientSecret => KeyVault.ServicePrincipal.ClientSecret; + private string TestSecretName => KeyVault.SecretName; + private string VaultUri => KeyVault.VaultUri; private string TestSecretVersion => Configuration.GetRequiredValue("Arcus:KeyVault:TestKeyVersion"); [Fact] diff --git a/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs b/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs index 3062cd91..16f1137e 100644 --- a/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs +++ b/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using System.Net; using System.Threading.Tasks; using Arcus.Security.Core; using Arcus.Security.Tests.Core.Fixture; using Arcus.Security.Tests.Integration.Fixture; +using Arcus.Security.Tests.Integration.KeyVault.Configuration; using Azure; using Azure.Identity; +using Microsoft.AspNetCore.Http; using Microsoft.Azure.KeyVault.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -26,22 +26,25 @@ public SecretStoreBuilderExtensionsTests(ITestOutputHelper testOutput) : base(te { } + private KeyVaultConfig KeyVault => Configuration.GetKeyVaultConfig(); + private string TenantId => KeyVault.Azure.TenantId; + private string ClientId => KeyVault.ServicePrincipal.ClientId; + private string ClientSecret => KeyVault.ServicePrincipal.ClientSecret; + private string TestSecretName => KeyVault.SecretName; + private string VaultUri => KeyVault.VaultUri; + private string MsiConnectionString => Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); + [Fact] public async Task AddAzureKeyVault_WithServicePrincipal_GetSecretSucceeds() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, applicationId, clientKey); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, ClientId, ClientSecret); }); // Assert @@ -49,8 +52,8 @@ public async Task AddAzureKeyVault_WithServicePrincipal_GetSecretSucceeds() { var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } } @@ -72,18 +75,13 @@ private void AssertNotNullSecret(string secretValue) public async Task AddAzureKeyVaultWithDependencyTracking_WithServicePrincipal_GetSecretSucceeds(bool trackDependency, int expectedTrackedDependencies) { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipalWithOptions(keyVaultUri, applicationId, clientKey, + stores.AddAzureKeyVaultWithServicePrincipalWithOptions(VaultUri, ClientId, ClientSecret, configureOptions: options => options.TrackDependency = trackDependency); }); @@ -92,8 +90,8 @@ public async Task AddAzureKeyVaultWithDependencyTracking_WithServicePrincipal_Ge { var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } AssertTrackedAzureKeyVaultDependency(expectedTrackedDependencies); @@ -103,22 +101,19 @@ public async Task AddAzureKeyVaultWithDependencyTracking_WithServicePrincipal_Ge public async Task AddAzureKeyVault_WithServicePrincipal_GetSecretFails() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, applicationId, clientKey); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, ClientId, ClientSecret); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + + var keyName = "UnknownSecretName"; Assert.Throws(() => provider.GetSecret(keyName)); Assert.Throws(() => provider.GetRawSecret(keyName)); await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); @@ -131,18 +126,13 @@ public async Task AddAzureKeyVault_WithServicePrincipal_GetSecretFails() public async Task AddAzureKeyVaultWithDependencyTracking_WithServicePrincipal_GetSecretFails(bool trackDependency, int expectedTrackedDependencies) { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipalWithOptions(keyVaultUri, applicationId, clientKey, + stores.AddAzureKeyVaultWithServicePrincipalWithOptions(VaultUri, ClientId, ClientSecret, configureOptions: options => options.TrackDependency = trackDependency); }); @@ -150,6 +140,8 @@ public async Task AddAzureKeyVaultWithDependencyTracking_WithServicePrincipal_Ge using (IHost host = builder.Build()) { var provider = host.Services.GetRequiredService(); + + var keyName = "UnknownSecretName"; await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); } @@ -162,25 +154,20 @@ public async Task AddAzureKeyVaultWithDependencyTracking_WithServicePrincipal_Ge public async Task AddAzureKeyVault_WithServicePrincipalToDots_GetsSecretSucceeds() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipalWithOptions( - keyVaultUri, applicationId, clientKey, mutateSecretName: secretName => secretName.Remove(0, 5)); + VaultUri, ClientId, ClientSecret, mutateSecretName: secretName => secretName.Remove(0, 5)); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); AssertNotNullSecret(await provider.GetRawSecretAsync(appendedKeyName)); } @@ -189,74 +176,61 @@ public async Task AddAzureKeyVault_WithServicePrincipalToDots_GetsSecretSucceeds public async Task AddAzureKeyVault_WithServicePrincipalWrongMutation_GetsSecretsFails() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipalWithOptions( - keyVaultUri, applicationId, clientKey, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, allowCaching: false); + VaultUri, ClientId, ClientSecret, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, allowCaching: false); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVault_WithCachedServicePrincipal_GetSecretSucceeds() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, applicationId, clientKey, allowCaching: true); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, ClientId, ClientSecret, allowCaching: true); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVault_WithCachedServicePrincipal_GetSecretFails() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, applicationId, clientKey, allowCaching: true); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, ClientId, ClientSecret, allowCaching: true); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + + var keyName = "UnknownSecretName"; Assert.Throws(() => provider.GetSecret(keyName)); Assert.Throws(() => provider.GetRawSecret(keyName)); await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); @@ -267,25 +241,20 @@ public async Task AddAzureKeyVault_WithCachedServicePrincipal_GetSecretFails() public async Task AddAzureKeyVault_WithCachedServicePrincipalRemovesPrefix_GetsSecretsSucceeds() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipalWithOptions( - keyVaultUri, applicationId, clientKey, allowCaching: true, mutateSecretName: secretName => secretName.Remove(0, 5)); + VaultUri, ClientId, ClientSecret, allowCaching: true, mutateSecretName: secretName => secretName.Remove(0, 5)); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); AssertNotNullSecret(await provider.GetRawSecretAsync(appendedKeyName)); } @@ -294,48 +263,39 @@ public async Task AddAzureKeyVault_WithCachedServicePrincipalRemovesPrefix_GetsS public async Task AddAzureKeyVault_WithCachedServicePrincipalWrongMutation_GetsSecretFails() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipalWithOptions( - keyVaultUri, applicationId, clientKey, allowCaching: true, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName); + VaultUri, ClientId, ClientSecret, allowCaching: true, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVault_WithManagedServiceIdentity_GetSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => stores.AddAzureKeyVaultWithManagedServiceIdentity(keyVaultUri, connectionString)); + builder.ConfigureSecretStore((_, stores) => stores.AddAzureKeyVaultWithManagedServiceIdentity(VaultUri, MsiConnectionString)); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } [Theory] @@ -344,15 +304,11 @@ public async Task AddAzureKeyVault_WithManagedServiceIdentity_GetSecretSucceeds( public async Task AddAzureKeyVaultWithOptions_WithManagedServiceIdentity_GetSecretSucceeds(bool trackDependency, int expectedTrackedDependencies) { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => stores.AddAzureKeyVaultWithManagedServiceIdentityWithOptions(keyVaultUri, connectionString, + builder.ConfigureSecretStore((_, stores) => stores.AddAzureKeyVaultWithManagedServiceIdentityWithOptions(VaultUri, MsiConnectionString, configureOptions: options => options.TrackDependency = trackDependency)); // Assert @@ -360,8 +316,8 @@ public async Task AddAzureKeyVaultWithOptions_WithManagedServiceIdentity_GetSecr { var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } AssertTrackedAzureKeyVaultDependency(expectedTrackedDependencies); @@ -371,22 +327,19 @@ public async Task AddAzureKeyVaultWithOptions_WithManagedServiceIdentity_GetSecr public async Task AddAzureKeyVault_WithManagedServiceIdentity_GetSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithManagedServiceIdentity(keyVaultUri, connectionString); + stores.AddAzureKeyVaultWithManagedServiceIdentity(VaultUri, MsiConnectionString); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + var keyName = "UnknownSecretName"; await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); } @@ -395,24 +348,20 @@ public async Task AddAzureKeyVault_WithManagedServiceIdentity_GetSecretFails() public async Task AddAzureKeyVault_WithManagedServiceIdentityRemovesPrefix_GetsSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedServiceIdentityWithOptions( - keyVaultUri, connectionString, mutateSecretName: secretName => secretName.Remove(0, 5)); + VaultUri, MsiConnectionString, mutateSecretName: secretName => secretName.Remove(0, 5)); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); AssertNotNullSecret(await provider.GetRawSecretAsync(appendedKeyName)); } @@ -421,52 +370,45 @@ public async Task AddAzureKeyVault_WithManagedServiceIdentityRemovesPrefix_GetsS public async Task AddAzureKeyVault_WithManagedServiceIdentityWrongMutation_GetsSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedServiceIdentityWithOptions( - keyVaultUri, connectionString, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName); + VaultUri, MsiConnectionString, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVault_WithCachedManagedServiceIdentity_GetSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithManagedServiceIdentity(keyVaultUri, connectionString: connectionString, cacheConfiguration: cacheConfiguration); + stores.AddAzureKeyVaultWithManagedServiceIdentity(VaultUri, connectionString: MsiConnectionString, cacheConfiguration: cacheConfiguration); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); Assert.True(cacheConfiguration.IsCalled); } @@ -474,23 +416,20 @@ public async Task AddAzureKeyVault_WithCachedManagedServiceIdentity_GetSecretSuc public async Task AddAzureKeyVault_WithCachedManagedServiceIdentity_GetSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithManagedServiceIdentity(keyVaultUri, connectionString: connectionString, cacheConfiguration: cacheConfiguration); + stores.AddAzureKeyVaultWithManagedServiceIdentity(VaultUri, connectionString: MsiConnectionString, cacheConfiguration: cacheConfiguration); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + var keyName = "UnknownSecretName"; Assert.Throws(() => provider.GetSecret(keyName)); Assert.Throws(() => provider.GetRawSecret(keyName)); await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); @@ -501,25 +440,21 @@ public async Task AddAzureKeyVault_WithCachedManagedServiceIdentity_GetSecretFai public async Task AddAzureKeyVault_WithCachedManagedServiceIdentityRemovesPrefix_GetsSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedServiceIdentityWithOptions( - keyVaultUri, connectionString: connectionString, cacheConfiguration: cacheConfiguration, mutateSecretName: secretName => secretName.Remove(0, 5)); + VaultUri, connectionString: MsiConnectionString, cacheConfiguration: cacheConfiguration, mutateSecretName: secretName => secretName.Remove(0, 5)); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); AssertNotNullSecret(await provider.GetRawSecretAsync(appendedKeyName)); Assert.True(cacheConfiguration.IsCalled); @@ -529,51 +464,44 @@ public async Task AddAzureKeyVault_WithCachedManagedServiceIdentityRemovesPrefix public async Task AddAzureKeyVault_WithCachedManagedServiceIdentityWrongMutation_GetsSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var connectionString = Configuration.GetValue("Arcus:MSI:AzureServicesAuth:ConnectionString"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedServiceIdentityWithOptions( - keyVaultUri, connectionString: connectionString, cacheConfiguration: cacheConfiguration, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName); + VaultUri, connectionString: MsiConnectionString, cacheConfiguration: cacheConfiguration, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVault_WithWrongServicePrincipalCredentials_Throws() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, "wrong-app-id", "wrong-access-key"); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, "wrong-app-id", "wrong-access-key"); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - var exceptionFromSecretAsync = await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - var exceptionFromRawSecretAsync = await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + var exceptionFromSecretAsync = await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + var exceptionFromRawSecretAsync = await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); var errorCode = "unauthorized_client"; Assert.Equal(errorCode, exceptionFromSecretAsync.ErrorCode); Assert.Equal(errorCode, exceptionFromRawSecretAsync.ErrorCode); @@ -585,23 +513,21 @@ public async Task AddAzureKeyVault_WithWrongUnauthorizedServicePrincipal_Throws( // Arrange string applicationId = Configuration.GetValue("Arcus:UnauthorizedServicePrincipal:ApplicationId"); var clientKey = Configuration.GetValue("Arcus:UnauthorizedServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, applicationId, clientKey); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, applicationId, clientKey); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - var exceptionFromSecretAsync = await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - var exceptionFromRawSecretAsync = await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + var exceptionFromSecretAsync = await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + var exceptionFromRawSecretAsync = await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); Assert.Equal(HttpStatusCode.Forbidden, exceptionFromSecretAsync.Response.StatusCode); Assert.Equal(HttpStatusCode.Forbidden, exceptionFromRawSecretAsync.Response.StatusCode); } @@ -610,52 +536,40 @@ public async Task AddAzureKeyVault_WithWrongUnauthorizedServicePrincipal_Throws( public async Task AddAzureKeyVaultWithTenantSimple_WithServicePrincipal_GetSecretSucceeds() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(provider.GetSecret(keyName)); - AssertNotNullSecret(provider.GetRawSecret(keyName)); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(provider.GetSecret(TestSecretName)); + AssertNotNullSecret(provider.GetRawSecret(TestSecretName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVaultWithTenant_WithServicePrincipal_GetSecretSucceeds() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); string prefix = "Test-"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipal( - keyVaultUri, - tenantId, - applicationId, - clientKey, - configureOptions: options => { }, + VaultUri, + TenantId, + ClientId, + ClientSecret, + configureOptions: _ => { }, name: "Azure Key Vault", mutateSecretName: secretName => secretName.Remove(0, prefix.Length)); }); @@ -664,7 +578,7 @@ public async Task AddAzureKeyVaultWithTenant_WithServicePrincipal_GetSecretSucce using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = prefix + TestSecretName; AssertNotNullSecret(provider.GetSecret(appendedKeyName)); AssertNotNullSecret(provider.GetRawSecret(appendedKeyName)); AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); @@ -677,19 +591,13 @@ public async Task AddAzureKeyVaultWithTenant_WithServicePrincipal_GetSecretSucce public async Task AddAzureKeyVaultWithTenantWithDependencyTracking_WithServicePrincipal_GetSecretSucceeds(bool trackDependency, int expectedTrackedDependencies) { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey, + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, configureOptions: options => options.TrackDependency = trackDependency, name: null, mutateSecretName: null); }); @@ -698,8 +606,8 @@ public async Task AddAzureKeyVaultWithTenantWithDependencyTracking_WithServicePr { var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } AssertTrackedAzureKeyVaultDependency(expectedTrackedDependencies); @@ -709,24 +617,19 @@ public async Task AddAzureKeyVaultWithTenantWithDependencyTracking_WithServicePr public async Task AddAzureKeyVaultWithTenant_WithServicePrincipal_GetSecretFails() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, configureOptions: null, name: null, mutateSecretName: null); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + var keyName = "UnknownSecretName"; Assert.Throws(() => provider.GetSecret(keyName)); Assert.Throws(() => provider.GetRawSecret(keyName)); await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); @@ -739,19 +642,13 @@ public async Task AddAzureKeyVaultWithTenant_WithServicePrincipal_GetSecretFails public async Task AddAzureKeyVaultWithTenantWithDependencyTracking_WithServicePrincipal_GetSecretFails(bool trackDependency, int expectedTrackedDependencies) { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey, + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, configureOptions: options => options.TrackDependency = trackDependency, name: null, mutateSecretName: null); }); @@ -759,6 +656,8 @@ public async Task AddAzureKeyVaultWithTenantWithDependencyTracking_WithServicePr using (IHost host = builder.Build()) { var provider = host.Services.GetRequiredService(); + + var keyName = "UnknownSecretName"; await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); } @@ -770,19 +669,13 @@ public async Task AddAzureKeyVaultWithTenantWithDependencyTracking_WithServicePr public async Task AddAzureKeyVaultWithTenant_WithServicePrincipalToDots_GetsSecretSucceeds() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipal( - keyVaultUri, tenantId, applicationId, clientKey, + VaultUri, TenantId, ClientId, ClientSecret, mutateSecretName: secretName => secretName.Remove(0, 5), name: null, configureOptions: null); @@ -792,7 +685,7 @@ public async Task AddAzureKeyVaultWithTenant_WithServicePrincipalToDots_GetsSecr using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(provider.GetSecret(appendedKeyName)); AssertNotNullSecret(provider.GetRawSecret(appendedKeyName)); AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); @@ -803,19 +696,13 @@ public async Task AddAzureKeyVaultWithTenant_WithServicePrincipalToDots_GetsSecr public async Task AddAzureKeyVaultWithTenant_WithServicePrincipalWrongMutation_GetsSecretsFails() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipal( - keyVaultUri, tenantId, applicationId, clientKey, + VaultUri, TenantId, ClientId, ClientSecret, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, configureOptions: null, name: null); @@ -825,62 +712,51 @@ public async Task AddAzureKeyVaultWithTenant_WithServicePrincipalWrongMutation_G using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipal_GetSecretSucceeds() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey, allowCaching: true, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, allowCaching: true, configureOptions: null, name: null, mutateSecretName: null); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(provider.GetSecret(keyName)); - AssertNotNullSecret(provider.GetRawSecret(keyName)); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(provider.GetSecret(TestSecretName)); + AssertNotNullSecret(provider.GetRawSecret(TestSecretName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipal_GetSecretFails() { // Arrange - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey, allowCaching: true, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, allowCaching: true, configureOptions: null, name: null, mutateSecretName: null); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + var keyName = "UnknownSecretName"; Assert.Throws(() => provider.GetSecret(keyName)); Assert.Throws(() => provider.GetRawSecret(keyName)); await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); @@ -891,19 +767,13 @@ public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipal_GetSecre public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipalRemovesPrefix_GetsSecretsSucceeds() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipal( - keyVaultUri, tenantId, applicationId, clientKey, allowCaching: true, + VaultUri, TenantId, ClientId, ClientSecret, allowCaching: true, mutateSecretName: secretName => secretName.Remove(0, 5), configureOptions: null, name: null); @@ -913,7 +783,7 @@ public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipalRemovesPr using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(provider.GetSecret(appendedKeyName)); AssertNotNullSecret(provider.GetRawSecret(appendedKeyName)); AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); @@ -924,19 +794,13 @@ public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipalRemovesPr public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipalWrongMutation_GetsSecretFails() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:ServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:ServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithServicePrincipal( - keyVaultUri, tenantId, applicationId, clientKey, allowCaching: true, + VaultUri, TenantId, ClientId, ClientSecret, allowCaching: true, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, configureOptions: null, name: null); @@ -946,39 +810,33 @@ public async Task AddAzureKeyVaultWithTenant_WithCachedServicePrincipalWrongMuta using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVault_WithManagedIdentity_GetSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - var clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => stores.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, clientId)); + builder.ConfigureSecretStore((_, stores) => stores.AddAzureKeyVaultWithManagedIdentity(VaultUri, ClientId)); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(provider.GetSecret(keyName)); - AssertNotNullSecret(provider.GetRawSecret(keyName)); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(provider.GetSecret(TestSecretName)); + AssertNotNullSecret(provider.GetRawSecret(TestSecretName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } } @@ -986,35 +844,28 @@ public async Task AddAzureKeyVault_WithManagedIdentity_GetSecretSucceeds() public async Task AddAzureKeyVaultSimple_WithManagedIdentity_GetSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - var clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); string prefix = "Test-"; - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedIdentity( - keyVaultUri, - clientId, - configureOptions: options => { }, + VaultUri, ClientId, + configureOptions: _ => { }, name: "Azure Key Vault", mutateSecretName: secretName => secretName.Remove(0, prefix.Length)); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(provider.GetSecret(appendedKeyName)); AssertNotNullSecret(provider.GetRawSecret(appendedKeyName)); AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); @@ -1028,31 +879,25 @@ public async Task AddAzureKeyVaultSimple_WithManagedIdentity_GetSecretSucceeds() public async Task AddAzureKeyVaultWithDependencyTracking_WithManagedIdentity_GetSecretSucceeds(bool trackDependency, int expectedTrackedDependencies) { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - var clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, clientId, configureOptions: options => options.TrackDependency = trackDependency, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithManagedIdentity(VaultUri, ClientId, configureOptions: options => options.TrackDependency = trackDependency, name: null, mutateSecretName: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) using (IHost host = builder.Build()) { var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); } AssertTrackedAzureKeyVaultDependency(expectedTrackedDependencies); @@ -1062,33 +907,27 @@ public async Task AddAzureKeyVaultWithDependencyTracking_WithManagedIdentity_Get public async Task AddAzureKeyVault_WithManagedIdentityRemovesPrefix_GetsSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedIdentity( - keyVaultUri, clientId, + VaultUri, ClientId, mutateSecretName: secretName => secretName.Remove(0, 5), configureOptions: null, name: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(provider.GetSecret(appendedKeyName)); AssertNotNullSecret(provider.GetRawSecret(appendedKeyName)); AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); @@ -1100,36 +939,30 @@ public async Task AddAzureKeyVault_WithManagedIdentityRemovesPrefix_GetsSecretSu public async Task AddAzureKeyVault_WithManagedIdentityWrongMutation_GetsSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedIdentity( - keyVaultUri, clientId, + VaultUri, ClientId, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, configureOptions: null, name: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } } @@ -1139,35 +972,29 @@ public async Task AddAzureKeyVault_WithManagedIdentityWrongMutation_GetsSecretFa public async Task AddAzureKeyVaultWithDependencyTracking_WithManagedIdentityWrongMutation_GetsSecretFails(bool trackDependency, int expectedTrackedDependencies) { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedIdentity( - keyVaultUri, clientId, + VaultUri, ClientId, configureOptions: options => options.TrackDependency = trackDependency, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, name: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using (IHost host = builder.Build()) { var provider = host.Services.GetRequiredService(); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } } @@ -1178,33 +1005,27 @@ public async Task AddAzureKeyVaultWithDependencyTracking_WithManagedIdentityWron public async Task AddAzureKeyVault_WithCachedManagedIdentity_GetSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, cacheConfiguration, clientId, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithManagedIdentity(VaultUri, cacheConfiguration, ClientId, configureOptions: null, name: null, mutateSecretName: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - AssertNotNullSecret(provider.GetSecret(keyName)); - AssertNotNullSecret(provider.GetRawSecret(keyName)); - AssertNotNullSecret(await provider.GetSecretAsync(keyName)); - AssertNotNullSecret(await provider.GetRawSecretAsync(keyName)); + AssertNotNullSecret(provider.GetSecret(TestSecretName)); + AssertNotNullSecret(provider.GetRawSecret(TestSecretName)); + AssertNotNullSecret(await provider.GetSecretAsync(TestSecretName)); + AssertNotNullSecret(await provider.GetRawSecretAsync(TestSecretName)); Assert.True(cacheConfiguration.IsCalled); } } @@ -1213,29 +1034,24 @@ public async Task AddAzureKeyVault_WithCachedManagedIdentity_GetSecretSucceeds() public async Task AddAzureKeyVault_WithCachedManagedIdentity_GetSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = "UnknownSecretName"; - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithManagedIdentity(keyVaultUri, clientId: clientId, cacheConfiguration: cacheConfiguration, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithManagedIdentity(VaultUri, clientId: ClientId, cacheConfiguration: cacheConfiguration, configureOptions: null, name: null, mutateSecretName: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); + var keyName = "UnknownSecretName"; Assert.Throws(() => provider.GetSecret(keyName)); Assert.Throws(() => provider.GetRawSecret(keyName)); await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); @@ -1247,34 +1063,28 @@ public async Task AddAzureKeyVault_WithCachedManagedIdentity_GetSecretFails() public async Task AddAzureKeyVault_WithCachedManagedIdentityRemovesPrefix_GetsSecretSucceeds() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedIdentity( - keyVaultUri, clientId: clientId, cacheConfiguration: cacheConfiguration, + VaultUri, clientId: ClientId, cacheConfiguration: cacheConfiguration, mutateSecretName: secretName => secretName.Remove(0, 5), configureOptions: null, name: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - string appendedKeyName = "Test-" + keyName; + string appendedKeyName = "Test-" + TestSecretName; AssertNotNullSecret(provider.GetSecret(appendedKeyName)); AssertNotNullSecret(provider.GetRawSecret(appendedKeyName)); AssertNotNullSecret(await provider.GetSecretAsync(appendedKeyName)); @@ -1287,37 +1097,31 @@ public async Task AddAzureKeyVault_WithCachedManagedIdentityRemovesPrefix_GetsSe public async Task AddAzureKeyVault_WithCachedManagedIdentityWrongMutation_GetsSecretFails() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - string clientId = Configuration.GetServicePrincipalClientId(); - string clientKey = Configuration.GetServicePrincipalClientSecret(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); var cacheConfiguration = new SpyCacheConfiguration(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { stores.AddAzureKeyVaultWithManagedIdentity( - keyVaultUri, clientId: clientId, cacheConfiguration: cacheConfiguration, + VaultUri, clientId: ClientId, cacheConfiguration: cacheConfiguration, mutateSecretName: secretName => "SOMETHING-WRONG-" + secretName, configureOptions: null, name: null); }); // Assert - using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, tenantId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, clientId)) - using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, clientKey)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureTenantIdEnvironmentVariable, TenantId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientIdVariable, ClientId)) + using (TemporaryEnvironmentVariable.Create(Constants.AzureServicePrincipalClientSecretVariable, ClientSecret)) { using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } } @@ -1325,59 +1129,48 @@ public async Task AddAzureKeyVault_WithCachedManagedIdentityWrongMutation_GetsSe public async Task AddAzureKeyVaultWithTenant_WithWrongServicePrincipalCredentials_Throws() { // Arrange - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - string tenantId = Configuration.GetTenantId(); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, "wrong-app-id", "wrong-access-key", configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, "wrong-app-id", "wrong-access-key", configureOptions: null, name: null, mutateSecretName: null); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - Assert.Throws(() => provider.GetSecret(keyName)); - Assert.Throws(() => provider.GetRawSecret(keyName)); - await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); + Assert.Throws(() => provider.GetSecret(TestSecretName)); + Assert.Throws(() => provider.GetRawSecret(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)); + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)); } [Fact] public async Task AddAzureKeyVaultWithTenant_WithWrongUnauthorizedServicePrincipal_Throws() { // Arrange - string tenantId = Configuration.GetTenantId(); - string applicationId = Configuration.GetValue("Arcus:UnauthorizedServicePrincipal:ApplicationId"); - var clientKey = Configuration.GetValue("Arcus:UnauthorizedServicePrincipal:AccessKey"); - var keyVaultUri = Configuration.GetValue("Arcus:KeyVault:Uri"); - var keyName = Configuration.GetValue("Arcus:KeyVault:TestKeyName"); - var builder = new HostBuilder(); - builder.UseSerilog(Logger, dispose: true); + builder.UseSerilog(SerilogLogger, dispose: true); // Act - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(keyVaultUri, tenantId, applicationId, clientKey, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, configureOptions: null, name: null, mutateSecretName: null); }); // Assert using IHost host = builder.Build(); var provider = host.Services.GetRequiredService(); - var exceptionFromSecretSync = Assert.Throws(() => provider.GetSecret(keyName)); - var exceptionFromRawSecretSync = Assert.Throws(() => provider.GetRawSecret(keyName)); - var exceptionFromSecretAsync = await Assert.ThrowsAsync(() => provider.GetSecretAsync(keyName)); - var exceptionFromRawSecretAsync = await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(keyName)); - Assert.Equal((int) HttpStatusCode.Forbidden, exceptionFromSecretSync.Status); - Assert.Equal((int) HttpStatusCode.Forbidden, exceptionFromRawSecretSync.Status); - Assert.Equal((int) HttpStatusCode.Forbidden, exceptionFromSecretAsync.Status); - Assert.Equal((int) HttpStatusCode.Forbidden, exceptionFromRawSecretAsync.Status); + Assert.All(new[] + { + Assert.Throws(() => provider.GetSecret(TestSecretName)), + Assert.Throws(() => provider.GetRawSecret(TestSecretName)), + await Assert.ThrowsAsync(() => provider.GetSecretAsync(TestSecretName)), + await Assert.ThrowsAsync(() => provider.GetRawSecretAsync(TestSecretName)) + }, exception => Assert.Equal(StatusCodes.Status403Forbidden, exception.Status)); } private void AssertTrackedAzureKeyVaultDependency(int expectedTrackedDependencyCount) diff --git a/src/Arcus.Security.Tests.Integration/appsettings.json b/src/Arcus.Security.Tests.Integration/appsettings.json index 79b21c98..dbd427c7 100644 --- a/src/Arcus.Security.Tests.Integration/appsettings.json +++ b/src/Arcus.Security.Tests.Integration/appsettings.json @@ -23,8 +23,11 @@ "VaultBin": "#{Arcus.HashiCorp.VaultBin}#", "UserPass": { "UserName": "arcus", - "Password": "123" - } + "Password": "123" + } + }, + "Dapr": { + "DaprBin": "#{Arcus.Dapr.DaprBin}#" }, "ApplicationInsights": { "InstrumentationKey": "#{Arcus.ApplicationInsights.InstrumentationKey}#" diff --git a/src/Arcus.Security.Tests.Unit/Arcus.Security.Tests.Unit.csproj b/src/Arcus.Security.Tests.Unit/Arcus.Security.Tests.Unit.csproj index 1de08105..f89e91c0 100644 --- a/src/Arcus.Security.Tests.Unit/Arcus.Security.Tests.Unit.csproj +++ b/src/Arcus.Security.Tests.Unit/Arcus.Security.Tests.Unit.csproj @@ -32,6 +32,7 @@ + diff --git a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs new file mode 100644 index 00000000..790de9b0 --- /dev/null +++ b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs @@ -0,0 +1,90 @@ +using System; +using Arcus.Security.Providers.KubernetesSecrets; +using Xunit; + +namespace Arcus.Security.Tests.Unit.Dapr +{ + public class DaprSecretProviderOptionsTests + { + [Theory] + [ClassData(typeof(Blanks))] + public void Set_WithoutGrpcEndpoint_Fails(string grpcEndpoint) + { + // Arrange + var options = new DaprSecretProviderOptions(); + + // Act / Assert + Assert.ThrowsAny(() => options.GrpcEndpoint = grpcEndpoint); + } + + [Theory] + [ClassData(typeof(Blanks))] + public void Set_WithoutHttpEndpoint_Fails(string httpEndpoint) + { + // Arrange + var options = new DaprSecretProviderOptions(); + + // Act / Assert + Assert.ThrowsAny(() => options.HttpEndpoint = httpEndpoint); + } + + [Theory] + [ClassData(typeof(Blanks))] + public void Set_WithoutDaprApiToken_Fails(string apiToken) + { + // Arrange + var options = new DaprSecretProviderOptions(); + + // Act / Assert + Assert.ThrowsAny(() => options.DaprApiToken = apiToken); + } + + [Theory] + [ClassData(typeof(Blanks))] + public void AddMetadata_WithoutKey_Fails(string key) + { + // Arrange + var options = new DaprSecretProviderOptions(); + var value = Guid.NewGuid().ToString(); + + // Act / Assert + Assert.ThrowsAny(() => options.AddMetadata(key, value)); + } + + [Theory] + [ClassData(typeof(Blanks))] + public void AddMetadata_WithoutValue_Fails(string value) + { + // Arrange + var options = new DaprSecretProviderOptions(); + var key = Guid.NewGuid().ToString(); + + // Act / Assert + Assert.ThrowsAny(() => options.AddMetadata(key, value)); + } + + [Fact] + public void AddMetadata_WithSameKey_Fails() + { + // Arrange + var options = new DaprSecretProviderOptions(); + var key = Guid.NewGuid().ToString(); + var value1 = Guid.NewGuid().ToString(); + var value2 = Guid.NewGuid().ToString(); + options.AddMetadata(key, value1); + + // Act / Assert + Assert.ThrowsAny(() => options.AddMetadata(key, value2)); + } + + [Fact] + public void Default_NestedSeparator_Available() + { + // Arrange + var options = new DaprSecretProviderOptions(); + + // Act / Assert + Assert.False(string.IsNullOrWhiteSpace(options.NestedSeparator)); + } + } +} diff --git a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs new file mode 100644 index 00000000..cae65e69 --- /dev/null +++ b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs @@ -0,0 +1,23 @@ +using System; +using Arcus.Security.Providers.KubernetesSecrets; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Arcus.Security.Tests.Unit.Dapr +{ + public class DaprSecretProviderTests + { + [Theory] + [ClassData(typeof(Blanks))] + public void Create_WithoutStoreName_Fails(string storeName) + { + Assert.ThrowsAny(() => new DaprSecretProvider(storeName, new DaprSecretProviderOptions(), NullLogger.Instance)); + } + + [Fact] + public void Create_WithoutOptions_Fails() + { + Assert.ThrowsAny(() => new DaprSecretProvider("ignored store", options: null, NullLogger.Instance)); + } + } +} diff --git a/src/Arcus.Security.Tests.Unit/Dapr/Extensions/SecretStoreExtensionsTests.cs b/src/Arcus.Security.Tests.Unit/Dapr/Extensions/SecretStoreExtensionsTests.cs new file mode 100644 index 00000000..be901d3f --- /dev/null +++ b/src/Arcus.Security.Tests.Unit/Dapr/Extensions/SecretStoreExtensionsTests.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Arcus.Security.Tests.Unit.Dapr.Extensions +{ + public class SecretStoreExtensionsTests + { + [Theory] + [ClassData(typeof(Blanks))] + public void AddDaprSecretStore_WithoutSecretStore_Fails(string secretStore) + { + // Arrange + var services = new ServiceCollection(); + + // Act / Assert + Assert.ThrowsAny(() => services.AddSecretStore(stores => stores.AddDaprSecretStore(secretStore))); + } + + [Theory] + [ClassData(typeof(Blanks))] + public void AddDaprSecretStoreWithConfig_WithoutSecretStore_Fails(string secretStore) + { + // Arrange + var services = new ServiceCollection(); + + // Act / Assert + Assert.ThrowsAny(() => services.AddSecretStore(stores => stores.AddDaprSecretStore(secretStore, opt => { }))); + } + } +} diff --git a/src/Arcus.Security.sln b/src/Arcus.Security.sln index 68868bbc..24163ba3 100644 --- a/src/Arcus.Security.sln +++ b/src/Arcus.Security.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.Security.Providers.Co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.Security.Tests.Runtimes.AzureFunctions", "Arcus.Security.Tests.Runtimes.AzureFunctions\Arcus.Security.Tests.Runtimes.AzureFunctions.csproj", "{B571C9E1-67D9-414D-9204-C5167B01A0E0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.Security.Providers.Dapr", "Arcus.Security.Providers.Dapr\Arcus.Security.Providers.Dapr.csproj", "{EDF973BE-D1F5-497E-A47E-EF81ABABEFC7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,6 +81,10 @@ Global {B571C9E1-67D9-414D-9204-C5167B01A0E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {B571C9E1-67D9-414D-9204-C5167B01A0E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {B571C9E1-67D9-414D-9204-C5167B01A0E0}.Release|Any CPU.Build.0 = Release|Any CPU + {EDF973BE-D1F5-497E-A47E-EF81ABABEFC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDF973BE-D1F5-497E-A47E-EF81ABABEFC7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDF973BE-D1F5-497E-A47E-EF81ABABEFC7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDF973BE-D1F5-497E-A47E-EF81ABABEFC7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +99,7 @@ Global {EBCA7E71-5F09-43B3-BC44-73B1980477C7} = {DD66B1E8-3676-4C13-8C37-AEA80E1C21B7} {D6749062-90BB-4828-86ED-FD8F89ACE8AB} = {DD66B1E8-3676-4C13-8C37-AEA80E1C21B7} {B571C9E1-67D9-414D-9204-C5167B01A0E0} = {C42FDAC3-5A31-4F39-BD92-C57C2CDAB2D2} + {EDF973BE-D1F5-497E-A47E-EF81ABABEFC7} = {DD66B1E8-3676-4C13-8C37-AEA80E1C21B7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D3310D31-C37D-47F4-A6D3-325EE3806698} From 85baf0ab82caef6644ecc7ce2f919172c7641383 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:00:35 +0200 Subject: [PATCH 02/16] pr-fix: unit tests --- .../Dapr/DaprSecretProviderOptionsTests.cs | 12 +----------- .../Dapr/DaprSecretProviderTests.cs | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs index 790de9b0..10488f78 100644 --- a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs +++ b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderOptionsTests.cs @@ -1,5 +1,5 @@ using System; -using Arcus.Security.Providers.KubernetesSecrets; +using Arcus.Security.Providers.Dapr; using Xunit; namespace Arcus.Security.Tests.Unit.Dapr @@ -76,15 +76,5 @@ public void AddMetadata_WithSameKey_Fails() // Act / Assert Assert.ThrowsAny(() => options.AddMetadata(key, value2)); } - - [Fact] - public void Default_NestedSeparator_Available() - { - // Arrange - var options = new DaprSecretProviderOptions(); - - // Act / Assert - Assert.False(string.IsNullOrWhiteSpace(options.NestedSeparator)); - } } } diff --git a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs index cae65e69..5882cb02 100644 --- a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs +++ b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs @@ -1,5 +1,5 @@ using System; -using Arcus.Security.Providers.KubernetesSecrets; +using Arcus.Security.Providers.Dapr; using Microsoft.Extensions.Logging.Abstractions; using Xunit; From 68a69be49a41c8fe2bc3e8df3c63f8d0c79b3b2d Mon Sep 17 00:00:00 2001 From: Stijn Moreels <9039753+stijnmoreels@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:54:22 +0200 Subject: [PATCH 03/16] pr-fix: remove `/templates` path in `run-integration-tests.yml` --- build/templates/run-integration-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/templates/run-integration-tests.yml b/build/templates/run-integration-tests.yml index ff7c59c2..ae112ace 100644 --- a/build/templates/run-integration-tests.yml +++ b/build/templates/run-integration-tests.yml @@ -28,11 +28,11 @@ steps: imageName: '${{ parameters.dockerProjectName }}:$(Build.BuildId)' containerName: '${{ parameters.dockerProjectName }}' ports: '$(Arcus.AzureFunctions.HttpPort):80' - - template: 'templates/download-dapr.yml' + - template: 'download-dapr.yml' parameters: targetFolder: '$(Build.SourcesDirectory)' daprBinVariableName: 'Arcus.Dapr.DaprBin' - - template: 'templates/download-hashicorp-vault.yml' + - template: 'download-hashicorp-vault.yml' parameters: targetFolder: '$(Build.SourcesDirectory)' version: $(HashiCorp.Vault.Version) @@ -48,4 +48,4 @@ steps: docker logs ${{ parameters.dockerProjectName }} failOnStderr: true displayName: Show ${{ parameters.dockerProjectName }} logs - condition: always() \ No newline at end of file + condition: always() From 7a507052725f43d500ab1e215a7fd32253ce27de Mon Sep 17 00:00:00 2001 From: Stijn Moreels <9039753+stijnmoreels@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:00:23 +0200 Subject: [PATCH 04/16] pr-fix: use `dapr` directly in Linux environment --- build/templates/download-dapr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/templates/download-dapr.yml b/build/templates/download-dapr.yml index f9fbe7ff..45d623e3 100644 --- a/build/templates/download-dapr.yml +++ b/build/templates/download-dapr.yml @@ -10,6 +10,6 @@ steps: - bash: | wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash dapr -h - echo "##vso[task.setvariable variable=Arcus.Dapr.DaprBin]/usr/local/bin" + echo "##vso[task.setvariable variable=Arcus.Dapr.DaprBin]dapr" workingDirectory: ${{ parameters.targetFolder }} - displayName: 'Download Dapr' \ No newline at end of file + displayName: 'Download Dapr' From 20892566dbbf78216fecf2fe2d088de691bceaf7 Mon Sep 17 00:00:00 2001 From: Stijn Moreels <9039753+stijnmoreels@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:03:00 +0200 Subject: [PATCH 05/16] pr-style: update w/ correct exception messages --- .../Dapr/Hosting/TestConfigExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs index 221fc1fd..87125b2d 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs @@ -30,7 +30,7 @@ public static FileInfo GetDaprInstallationFilePath(this TestConfig configuration { throw new KeyNotFoundException( "Could not find the installation file path of the Dapr Sidecar in the local app settings" - + "please install the HashiCorp Vault on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + $"and add the installation folder as configuration key '{key}' to your local app settings"); } @@ -38,7 +38,7 @@ public static FileInfo GetDaprInstallationFilePath(this TestConfig configuration { throw new KeyNotFoundException( $"Could not find the installation file path of the Dapr Sidecar in the local app settings because the appsettings token '{filePath}' is not yet replaced," - + "please install the HashiCorp Vault on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + $"and add the installation folder as configuration key '{key}' to your local app settings"); } @@ -58,7 +58,7 @@ public static FileInfo GetDaprInstallationFilePath(this TestConfig configuration if (!file.Exists || !file.Name.StartsWith("dapr")) { throw new FileNotFoundException( - $"Could not find file path returned for key '{key}' because it doesn't point to valid Dapr Sidecar execution file ('vault'), " + $"Could not find file path returned for key '{key}' because it doesn't point to valid Dapr Sidecar execution file ('dapr'), " + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + $"and add the installation folder as configuration key '{key}' to your local app settings"); } From f6399499fddaca480f905b08e81347f976300a7d Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 6 Jul 2023 06:57:41 +0200 Subject: [PATCH 06/16] pr-fix: use cross-platform dapr installation --- .../Dapr/Hosting/DaprSidecarFixture.cs | 6 ++-- .../Dapr/Hosting/DaprSidecarOptions.cs | 27 ++++++++++++--- .../Dapr/Hosting/TestConfigExtensions.cs | 33 ++++--------------- .../Dapr/Resources/DaprSecretStore.cs | 32 ------------------ 4 files changed, 31 insertions(+), 67 deletions(-) delete mode 100644 src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs index 0f3d4c92..52d90895 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs @@ -92,7 +92,7 @@ public static async Task StartSideCarAsync(TestConfig config var options = new DaprSidecarOptions(); configureOptions?.Invoke(options); - options.WriteSecretConfig(); + options.WriteSecretStoreConfigToDisk(); Process process = CreateProcess(configuration, port, options); var fixture = new DaprSidecarFixture(process, port, options, logger); @@ -103,14 +103,14 @@ public static async Task StartSideCarAsync(TestConfig config private static Process CreateProcess(TestConfig configuration, int port, DaprSidecarOptions options) { - FileInfo daprExeFile = configuration.GetDaprInstallationFilePath(); + string daprExeFileName = configuration.GetDaprInstallationFileName(); string vaultArgs = String.Join(" ", "run", $"--resources-path {nameof(Dapr)}/Resources/{options.StoreType}", $"--app-port 6002 --dapr-http-port 3601 --dapr-grpc-port {port}"); - var startInfo = new ProcessStartInfo(daprExeFile.FullName, vaultArgs) + var startInfo = new ProcessStartInfo(daprExeFileName, vaultArgs) { WorkingDirectory = Directory.GetCurrentDirectory(), UseShellExecute = false, diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs index 70d46631..54dd98d0 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs @@ -7,10 +7,7 @@ namespace Arcus.Security.Tests.Integration.Dapr.Hosting { - public enum DaprStoreType - { - Local, AzureKeyVault - } + public enum DaprStoreType { None, Local, AzureKeyVault } /// /// Represents the available user options for the . @@ -20,8 +17,15 @@ public class DaprSidecarOptions private JObject _startObject; private KeyVaultConfig _config; + /// + /// Gets the type of the secret source from where the Dapr runtime will get its secrets. + /// public DaprStoreType StoreType { get; private set; } + /// + /// Gets the name of the Dapr secret store from where the Dapr runtime will gets its secrets. + /// + /// Thrown when no Dapr secret store type was configured. public string StoreName { get @@ -36,9 +40,16 @@ public string StoreName } } + /// + /// Use Azure Key Vault to load the secrets into the Dapr secret store. + /// + /// The integration test configuration to load the authentication values to interact with Azure Key Vault. + /// Thrown when the is null. + /// Thrown when this method is called after the Dapr Sidecar was already configured with a secret source. public DaprSidecarOptions LoadKeyVault(TestConfig configuration) { Guard.NotNull(configuration, nameof(configuration)); + Guard.For(() => StoreType is not DaprStoreType.None, $"Cannot load Azure Key Vault secrets into the Dapr secret store because the Dapr secret store was already configured: {StoreType}"); _config = configuration.GetKeyVaultConfig(); StoreType = DaprStoreType.AzureKeyVault; @@ -51,9 +62,11 @@ public DaprSidecarOptions LoadKeyVault(TestConfig configuration) /// /// The Dapr local secret store. /// Thrown when the is null. + /// Thrown when this method is called after the Dapr Sidecar was already configured with a secret source. public DaprSidecarOptions LoadSecrets(JObject secretStore) { Guard.NotNull(secretStore, nameof(secretStore)); + Guard.For(() => StoreType is not DaprStoreType.None, $"Cannot load Azure Key Vault secrets into the Dapr secret store because the Dapr secret store was already configured: {StoreType}"); _startObject = secretStore; StoreType = DaprStoreType.Local; @@ -61,7 +74,11 @@ public DaprSidecarOptions LoadSecrets(JObject secretStore) return this; } - internal void WriteSecretConfig() + /// + /// Writes the previously configured Dapr secret store configuration to disk. + /// + /// Thrown when no Dapr secret store type was configured. + internal void WriteSecretStoreConfigToDisk() { switch (StoreType) { diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs index 221fc1fd..8730ab61 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs @@ -19,14 +19,14 @@ public static class TestConfigExtensions /// Thrown when the is null. /// Thrown when there is no Dapr installation file path present in the application settings. /// Thrown when a Dapr installation on the system where the tests are running cannot be found. - public static FileInfo GetDaprInstallationFilePath(this TestConfig configuration) + public static string GetDaprInstallationFileName(this TestConfig configuration) { Guard.NotNull(configuration, nameof(configuration)); var key = "Arcus:Dapr:DaprBin"; - string filePath = configuration.GetValue(key); + string fileName = configuration.GetValue(key); - if (string.IsNullOrWhiteSpace(filePath)) + if (string.IsNullOrWhiteSpace(fileName)) { throw new KeyNotFoundException( "Could not find the installation file path of the Dapr Sidecar in the local app settings" @@ -34,36 +34,15 @@ public static FileInfo GetDaprInstallationFilePath(this TestConfig configuration + $"and add the installation folder as configuration key '{key}' to your local app settings"); } - if (filePath.StartsWith("#{") && filePath.EndsWith("}#")) + if (fileName.StartsWith("#{") && fileName.EndsWith("}#")) { throw new KeyNotFoundException( - $"Could not find the installation file path of the Dapr Sidecar in the local app settings because the appsettings token '{filePath}' is not yet replaced," + $"Could not find the installation file path of the Dapr Sidecar in the local app settings because the appsettings token '{fileName}' is not yet replaced," + "please install the HashiCorp Vault on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + $"and add the installation folder as configuration key '{key}' to your local app settings"); } - FileInfo file; - try - { - file = new FileInfo(filePath); - } - catch (Exception exception) - { - throw new FileNotFoundException( - $"Could not find file path returned for key '{key}' because it doesn't point to valid Dapr Sidecar execution file, " - + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " - + $"and add the installation folder as configuration key '{key}' to your local app settings", exception); - } - - if (!file.Exists || !file.Name.StartsWith("dapr")) - { - throw new FileNotFoundException( - $"Could not find file path returned for key '{key}' because it doesn't point to valid Dapr Sidecar execution file ('vault'), " - + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " - + $"and add the installation folder as configuration key '{key}' to your local app settings"); - } - - return file; + return fileName; } } } diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs b/src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs deleted file mode 100644 index 9ad13a24..00000000 --- a/src/Arcus.Security.Tests.Integration/Dapr/Resources/DaprSecretStore.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Arcus.Security.Tests.Integration.Dapr.Resources -{ - /// - /// Represents the schema of the YAML file of the Dapr local secret store. - /// - public class DaprSecretStore - { - public string ApiVersion { get; set; } - public string Kind { get; set; } - public DaprSecretStoreMetaData Metadata { get; set; } - public DaprSecretStoreSpec Spec { get; set; } - } - - public class DaprSecretStoreMetaData - { - public string Name { get; set; } - public string Namespace { get; set; } - } - - public class DaprSecretStoreSpec - { - public string Type { get; set; } - public string Version { get; set; } - public DaprSecretStoreSpecMetaData[] Metadata { get; set; } - } - - public class DaprSecretStoreSpecMetaData - { - public string Name { get; set; } - public object Value { get; set; } - } -} From c05179fffa2ce05fc8620b9292e064fed472a827 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 6 Jul 2023 06:58:30 +0200 Subject: [PATCH 07/16] pr-remove: out-of-context unit test --- .../Dapr/DaprSecretProviderTests.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs index 5882cb02..7b33db67 100644 --- a/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs +++ b/src/Arcus.Security.Tests.Unit/Dapr/DaprSecretProviderTests.cs @@ -13,11 +13,5 @@ public void Create_WithoutStoreName_Fails(string storeName) { Assert.ThrowsAny(() => new DaprSecretProvider(storeName, new DaprSecretProviderOptions(), NullLogger.Instance)); } - - [Fact] - public void Create_WithoutOptions_Fails() - { - Assert.ThrowsAny(() => new DaprSecretProvider("ignored store", options: null, NullLogger.Instance)); - } } } From 09d6cbb36c495a9c6e600d2bbb2b435e98d4b295 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:26:31 +0200 Subject: [PATCH 08/16] pr-fix: update with correct file name in exception message --- .../Dapr/Hosting/TestConfigExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs index 94e4ccbd..5f4cfcb5 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/TestConfigExtensions.cs @@ -37,7 +37,7 @@ public static string GetDaprInstallationFileName(this TestConfig configuration) if (fileName.StartsWith("#{") && fileName.EndsWith("}#")) { throw new KeyNotFoundException( - $"Could not find the installation file path of the Dapr Sidecar in the local app settings because the appsettings token '{filePath}' is not yet replaced," + $"Could not find the installation file path of the Dapr Sidecar in the local app settings because the appsettings token '{fileName}' is not yet replaced," + "please install the Dapr Sidecar on this machine (https://docs.dapr.io/getting-started/install-dapr-cli/) " + $"and add the installation folder as configuration key '{key}' to your local app settings"); } From d8ffedb354d670a8643d007851f6eee05533a53a Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:41:48 +0200 Subject: [PATCH 09/16] pr-fix: add exception logging --- .../Dapr/Hosting/DaprSidecarFixture.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs index 52d90895..d56a957a 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs @@ -3,11 +3,9 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; -using System.Linq; using System.Threading.Tasks; using Arcus.Security.Core; using Arcus.Security.Providers.Dapr; -using Arcus.Security.Tests.Integration.Dapr.Resources; using Arcus.Security.Tests.Integration.Fixture; using Arcus.Testing.Logging; using Dapr.Client; @@ -16,12 +14,9 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json.Linq; using Polly; using Serilog; using Xunit; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Arcus.Security.Tests.Integration.Dapr.Hosting @@ -48,8 +43,6 @@ public enum TearDownOptions /// public sealed class DaprSidecarFixture : IAsyncDisposable { - private const string DaprLocalSecretStore = "localsecretstore"; - private readonly Process _process; private readonly DaprSidecarOptions _options; private readonly ILogger _logger; @@ -105,7 +98,7 @@ private static Process CreateProcess(TestConfig configuration, int port, DaprSid { string daprExeFileName = configuration.GetDaprInstallationFileName(); - string vaultArgs = String.Join(" ", + string vaultArgs = string.Join(" ", "run", $"--resources-path {nameof(Dapr)}/Resources/{options.StoreType}", $"--app-port 6002 --dapr-http-port 3601 --dapr-grpc-port {port}"); @@ -134,11 +127,15 @@ private async Task StartProcessAsync() PolicyResult healthResult = await Policy.TimeoutAsync(TimeSpan.FromSeconds(20)) - .WrapAsync(Policy.Handle() + .WrapAsync(Policy.Handle(ex => + { + _logger.LogError(ex, "Failed to contact Dapr Sidecar: {Message}, retrying...", ex.Message); + return true; + }) .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(100))) .ExecuteAndCaptureAsync(async () => { - _logger.LogTrace("Checking Dapr Sidecar health..."); + _logger.LogTrace("Checking Dapr Sidecar availability..."); using var client = new DaprClientBuilder().UseGrpcEndpoint(Endpoint.OriginalString).Build(); await client.GetMetadataAsync(); @@ -213,7 +210,7 @@ private ISecretProvider CreateSecretProvider( logging.AddProvider(new CustomLoggerProvider(_logger)); }); - builder.ConfigureSecretStore((config, stores) => + builder.ConfigureSecretStore((_, stores) => { configureSecretStore(stores); }); From 03f9ca14385da6484f462e2c652dc33ad9796375 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:53:01 +0200 Subject: [PATCH 10/16] pr-fix: increase time-out --- .../Dapr/Hosting/DaprSidecarFixture.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs index d56a957a..a73ba55d 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs @@ -126,12 +126,8 @@ private async Task StartProcessAsync() } PolicyResult healthResult = - await Policy.TimeoutAsync(TimeSpan.FromSeconds(20)) - .WrapAsync(Policy.Handle(ex => - { - _logger.LogError(ex, "Failed to contact Dapr Sidecar: {Message}, retrying...", ex.Message); - return true; - }) + await Policy.TimeoutAsync(TimeSpan.FromSeconds(30)) + .WrapAsync(Policy.Handle() .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(100))) .ExecuteAndCaptureAsync(async () => { From 6539ed373ac420b17f11a4ffc18a0e9e1eeb7a93 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 6 Jul 2023 08:17:47 +0200 Subject: [PATCH 11/16] pr-fix: correct with dapr logging --- .../Dapr/Hosting/DaprSidecarFixture.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs index a73ba55d..3389eddb 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs @@ -101,7 +101,8 @@ private static Process CreateProcess(TestConfig configuration, int port, DaprSid string vaultArgs = string.Join(" ", "run", $"--resources-path {nameof(Dapr)}/Resources/{options.StoreType}", - $"--app-port 6002 --dapr-http-port 3601 --dapr-grpc-port {port}"); + $"--app-id arcus-security-dapr --app-port 6002 --dapr-http-port 3601 --dapr-grpc-port {port}", + "--log-level debug --enable-api-logging true"); var startInfo = new ProcessStartInfo(daprExeFileName, vaultArgs) { From 9ebb6aec44329594c21dab0554afd7a979d0539c Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 13 Jul 2023 06:35:22 +0200 Subject: [PATCH 12/16] pr-fix: update w/o app-logging --- .../Dapr/DaprSecretProviderTests.cs | 10 ++++------ .../Dapr/Hosting/DaprSidecarFixture.cs | 13 +++++++++---- .../Dapr/Hosting/DaprSidecarOptions.cs | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs b/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs index d4cae843..c625dece 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/DaprSecretProviderTests.cs @@ -23,21 +23,19 @@ public DaprSecretProviderTests(ITestOutputHelper outputWriter) : base(outputWrit { } - private KeyVaultConfig KeyVault => Configuration.GetKeyVaultConfig(); - [Fact] public async Task Dapr_WithAzureKeyVault_Succeeds() { // Arrange + KeyVaultConfig keyVaultConfig = Configuration.GetKeyVaultConfig(); await using DaprSidecarFixture sideCar = await StartSideCarAsync(opt => { - opt.LoadKeyVault(Configuration); + opt.LoadKeyVault(keyVaultConfig); }); ISecretProvider provider = sideCar.GetSecretProvider(); - - Assert.NotNull(await provider.GetRawSecretAsync(KeyVault.SecretName)); - Assert.NotNull((await provider.GetSecretAsync(KeyVault.SecretName)).Value); + Assert.NotNull(await provider.GetRawSecretAsync(keyVaultConfig.SecretName)); + Assert.NotNull((await provider.GetSecretAsync(keyVaultConfig.SecretName)).Value); } [Fact] diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs index 3389eddb..79b2bdf4 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarFixture.cs @@ -102,14 +102,15 @@ private static Process CreateProcess(TestConfig configuration, int port, DaprSid "run", $"--resources-path {nameof(Dapr)}/Resources/{options.StoreType}", $"--app-id arcus-security-dapr --app-port 6002 --dapr-http-port 3601 --dapr-grpc-port {port}", - "--log-level debug --enable-api-logging true"); + "--log-level debug"); var startInfo = new ProcessStartInfo(daprExeFileName, vaultArgs) { WorkingDirectory = Directory.GetCurrentDirectory(), UseShellExecute = false, CreateNoWindow = true, - RedirectStandardOutput = true + RedirectStandardOutput = true, + RedirectStandardError = true }; return new Process { StartInfo = startInfo }; @@ -118,7 +119,7 @@ private static Process CreateProcess(TestConfig configuration, int port, DaprSid private async Task StartProcessAsync() { _logger.LogTrace("Starting Dapr Sidecar: {FileName} {Arguments}", _process.StartInfo.FileName, _process.StartInfo.Arguments); - + bool isStarted = _process.Start(); if (!isStarted) { @@ -240,7 +241,11 @@ private async Task StopProcessAsync() { _logger.LogTrace("Dapr Sidecar read standard output..."); string output = await _process.StandardOutput.ReadToEndAsync(); - _logger.LogDebug("Dapr Sidecar console: {Output}", output); + _logger.LogDebug("Dapr Sidecar output: {Output}", output); + + _logger.LogTrace("Dapr Sidecar read standard error..."); + string error = await _process.StandardError.ReadToEndAsync(); + _logger.LogDebug("Dapr Sidecar error: {Error}", error); } _logger.LogTrace("Stop Dapr Sidecar at {Endpoint}", Endpoint); diff --git a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs index 54dd98d0..a2ced2af 100644 --- a/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs +++ b/src/Arcus.Security.Tests.Integration/Dapr/Hosting/DaprSidecarOptions.cs @@ -46,12 +46,12 @@ public string StoreName /// The integration test configuration to load the authentication values to interact with Azure Key Vault. /// Thrown when the is null. /// Thrown when this method is called after the Dapr Sidecar was already configured with a secret source. - public DaprSidecarOptions LoadKeyVault(TestConfig configuration) + public DaprSidecarOptions LoadKeyVault(KeyVaultConfig configuration) { Guard.NotNull(configuration, nameof(configuration)); Guard.For(() => StoreType is not DaprStoreType.None, $"Cannot load Azure Key Vault secrets into the Dapr secret store because the Dapr secret store was already configured: {StoreType}"); - _config = configuration.GetKeyVaultConfig(); + _config = configuration; StoreType = DaprStoreType.AzureKeyVault; return this; From b1a1d514b2bf67db929b98b05d50e549979015f4 Mon Sep 17 00:00:00 2001 From: Stijn Moreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 13 Jul 2023 07:10:25 +0200 Subject: [PATCH 13/16] Update download-dapr.yml --- build/templates/download-dapr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/build/templates/download-dapr.yml b/build/templates/download-dapr.yml index 45d623e3..c696bfac 100644 --- a/build/templates/download-dapr.yml +++ b/build/templates/download-dapr.yml @@ -9,6 +9,7 @@ parameters: steps: - bash: | wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash + dapr init dapr -h echo "##vso[task.setvariable variable=Arcus.Dapr.DaprBin]dapr" workingDirectory: ${{ parameters.targetFolder }} From 944f3dd34fda4a91418a22b5b3b5d8f082ecfc40 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:09:36 +0200 Subject: [PATCH 14/16] pr-fix: use correct unauthorized service principal --- .../KeyVault/SecretStoreBuilderExtensionsTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs b/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs index 16f1137e..1ba0e60f 100644 --- a/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs +++ b/src/Arcus.Security.Tests.Integration/KeyVault/SecretStoreBuilderExtensionsTests.cs @@ -1153,11 +1153,13 @@ public async Task AddAzureKeyVaultWithTenant_WithWrongUnauthorizedServicePrincip // Arrange var builder = new HostBuilder(); builder.UseSerilog(SerilogLogger, dispose: true); + string clientId = Configuration.GetRequiredValue("Arcus:UnauthorizedServicePrincipal:ApplicationId"); + string clientSecret = Configuration.GetRequiredValue("Arcus:UnauthorizedServicePrincipal:AccessKey"); // Act builder.ConfigureSecretStore((_, stores) => { - stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, ClientId, ClientSecret, configureOptions: null, name: null, mutateSecretName: null); + stores.AddAzureKeyVaultWithServicePrincipal(VaultUri, TenantId, clientId, clientSecret, configureOptions: null, name: null, mutateSecretName: null); }); // Assert From 013f10bed5b041acfff0058e6b48d0636550ba03 Mon Sep 17 00:00:00 2001 From: Stijn Moreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:35:36 +0200 Subject: [PATCH 15/16] Update docs/preview/03-Features/secret-store/provider/dapr-secret-store.md Co-authored-by: Frederik Gheysels --- .../03-Features/secret-store/provider/dapr-secret-store.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md b/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md index 75162b6b..d5291d26 100644 --- a/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md +++ b/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md @@ -10,7 +10,7 @@ By using this secret provider, you still benefit from all the Arcus secret store ⛔ Does not support [synchronous secret retrieval](../../secrets/general.md). ## Installation -Adding secrets from Dapr into the secret store requires following package: +Using the Dapr secrets building block with Arcus requires the following package: ```shell PM > Install-Package Arcus.Security.Providers.Dapr From e4c91742cd5c6bb4f5ddef979ececabf3dcdb352 Mon Sep 17 00:00:00 2001 From: Stijn Moreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:35:48 +0200 Subject: [PATCH 16/16] Update docs/preview/03-Features/secret-store/provider/dapr-secret-store.md Co-authored-by: Frederik Gheysels --- .../03-Features/secret-store/provider/dapr-secret-store.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md b/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md index d5291d26..80c9feaf 100644 --- a/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md +++ b/docs/preview/03-Features/secret-store/provider/dapr-secret-store.md @@ -17,7 +17,7 @@ PM > Install-Package Arcus.Security.Providers.Dapr ``` ## Configuration -After installing the package, the additional extensions becomes available when building the secret store. +After installing the package, the extensions methods for using the Dapr components becomes available when building the secret store. ```csharp using Microsoft.Extensions.Hosting;