Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable vault name mapping and error suppression #1231

Merged
merged 11 commits into from
Feb 16, 2024
Merged
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
36 changes: 29 additions & 7 deletions src/Dapr.Extensions.Configuration/DaprSecretDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -21,8 +21,11 @@ namespace Dapr.Extensions.Configuration
public class DaprSecretDescriptor
{
/// <summary>
/// Gets or sets the secret name.
/// The name of the secret to retrieve from the Dapr secret store.
/// </summary>
/// <remarks>
/// If the <see cref="SecretKey"/> is not specified, this value will also be used as the key to retrieve the secret from the associated source secret store.
/// </remarks>
public string SecretName { get; }

/// <summary>
Expand All @@ -31,20 +34,39 @@ public class DaprSecretDescriptor
public IReadOnlyDictionary<string, string> Metadata { get; }

/// <summary>
/// Secret Descriptor Construcutor
/// A value indicating whether to throw an exception if the secret is not found in the source secret store.
/// </summary>
/// <remarks>
/// Setting this value to <see langword="false"/> will suppress the exception; otherwise, <see langword="true"/> will not.
/// </remarks>
public bool IsRequired { get; }

/// <summary>
/// The secret key that maps to the <see cref="SecretName"/> to retrieve from the source secret store.
/// </summary>
/// <remarks>
/// Use this property when the <see cref="SecretName"/> does not match the key used to retrieve the secret from the source secret store.
/// </remarks>
public string SecretKey { get; }

/// <summary>
/// Secret Descriptor Constructor
/// </summary>
public DaprSecretDescriptor(string secretName) : this(secretName, new Dictionary<string, string>())
public DaprSecretDescriptor(string secretName, bool isRequired = true, string secretKey = "")
: this(secretName, new Dictionary<string, string>(), isRequired, secretKey)
{

}

/// <summary>
/// Secret Descriptor Construcutor
/// Secret Descriptor Constructor
/// </summary>
public DaprSecretDescriptor(string secretName, IReadOnlyDictionary<string, string> metadata)
public DaprSecretDescriptor(string secretName, IReadOnlyDictionary<string, string> metadata, bool isRequired = true, string secretKey = "")
{
SecretName = secretName;
Metadata = metadata;
IsRequired = isRequired;
SecretKey = string.IsNullOrEmpty(secretKey) ? secretName : secretKey;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -54,7 +54,7 @@ public DaprSecretStoreConfigurationProvider(
bool normalizeKey,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client) : this(store, normalizeKey, null, secretDescriptors, client, DefaultSidecarWaitTimeout)
{
{
}

/// <summary>
Expand Down 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,34 @@ 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)
{
if (secretDescriptor.IsRequired)
{
throw;
}
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]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -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,32 +168,127 @@ 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 } });

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()
{
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(storeName, secretDescriptors, daprClient.Object)
.Build();

config[secretName].Should().Be(secretValue);
}

[Fact]
public void LoadSecrets_FromSecretStoreNotRequiredAndDoesNotExist_ShouldNotThrowException()
{
var storeName = "store";
var secretName = "ConnectionStrings:DatabaseConnStr";

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

var httpClient = new TestHttpClient
{
Handler = async entry =>
{
await SendEmptyResponse(entry);
}
};

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

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

config[secretName].Should().BeNull();
}

[Fact]
public void LoadSecrets_FromSecretStoreRequiredAndDoesNotExist_ShouldThrowException()
{
var storeName = "store";
var secretName = "ConnectionStrings:DatabaseConnStr";

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

var httpClient = new TestHttpClient
{
Handler = async entry =>
{
await SendEmptyResponse(entry);
}
};

var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.Build();

var ex = Assert.Throws<DaprException>(() =>
{
var config = CreateBuilder()
.AddDaprSecretStore(storeName, secretDescriptors, daprClient)
.Build();
});

config["first_secret"].Should().Be("secret1");
config["second_secret"].Should().Be("secret2");
Assert.Contains("Secret", ex.Message);
}

//Here

[Fact]
public void AddDaprSecretStore_WithoutStore_ReportsError()
{
Expand Down Expand Up @@ -565,7 +661,7 @@ await SendResponseWithSecrets(new Dictionary<string, string>()
["otherSecretName≡value"] = "secret",
}, entry);
}
}
}
}
};

Expand Down
Loading