Skip to content

Commit

Permalink
Add control over use of unsafe remotes (git-ecosystem#1721)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Matthew John Cheetham authored Oct 7, 2024
2 parents 2d10c92 + fc067e8 commit 7b60eee
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 24 deletions.
19 changes: 19 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,24 @@ Defaults to false (use hardware acceleration where available).

---

### credential.allowUnsafeRemotes

Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP
URLs. This setting is not recommended for general use and should only be used
when necessary.

Defaults false (disallow unsafe remote URLs).

#### Example

```shell
git config --global credential.allowUnsafeRemotes true
```

**Also see: [GCM_ALLOW_UNSAFE_REMOTES][gcm-allow-unsafe-remotes]**

---

### credential.autoDetectTimeout

Set the maximum length of time, in milliseconds, that GCM should wait for a
Expand Down Expand Up @@ -1024,6 +1042,7 @@ Defaults to disabled.
[envars]: environment.md
[freedesktop-ss]: https://specifications.freedesktop.org/secret-service-spec/
[gcm-allow-windowsauth]: environment.md#GCM_ALLOW_WINDOWSAUTH
[gcm-allow-unsafe-remotes]: environment.md#GCM_ALLOW_UNSAFE_REMOTES
[gcm-authority]: environment.md#GCM_AUTHORITY-deprecated
[gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT
[gcm-azrepos-credentialtype]: environment.md#GCM_AZREPOS_CREDENTIALTYPE
Expand Down
29 changes: 28 additions & 1 deletion docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,32 @@ Defaults to false (use hardware acceleration where available).

---

### GCM_ALLOW_UNSAFE_REMOTES

Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP
URLs. This setting is not recommended for general use and should only be used
when necessary.

Defaults false (disallow unsafe remote URLs).

#### Example

##### Windows

```batch
SET GCM_ALLOW_UNSAFE_REMOTES=true
```

##### macOS/Linux

```bash
export GCM_ALLOW_UNSAFE_REMOTES=true
```

**Also see: [credential.allowUnsafeRemotes][credential-allowunsaferemotes]**

---

### GCM_AUTODETECT_TIMEOUT

Set the maximum length of time, in milliseconds, that GCM should wait for a
Expand Down Expand Up @@ -1153,7 +1179,8 @@ Defaults to disabled.
[autodetect]: autodetect.md
[azure-access-tokens]: azrepos-users-and-tokens.md
[configuration]: configuration.md
[credential-allowwindowsauth]: environment.md#credentialallowWindowsAuth
[credential-allowwindowsauth]: configuration.md#credentialallowwindowsauth
[credential-allowunsaferemotes]: configuration.md#credentialallowunsaferemotes
[credential-authority]: configuration.md#credentialauthority-deprecated
[credential-autodetecttimeout]: configuration.md#credentialautodetecttimeout
[credential-azrepos-credential-type]: configuration.md#credentialazreposcredentialtype
Expand Down
18 changes: 18 additions & 0 deletions docs/netconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,22 @@ network traffic inspection tool such as [Telerik Fiddler][telerik-fiddler]. If
you are using such tools please consult their documentation for trusting the
proxy root certificates.

---

## Unsafe Remote URLs

If you are using a remote URL that is not considered safe, such as unencrypted
HTTP (remote URLs that start with `http://`), host providers may prevent you
from authenticating with your credentials.

In this case, you should consider using a HTTPS (starting with `https://`)
remote URL to ensure your credentials are transmitted securely.

If you accept the risks associated with using an unsafe remote URL, you can
configure GCM to allow the use of unsafe remote URLS by setting the environment
variable [`GCM_ALLOW_UNSAFE_REMOTES`][unsafe-envar], or by using the Git
configuration option [`credential.allowUnsafeRemotes`][unsafe-config] to `true`.

[environment]: environment.md
[configuration]: configuration.md
[git-http-proxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy
Expand All @@ -212,3 +228,5 @@ proxy root certificates.
[git-ssl-no-verify]: https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_networking
[git-http-ssl-verify]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslVerify
[telerik-fiddler]: https://www.telerik.com/fiddler
[unsafe-envar]: environment.md#gcm_allow_unsafe_remotes
[unsafe-config]: configuration.md#credentialallowunsaferemotes
13 changes: 8 additions & 5 deletions src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public bool IsSupported(InputArguments input)
return false;
}

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

var authModes = await GetSupportedAuthenticationModesAsync(input);
Expand Down
3 changes: 3 additions & 0 deletions src/shared/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public static class EnvironmentVariables
public const string OAuthDefaultUserName = "GCM_OAUTH_DEFAULT_USERNAME";
public const string GcmDevUseLegacyUiHelpers = "GCM_DEV_USELEGACYUIHELPERS";
public const string GcmGuiSoftwareRendering = "GCM_GUI_SOFTWARE_RENDERING";
public const string GcmAllowUnsafeRemotes = "GCM_ALLOW_UNSAFE_REMOTES";
}

public static class Http
Expand Down Expand Up @@ -163,6 +164,7 @@ public static class Credential
public const string MsAuthUseDefaultAccount = "msauthUseDefaultAccount";
public const string GuiSoftwareRendering = "guiSoftwareRendering";
public const string GpgPassStorePath = "gpgPassStorePath";
public const string AllowUnsafeRemotes = "allowUnsafeRemotes";

public const string OAuthAuthenticationModes = "oauthAuthModes";
public const string OAuthClientId = "oauthClientId";
Expand Down Expand Up @@ -226,6 +228,7 @@ public static class HelpUrls
public const string GcmAutoDetect = "https://aka.ms/gcm/autodetect";
public const string GcmDefaultAccount = "https://aka.ms/gcm/defaultaccount";
public const string GcmMultipleUsers = "https://aka.ms/gcm/multipleusers";
public const string GcmUnsafeRemotes = "https://aka.ms/gcm/unsaferemotes";
}

private static Version _gcmVersion;
Expand Down
11 changes: 11 additions & 0 deletions src/shared/Core/GenericHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
{
ThrowIfDisposed();

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

Uri uri = input.GetRemoteUri();

// Determine the if the host supports Windows Integration Authentication (WIA) or OAuth
Expand Down
11 changes: 11 additions & 0 deletions src/shared/Core/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ public interface ISettings : IDisposable
/// </summary>
bool UseSoftwareRendering { get; }

/// <summary>
/// Permit the use of unsafe remotes URLs such as regular HTTP.
/// </summary>
bool AllowUnsafeRemotes { get; }

/// <summary>
/// Get TRACE2 settings.
/// </summary>
Expand Down Expand Up @@ -580,6 +585,12 @@ public bool UseSoftwareRendering
}
}

public bool AllowUnsafeRemotes =>
TryGetSetting(KnownEnvars.GcmAllowUnsafeRemotes,
KnownGitCfg.Credential.SectionName,
KnownGitCfg.Credential.AllowUnsafeRemotes,
out string str) && str.ToBooleanyOrDefault(false);

public Trace2Settings GetTrace2Settings()
{
var settings = new Trace2Settings();
Expand Down
7 changes: 5 additions & 2 deletions src/shared/GitHub/GitHubHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,13 @@ public virtual Task EraseCredentialAsync(InputArguments input)
ThrowIfDisposed();

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

string service = GetServiceName(remoteUri);
Expand Down
7 changes: 5 additions & 2 deletions src/shared/GitLab/GitLabHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
ThrowIfDisposed();

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

Uri remoteUri = input.GetRemoteUri();
Expand Down
29 changes: 15 additions & 14 deletions src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public bool IsSupported(InputArguments input)
return false;
}

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

private async Task<ICredential> GeneratePersonalAccessTokenAsync(InputArguments input)
private void ThrowIfUnsafeRemote(InputArguments input)
{
ThrowIfDisposed();

// We should not allow unencrypted communication and should inform the user
if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
if (!_context.Settings.AllowUnsafeRemotes &&
StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
{
throw new Trace2Exception(_context.Trace2,
"Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS.");
"Unencrypted HTTP is not recommended for Azure Repos. " +
"Ensure the repository remote URL is using HTTPS " +
$"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes.");
}
}

private async Task<ICredential> GeneratePersonalAccessTokenAsync(InputArguments input)
{
ThrowIfDisposed();
ThrowIfUnsafeRemote(input);

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

private async Task<IMicrosoftAuthenticationResult> GetAzureAccessTokenAsync(InputArguments input)
{
ThrowIfUnsafeRemote(input);

Uri remoteWithUserUri = input.GetRemoteUri(includeUser: true);
string userName = input.UserName;

// We should not allow unencrypted communication and should inform the user
if (StringComparer.OrdinalIgnoreCase.Equals(remoteWithUserUri.Scheme, "http"))
{
throw new Trace2Exception(_context.Trace2,
"Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS.");
}

Uri orgUri = UriHelpers.CreateOrganizationUri(remoteWithUserUri, out string orgName);

_context.Trace.WriteLine($"Determining Microsoft Authentication authority for Azure DevOps organization '{orgName}'...");
Expand Down
4 changes: 4 additions & 0 deletions src/shared/TestInfrastructure/Objects/TestSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class TestSettings : ISettings

public bool UseMsAuthDefaultAccount { get; set; }

public bool AllowUnsafeRemotes { get; set; } = false;

public Trace2Settings GetTrace2Settings()
{
return new Trace2Settings()
Expand Down Expand Up @@ -189,6 +191,8 @@ ProxyConfiguration ISettings.GetProxyConfiguration()

bool ISettings.UseSoftwareRendering => false;

bool ISettings.AllowUnsafeRemotes => AllowUnsafeRemotes;

#endregion

#region IDisposable
Expand Down

0 comments on commit 7b60eee

Please sign in to comment.