Skip to content

Commit

Permalink
Merge pull request #404 from mjcheetham/os-config
Browse files Browse the repository at this point in the history
Add ability to specify default settings values from the registry on Windows
  • Loading branch information
mjcheetham authored Aug 10, 2021
2 parents 93d14e3 + 1cefe59 commit 91e4a84
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ See detailed information [here](https://aka.ms/gcmcore-httpproxy).
- [Command-line usage](docs/usage.md)
- [Configuration options](docs/configuration.md)
- [Environment variables](docs/environment.md)
- [Enterprise configuration](docs/enterprise-config.md)
- [Network and HTTP configuration](docs/netconfig.md)
- [Architectural overview](docs/architecture.md)
- [Host provider specification](docs/hostprovider.md)
Expand Down
3 changes: 2 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
[Git Credential Manager Core](usage.md) works out of the box for most users.

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.

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`.

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

Additionally, GCM Core respects several GCM-specific [environment variables](environment.md) **which take precedence over configuration options.**
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.

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.

Expand Down
61 changes: 61 additions & 0 deletions docs/enterprise-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Enterprise configuration defaults

Git Credential Manager Core (GCM Core) can be configured using multiple
different mechanisms. In order of preference, those mechanisms are:

1. [Environment variables](environment.md)
2. [Standard Git configuration files](configuration.md)
1. Repository/local configuration (`.git/config`)
2. User/global configuration (`$HOME/.gitconfig` or `%HOME%\.gitconfig`)
3. Installation/system configuration (`etc/gitconfig`)
3. Enterprise system administrator defaults
4. Compiled default values

This model largely matches what Git itself supports, namely environment
variables that take precedence over Git configuration files.

The addition of the enterprise system administrator defaults enables those
administrators to configure many GCM settings using familiar MDM tooling, rather
than having to modify the Git installation configuration files.

### User Freedom

We believe the user should _always_ be at liberty to configure
Git and GCM exactly as they wish. By prefering environment variables and Git
configuration files over system admin values, these only act as _default values_
that can always be overriden by the user in the usual ways.

## Windows

Default setting values come from the Windows Registry, specifically the
following keys:

**32-bit Windows**

```text
HKEY_LOCAL_MACHINE\SOFTWARE\GitCredentialManager\Configuration
```

**64-bit Windows**

```text
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GitCredentialManager\Configuration
```

> GCM Core is a 32-bit executable on Windows. When running on a 64-bit
installation of Windows registry access is transparently redirected to the
`WOW6432Node` node.

By using the Windows Registry, system administrators can use Group Policy to
easily set defaults for GCM Core's settings.

The names and possible values of all settings under this key are the same as
those of the [Git configuration](configuration.md) settings.

The type of each registry key can be either `REG_SZ` (string) or `REG_DWORD`
(integer).


## macOS/Linux

Default configuration setting stores has not been implemented.
2 changes: 1 addition & 1 deletion docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

Git Credential Manager Core (GCM Core) can be configured using environment variables. **Environment variables take precedence over [configuration](configuration.md) options.**
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)**.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
// Licensed under the MIT license.
using System;
using Xunit;
using Microsoft.Git.CredentialManager.Interop;
using Microsoft.Git.CredentialManager.Interop.MacOS;
using Microsoft.Git.CredentialManager.Interop.MacOS.Native;

namespace Microsoft.Git.CredentialManager.Tests.Interop.MacOS
{
public class MacOSKeychainTests
{
private const string TestNamespace = "git-test";

[PlatformFact(Platforms.MacOS)]
[SkippablePlatformFact(Platforms.MacOS)]
public void MacOSKeychain_ReadWriteDelete()
{
var keychain = new MacOSKeychain(TestNamespace);
Expand All @@ -32,6 +34,19 @@ public void MacOSKeychain_ReadWriteDelete()
Assert.Equal(account, outCredential.Account);
Assert.Equal(password, outCredential.Password);
}
// There is an unknown issue that the keychain can sometimes get itself in where all API calls
// result in an errSecAuthFailed error. The only solution seems to be a machine restart, which
// isn't really possible in CI!
// The problem has plagued others who are calling the same Keychain APIs from C# such as the
// MSAL.NET team - they don't know either. It might have something to do with the code signing
// signature of the binary (our collective best theory).
// It's probably only diagnosable at this point by Apple, but we don't have a reliable way to
// reproduce the problem.
// For now we will just mark the test as "skipped" when we hit this problem.
catch (InteropException iex) when (iex.ErrorCode == SecurityFramework.ErrorSecAuthFailed)
{
AssertEx.Skip("macOS Keychain is in an invalid state (errSecAuthFailed)");
}
finally
{
// Ensure we clean up after ourselves even in case of 'get' failures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public CommandContext(string appPath)
gitPath,
FileSystem.GetCurrentDirectory()
);
Settings = new Settings(Environment, Git);
Settings = new WindowsSettings(Environment, Git, Trace);
CredentialStore = new WindowsCredentialManager(Settings.CredentialNamespace);
}
else if (PlatformUtils.IsMacOS())
Expand Down
6 changes: 6 additions & 0 deletions src/shared/Microsoft.Git.CredentialManager/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public static class Remote
}
}

public static class WindowsRegistry
{
public const string HKAppBasePath = @"SOFTWARE\GitCredentialManager";
public const string HKConfigurationPath = HKAppBasePath + @"\Configuration";
}

public static class HelpUrls
{
public const string GcmProjectUrl = "https://aka.ms/gcmcore";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

namespace Microsoft.Git.CredentialManager.Interop.Windows
{
/// <summary>
/// Reads settings from Git configuration, environment variables, and defaults from the Windows Registry.
/// </summary>
public class WindowsSettings : Settings
{
private readonly ITrace _trace;

public WindowsSettings(IEnvironment environment, IGit git, ITrace trace)
: base(environment, git)
{
EnsureArgument.NotNull(trace, nameof(trace));
_trace = trace;

PlatformUtils.EnsureWindows();
}

protected override bool TryGetExternalDefault(string section, string property, out string value)
{
value = null;

#if NETFRAMEWORK
// Check for machine (HKLM) registry keys that match the Git configuration name.
// These can be set by system administrators via Group Policy, so make useful defaults.
using (Win32.RegistryKey configKey = Win32.Registry.LocalMachine.OpenSubKey(Constants.WindowsRegistry.HKConfigurationPath))
{
if (configKey is null)
{
// No configuration key exists
return false;
}

string name = $"{section}.{property}";
object registryValue = configKey.GetValue(name);
if (registryValue is null)
{
// No property exists
return false;
}

value = registryValue.ToString();
_trace.WriteLine($"Default setting found in registry: {name}={value}");

return true;
}
#else
return base.TryGetExternalDefault(section, property, out value);
#endif
}
}
}
20 changes: 20 additions & 0 deletions src/shared/Microsoft.Git.CredentialManager/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,29 @@ public IEnumerable<string> GetSettingValues(string envarName, string section, st
{
yield return value;
}

// Check for an externally specified default value
if (TryGetExternalDefault(section, property, out string defaultValue))
{
yield return defaultValue;
}
}
}

/// <summary>
/// Try to get the default value for a configuration setting.
/// This may come from external policies or the Operating System.
/// </summary>
/// <param name="section">Configuration section name.</param>
/// <param name="property">Configuration property name.</param>
/// <param name="value">Value of the configuration setting, or null.</param>
/// <returns>True if a default setting has been set, false otherwise.</returns>
protected virtual bool TryGetExternalDefault(string section, string property, out string value)
{
value = null;
return false;
}

public Uri RemoteUri { get; set; }

public bool IsDebuggingEnabled => _environment.Variables.GetBooleanyOrDefault(KnownEnvars.GcmDebug, false);
Expand Down
17 changes: 17 additions & 0 deletions src/shared/TestInfrastructure/AssertEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Xunit;

namespace Microsoft.Git.CredentialManager.Tests
{
public static class AssertEx
{
/// <summary>
/// Requires the fact or theory be marked with the <see cref="SkippableFactAttribute"/>
/// or <see cref="SkippableTheoryAttribute"/>.
/// </summary>
/// <param name="reason">Reason the test has been skipped.</param>
public static void Skip(string reason)
{
Xunit.Skip.If(true, reason);
}
}
}
22 changes: 22 additions & 0 deletions src/shared/TestInfrastructure/PlatformAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@ public PlatformTheoryAttribute(Platforms platforms)
}
}

public class SkippablePlatformFactAttribute : SkippableFactAttribute
{
public SkippablePlatformFactAttribute(Platforms platforms)
{
Xunit.Skip.IfNot(
XunitHelpers.IsSupportedPlatform(platforms),
"Test not supported on this platform."
);
}
}

public class SkippablePlatformTheoryAttribute : SkippableTheoryAttribute
{
public SkippablePlatformTheoryAttribute(Platforms platforms)
{
Xunit.Skip.IfNot(
XunitHelpers.IsSupportedPlatform(platforms),
"Test not supported on this platform."
);
}
}

internal static class XunitHelpers
{
public static bool IsSupportedPlatform(Platforms platforms)
Expand Down
1 change: 1 addition & 0 deletions src/shared/TestInfrastructure/TestInfrastructure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit 91e4a84

Please sign in to comment.