git-credential-manager
git-credential-manager copied to clipboard
Unable to use Windows Authentication in recent builds of GCM from headless Windows environment
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
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.
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.
Closing as the dpapi
store works in headless mode.