Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions src/Dapr.Extensions.Configuration/DaprSecretDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,46 @@ public class DaprSecretDescriptor
/// A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.
/// </summary>
public IReadOnlyDictionary<string, string> Metadata { get; }

/// <summary>
/// This flag indicates if this field's existence should trigger an exception. Setting it to "false"
/// will suppress the exception whereas setting it to "true" will not suppress it.
/// </summary>
public bool IsRequired { get; }

/// <summary>
/// SecretKey value is mapping value to Vault Name. If The application's Secret Name and Secret in the
/// Vault Name is different then you can use this flag to specify Vault Secret Name.
/// </summary>
public string SecretKey { get; }

/// <summary>
/// Secret Descriptor Construcutor
/// Secret Descriptor Constructor
/// </summary>
public DaprSecretDescriptor(string secretName) : this(secretName, new Dictionary<string, string>())
{

}

/// <summary>
/// Secret Descriptor Construcutor
/// Secret Descriptor Constructor
/// </summary>
public DaprSecretDescriptor(string secretName, IReadOnlyDictionary<string, string> metadata) :
this(secretName, metadata, true, secretName)
{

}

/// <summary>
/// Secret Descriptor Constructor
/// </summary>
public DaprSecretDescriptor(string secretName, IReadOnlyDictionary<string, string> metadata)
public DaprSecretDescriptor(string secretName, IReadOnlyDictionary<string, string> metadata,
bool isRequired, string secretKey)
Comment thread
yash-nisar marked this conversation as resolved.
{
SecretName = secretName;
Metadata = metadata;
IsRequired = isRequired;
SecretKey = secretKey;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ private string NormalizeKey(string key)
return key;
}

/// <summary>
/// Loads the configuration by calling the asynchronous LoadAsync method and blocking the calling
/// thread until the operation is completed.
/// </summary>
public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();

private async Task LoadAsync()
Expand All @@ -197,16 +201,39 @@ private async Task LoadAsync()
{
foreach (var secretDescriptor in secretDescriptors)
{
var result = await client.GetSecretAsync(store, secretDescriptor.SecretName, secretDescriptor.Metadata).ConfigureAwait(false);

Dictionary<string, string> result;

try
{
result = await client
.GetSecretAsync(store, secretDescriptor.SecretKey, secretDescriptor.Metadata)
.ConfigureAwait(false);
}
catch (DaprException e)
{
if (secretDescriptor.IsRequired)
{
throw e;
}

Console.WriteLine($"'{secretDescriptor.SecretName}' doesn't exists in Key Vault but because " +
$"it doesn't require existence so all errors suppressed!");
Comment thread
yash-nisar marked this conversation as resolved.

result = new Dictionary<string, string>();
}

foreach (var key in result.Keys)
{
if (data.ContainsKey(key))
{
throw new InvalidOperationException($"A duplicate key '{key}' was found in the secret store '{store}'. Please remove any duplicates from your secret store.");
throw new InvalidOperationException($"A duplicate key '{key}' was found in the " +
$"secret store '{store}'. Please remove any " +
$"duplicates from your secret store.");
}

data.Add(normalizeKey ? NormalizeKey(key) : key, result[key]);
data.Add(normalizeKey ? NormalizeKey(secretDescriptor.SecretName) : secretDescriptor.SecretName,
result[key]);
Comment thread
yash-nisar marked this conversation as resolved.
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PackageReference>
<PackageReference Include="FluentAssertions" Version="5.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using FluentAssertions;
using Grpc.Net.Client;
using Microsoft.Extensions.Configuration;
using Moq;
using Xunit;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;

Expand Down Expand Up @@ -142,23 +143,23 @@ public void AddDaprSecretStore_UsingDescriptors_DuplicateSecret_ReportsError()
[Fact]
public void LoadSecrets_FromSecretStoreThatReturnsOneValue()
{
// Configure Client
var httpClient = new TestHttpClient()
var storeName = "store";
var secretKey = "secretName";
var secretValue = "secret";

var secretDescriptors = new[]
{
Handler = async (entry) =>
{
var secrets = new Dictionary<string, string>() { { "secretName", "secret" } };
await SendResponseWithSecrets(secrets, entry);
}
new DaprSecretDescriptor(secretKey),
};

var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();

var daprClient = new Mock<DaprClient>();

daprClient.Setup(c => c.GetSecretAsync(storeName, secretKey,
It.IsAny<Dictionary<string, string>>(), default))
.ReturnsAsync(new Dictionary<string, string> { { secretKey, secretValue } });

var config = CreateBuilder()
.AddDaprSecretStore("store", new DaprSecretDescriptor[] { new DaprSecretDescriptor("secretName") }, daprClient)
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();

config["secretName"].Should().Be("secret");
Expand All @@ -167,29 +168,61 @@ public void LoadSecrets_FromSecretStoreThatReturnsOneValue()
[Fact]
public void LoadSecrets_FromSecretStoreThatCanReturnsMultipleValues()
{
// Configure Client
var httpClient = new TestHttpClient()
var storeName = "store";
var firstSecretKey = "first_secret";
var secondSecretKey = "second_secret";
var firstSecretValue = "secret1";
var secondSecretValue = "secret2";

var secretDescriptors = new[]
{
Handler = async (entry) =>
{
var secrets = new Dictionary<string, string>() {
{ "first_secret", "secret1" },
{ "second_secret", "secret2" }};
await SendResponseWithSecrets(secrets, entry);
}
new DaprSecretDescriptor(firstSecretKey),
new DaprSecretDescriptor(secondSecretKey),
};

var daprClient = new Mock<DaprClient>();

daprClient.Setup(c => c.GetSecretAsync(storeName, firstSecretKey,
It.IsAny<Dictionary<string, string>>(), default))
.ReturnsAsync(new Dictionary<string, string> { { firstSecretKey, firstSecretValue } });

var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
daprClient.Setup(c => c.GetSecretAsync(storeName, secondSecretKey,
It.IsAny<Dictionary<string, string>>(), default))
.ReturnsAsync(new Dictionary<string, string> { { secondSecretKey, secondSecretValue } });

var config = CreateBuilder()
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();

config[firstSecretKey].Should().Be(firstSecretValue);
config[secondSecretKey].Should().Be(secondSecretValue);
}

[Fact]
public void LoadSecrets_FromSecretStoreWithADifferentSecretKeyAndName()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should really have a test for required = false as well.

{
var storeName = "store";
var secretKey = "Microsservice-DatabaseConnStr";
var secretName = "ConnectionStrings:DatabaseConnStr";
var secretValue = "secret1";

var secretDescriptors = new[]
{
new DaprSecretDescriptor(secretName, new Dictionary<string, string>(), true,
secretKey)
};

var daprClient = new Mock<DaprClient>();

daprClient.Setup(c => c.GetSecretAsync(storeName, secretKey,
It.IsAny<Dictionary<string, string>>(), default))
.ReturnsAsync(new Dictionary<string, string> { { secretKey, secretValue } });

var config = CreateBuilder()
.AddDaprSecretStore("store", new DaprSecretDescriptor[] { new DaprSecretDescriptor("secretName") }, daprClient)
.Build();
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();

config["first_secret"].Should().Be("secret1");
config["second_secret"].Should().Be("secret2");
config[secretName].Should().Be(secretValue);
}

//Here
Expand Down