Skip to content

Commit 91e4a84

Browse files
authored
Merge pull request #404 from mjcheetham/os-config
Add ability to specify default settings values from the registry on Windows
2 parents 93d14e3 + 1cefe59 commit 91e4a84

File tree

12 files changed

+203
-4
lines changed

12 files changed

+203
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ See detailed information [here](https://aka.ms/gcmcore-httpproxy).
189189
- [Command-line usage](docs/usage.md)
190190
- [Configuration options](docs/configuration.md)
191191
- [Environment variables](docs/environment.md)
192+
- [Enterprise configuration](docs/enterprise-config.md)
192193
- [Network and HTTP configuration](docs/netconfig.md)
193194
- [Architectural overview](docs/architecture.md)
194195
- [Host provider specification](docs/hostprovider.md)

docs/configuration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
[Git Credential Manager Core](usage.md) works out of the box for most users.
44

55
Git Credential Manager Core (GCM Core) can be configured using Git's configuration files, and follows all of the same rules Git does when consuming the files.
6+
67
Global configuration settings override system configuration settings, and local configuration settings override global settings; and because the configuration details exist within Git's configuration files you can use Git's `git config` utility to set, unset, and alter the setting values. All of GCM Core's configuration settings begin with the term `credential`.
78

89
GCM Core honors several levels of settings, in addition to the standard local \> global \> system tiering Git uses.
910
URL-specific settings or overrides can be applied to any value in the `credential` namespace with the syntax below.
1011

11-
Additionally, GCM Core respects several GCM-specific [environment variables](environment.md) **which take precedence over configuration options.**
12+
Additionally, GCM Core respects several GCM-specific [environment variables](environment.md) **which take precedence over configuration options**. System administrators may also configure [default values](enterprise-config.md) for many settings used by GCM Core.
1213

1314
GCM Core will only be used by Git if it is installed and configured. Use `git config --global credential.helper manager-core` to assign GCM Core as your credential helper. Use `git config credential.helper` to see the current configuration.
1415

docs/enterprise-config.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Enterprise configuration defaults
2+
3+
Git Credential Manager Core (GCM Core) can be configured using multiple
4+
different mechanisms. In order of preference, those mechanisms are:
5+
6+
1. [Environment variables](environment.md)
7+
2. [Standard Git configuration files](configuration.md)
8+
1. Repository/local configuration (`.git/config`)
9+
2. User/global configuration (`$HOME/.gitconfig` or `%HOME%\.gitconfig`)
10+
3. Installation/system configuration (`etc/gitconfig`)
11+
3. Enterprise system administrator defaults
12+
4. Compiled default values
13+
14+
This model largely matches what Git itself supports, namely environment
15+
variables that take precedence over Git configuration files.
16+
17+
The addition of the enterprise system administrator defaults enables those
18+
administrators to configure many GCM settings using familiar MDM tooling, rather
19+
than having to modify the Git installation configuration files.
20+
21+
### User Freedom
22+
23+
We believe the user should _always_ be at liberty to configure
24+
Git and GCM exactly as they wish. By prefering environment variables and Git
25+
configuration files over system admin values, these only act as _default values_
26+
that can always be overriden by the user in the usual ways.
27+
28+
## Windows
29+
30+
Default setting values come from the Windows Registry, specifically the
31+
following keys:
32+
33+
**32-bit Windows**
34+
35+
```text
36+
HKEY_LOCAL_MACHINE\SOFTWARE\GitCredentialManager\Configuration
37+
```
38+
39+
**64-bit Windows**
40+
41+
```text
42+
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GitCredentialManager\Configuration
43+
```
44+
45+
> GCM Core is a 32-bit executable on Windows. When running on a 64-bit
46+
installation of Windows registry access is transparently redirected to the
47+
`WOW6432Node` node.
48+
49+
By using the Windows Registry, system administrators can use Group Policy to
50+
easily set defaults for GCM Core's settings.
51+
52+
The names and possible values of all settings under this key are the same as
53+
those of the [Git configuration](configuration.md) settings.
54+
55+
The type of each registry key can be either `REG_SZ` (string) or `REG_DWORD`
56+
(integer).
57+
58+
59+
## macOS/Linux
60+
61+
Default configuration setting stores has not been implemented.

docs/environment.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[Git Credential Manager Core](usage.md) works out of the box for most users. Configuration options are available to customize or tweak behavior.
44

5-
Git Credential Manager Core (GCM Core) can be configured using environment variables. **Environment variables take precedence over [configuration](configuration.md) options.**
5+
Git Credential Manager Core (GCM Core) can be configured using environment variables. **Environment variables take precedence over [configuration](configuration.md) options and enterprise system administrator [default values](enterprise-config.md)**.
66

77
For the complete list of environment variables GCM Core understands, see the list below.
88

src/shared/Microsoft.Git.CredentialManager.Tests/Interop/MacOS/MacOSKeychainTests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
// Licensed under the MIT license.
33
using System;
44
using Xunit;
5+
using Microsoft.Git.CredentialManager.Interop;
56
using Microsoft.Git.CredentialManager.Interop.MacOS;
7+
using Microsoft.Git.CredentialManager.Interop.MacOS.Native;
68

79
namespace Microsoft.Git.CredentialManager.Tests.Interop.MacOS
810
{
911
public class MacOSKeychainTests
1012
{
1113
private const string TestNamespace = "git-test";
1214

13-
[PlatformFact(Platforms.MacOS)]
15+
[SkippablePlatformFact(Platforms.MacOS)]
1416
public void MacOSKeychain_ReadWriteDelete()
1517
{
1618
var keychain = new MacOSKeychain(TestNamespace);
@@ -32,6 +34,19 @@ public void MacOSKeychain_ReadWriteDelete()
3234
Assert.Equal(account, outCredential.Account);
3335
Assert.Equal(password, outCredential.Password);
3436
}
37+
// There is an unknown issue that the keychain can sometimes get itself in where all API calls
38+
// result in an errSecAuthFailed error. The only solution seems to be a machine restart, which
39+
// isn't really possible in CI!
40+
// The problem has plagued others who are calling the same Keychain APIs from C# such as the
41+
// MSAL.NET team - they don't know either. It might have something to do with the code signing
42+
// signature of the binary (our collective best theory).
43+
// It's probably only diagnosable at this point by Apple, but we don't have a reliable way to
44+
// reproduce the problem.
45+
// For now we will just mark the test as "skipped" when we hit this problem.
46+
catch (InteropException iex) when (iex.ErrorCode == SecurityFramework.ErrorSecAuthFailed)
47+
{
48+
AssertEx.Skip("macOS Keychain is in an invalid state (errSecAuthFailed)");
49+
}
3550
finally
3651
{
3752
// Ensure we clean up after ourselves even in case of 'get' failures

src/shared/Microsoft.Git.CredentialManager/CommandContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public CommandContext(string appPath)
101101
gitPath,
102102
FileSystem.GetCurrentDirectory()
103103
);
104-
Settings = new Settings(Environment, Git);
104+
Settings = new WindowsSettings(Environment, Git, Trace);
105105
CredentialStore = new WindowsCredentialManager(Settings.CredentialNamespace);
106106
}
107107
else if (PlatformUtils.IsMacOS())

src/shared/Microsoft.Git.CredentialManager/Constants.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ public static class Remote
107107
}
108108
}
109109

110+
public static class WindowsRegistry
111+
{
112+
public const string HKAppBasePath = @"SOFTWARE\GitCredentialManager";
113+
public const string HKConfigurationPath = HKAppBasePath + @"\Configuration";
114+
}
115+
110116
public static class HelpUrls
111117
{
112118
public const string GcmProjectUrl = "https://aka.ms/gcmcore";
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
namespace Microsoft.Git.CredentialManager.Interop.Windows
5+
{
6+
/// <summary>
7+
/// Reads settings from Git configuration, environment variables, and defaults from the Windows Registry.
8+
/// </summary>
9+
public class WindowsSettings : Settings
10+
{
11+
private readonly ITrace _trace;
12+
13+
public WindowsSettings(IEnvironment environment, IGit git, ITrace trace)
14+
: base(environment, git)
15+
{
16+
EnsureArgument.NotNull(trace, nameof(trace));
17+
_trace = trace;
18+
19+
PlatformUtils.EnsureWindows();
20+
}
21+
22+
protected override bool TryGetExternalDefault(string section, string property, out string value)
23+
{
24+
value = null;
25+
26+
#if NETFRAMEWORK
27+
// Check for machine (HKLM) registry keys that match the Git configuration name.
28+
// These can be set by system administrators via Group Policy, so make useful defaults.
29+
using (Win32.RegistryKey configKey = Win32.Registry.LocalMachine.OpenSubKey(Constants.WindowsRegistry.HKConfigurationPath))
30+
{
31+
if (configKey is null)
32+
{
33+
// No configuration key exists
34+
return false;
35+
}
36+
37+
string name = $"{section}.{property}";
38+
object registryValue = configKey.GetValue(name);
39+
if (registryValue is null)
40+
{
41+
// No property exists
42+
return false;
43+
}
44+
45+
value = registryValue.ToString();
46+
_trace.WriteLine($"Default setting found in registry: {name}={value}");
47+
48+
return true;
49+
}
50+
#else
51+
return base.TryGetExternalDefault(section, property, out value);
52+
#endif
53+
}
54+
}
55+
}

src/shared/Microsoft.Git.CredentialManager/Settings.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,29 @@ public IEnumerable<string> GetSettingValues(string envarName, string section, st
289289
{
290290
yield return value;
291291
}
292+
293+
// Check for an externally specified default value
294+
if (TryGetExternalDefault(section, property, out string defaultValue))
295+
{
296+
yield return defaultValue;
297+
}
292298
}
293299
}
294300

301+
/// <summary>
302+
/// Try to get the default value for a configuration setting.
303+
/// This may come from external policies or the Operating System.
304+
/// </summary>
305+
/// <param name="section">Configuration section name.</param>
306+
/// <param name="property">Configuration property name.</param>
307+
/// <param name="value">Value of the configuration setting, or null.</param>
308+
/// <returns>True if a default setting has been set, false otherwise.</returns>
309+
protected virtual bool TryGetExternalDefault(string section, string property, out string value)
310+
{
311+
value = null;
312+
return false;
313+
}
314+
295315
public Uri RemoteUri { get; set; }
296316

297317
public bool IsDebuggingEnabled => _environment.Variables.GetBooleanyOrDefault(KnownEnvars.GcmDebug, false);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Xunit;
2+
3+
namespace Microsoft.Git.CredentialManager.Tests
4+
{
5+
public static class AssertEx
6+
{
7+
/// <summary>
8+
/// Requires the fact or theory be marked with the <see cref="SkippableFactAttribute"/>
9+
/// or <see cref="SkippableTheoryAttribute"/>.
10+
/// </summary>
11+
/// <param name="reason">Reason the test has been skipped.</param>
12+
public static void Skip(string reason)
13+
{
14+
Xunit.Skip.If(true, reason);
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)