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

machine.config for .NET Core #32307

Open
eerhardt opened this issue Feb 14, 2020 · 10 comments
Open

machine.config for .NET Core #32307

eerhardt opened this issue Feb 14, 2020 · 10 comments

Comments

@eerhardt
Copy link
Member

In the .NET Framework, System.Configuration allowed a machine.config file that would apply globally to every .NET process on the machine.

https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/#machine-configuration-files

This made sense, because you could configure the framework using this file, and it would apply for all processes.

In .NET Core this concept makes much less sense.

  1. Nothing in the framework is configured using System.Configuration. Instead, we use runtimeconfig.json.
  2. With the advent of self-contained applications and multiple "hive" installations, the concept of a config file that applies to the whole machine doesn't make sense. A self-contained application should run the same no matter if some global "machine.config" file exists or not.

Today in .NET Core, when you use the System.Configuration.ConfigurationManager package, it will still try to load a machine.config file, however the location it probes for this file is relative to the current application (using AppDomain.CurrentDomain.BaseDirectory):

internal static string MachineConfigFilePath
{
get
{
if (s_machineConfigFilePath == null)
{
string directory = AppDomain.CurrentDomain.BaseDirectory;
s_machineConfigFilePath = Path.Combine(Path.Combine(directory, MachineConfigSubdirectory),
MachineConfigFilename);
}
return s_machineConfigFilePath;
}
}

(NOTE: For .NET Core 2.0, the code still used the old .NET Framework implementation of calling RuntimeEnvironment.GetRuntimeDirectory() to get the directory. This was changed in 2.1 to use BaseDirectory with dotnet/corefx#20488.)

Typically that file never exists. When if it doesn't ConfigurationManager will load a default/hard-coded machine.config:

private const string ImplicitMachineConfig =
@"<configuration>
<configSections>
<section name='appSettings' type='System.Configuration.AppSettingsSection, System.Configuration.ConfigurationManager' restartOnExternalChanges='false' requirePermission='false' />
<section name='connectionStrings' type='System.Configuration.ConnectionStringsSection, System.Configuration.ConfigurationManager' requirePermission='false' />
<section name='mscorlib' type='System.Configuration.IgnoreSection, System.Configuration.ConfigurationManager' allowLocation='false' />
<section name='runtime' type='System.Configuration.IgnoreSection, System.Configuration.ConfigurationManager' allowLocation='false' />
<section name='assemblyBinding' type='System.Configuration.IgnoreSection, System.Configuration.ConfigurationManager' allowLocation='false' />
<section name='satelliteassemblies' type='System.Configuration.IgnoreSection, System.Configuration.ConfigurationManager' allowLocation='false' />
<section name='startup' type='System.Configuration.IgnoreSection, System.Configuration.ConfigurationManager' allowLocation='false' />
</configSections>
<configProtectedData defaultProvider='RsaProtectedConfigurationProvider'>
<providers>
<add name = 'RsaProtectedConfigurationProvider' type='System.Configuration.RsaProtectedConfigurationProvider, System.Configuration.ConfigurationManager' description='Uses RsaCryptoServiceProvider to encrypt and decrypt' keyContainerName='NetFrameworkConfigurationKey' cspProviderName='' useMachineContainer='true' useOAEP='false' />
<add name = 'DataProtectionConfigurationProvider' type='System.Configuration.DpapiProtectedConfigurationProvider, System.Configuration.ConfigurationManager' description='Uses CryptProtectData and CryptUnProtectData Windows APIs to encrypt and decrypt' useMachineProtection='true' keyEntropy='' />
</providers>
</configProtectedData>
<connectionStrings>
<add name = 'LocalSqlServer' connectionString='data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true' providerName='System.Data.SqlClient' />
</connectionStrings>
</configuration>";

The problem with the current behavior comes when AppDomain.CurrentDomain.BaseDirectory doesn't return a path. For example, when using a custom native host - see #25027 - BaseDirectory returns null.
In this situation, ConfigurationManager tries to load a Config/machine.config stream. And since this path isn't rooted, it tries to issue a WebRequest for this stream:

// the streamName can either be a file name, or a URI
if (IsFile(streamName)) return Host.OpenStreamForRead(streamName);
if (streamName == null) return null;
// scheme is http
WebClient client = new WebClient();
// Try using default credentials
try
{
client.Credentials = CredentialCache.DefaultCredentials;
}
catch { }
byte[] fileData = null;
try
{
fileData = client.DownloadData(streamName);

WebClient is smart enough to realize this is a request for a file, tries to load the file - relative to the current working directory, which is probably not good from a security perspective. When the file doesn't exist it throws a few 1st chance exceptions, which are caught and then null is returned.

Proposal

We should just cut support for reading a machine.config file completely from ConfigurationManager on .NET Core and always return the default/hard-coded machine.config contents. No need to probe for files that don't exist. No need to issue a WebRequest and catch exceptions either.

All settings can be overridden by the App.config. And since we are only probing the app's directory (or the current working directory if using a native host) it doesn't make sense to split the configuration across multiple files in the app's directory.

/cc @ericstj @JeremyKuhne @maryamariyan @safern

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Configuration untriaged New issue has not been triaged by the area owner labels Feb 14, 2020
@JeremyKuhne
Copy link
Member

I spaced on the change away from RuntimeEnvironment.GetRuntimeDirectory(). :( machine.config is really runtime.config, but there was only ever one instance of the runtime until Core.

The idea here was that you could still set for whichever runtime instance that you were using. You could trump the settings completely, which isn't possible if we load the implicit one. I don't recall if you can override the config sections from the higher-level config, but I think the answer is no. If so, this can potentially be problematic when you take user config files into account.

@eerhardt
Copy link
Member Author

@JeremyKuhne - I'm not sure I follow your response. Do you agree with the proposal to remove trying to probe for a machine.config file on .NET Core?

If we shouldn't remove it, what do you propose we do instead? I don't think we should probe the RuntimeEnvironment.GetRuntimeDirectory() directory because we will never find that file there. Probing the AppDomain.BaseDirectory for Config\machine.config, like we are doing now, doesn't really make sense to me either. Are you saying this is how someone would override our implicit machine.config file, if they need to?

@JeremyKuhne
Copy link
Member

Are you saying this is how someone would override our implicit machine.config file, if they need to?

Yes, the need to trump it so you can inject in front of the user.config is important. I'm ok with making the probe app-specific, but I think we still need it.

@jkotas
Copy link
Member

jkotas commented Feb 25, 2020

Yes, the need to trump it so you can inject in front of the user.config is important.

Do we know about anybody who is using this override for real app?

@ericstj ericstj added bug and removed untriaged New issue has not been triaged by the area owner labels Jul 7, 2020
@ericstj ericstj modified the milestones: 6.0.0, 5.0.0 Jul 7, 2020
@ericstj
Copy link
Member

ericstj commented Jul 7, 2020

I don't believe anyone is using this if we can absorb this breaking change in 5.0.0 that seems best to do now (rather than do it in 6.0.0 which we expect to move to LTS). If it breaks anyone they can revert to an earlier package and let us know they need it and we can bring it back.

@ericstj ericstj modified the milestones: 5.0.0, 6.0.0 Aug 28, 2020
@ericstj
Copy link
Member

ericstj commented Aug 28, 2020

@safern I don't think this meets the bar at this point in 5.0.0. Moving out.

@habibcs
Copy link

habibcs commented Apr 7, 2021

We have a system with lot of services/APIs with .NET 461 .NET472 and have a huge dependency on the machine level config files.
We now gradually adding newer APIs with .NET Core 31 and .NET5.

I do not find any consistent solution, especially every new version update, changes the path to the runtime/sdk.

Therefore I resort to defining the global config location for our middleware (hundreds of) services/APIs.
And in those NetCore31/Net5 services we manually load from that global location (via environment variable).
And then feed the path to load config via something like ConfigurationManager.OpenMappedMachineConfiguration()

This is our approach. If this is more or less the standard or the right way, then skipping .NET dependency completely away from Machine config should be taken and highlighted clearly in the documentation.

Thanks, Habib

@safern safern removed their assignment Apr 8, 2021
@krwq
Copy link
Member

krwq commented Jul 22, 2021

[Triage] Seems it's non essential for 6.0.

@ericstj ericstj added this to the 8.0.0 milestone Dec 8, 2022
@steveharter steveharter modified the milestones: 8.0.0, 9.0.0 Aug 2, 2023
@khurshid-alam
Copy link

khurshid-alam commented Sep 4, 2023

Can anyone answer Where is the machine.config for linux ?

I wanted to set

<system.web>
    <processModel maxWorkerThreads="100" maxIoThreads="100" minWorkerThreads="50"/>
    <httpRuntime minFreeThreads="704" minLocalRequestFreeThreads="608"/>
</system.web>

putting machine.config in the root directory doesn't seem to have worked. We only have web.config in root directory.

We run it as dotnet app.dll --urls=http://0.0.0.0:5000

@jkotas
Copy link
Member

jkotas commented Sep 4, 2023

I assume that you are using ASP.NET Core. machine.config and web.config is not used by ASP.NET Core. ASP.NET Core uses different configuration system, documented at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration

@stephentoub stephentoub modified the milestones: 9.0.0, Future Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

No branches or pull requests