Skip to content

Commit 7b60eee

Browse files
author
Matthew John Cheetham
authored
Add control over use of unsafe remotes (git-ecosystem#1721)
Today, all the custom host providers (Azure Repos, Bitbucket, GitHub, GitLab) block the use of HTTP (unencrypted) remote URLs and error out. Only the generic host provider permits HTTP remotes. From git-ecosystem#1694, we learn that a common use case for self/corporate hosted Git servers is to use HTTP remotes. Even if this is **_not recommended_**, GCM should not outright block these. Instead, we now add an option, `GCM_ALLOW_UNSAFE_REMOTES` or `credential.allowUnsafeRemotes`, for the user to explicitly set to allow the use of these unsafe remotes. For the generic host provider we only print a warning when using HTTP remotes to reduce the churn for existing users who rely on GCM for HTTP remotes.
2 parents 2d10c92 + fc067e8 commit 7b60eee

File tree

11 files changed

+127
-24
lines changed

11 files changed

+127
-24
lines changed

docs/configuration.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,24 @@ Defaults to false (use hardware acceleration where available).
255255

256256
---
257257

258+
### credential.allowUnsafeRemotes
259+
260+
Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP
261+
URLs. This setting is not recommended for general use and should only be used
262+
when necessary.
263+
264+
Defaults false (disallow unsafe remote URLs).
265+
266+
#### Example
267+
268+
```shell
269+
git config --global credential.allowUnsafeRemotes true
270+
```
271+
272+
**Also see: [GCM_ALLOW_UNSAFE_REMOTES][gcm-allow-unsafe-remotes]**
273+
274+
---
275+
258276
### credential.autoDetectTimeout
259277

260278
Set the maximum length of time, in milliseconds, that GCM should wait for a
@@ -1024,6 +1042,7 @@ Defaults to disabled.
10241042
[envars]: environment.md
10251043
[freedesktop-ss]: https://specifications.freedesktop.org/secret-service-spec/
10261044
[gcm-allow-windowsauth]: environment.md#GCM_ALLOW_WINDOWSAUTH
1045+
[gcm-allow-unsafe-remotes]: environment.md#GCM_ALLOW_UNSAFE_REMOTES
10271046
[gcm-authority]: environment.md#GCM_AUTHORITY-deprecated
10281047
[gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT
10291048
[gcm-azrepos-credentialtype]: environment.md#GCM_AZREPOS_CREDENTIALTYPE

docs/environment.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,32 @@ Defaults to false (use hardware acceleration where available).
302302

303303
---
304304

305+
### GCM_ALLOW_UNSAFE_REMOTES
306+
307+
Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP
308+
URLs. This setting is not recommended for general use and should only be used
309+
when necessary.
310+
311+
Defaults false (disallow unsafe remote URLs).
312+
313+
#### Example
314+
315+
##### Windows
316+
317+
```batch
318+
SET GCM_ALLOW_UNSAFE_REMOTES=true
319+
```
320+
321+
##### macOS/Linux
322+
323+
```bash
324+
export GCM_ALLOW_UNSAFE_REMOTES=true
325+
```
326+
327+
**Also see: [credential.allowUnsafeRemotes][credential-allowunsaferemotes]**
328+
329+
---
330+
305331
### GCM_AUTODETECT_TIMEOUT
306332

307333
Set the maximum length of time, in milliseconds, that GCM should wait for a
@@ -1153,7 +1179,8 @@ Defaults to disabled.
11531179
[autodetect]: autodetect.md
11541180
[azure-access-tokens]: azrepos-users-and-tokens.md
11551181
[configuration]: configuration.md
1156-
[credential-allowwindowsauth]: environment.md#credentialallowWindowsAuth
1182+
[credential-allowwindowsauth]: configuration.md#credentialallowwindowsauth
1183+
[credential-allowunsaferemotes]: configuration.md#credentialallowunsaferemotes
11571184
[credential-authority]: configuration.md#credentialauthority-deprecated
11581185
[credential-autodetecttimeout]: configuration.md#credentialautodetecttimeout
11591186
[credential-azrepos-credential-type]: configuration.md#credentialazreposcredentialtype

docs/netconfig.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,22 @@ network traffic inspection tool such as [Telerik Fiddler][telerik-fiddler]. If
191191
you are using such tools please consult their documentation for trusting the
192192
proxy root certificates.
193193

194+
---
195+
196+
## Unsafe Remote URLs
197+
198+
If you are using a remote URL that is not considered safe, such as unencrypted
199+
HTTP (remote URLs that start with `http://`), host providers may prevent you
200+
from authenticating with your credentials.
201+
202+
In this case, you should consider using a HTTPS (starting with `https://`)
203+
remote URL to ensure your credentials are transmitted securely.
204+
205+
If you accept the risks associated with using an unsafe remote URL, you can
206+
configure GCM to allow the use of unsafe remote URLS by setting the environment
207+
variable [`GCM_ALLOW_UNSAFE_REMOTES`][unsafe-envar], or by using the Git
208+
configuration option [`credential.allowUnsafeRemotes`][unsafe-config] to `true`.
209+
194210
[environment]: environment.md
195211
[configuration]: configuration.md
196212
[git-http-proxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy
@@ -212,3 +228,5 @@ proxy root certificates.
212228
[git-ssl-no-verify]: https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_networking
213229
[git-http-ssl-verify]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslVerify
214230
[telerik-fiddler]: https://www.telerik.com/fiddler
231+
[unsafe-envar]: environment.md#gcm_allow_unsafe_remotes
232+
[unsafe-config]: configuration.md#credentialallowunsaferemotes

src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ public bool IsSupported(InputArguments input)
5555
return false;
5656
}
5757

58-
// We do not support unencrypted HTTP communications to Bitbucket,
59-
// but we report `true` here for HTTP so that we can show a helpful
58+
// We do not recommend unencrypted HTTP communications to Bitbucket, but it is possible.
59+
// Therefore, we report `true` here for HTTP so that we can show a helpful
6060
// error message for the user in `GetCredentialAsync`.
6161
return (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") ||
6262
StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https")) &&
@@ -81,11 +81,14 @@ public bool IsSupported(HttpResponseMessage response)
8181
public async Task<ICredential> GetCredentialAsync(InputArguments input)
8282
{
8383
// We should not allow unencrypted communication and should inform the user
84-
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")
85-
&& BitbucketHelper.IsBitbucketOrg(input))
84+
if (!_context.Settings.AllowUnsafeRemotes &&
85+
StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") &&
86+
BitbucketHelper.IsBitbucketOrg(input))
8687
{
8788
throw new Trace2Exception(_context.Trace2,
88-
"Unencrypted HTTP is not supported for Bitbucket.org. Ensure the repository remote URL is using HTTPS.");
89+
"Unencrypted HTTP is not recommended for Bitbucket.org. " +
90+
"Ensure the repository remote URL is using HTTPS " +
91+
$"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes.");
8992
}
9093

9194
var authModes = await GetSupportedAuthenticationModesAsync(input);

src/shared/Core/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public static class EnvironmentVariables
119119
public const string OAuthDefaultUserName = "GCM_OAUTH_DEFAULT_USERNAME";
120120
public const string GcmDevUseLegacyUiHelpers = "GCM_DEV_USELEGACYUIHELPERS";
121121
public const string GcmGuiSoftwareRendering = "GCM_GUI_SOFTWARE_RENDERING";
122+
public const string GcmAllowUnsafeRemotes = "GCM_ALLOW_UNSAFE_REMOTES";
122123
}
123124

124125
public static class Http
@@ -163,6 +164,7 @@ public static class Credential
163164
public const string MsAuthUseDefaultAccount = "msauthUseDefaultAccount";
164165
public const string GuiSoftwareRendering = "guiSoftwareRendering";
165166
public const string GpgPassStorePath = "gpgPassStorePath";
167+
public const string AllowUnsafeRemotes = "allowUnsafeRemotes";
166168

167169
public const string OAuthAuthenticationModes = "oauthAuthModes";
168170
public const string OAuthClientId = "oauthClientId";
@@ -226,6 +228,7 @@ public static class HelpUrls
226228
public const string GcmAutoDetect = "https://aka.ms/gcm/autodetect";
227229
public const string GcmDefaultAccount = "https://aka.ms/gcm/defaultaccount";
228230
public const string GcmMultipleUsers = "https://aka.ms/gcm/multipleusers";
231+
public const string GcmUnsafeRemotes = "https://aka.ms/gcm/unsaferemotes";
229232
}
230233

231234
private static Version _gcmVersion;

src/shared/Core/GenericHostProvider.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
5454
{
5555
ThrowIfDisposed();
5656

57+
// We only want to *warn* about HTTP remotes for the generic provider because it supports all protocols
58+
// and, historically, we never blocked HTTP remotes in this provider.
59+
// The user can always set the 'GCM_ALLOW_UNSAFE' setting to silence the warning.
60+
if (!Context.Settings.AllowUnsafeRemotes &&
61+
StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
62+
{
63+
Context.Streams.Error.WriteLine(
64+
"warning: use of unencrypted HTTP remote URLs is not recommended; " +
65+
$"see {Constants.HelpUrls.GcmUnsafeRemotes} for more information.");
66+
}
67+
5768
Uri uri = input.GetRemoteUri();
5869

5970
// Determine the if the host supports Windows Integration Authentication (WIA) or OAuth

src/shared/Core/Settings.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ public interface ISettings : IDisposable
189189
/// </summary>
190190
bool UseSoftwareRendering { get; }
191191

192+
/// <summary>
193+
/// Permit the use of unsafe remotes URLs such as regular HTTP.
194+
/// </summary>
195+
bool AllowUnsafeRemotes { get; }
196+
192197
/// <summary>
193198
/// Get TRACE2 settings.
194199
/// </summary>
@@ -580,6 +585,12 @@ public bool UseSoftwareRendering
580585
}
581586
}
582587

588+
public bool AllowUnsafeRemotes =>
589+
TryGetSetting(KnownEnvars.GcmAllowUnsafeRemotes,
590+
KnownGitCfg.Credential.SectionName,
591+
KnownGitCfg.Credential.AllowUnsafeRemotes,
592+
out string str) && str.ToBooleanyOrDefault(false);
593+
583594
public Trace2Settings GetTrace2Settings()
584595
{
585596
var settings = new Trace2Settings();

src/shared/GitHub/GitHubHostProvider.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,13 @@ public virtual Task EraseCredentialAsync(InputArguments input)
285285
ThrowIfDisposed();
286286

287287
// We should not allow unencrypted communication and should inform the user
288-
if (StringComparer.OrdinalIgnoreCase.Equals(remoteUri.Scheme, "http"))
288+
if (!_context.Settings.AllowUnsafeRemotes &&
289+
StringComparer.OrdinalIgnoreCase.Equals(remoteUri.Scheme, "http"))
289290
{
290291
throw new Trace2Exception(_context.Trace2,
291-
"Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS.");
292+
"Unencrypted HTTP is not recommended for GitHub. " +
293+
"Ensure the repository remote URL is using HTTPS " +
294+
$"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes.");
292295
}
293296

294297
string service = GetServiceName(remoteUri);

src/shared/GitLab/GitLabHostProvider.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,13 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
9595
ThrowIfDisposed();
9696

9797
// We should not allow unencrypted communication and should inform the user
98-
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
98+
if (!Context.Settings.AllowUnsafeRemotes &&
99+
StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
99100
{
100101
throw new Trace2Exception(Context.Trace2,
101-
"Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS.");
102+
"Unencrypted HTTP is not recommended for GitLab. " +
103+
"Ensure the repository remote URL is using HTTPS " +
104+
$"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes.");
102105
}
103106

104107
Uri remoteUri = input.GetRemoteUri();

src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public bool IsSupported(InputArguments input)
5959
return false;
6060
}
6161

62-
// We do not support unencrypted HTTP communications to Azure Repos,
62+
// We do not recommend unencrypted HTTP communications to Azure Repos,
6363
// but we report `true` here for HTTP so that we can show a helpful
6464
// error message for the user in `CreateCredentialAsync`.
6565
return input.TryGetHostAndPort(out string hostName, out _)
@@ -208,16 +208,22 @@ protected override void ReleaseManagedResources()
208208
base.ReleaseManagedResources();
209209
}
210210

211-
private async Task<ICredential> GeneratePersonalAccessTokenAsync(InputArguments input)
211+
private void ThrowIfUnsafeRemote(InputArguments input)
212212
{
213-
ThrowIfDisposed();
214-
215-
// We should not allow unencrypted communication and should inform the user
216-
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
213+
if (!_context.Settings.AllowUnsafeRemotes &&
214+
StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
217215
{
218216
throw new Trace2Exception(_context.Trace2,
219-
"Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS.");
217+
"Unencrypted HTTP is not recommended for Azure Repos. " +
218+
"Ensure the repository remote URL is using HTTPS " +
219+
$"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes.");
220220
}
221+
}
222+
223+
private async Task<ICredential> GeneratePersonalAccessTokenAsync(InputArguments input)
224+
{
225+
ThrowIfDisposed();
226+
ThrowIfUnsafeRemote(input);
221227

222228
Uri remoteUserUri = input.GetRemoteUri(includeUser: true);
223229
Uri orgUri = UriHelpers.CreateOrganizationUri(remoteUserUri, out _);
@@ -257,16 +263,11 @@ private async Task<ICredential> GeneratePersonalAccessTokenAsync(InputArguments
257263

258264
private async Task<IMicrosoftAuthenticationResult> GetAzureAccessTokenAsync(InputArguments input)
259265
{
266+
ThrowIfUnsafeRemote(input);
267+
260268
Uri remoteWithUserUri = input.GetRemoteUri(includeUser: true);
261269
string userName = input.UserName;
262270

263-
// We should not allow unencrypted communication and should inform the user
264-
if (StringComparer.OrdinalIgnoreCase.Equals(remoteWithUserUri.Scheme, "http"))
265-
{
266-
throw new Trace2Exception(_context.Trace2,
267-
"Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS.");
268-
}
269-
270271
Uri orgUri = UriHelpers.CreateOrganizationUri(remoteWithUserUri, out string orgName);
271272

272273
_context.Trace.WriteLine($"Determining Microsoft Authentication authority for Azure DevOps organization '{orgName}'...");

0 commit comments

Comments
 (0)