-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
[API Proposal]: Customizable IConfigurationRoot.GetDebugView() for hiding the value #60065
Comments
Tagging subscribers to this area: @maryamariyan, @safern Issue DetailsBackground and motivationAs the GetDebugView() method will display the whole configuration, we want to have it in logs for debug purposes. This brings a security risk as some secrets such as connection string, Identity credentials and others might be included in the configuration. API Proposal// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// Extension methods for <see cref="IConfigurationRoot"/>.
/// </summary>
public static class ConfigurationRootExtensions
{
/// <summary>
/// Generates a human-readable view of the configuration showing where each value came from.
/// </summary>
/// <returns> The debug view. </returns>
public static string GetDebugView(this IConfigurationRoot root)
{
return GetDebugView(root, null);
}
/// <summary>
/// Generates a human-readable view of the configuration showing where each value came from.
/// </summary>
/// <param name="root">Configuration root</param>
/// <param name="processValue">
/// Function for processing the value e.g. hiding secrets
/// Parameters:
/// Key: Key of the current configuration section
/// Path: Full path to the configuration section
/// Value: Value of the configuration section
/// ConfigurationProvider: Provider of the value of the configuration section
/// returns: Value is used to assign as the Value of the configuration section
/// </param>
/// <returns> The debug view. </returns>
public static string GetDebugView(this IConfigurationRoot root, Func<string, string, string, IConfigurationProvider, string> processValue)
{
void RecurseChildren(
StringBuilder stringBuilder,
IEnumerable<IConfigurationSection> children,
string indent)
{
foreach (IConfigurationSection child in children)
{
(string Value, IConfigurationProvider Provider) valueAndProvider = GetValueAndProvider(root, child.Path);
if (valueAndProvider.Provider != null)
{
var value = processValue != null
? processValue(child.Key, child.Path, valueAndProvider.Value, valueAndProvider.Provider)
: valueAndProvider.Value;
stringBuilder
.Append(indent)
.Append(child.Key)
.Append('=')
.Append(value)
.Append(" (")
.Append(valueAndProvider.Provider)
.AppendLine(")");
}
else
{
stringBuilder
.Append(indent)
.Append(child.Key)
.AppendLine(":");
}
RecurseChildren(stringBuilder, child.GetChildren(), indent + " ");
}
}
var builder = new StringBuilder();
RecurseChildren(builder, root.GetChildren(), "");
return builder.ToString();
}
private static (string Value, IConfigurationProvider Provider) GetValueAndProvider(
IConfigurationRoot root,
string key)
{
foreach (IConfigurationProvider provider in root.Providers.Reverse())
{
if (provider.TryGet(key, out string value))
{
return (value, provider);
}
}
return (null, null);
}
}
} API Usagepublic Startup(IWebHostEnvironment env, IConfiguration configuration)
{
...
if (configuration is IConfigurationRoot configurationRoot)
{
var startupLoggerFactory = StartupLoggerHelper.CreateStartupLoggerFactory(configuration, env);
var startupLogger = startupLoggerFactory.CreateLogger<Startup>();
startupLogger.LogDebug(configurationRoot.GetDebugView(HideSecrets));
}
}
private string HideSecrets(string key, string path, string value, IConfigurationProvider provider)
{
var providerName = provider.ToString();
if (providerName.Contains("KeyVault"))
{
return "*** secret ***";
}
return value;
} RisksNone as far as I know.
|
@Andree643 seems reasonable to me. I do have a question, do you see a scenario where someone might care about the key and the path when processing the value, or is the Provider enough to know if it is a secret value or not? |
@safern I actually do. At least the path is really useful when you want to hide only a single specific value by exact path or to check the path for existence of some substring such as "ConnectionString". At least I assume it would be helpful to give the developers access to these properties since we have them and might be useful for them. The key might not be needed as it's already as a suffix of the path, but it can be more efficient to check only the key when needed rather then parsing the path. |
Sounds good. I'll mark it as ready for review as it looks good as proposed, we can discuss if we want the key or not during the review of the API. |
namespace Microsoft.Extensions.Configuration
{
public readonly struct ConfigurationDebugViewContext
{
public ConfigurationDebugViewContext(string path, string key, string value, IConfigurationProvider configurationProvider);
public string Path { get; }
public string Key { get; }
public string Value { get; }
public IConfigurationProvider ConfigurationProvider { get; }
}
public static class ConfigurationRootExtensions
{
// Existing
// public static string GetDebugView(this IConfigurationRoot root);
public static string GetDebugView(this IConfigurationRoot root, Func<ConfigurationDebugViewContext, string> processValue);
}
} |
...
public interface IConfigurationProvider
{
/// <summary>
/// Tries to get a configuration value for the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns><c>True</c> if a value for the specified key was found, otherwise <c>false</c>.</returns>
bool TryGet(string key, out string value);
... |
@safern Could you please help with the build agent? I'm getting following error:
|
Is this closed by #60391? |
Yes, it looks like we missed this. Thanks for the heads up. Fixed by #60391 |
Background and motivation
As the GetDebugView() method will display the whole configuration, we want to have it in logs for debug purposes. This brings a security risk as some secrets such as connection string, Identity credentials and others might be included in the configuration.
API Proposal
API Usage
Risks
None as far as I know.
The text was updated successfully, but these errors were encountered: