Skip to content

Commit

Permalink
Placeholder/encryption configuration refactorings (#1354)
Browse files Browse the repository at this point in the history
* Placeholder/encryption configuration refactorings

# Placeholder/encryption configuration providers
- Extract duplicate code from configuration sources/providers
- Fix reloading and change notification when binding to options
- Remove special-cased code paths for `ConfigurationManager` (no longer needed)
- Add trace-level logging, using source generator
- Refactor internal methods to recursively search through configuration sources/providers and update call sites
- Wire `Configuration.Encryption` from `AddSteeltoe` in Bootstrap

## Placeholder
- Fixes in placeholder substitution when using '.' (Spring-style) key separator with default value

## Encryption
- Rename various types and methods containing Encrypt* that actually decrypt.
- Remove `IConfiguration` extension methods, lazily create decryptor from loaded configuration
- Fix regex used in decryption, tweak its options, and use source generator

## Config Server
- Change: Do not implicitly add placeholder anymore; nesting of placeholder/encryption depends on what the user needs
- Remove duplicate tests

# Env actuator (Management)
- Fix host configuration from not being included in the response
- Include an empty placeholder/encryption property source in the response when used

* Review feedback: rename variables

* Update src/Configuration/src/Placeholder/PropertyPlaceHolderHelper.cs

Co-authored-by: Tim Hess <[email protected]>

---------

Co-authored-by: Tim Hess <[email protected]>
  • Loading branch information
bart-vmware and TimHess authored Sep 12, 2024
1 parent 50b22fa commit 44602e7
Show file tree
Hide file tree
Showing 115 changed files with 2,157 additions and 3,865 deletions.
9 changes: 9 additions & 0 deletions src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Steeltoe.Common.Logging;
using Steeltoe.Configuration.CloudFoundry;
using Steeltoe.Configuration.ConfigServer;
using Steeltoe.Configuration.Encryption;
using Steeltoe.Configuration.Placeholder;
using Steeltoe.Configuration.RandomValue;
using Steeltoe.Connectors.CosmosDb;
Expand Down Expand Up @@ -71,6 +72,7 @@ public void ConfigureSteeltoe()
}

WireIfLoaded(WireRandomValueProvider, SteeltoeAssemblyNames.ConfigurationRandomValue);
WireIfLoaded(WireDecryptionProvider, SteeltoeAssemblyNames.ConfigurationEncryption);
WireIfLoaded(WirePlaceholderResolver, SteeltoeAssemblyNames.ConfigurationPlaceholder);
WireIfLoaded(WireConnectors, SteeltoeAssemblyNames.Connectors);
WireIfLoaded(WireDynamicSerilog, SteeltoeAssemblyNames.LoggingDynamicSerilog);
Expand Down Expand Up @@ -104,6 +106,13 @@ private void WireRandomValueProvider()
_logger.LogInformation("Configured random value configuration provider");
}

private void WireDecryptionProvider()
{
_wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddDecryption(_loggerFactory));

_logger.LogInformation("Configured decryption configuration provider");
}

private void WirePlaceholderResolver()
{
_wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddPlaceholderResolver(_loggerFactory));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationCloudFoundry = "Steeltoe.Configuration.CloudFoundry" -> string!
const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer" -> string!
const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationEncryption = "Steeltoe.Configuration.Encryption" -> string!
const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationPlaceholder = "Steeltoe.Configuration.Placeholder" -> string!
const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationRandomValue = "Steeltoe.Configuration.RandomValue" -> string!
const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.Connectors = "Steeltoe.Connectors" -> string!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ProjectReference Include="..\..\..\Common\src\Hosting\Steeltoe.Common.Hosting.csproj" />
<ProjectReference Include="..\..\..\Common\src\Logging\Steeltoe.Common.Logging.csproj" />
<ProjectReference Include="..\..\..\Configuration\src\ConfigServer\Steeltoe.Configuration.ConfigServer.csproj" PrivateAssets="All" />
<ProjectReference Include="..\..\..\Configuration\src\Encryption\Steeltoe.Configuration.Encryption.csproj" PrivateAssets="All" />
<ProjectReference Include="..\..\..\Configuration\src\RandomValue\Steeltoe.Configuration.RandomValue.csproj" PrivateAssets="All" />
<ProjectReference Include="..\..\..\Configuration\src\Placeholder\Steeltoe.Configuration.Placeholder.csproj" PrivateAssets="All" />
<ProjectReference Include="..\..\..\Connectors\src\Connectors\Steeltoe.Connectors.csproj" PrivateAssets="All" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static class SteeltoeAssemblyNames
public const string ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer";
public const string ConfigurationRandomValue = "Steeltoe.Configuration.RandomValue";
public const string ConfigurationPlaceholder = "Steeltoe.Configuration.Placeholder";
public const string ConfigurationEncryption = "Steeltoe.Configuration.Encryption";
public const string Connectors = "Steeltoe.Connectors";
public const string DiscoveryConfiguration = "Steeltoe.Discovery.Configuration";
public const string DiscoveryConsul = "Steeltoe.Discovery.Consul";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Steeltoe.Configuration.CloudFoundry;
using Steeltoe.Configuration.CloudFoundry.ServiceBinding;
using Steeltoe.Configuration.ConfigServer;
using Steeltoe.Configuration.Encryption;
using Steeltoe.Configuration.Kubernetes.ServiceBinding;
using Steeltoe.Configuration.Placeholder;
using Steeltoe.Configuration.RandomValue;
Expand Down Expand Up @@ -85,6 +86,17 @@ public async Task RandomValueConfiguration_IsAutowired(HostBuilderType hostBuild
AssertRandomValueConfigurationIsAutowired(hostWrapper);
}

[Theory]
[InlineData(HostBuilderType.Host)]
[InlineData(HostBuilderType.WebHost)]
[InlineData(HostBuilderType.WebApplication)]
public async Task EncryptionConfiguration_IsAutowired(HostBuilderType hostBuilderType)
{
await using HostWrapper hostWrapper = HostWrapperFactory.GetForOnly(SteeltoeAssemblyNames.ConfigurationEncryption, hostBuilderType);

AssertEncryptionConfigurationIsAutowired(hostWrapper);
}

[Theory]
[InlineData(HostBuilderType.Host)]
[InlineData(HostBuilderType.WebHost)]
Expand Down Expand Up @@ -220,6 +232,7 @@ public async Task Everything_IsAutowired(HostBuilderType hostBuilderType)
AssertConfigServerConfigurationIsAutowired(hostWrapper);
AssertCloudFoundryConfigurationIsAutowired(hostWrapper);
AssertRandomValueConfigurationIsAutowired(hostWrapper);
AssertEncryptionConfigurationIsAutowired(hostWrapper);
AssertPlaceholderResolverIsAutowired(hostWrapper);
AssertConnectorsAreAutowired(hostWrapper);
AssertDynamicSerilogIsAutowired(hostWrapper);
Expand All @@ -238,37 +251,44 @@ private static void AssertConfigServerConfigurationIsAutowired(HostWrapper hostW
{
var configuration = hostWrapper.Services.GetRequiredService<IConfiguration>();

configuration.FindConfigurationProvider<CloudFoundryConfigurationProvider>().Should().NotBeNull();
configuration.FindConfigurationProvider<ConfigServerConfigurationProvider>().Should().NotBeNull();
configuration.EnumerateProviders<CloudFoundryConfigurationProvider>().Should().HaveCount(1);
configuration.EnumerateProviders<ConfigServerConfigurationProvider>().Should().HaveCount(1);
}

private static void AssertCloudFoundryConfigurationIsAutowired(HostWrapper hostWrapper)
{
var configuration = hostWrapper.Services.GetRequiredService<IConfiguration>();

configuration.FindConfigurationProvider<CloudFoundryConfigurationProvider>().Should().NotBeNull();
configuration.EnumerateProviders<CloudFoundryConfigurationProvider>().Should().HaveCount(1);
}

private static void AssertRandomValueConfigurationIsAutowired(HostWrapper hostWrapper)
{
var configuration = hostWrapper.Services.GetRequiredService<IConfiguration>();

configuration.FindConfigurationProvider<RandomValueProvider>().Should().NotBeNull();
configuration.EnumerateProviders<RandomValueProvider>().Should().HaveCount(1);
}

private static void AssertEncryptionConfigurationIsAutowired(HostWrapper hostWrapper)
{
var configurationRoot = (IConfigurationRoot)hostWrapper.Services.GetRequiredService<IConfiguration>();

configurationRoot.EnumerateProviders<DecryptionConfigurationProvider>().Should().HaveCount(1);
}

private static void AssertPlaceholderResolverIsAutowired(HostWrapper hostWrapper)
{
var configurationRoot = (IConfigurationRoot)hostWrapper.Services.GetRequiredService<IConfiguration>();

configurationRoot.Providers.OfType<PlaceholderResolverProvider>().Should().HaveCount(1);
configurationRoot.EnumerateProviders<PlaceholderConfigurationProvider>().Should().HaveCount(1);
}

private static void AssertConnectorsAreAutowired(HostWrapper hostWrapper)
{
var configuration = hostWrapper.Services.GetRequiredService<IConfiguration>();

configuration.FindConfigurationProvider<KubernetesServiceBindingConfigurationProvider>().Should().NotBeNull();
configuration.FindConfigurationProvider<CloudFoundryServiceBindingConfigurationProvider>().Should().NotBeNull();
configuration.EnumerateProviders<KubernetesServiceBindingConfigurationProvider>().Should().NotBeEmpty();
configuration.EnumerateProviders<CloudFoundryServiceBindingConfigurationProvider>().Should().HaveCount(1);

hostWrapper.Services.GetService<ConnectorFactory<CosmosDbOptions, CosmosClient>>().Should().NotBeNull();
hostWrapper.Services.GetService<ConnectorFactory<MongoDbOptions, IMongoClient>>().Should().NotBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

<ItemGroup>
<ProjectReference Include="..\..\..\Configuration\src\ConfigServer\Steeltoe.Configuration.ConfigServer.csproj" />
<ProjectReference Include="..\..\..\Configuration\src\Encryption\Steeltoe.Configuration.Encryption.csproj" />
<ProjectReference Include="..\..\..\Configuration\src\Placeholder\Steeltoe.Configuration.Placeholder.csproj" />
<ProjectReference Include="..\..\..\Configuration\src\RandomValue\Steeltoe.Configuration.RandomValue.csproj" />
<ProjectReference Include="..\..\..\Connectors\src\Connectors\Steeltoe.Connectors.csproj" />
<ProjectReference Include="..\..\..\Discovery\src\Configuration\Steeltoe.Discovery.Configuration.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;

namespace Steeltoe.Connectors.Test;
namespace Steeltoe.Common.TestResources;

internal sealed class MemoryFileProvider : IFileProvider
public sealed class MemoryFileProvider : IFileProvider
{
private static readonly char[] DirectorySeparators =
[
Expand Down
3 changes: 2 additions & 1 deletion src/Configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Steeltoe configuration providers can:
- Interact with [Spring Cloud Config](https://spring.io/projects/spring-cloud-config)
- Read [Cloud Foundry environment variables](https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html)
- Replace property placeholders
- Decrypt encrypted values
- Provide random values

For more information on how to use these components see the [Steeltoe documentation](https://steeltoe.io/).
Expand All @@ -19,7 +20,7 @@ See the `Configuration` directory inside the [Samples](https://github.com/Steelt

### Unstructured data files

Unlike the Java version of the configuration server client, the Steeltoe client currently only supports property and yaml files; not plain text.
Unlike the Java version of the Config Server client, the Steeltoe client currently only supports property and yaml files; not plain text.

### Server initiated reload

Expand Down
146 changes: 146 additions & 0 deletions src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;

namespace Steeltoe.Configuration;

internal abstract partial class CompositeConfigurationProvider : IConfigurationProvider, IDisposable
{
private readonly IList<IConfigurationProvider> _providers;
private readonly ILogger<CompositeConfigurationProvider> _logger;
private bool _isDisposed;

protected internal IConfigurationRoot? ConfigurationRoot { get; private set; }

protected CompositeConfigurationProvider(IList<IConfigurationProvider> providers, ILoggerFactory loggerFactory)
{
ArgumentNullException.ThrowIfNull(providers);
ArgumentNullException.ThrowIfNull(loggerFactory);

_providers = providers;
_logger = loggerFactory.CreateLogger<CompositeConfigurationProvider>();
}

public IChangeToken GetReloadToken()
{
LogGetReloadToken(GetType().Name);
return ConfigurationRoot?.GetReloadToken()!;
}

public void Load()
{
Load(ConfigurationRoot != null);
}

private void Load(bool isReload)
{
LogLoad(GetType().Name, isReload);

if (isReload)
{
ConfigurationRoot!.Reload();
}
else
{
LogCreateConfigurationRoot(GetType().Name, _providers.Count);
ConfigurationRoot = new ConfigurationRoot(_providers);
}
}

public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
{
string[] earlierKeysArray = earlierKeys as string[] ?? earlierKeys.ToArray();
#pragma warning disable S3236 // Caller information arguments should not be provided explicitly
ArgumentNullException.ThrowIfNull(earlierKeysArray, nameof(earlierKeys));
#pragma warning restore S3236 // Caller information arguments should not be provided explicitly

LogGetChildKeys(GetType().Name, earlierKeysArray, parentPath);

IConfiguration? section = parentPath == null ? ConfigurationRoot : ConfigurationRoot?.GetSection(parentPath);

if (section == null)
{
return earlierKeysArray;
}

List<string> keys = [];
keys.AddRange(section.GetChildren().Select(child => child.Key));
keys.AddRange(earlierKeysArray);
keys.Sort(ConfigurationKeyComparer.Instance);
return keys;
}

public virtual bool TryGet(string key, out string? value)
{
ArgumentNullException.ThrowIfNull(key);

LogTryGet(GetType().Name, key);

value = ConfigurationRoot?.GetValue<string>(key);
bool found = value != null;

if (found)
{
LogTryGetFound(GetType().Name, key, value!);
}

return found;
}

public void Set(string key, string? value)
{
ArgumentNullException.ThrowIfNull(key);

LogSet(GetType().Name, key, value);

if (ConfigurationRoot != null)
{
ConfigurationRoot[key] = value;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing && !_isDisposed && ConfigurationRoot is IDisposable disposable)
{
_isDisposed = true;

LogDispose(GetType().Name);
disposable.Dispose();
}
}

[LoggerMessage(Level = LogLevel.Trace, Message = "GetReloadToken from {Type}.")]
private partial void LogGetReloadToken(string type);

[LoggerMessage(Level = LogLevel.Trace, Message = "Load from {Type} with isReload {IsReload}.")]
private partial void LogLoad(string type, bool isReload);

[LoggerMessage(Level = LogLevel.Trace, Message = "CreateConfigurationRoot from {Type} with {ProviderCount} providers.")]
private partial void LogCreateConfigurationRoot(string type, int providerCount);

[LoggerMessage(Level = LogLevel.Trace, Message = "GetChildKeys from {Type} with earlierKeys [{EarlierKeys}] and parentPath '{ParentPath}'.")]
private partial void LogGetChildKeys(string type, string[] earlierKeys, string? parentPath);

[LoggerMessage(Level = LogLevel.Trace, Message = "TryGet from {Type} with key '{Key}'.")]
private partial void LogTryGet(string type, string key);

[LoggerMessage(Level = LogLevel.Trace, Message = "TryGet from {Type} with key '{Key}' found value '{Value}'.")]
private partial void LogTryGetFound(string type, string key, string value);

[LoggerMessage(Level = LogLevel.Trace, Message = "Set from {Type} with key '{Key}' and value '{Value}'.")]
private partial void LogSet(string type, string key, string? value);

[LoggerMessage(Level = LogLevel.Trace, Message = "Dispose from {Type}.")]
private partial void LogDispose(string type);
}
67 changes: 0 additions & 67 deletions src/Configuration/src/Abstractions/ConfigurationExtensions.cs

This file was deleted.

Loading

0 comments on commit 44602e7

Please sign in to comment.