Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/wam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Web Account Manager integration

## Running as administrator

The Windows broker ("WAM") makes heavy use of
[COM](https://docs.microsoft.com/en-us/windows/win32/com/the-component-object-model),
an IPC and RPC technology built-in to the Windows operating system. In order to
integrate with WAM, Git Credential Manager and the underlying
[Microsoft Authentication Library (MSAL)](https://aka.ms/msal-net)
must use COM interfaces and remote procedure calls (RPC).

When you run Git Credential Manager as an elevated process, such as when you run
a `git` command from an Administrator command-prompt or perform Git operations
from Visual Studio running as Administrator, some of the calls made between GCM
and WAM may fail due to differing process security levels.

If you have enabled using the broker and GCM detects it is running in an
elevated process, it will automatically attempt to modify the COM security
settings for the running process so that GCM and WAM can work together.

However, this automatic process security change is not guaranteed to succeed
depending on various external factors like registry or system-wide COM settings.
If GCM fails to modify the process COM security settings, a warning message is
printed and use of the broker is disabled for this invocation of GCM:

```text
warning: broker initialization failed
Failed to set COM process security to allow Windows broker from an elevated process (0x80010119).
See https://aka.ms/gcmcore-wamadmin for more information.
```

### Possible solutions

In order to fix the problem there are a few options:

1. Do not run Git or Git Credential Manager as elevated processes.
2. Disable the broker by setting the
[`GCM_MSAUTH_USEBROKER`](environment.md#gcm_msauth_usebroker)
environment variable or the
[`credential.msauthUseBroker`](configuration.md#credentialmsauthusebroker)
Git configuration setting to `false`.
18 changes: 17 additions & 1 deletion src/shared/Git-Credential-Manager/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Atlassian.Bitbucket;
using GitHub;
using Microsoft.AzureRepos;
using Microsoft.Git.CredentialManager.Authentication;

namespace Microsoft.Git.CredentialManager
{
Expand All @@ -18,6 +18,22 @@ public static void Main(string[] args)
using (var context = new CommandContext(appPath))
using (var app = new Application(context))
{
// Workaround for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2560
if (MicrosoftAuthentication.CanUseBroker(context))
{
try
{
MicrosoftAuthentication.InitializeBroker();
}
catch (Exception ex)
{
context.Streams.Error.WriteLine(
"warning: broker initialization failed{0}{1}",
Environment.NewLine, ex.Message
);
}
}

// Register all supported host providers at the normal priority.
// The generic provider should never win against a more specific one, so register it with low priority.
app.RegisterProvider(new AzureReposHostProvider(context), HostProviderPriority.Normal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
Expand Down Expand Up @@ -44,19 +43,74 @@ public class MicrosoftAuthentication : AuthenticationBase, IMicrosoftAuthenticat
"live", "liveconnect", "liveid",
};

#region Broker Initialization

public static bool IsBrokerInitialized { get; private set; }

public static void InitializeBroker()
{
if (IsBrokerInitialized)
{
return;
}

IsBrokerInitialized = true;

// Broker is only supported on Windows 10
if (!PlatformUtils.IsWindows10())
{
return;
}

// Nothing to do when not an elevated user
if (!PlatformUtils.IsElevatedUser())
{
return;
}

// Lower COM security so that MSAL can make the calls to WAM
int result = Interop.Windows.Native.Ole32.CoInitializeSecurity(
IntPtr.Zero,
-1,
IntPtr.Zero,
IntPtr.Zero,
Interop.Windows.Native.Ole32.RpcAuthnLevel.None,
Interop.Windows.Native.Ole32.RpcImpLevel.Impersonate,
IntPtr.Zero,
Interop.Windows.Native.Ole32.EoAuthnCap.None,
IntPtr.Zero
);

if (result != 0)
{
throw new Exception(
$"Failed to set COM process security to allow Windows broker from an elevated process (0x{result:x})." +
Environment.NewLine +
$"See {Constants.HelpUrls.GcmWamComSecurity} for more information.");
}
}

#endregion

public MicrosoftAuthentication(ICommandContext context)
: base(context) {}
: base(context) { }

#region IMicrosoftAuthentication

public async Task<IMicrosoftAuthenticationResult> GetTokenAsync(
string authority, string clientId, Uri redirectUri, string[] scopes, string userName)
{
// Check if we can and should use OS broker authentication
bool useBroker = CanUseBroker();
if (useBroker)
bool useBroker = false;
if (CanUseBroker(Context))
{
Context.Trace.WriteLine("OS broker is available and enabled.");
// Can only use the broker if it has been initialized
useBroker = IsBrokerInitialized;

if (IsBrokerInitialized)
Context.Trace.WriteLine("OS broker is available and enabled.");
else
Context.Trace.WriteLine("OS broker has not been initialized and cannot not be used.");
}

// Create the public client application for authentication
Expand Down Expand Up @@ -396,19 +450,19 @@ public HttpClient GetHttpClient()

#region Auth flow capability detection

private bool CanUseBroker()
public static bool CanUseBroker(ICommandContext context)
{
#if NETFRAMEWORK
// We only support the broker on Windows 10 and require an interactive session
if (!Context.SessionManager.IsDesktopSession || !PlatformUtils.IsWindows10())
if (!context.SessionManager.IsDesktopSession || !PlatformUtils.IsWindows10())
{
return false;
}

// Default to not using the OS broker
const bool defaultValue = false;

if (Context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker,
if (context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker,
Constants.GitConfiguration.Credential.SectionName,
Constants.GitConfiguration.Credential.MsAuthUseBroker,
out string valueStr))
Expand Down
1 change: 1 addition & 0 deletions src/shared/Microsoft.Git.CredentialManager/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public static class HelpUrls
public const string GcmHttpProxyGuide = "https://aka.ms/gcmcore-httpproxy";
public const string GcmTlsVerification = "https://aka.ms/gcmcore-tlsverify";
public const string GcmLinuxCredStores = "https://aka.ms/gcmcore-linuxcredstores";
public const string GcmWamComSecurity = "https://aka.ms/gcmcore-wamadmin";
}

private static Version _gcmVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ public static class Unistd

[DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int getppid();

[DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int geteuid();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.InteropServices;

namespace Microsoft.Git.CredentialManager.Interop.Windows.Native
{
public static class Ole32
{
private const string LibraryName = "ole32.dll";

public const uint RPC_E_TOO_LATE = 0x80010119;

[DllImport(LibraryName)]
public static extern int CoInitializeSecurity(
IntPtr pVoid,
int cAuthSvc,
IntPtr asAuthSvc,
IntPtr pReserved1,
RpcAuthnLevel level,
RpcImpLevel impers,
IntPtr pAuthList,
EoAuthnCap dwCapabilities,
IntPtr pReserved3);

public enum RpcAuthnLevel
{
Default = 0,
None = 1,
Connect = 2,
Call = 3,
Pkt = 4,
PktIntegrity = 5,
PktPrivacy = 6
}

public enum RpcImpLevel
{
Default = 0,
Anonymous = 1,
Identify = 2,
Impersonate = 3,
Delegate = 4
}

public enum EoAuthnCap
{
None = 0x00,
MutualAuth = 0x01,
StaticCloaking = 0x20,
DynamicCloaking = 0x40,
AnyAuthority = 0x80,
MakeFullSIC = 0x100,
Default = 0x800,
SecureRefs = 0x02,
AccessControl = 0x04,
AppID = 0x08,
Dynamic = 0x10,
RequireFullSIC = 0x200,
AutoImpersonate = 0x400,
NoCustomMarshal = 0x2000,
DisableAAA = 0x1000
}
}
}
19 changes: 19 additions & 0 deletions src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System;
using System.Runtime.InteropServices;
using Microsoft.Git.CredentialManager.Interop.Posix.Native;

namespace Microsoft.Git.CredentialManager
{
Expand Down Expand Up @@ -136,6 +137,24 @@ public static void EnsurePosix()
}
}

public static bool IsElevatedUser()
{
if (IsWindows())
{
#if NETFRAMEWORK
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
#endif
}
else if (IsPosix())
{
return Unistd.geteuid() == 0;
}

return false;
}

#region Platform information helper methods

private static string GetOSType()
Expand Down