git-credential-manager icon indicating copy to clipboard operation
git-credential-manager copied to clipboard

Unable to use Windows Authentication in recent builds of GCM from headless Windows environment

Open yaakov-h opened this issue 2 years ago • 2 comments

Which version of GCM are you using?

2.0.696+4365b917da

on Windows Server 2022 v21H2 with git version 2.36.0.windows.1

Which Git host provider are you trying to connect to?

  • [ ] Azure DevOps
  • [X] Azure DevOps Server (TFS/on-prem)
  • [ ] GitHub
  • [ ] GitHub Enterprise
  • [ ] Bitbucket
  • [ ] Other - please describe

Can you access the remote repository directly in the browser using the remote URL?

From a terminal, run git remote -v to see your remote URL.

  • [X] Yes
  • [ ] No, I get a permission error
  • [ ] No, for a different reason - please describe

Expected behavior

I am authenticated and my Git operation completes successfully.

Actual behavior

From a user-interactive Remote Desktop session, authentication succeeds.

From an automated integration test in our CI/CD pipeline, which runs a user-interactive desktop session, authentication fails when talking to the Windows Credential Manager API, then falls back to prompting the user for credentials on the command-line.

Additional Information Previously this succeeded, it worked with GCM v2.0.498 and previous versions.

I suspect that this broke in #464 (GCM v2.0.567 and newer) by introducing an explicit upfront check for wincredman persistence (WindowsCredentialManager.CanPersist()).

What is also interesting is that on these machines we have disabled interactive credentials (credential.interactive=false as shown below), yet nevertheless we get dropped back into a prompt for the Username, only for that to fail since there is no interactive stdin.

The sessions that this fails in are not the documented SSH and similar scenarios where wincredman is expected to fail, but automated interactive logins. These machines have HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon and related Registry items set to automatically enter an interactive session on boot.

Logs

git config --system -l:

diff.astextplain.textconv=astextplain
http.sslbackend=openssl
http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
core.autocrlf=true
core.fscache=true
core.symlinks=false
pull.rebase=false
credential.helper=manager-core
credential.interactive=false
credential.https://dev.azure.com.usehttppath=true
init.defaultbranch=master

git config --global -l:

credential.https://devops-test.wtg.zone.provider=generic
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true

Failure log (from CI): auth-interactive-failure.txt

Success log (from RDP): auth-interactive-success.txt

yaakov-h avatar Aug 03 '22 07:08 yaakov-h

Hello @yaakov-h

From your (successful) logs I can see that GCM is detecting, and Git is directly using, Windows Integrated Authentication for auth (Negotiate; NTLM or Kerberos) and doesn't really need to be using GCM at all.

One workaround you can try here is simply disabling GCM completely by setting credential.helper to the empty string "", or unsetting all credential.helper values in Git config.

10:18:33.590756 run-command.c:654       trace: run_command: git-credential-manager-core get
...
10:18:37.293870 ...GitCommandBase.cs:48 trace: [ExecuteAsync]   protocol=https
10:18:37.293870 ...GitCommandBase.cs:48 trace: [ExecuteAsync]   host=devops-test.wtg.zone
...
10:18:37.340608 ...oviderRegistry.cs:99 trace: [GetProviderAsync] Host provider override was set id='generic'
10:18:37.340608 ...GitCommandBase.cs:50 trace: [ExecuteAsync] Host provider 'Generic' was selected.
10:18:37.340608 ...\HostProvider.cs:126 trace: [GetCredentialAsync] Looking for existing credential in store with service=https://devops-test.wtg.zone account=...
10:18:37.465568 ...\HostProvider.cs:131 trace: [GetCredentialAsync] No existing credentials found.
10:18:37.465568 ...\HostProvider.cs:134 trace: [GetCredentialAsync] Creating new credential...
10:18:37.528051 ...icHostProvider.cs:58 trace: [GenerateCredentialAsync] Checking host 'https://devops-test.wtg.zone/' for Windows Integrated Authentication...
10:18:37.528051 ...Authentication.cs:34 trace: [GetIsSupportedAsync] HTTP: HEAD https://devops-test.wtg.zone/
...
10:18:38.246789 ...Authentication.cs:37 trace: [GetIsSupportedAsync] HTTP: Response code ignored.
10:18:38.246789 ...Authentication.cs:39 trace: [GetIsSupportedAsync] Inspecting WWW-Authenticate headers...
10:18:38.246789 ...Authentication.cs:44 trace: [GetIsSupportedAsync] Found WWW-Authenticate header for Negotiate
10:18:38.246789 ...Authentication.cs:49 trace: [GetIsSupportedAsync] Found WWW-Authenticate header for NTLM
10:18:38.246789 ...icHostProvider.cs:67 trace: [GenerateCredentialAsync] Host supports WIA - generating empty credential...
10:18:38.246789 ...\HostProvider.cs:136 trace: [GetCredentialAsync] Credential created.

I suspect that this broke in https://github.com/GitCredentialManager/git-credential-manager/pull/464 (GCM v2.0.567 and newer) by introducing an explicit upfront check for wincredman persistence (WindowsCredentialManager.CanPersist()).

You are correct here. We added an explicit check to ensure we could read/write credentials correctly. However, since you're not actually storing any real credentials..

10:18:38.793742 ...GitCommandBase.cs:33 trace: [ExecuteAsync] Start 'store' command...
...
10:18:38.809289 ...GitCommandBase.cs:48 trace: [ExecuteAsync]   protocol=https
10:18:38.809289 ...GitCommandBase.cs:48 trace: [ExecuteAsync]   host=devops-test.wtg.zone
10:18:38.809289 ...GitCommandBase.cs:48 trace: [ExecuteAsync]   username=
10:18:38.809289 ...GitCommandBase.cs:48 trace: [ExecuteAsync]   password=********
...
10:18:38.840592 ...\HostProvider.cs:155 trace: [StoreCredentialAsync] Not storing empty credential.
10:18:38.840592 ...GitCommandBase.cs:54 trace: [ExecuteAsync] End 'store' command...

Using GCM from automated environments like CI is not advised as GCM was designed primarily for use by an interactive user. I'll have a think about what we can do to potentially improve this situation, but in general try to avoid using GCM in CI.

mjcheetham avatar Aug 08 '22 14:08 mjcheetham

If I disable the credential helper / GCM entirely with git config --system --unset credential.helper, Git dumps me into a credentials prompt:

>git clone https://devops-test.wtg.zone/DefaultCollection/TestProject/_git/TestProject
13:52:57.581000 exec-cmd.c:237          trace: resolved executable dir: C:/Program Files/Git/mingw64/bin
13:52:57.581000 git.c:459               trace: built-in: git clone https://devops-test.wtg.zone/DefaultCollection/TestProject/_git/TestProject
Cloning into 'TestProject'...
13:52:57.597844 run-command.c:654       trace: run_command: git remote-https origin https://devops-test.wtg.zone/DefaultCollection/TestProject/_git/TestProject
13:52:57.612868 exec-cmd.c:237          trace: resolved executable dir: C:/Program Files/Git/mingw64/libexec/git-core
13:52:57.612868 git.c:748               trace: exec: git-remote-https origin https://devops-test.wtg.zone/DefaultCollection/TestProject/_git/TestProject
13:52:57.612868 run-command.c:654       trace: run_command: git-remote-https origin https://devops-test.wtg.zone/DefaultCollection/TestProject/_git/TestProject
13:52:57.629149 exec-cmd.c:237          trace: resolved executable dir: C:/Program Files/Git/mingw64/libexec/git-core
13:52:57.707531 run-command.c:654       trace: run_command: bash -c 'cat >/dev/tty && read -r line </dev/tty && echo "$line"'
Username for 'https://devops-test.wtg.zone':

If I feed it blanks then it succeeds anyway, but I'm not confident in using this in automation where there is no stdin stream.

I'm going to try git config --system credential.credentialStore dpapi as an alternative workaround, it seems to work interactively at least.

yaakov-h avatar Aug 11 '22 03:08 yaakov-h

Closing as the dpapi store works in headless mode.

mjcheetham avatar Sep 22 '22 18:09 mjcheetham