Add Proxy Support for self-hosted runner. (#206)

This commit is contained in:
Tingluo Huang
2019-12-09 15:15:54 -05:00
committed by GitHub
parent 56e18f3606
commit d81a7656a4
24 changed files with 743 additions and 682 deletions

View File

@@ -9,9 +9,6 @@ varCheckList=(
'GRADLE_HOME'
'NVM_BIN'
'NVM_PATH'
'VSTS_HTTP_PROXY'
'VSTS_HTTP_PROXY_USERNAME'
'VSTS_HTTP_PROXY_PASSWORD'
'LD_LIBRARY_PATH'
'PERL5LIB'
)

View File

@@ -29,9 +29,6 @@ namespace GitHub.Runner.Common
Service,
CredentialStore,
Certificates,
Proxy,
ProxyCredentials,
ProxyBypass,
Options,
}
@@ -97,8 +94,6 @@ namespace GitHub.Runner.Common
public static readonly string Auth = "auth";
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
public static readonly string Pool = "pool";
public static readonly string ProxyUrl = "proxyurl";
public static readonly string ProxyUserName = "proxyusername";
public static readonly string SslCACert = "sslcacert";
public static readonly string SslClientCert = "sslclientcert";
public static readonly string SslClientCertKey = "sslclientcertkey";
@@ -111,14 +106,12 @@ namespace GitHub.Runner.Common
// Secret args. Must be added to the "Secrets" getter as well.
public static readonly string Password = "password";
public static readonly string ProxyPassword = "proxypassword";
public static readonly string SslClientCertPassword = "sslclientcertpassword";
public static readonly string Token = "token";
public static readonly string WindowsLogonPassword = "windowslogonpassword";
public static string[] Secrets => new[]
{
Password,
ProxyPassword,
SslClientCertPassword,
Token,
WindowsLogonPassword,

View File

@@ -26,6 +26,7 @@ namespace GitHub.Runner.Common
ShutdownReason RunnerShutdownReason { get; }
ISecretMasker SecretMasker { get; }
ProductInfoHeaderValue UserAgent { get; }
RunnerWebProxy WebProxy { get; }
string GetDirectory(WellKnownDirectory directory);
string GetConfigFile(WellKnownConfigFile configFile);
Tracing GetTrace(string name);
@@ -67,12 +68,14 @@ namespace GitHub.Runner.Common
private IDisposable _diagListenerSubscription;
private StartupType _startupType;
private string _perfFile;
private RunnerWebProxy _webProxy = new RunnerWebProxy();
public event EventHandler Unloading;
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
public ShutdownReason RunnerShutdownReason { get; private set; }
public ISecretMasker SecretMasker => _secretMasker;
public ProductInfoHeaderValue UserAgent => _userAgent;
public RunnerWebProxy WebProxy => _webProxy;
public HostContext(string hostType, string logFile = null)
{
// Validate args.
@@ -147,6 +150,48 @@ namespace GitHub.Runner.Common
_trace.Error(ex);
}
}
// Check and trace proxy info
if (!string.IsNullOrEmpty(WebProxy.HttpProxyAddress))
{
if (string.IsNullOrEmpty(WebProxy.HttpProxyUsername) && string.IsNullOrEmpty(WebProxy.HttpProxyPassword))
{
_trace.Info($"Configuring anonymous proxy {WebProxy.HttpProxyAddress} for all HTTP requests.");
}
else
{
// Register proxy password as secret
if (!string.IsNullOrEmpty(WebProxy.HttpProxyPassword))
{
this.SecretMasker.AddValue(WebProxy.HttpProxyPassword);
}
_trace.Info($"Configuring authenticated proxy {WebProxy.HttpProxyAddress} for all HTTP requests.");
}
}
if (!string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
{
if (string.IsNullOrEmpty(WebProxy.HttpsProxyUsername) && string.IsNullOrEmpty(WebProxy.HttpsProxyPassword))
{
_trace.Info($"Configuring anonymous proxy {WebProxy.HttpsProxyAddress} for all HTTPS requests.");
}
else
{
// Register proxy password as secret
if (!string.IsNullOrEmpty(WebProxy.HttpsProxyPassword))
{
this.SecretMasker.AddValue(WebProxy.HttpsProxyPassword);
}
_trace.Info($"Configuring authenticated proxy {WebProxy.HttpsProxyAddress} for all HTTPS requests.");
}
}
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
{
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
}
}
public RunMode RunMode
@@ -282,24 +327,6 @@ namespace GitHub.Runner.Common
".certificates");
break;
case WellKnownConfigFile.Proxy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxy");
break;
case WellKnownConfigFile.ProxyCredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxycredentials");
break;
case WellKnownConfigFile.ProxyBypass:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxybypass");
break;
case WellKnownConfigFile.Options:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
@@ -580,8 +607,7 @@ namespace GitHub.Runner.Common
public static HttpClientHandler CreateHttpClientHandler(this IHostContext context)
{
HttpClientHandler clientHandler = new HttpClientHandler();
var runnerWebProxy = context.GetService<IRunnerWebProxy>();
clientHandler.Proxy = runnerWebProxy.WebProxy;
clientHandler.Proxy = context.WebProxy;
return clientHandler;
}
}

View File

@@ -1,196 +0,0 @@
using GitHub.Runner.Common.Util;
using System;
using System.Linq;
using System.Net;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(RunnerWebProxy))]
public interface IRunnerWebProxy : IRunnerService
{
string ProxyAddress { get; }
string ProxyUsername { get; }
string ProxyPassword { get; }
List<string> ProxyBypassList { get; }
IWebProxy WebProxy { get; }
}
public class RunnerWebProxy : RunnerService, IRunnerWebProxy
{
private readonly List<Regex> _regExBypassList = new List<Regex>();
private readonly List<string> _bypassList = new List<string>();
private RunnerWebProxyCore _runnerWebProxy = new RunnerWebProxyCore();
public string ProxyAddress { get; private set; }
public string ProxyUsername { get; private set; }
public string ProxyPassword { get; private set; }
public List<string> ProxyBypassList => _bypassList;
public IWebProxy WebProxy => _runnerWebProxy;
public override void Initialize(IHostContext context)
{
base.Initialize(context);
LoadProxySetting();
}
// This should only be called from config
public void SetupProxy(string proxyAddress, string proxyUsername, string proxyPassword)
{
ArgUtil.NotNullOrEmpty(proxyAddress, nameof(proxyAddress));
Trace.Info($"Update proxy setting from '{ProxyAddress ?? string.Empty}' to'{proxyAddress}'");
ProxyAddress = proxyAddress;
ProxyUsername = proxyUsername;
ProxyPassword = proxyPassword;
if (string.IsNullOrEmpty(ProxyUsername) || string.IsNullOrEmpty(ProxyPassword))
{
Trace.Info($"Config proxy use DefaultNetworkCredentials.");
}
else
{
Trace.Info($"Config authentication proxy as: {ProxyUsername}.");
}
_runnerWebProxy.Update(ProxyAddress, ProxyUsername, ProxyPassword, ProxyBypassList);
}
// This should only be called from config
public void SaveProxySetting()
{
if (!string.IsNullOrEmpty(ProxyAddress))
{
string proxyConfigFile = HostContext.GetConfigFile(WellKnownConfigFile.Proxy);
IOUtil.DeleteFile(proxyConfigFile);
Trace.Info($"Store proxy configuration to '{proxyConfigFile}' for proxy '{ProxyAddress}'");
File.WriteAllText(proxyConfigFile, ProxyAddress);
File.SetAttributes(proxyConfigFile, File.GetAttributes(proxyConfigFile) | FileAttributes.Hidden);
string proxyCredFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyCredentials);
IOUtil.DeleteFile(proxyCredFile);
if (!string.IsNullOrEmpty(ProxyUsername) && !string.IsNullOrEmpty(ProxyPassword))
{
string lookupKey = Guid.NewGuid().ToString("D").ToUpperInvariant();
Trace.Info($"Store proxy credential lookup key '{lookupKey}' to '{proxyCredFile}'");
File.WriteAllText(proxyCredFile, lookupKey);
File.SetAttributes(proxyCredFile, File.GetAttributes(proxyCredFile) | FileAttributes.Hidden);
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Write($"GITHUB_ACTIONS_RUNNER_PROXY_{lookupKey}", ProxyUsername, ProxyPassword);
}
}
else
{
Trace.Info("No proxy configuration exist.");
}
}
// This should only be called from unconfig
public void DeleteProxySetting()
{
string proxyCredFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyCredentials);
if (File.Exists(proxyCredFile))
{
Trace.Info("Delete proxy credential from credential store.");
string lookupKey = File.ReadAllLines(proxyCredFile).FirstOrDefault();
if (!string.IsNullOrEmpty(lookupKey))
{
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Delete($"GITHUB_ACTIONS_RUNNER_PROXY_{lookupKey}");
}
Trace.Info($"Delete .proxycredentials file: {proxyCredFile}");
IOUtil.DeleteFile(proxyCredFile);
}
string proxyBypassFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyBypass);
if (File.Exists(proxyBypassFile))
{
Trace.Info($"Delete .proxybypass file: {proxyBypassFile}");
IOUtil.DeleteFile(proxyBypassFile);
}
string proxyConfigFile = HostContext.GetConfigFile(WellKnownConfigFile.Proxy);
Trace.Info($"Delete .proxy file: {proxyConfigFile}");
IOUtil.DeleteFile(proxyConfigFile);
}
private void LoadProxySetting()
{
string proxyConfigFile = HostContext.GetConfigFile(WellKnownConfigFile.Proxy);
if (File.Exists(proxyConfigFile))
{
// we expect the first line of the file is the proxy url
Trace.Verbose($"Try read proxy setting from file: {proxyConfigFile}.");
ProxyAddress = File.ReadLines(proxyConfigFile).FirstOrDefault() ?? string.Empty;
ProxyAddress = ProxyAddress.Trim();
Trace.Verbose($"{ProxyAddress}");
}
if (!string.IsNullOrEmpty(ProxyAddress) && !Uri.IsWellFormedUriString(ProxyAddress, UriKind.Absolute))
{
Trace.Info($"The proxy url is not a well formed absolute uri string: {ProxyAddress}.");
ProxyAddress = string.Empty;
}
if (!string.IsNullOrEmpty(ProxyAddress))
{
Trace.Info($"Config proxy at: {ProxyAddress}.");
string proxyCredFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyCredentials);
if (File.Exists(proxyCredFile))
{
string lookupKey = File.ReadAllLines(proxyCredFile).FirstOrDefault();
if (!string.IsNullOrEmpty(lookupKey))
{
var credStore = HostContext.GetService<IRunnerCredentialStore>();
var proxyCred = credStore.Read($"GITHUB_ACTIONS_RUNNER_PROXY_{lookupKey}");
ProxyUsername = proxyCred.UserName;
ProxyPassword = proxyCred.Password;
}
}
if (!string.IsNullOrEmpty(ProxyPassword))
{
HostContext.SecretMasker.AddValue(ProxyPassword);
}
if (string.IsNullOrEmpty(ProxyUsername) || string.IsNullOrEmpty(ProxyPassword))
{
Trace.Info($"Config proxy use DefaultNetworkCredentials.");
}
else
{
Trace.Info($"Config authentication proxy as: {ProxyUsername}.");
}
string proxyBypassFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyBypass);
if (File.Exists(proxyBypassFile))
{
Trace.Verbose($"Try read proxy bypass list from file: {proxyBypassFile}.");
foreach (string bypass in File.ReadAllLines(proxyBypassFile))
{
if (string.IsNullOrWhiteSpace(bypass))
{
continue;
}
else
{
Trace.Info($"Bypass proxy for: {bypass}.");
ProxyBypassList.Add(bypass.Trim());
}
}
}
_runnerWebProxy.Update(ProxyAddress, ProxyUsername, ProxyPassword, ProxyBypassList);
}
else
{
Trace.Info($"No proxy setting found.");
}
}
}
}

View File

@@ -37,9 +37,8 @@ namespace GitHub.Runner.Listener
{
try
{
var runnerWebProxy = HostContext.GetService<IRunnerWebProxy>();
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, runnerWebProxy.WebProxy, runnerCertManager.VssClientCertificateManager);
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy, runnerCertManager.VssClientCertificateManager);
_inConfigStage = true;
_completedCommand.Reset();

View File

@@ -47,9 +47,6 @@ namespace GitHub.Runner.Listener
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
Constants.Runner.CommandLine.Args.Password,
Constants.Runner.CommandLine.Args.Pool,
Constants.Runner.CommandLine.Args.ProxyPassword,
Constants.Runner.CommandLine.Args.ProxyUrl,
Constants.Runner.CommandLine.Args.ProxyUserName,
Constants.Runner.CommandLine.Args.SslCACert,
Constants.Runner.CommandLine.Args.SslClientCert,
Constants.Runner.CommandLine.Args.SslClientCertKey,
@@ -290,21 +287,6 @@ namespace GitHub.Runner.Listener
return GetArg(Constants.Runner.CommandLine.Args.StartupType);
}
public string GetProxyUrl()
{
return GetArg(Constants.Runner.CommandLine.Args.ProxyUrl);
}
public string GetProxyUserName()
{
return GetArg(Constants.Runner.CommandLine.Args.ProxyUserName);
}
public string GetProxyPassword()
{
return GetArg(Constants.Runner.CommandLine.Args.ProxyPassword);
}
public bool GetSkipCertificateValidation()
{
return TestFlag(Constants.Runner.CommandLine.Flags.SslSkipCertValidation);

View File

@@ -86,24 +86,6 @@ namespace GitHub.Runner.Listener.Configuration
throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.");
}
// Populate proxy setting from commandline args
var runnerProxy = HostContext.GetService<IRunnerWebProxy>();
bool saveProxySetting = false;
string proxyUrl = command.GetProxyUrl();
if (!string.IsNullOrEmpty(proxyUrl))
{
if (!Uri.IsWellFormedUriString(proxyUrl, UriKind.Absolute))
{
throw new ArgumentOutOfRangeException(nameof(proxyUrl));
}
Trace.Info("Reset proxy base on commandline args.");
string proxyUserName = command.GetProxyUserName();
string proxyPassword = command.GetProxyPassword();
(runnerProxy as RunnerWebProxy).SetupProxy(proxyUrl, proxyUserName, proxyPassword);
saveProxySetting = true;
}
// Populate cert setting from commandline args
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
bool saveCertSetting = false;
@@ -371,12 +353,6 @@ namespace GitHub.Runner.Listener.Configuration
_store.SaveSettings(runnerSettings);
if (saveProxySetting)
{
Trace.Info("Save proxy setting to disk.");
(runnerProxy as RunnerWebProxy).SaveProxySetting();
}
if (saveCertSetting)
{
Trace.Info("Save agent cert setting to disk.");
@@ -515,8 +491,6 @@ namespace GitHub.Runner.Listener.Configuration
currentAction = "Removing .runner";
if (isConfigured)
{
// delete proxy setting
(HostContext.GetService<IRunnerWebProxy>() as RunnerWebProxy).DeleteProxySetting();
// delete agent cert setting
(HostContext.GetService<IRunnerCertificateManager>() as RunnerCertificateManager).DeleteCertificateSetting();

View File

@@ -79,8 +79,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
{
// Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext));
Uri proxyUrlWithCred = null;
string proxyUrlWithCredString = null;
bool useSelfSignedCACert = false;
bool useClientCert = false;
string clientCertPrivateKeyAskPassFile = null;
@@ -164,25 +162,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
// 3. git version greater than 2.14.2 if use SChannel for SSL backend (Windows only)
RequirementCheck(executionContext, gitCommandManager, gitLfsSupport);
// prepare credentail embedded urls
var runnerProxy = executionContext.GetProxyConfiguration();
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
proxyUrlWithCred = UrlUtil.GetCredentialEmbeddedUrl(new Uri(runnerProxy.ProxyAddress), runnerProxy.ProxyUsername, runnerProxy.ProxyPassword);
// uri.absoluteuri will not contains port info if the scheme is http/https and the port is 80/443
// however, git.exe always require you provide port info, if nothing passed in, it will use 1080 as default
// as result, we need prefer the uri.originalstring when it's different than uri.absoluteuri.
if (string.Equals(proxyUrlWithCred.AbsoluteUri, proxyUrlWithCred.OriginalString, StringComparison.OrdinalIgnoreCase))
{
proxyUrlWithCredString = proxyUrlWithCred.AbsoluteUri;
}
else
{
proxyUrlWithCredString = proxyUrlWithCred.OriginalString;
}
}
// prepare askpass for client cert private key, if the repository's endpoint url match the runner config url
var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (runnerCert != null && Uri.Compare(repositoryUrl, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
@@ -373,13 +352,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
}
// always remove any possible left proxy setting from git config, the proxy setting may contains credential
if (await gitCommandManager.GitConfigExist(executionContext, targetPath, $"http.proxy"))
{
executionContext.Debug("Remove any proxy setting from git config.");
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.proxy", string.Empty);
}
List<string> additionalFetchArgs = new List<string>();
List<string> additionalLfsFetchArgs = new List<string>();
@@ -389,15 +361,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalFetchArgs.Add($"-c http.extraheader=\"AUTHORIZATION: {GenerateBasicAuthHeader(executionContext, accessToken)}\"");
}
// Prepare proxy config for fetch.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git fetch.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
additionalLfsFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
@@ -539,14 +502,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.extraheader=\"AUTHORIZATION: {GenerateBasicAuthHeader(executionContext, accessToken)}\"");
}
// Prepare proxy config for submodule update.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git submodule update.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalSubmoduleUpdateArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
@@ -637,7 +592,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
int exitCode_configUnset = await gitCommandManager.GitConfigUnset(executionContext, targetPath, configKey);
if (exitCode_configUnset != 0)
{
// if unable to use git.exe unset http.extraheader, http.proxy or core.askpass, modify git config file on disk. make sure we don't left credential.
// if unable to use git.exe unset http.extraheader or core.askpass, modify git config file on disk. make sure we don't left credential.
if (!string.IsNullOrEmpty(configValue))
{
executionContext.Warning("An unsuccessful attempt was made using git command line to remove \"http.extraheader\" from the git config. Attempting to modify the git config file directly to remove the credential.");
@@ -650,9 +605,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
string setting = $"extraheader = {configValue}";
gitConfigContent = Regex.Replace(gitConfigContent, setting, string.Empty, RegexOptions.IgnoreCase);
setting = $"proxy = {configValue}";
gitConfigContent = Regex.Replace(gitConfigContent, setting, string.Empty, RegexOptions.IgnoreCase);
setting = $"askpass = {configValue}";
gitConfigContent = Regex.Replace(gitConfigContent, setting, string.Empty, RegexOptions.IgnoreCase);

View File

@@ -65,8 +65,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
// Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext));
Dictionary<string, string> configModifications = new Dictionary<string, string>();
Uri proxyUrlWithCred = null;
string proxyUrlWithCredString = null;
bool useSelfSignedCACert = false;
bool useClientCert = false;
string clientCertPrivateKeyAskPassFile = null;
@@ -153,18 +151,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
// 3. git version greater than 2.14.2 if use SChannel for SSL backend (Windows only)
RequirementCheck(executionContext, gitCommandManager, gitLfsSupport);
// prepare credentail embedded urls
var runnerProxy = executionContext.GetProxyConfiguration();
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
proxyUrlWithCred = UrlUtil.GetCredentialEmbeddedUrl(new Uri(runnerProxy.ProxyAddress), runnerProxy.ProxyUsername, runnerProxy.ProxyPassword);
// uri.absoluteuri will not contains port info if the scheme is http/https and the port is 80/443
// however, git.exe always require you provide port info, if nothing passed in, it will use 1080 as default
// as result, we need prefer the uri.originalstring over uri.absoluteuri.
proxyUrlWithCredString = proxyUrlWithCred.OriginalString;
}
// prepare askpass for client cert private key, if the repository's endpoint url match the runner config url
var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (runnerCert != null && Uri.Compare(repositoryUrl, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
@@ -355,13 +341,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
}
// always remove any possible left proxy setting from git config, the proxy setting may contains credential
if (await gitCommandManager.GitConfigExist(executionContext, targetPath, $"http.proxy"))
{
executionContext.Debug("Remove any proxy setting from git config.");
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.proxy", string.Empty);
}
List<string> additionalFetchArgs = new List<string>();
List<string> additionalLfsFetchArgs = new List<string>();
@@ -376,15 +355,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
throw new InvalidOperationException($"Git config failed with exit code: {exitCode_config}");
}
// Prepare proxy config for fetch.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git fetch.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
additionalLfsFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
@@ -514,14 +484,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
List<string> additionalSubmoduleUpdateArgs = new List<string>();
// Prepare proxy config for submodule update.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git submodule update.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalSubmoduleUpdateArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
@@ -592,7 +554,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
GitCliManager gitCommandManager = new GitCliManager();
await gitCommandManager.LoadGitExecutionInfo(executionContext);
executionContext.Debug("Remove any extraheader and proxy setting from git config.");
executionContext.Debug("Remove any extraheader setting from git config.");
var configKeys = JsonUtility.FromString<List<string>>(Environment.GetEnvironmentVariable("STATE_modifiedgitconfig"));
if (configKeys?.Count > 0)
{
@@ -677,7 +639,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
int exitCode_configUnset = await gitCommandManager.GitConfigUnset(executionContext, targetPath, configKey);
if (exitCode_configUnset != 0)
{
// if unable to use git.exe unset http.extraheader, http.proxy or core.askpass, modify git config file on disk. make sure we don't left credential.
// if unable to use git.exe unset http.extraheader or core.askpass, modify git config file on disk. make sure we don't left credential.
if (!string.IsNullOrEmpty(configValue))
{
executionContext.Warning("An unsuccessful attempt was made using git command line to remove \"http.extraheader\" from the git config. Attempting to modify the git config file directly to remove the credential.");

View File

@@ -24,6 +24,7 @@ namespace GitHub.Runner.Sdk
{
private readonly string DebugEnvironmentalVariable = "ACTIONS_STEP_DEBUG";
private VssConnection _connection;
private RunnerWebProxy _webProxy;
private readonly object _stdoutLock = new object();
private readonly ITraceWriter _trace; // for unit tests
@@ -57,6 +58,19 @@ namespace GitHub.Runner.Sdk
}
}
[JsonIgnore]
public RunnerWebProxy WebProxy
{
get
{
if (_webProxy == null)
{
_webProxy = new RunnerWebProxy();
}
return _webProxy;
}
}
public VssConnection InitializeVssConnection()
{
var headerValues = new List<ProductInfoHeaderValue>();
@@ -84,15 +98,7 @@ namespace GitHub.Runner.Sdk
}
}
var proxySetting = GetProxyConfiguration();
if (proxySetting != null)
{
if (!string.IsNullOrEmpty(proxySetting.ProxyAddress))
{
VssHttpMessageHandler.DefaultWebProxy = new RunnerWebProxyCore(proxySetting.ProxyAddress, proxySetting.ProxyUsername, proxySetting.ProxyPassword, proxySetting.ProxyBypassList);
}
}
VssHttpMessageHandler.DefaultWebProxy = this.WebProxy;
ServiceEndpoint systemConnection = this.Endpoints.FirstOrDefault(e => string.Equals(e.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
ArgUtil.NotNull(systemConnection.Url, nameof(systemConnection.Url));
@@ -255,29 +261,6 @@ namespace GitHub.Runner.Sdk
}
}
public RunnerWebProxySettings GetProxyConfiguration()
{
string proxyUrl = GetRunnerContext("ProxyUrl");
if (!string.IsNullOrEmpty(proxyUrl))
{
string proxyUsername = GetRunnerContext("ProxyUsername");
string proxyPassword = GetRunnerContext("ProxyPassword");
List<string> proxyBypassHosts = StringUtil.ConvertFromJson<List<string>>(GetRunnerContext("ProxyBypassList") ?? "[]");
return new RunnerWebProxySettings()
{
ProxyAddress = proxyUrl,
ProxyUsername = proxyUsername,
ProxyPassword = proxyPassword,
ProxyBypassList = proxyBypassHosts,
WebProxy = new RunnerWebProxyCore(proxyUrl, proxyUsername, proxyPassword, proxyBypassHosts)
};
}
else
{
return null;
}
}
private string Escape(string input)
{
foreach (var mapping in _commandEscapeMappings)

View File

@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
namespace GitHub.Runner.Sdk
{
public struct ByPassInfo
{
public string Host { get; set; }
public string Port { get; set; }
};
public class RunnerWebProxy : IWebProxy
{
private string _httpProxyAddress;
private string _httpProxyUsername;
private string _httpProxyPassword;
private string _httpsProxyAddress;
private string _httpsProxyUsername;
private string _httpsProxyPassword;
private readonly List<ByPassInfo> _noProxyList = new List<ByPassInfo>();
private readonly HashSet<string> _noProxyUnique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly Regex _validIpRegex = new Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.Compiled);
public string HttpProxyAddress => _httpProxyAddress;
public string HttpProxyUsername => _httpProxyUsername;
public string HttpProxyPassword => _httpProxyPassword;
public string HttpsProxyAddress => _httpsProxyAddress;
public string HttpsProxyUsername => _httpsProxyUsername;
public string HttpsProxyPassword => _httpsProxyPassword;
public List<ByPassInfo> NoProxyList => _noProxyList;
public ICredentials Credentials { get; set; }
public RunnerWebProxy()
{
Credentials = new CredentialCache();
var httpProxyAddress = Environment.GetEnvironmentVariable("http_proxy");
if (string.IsNullOrEmpty(httpProxyAddress))
{
httpProxyAddress = Environment.GetEnvironmentVariable("HTTP_PROXY");
}
httpProxyAddress = httpProxyAddress?.Trim();
var httpsProxyAddress = Environment.GetEnvironmentVariable("https_proxy");
if (string.IsNullOrEmpty(httpsProxyAddress))
{
httpsProxyAddress = Environment.GetEnvironmentVariable("HTTPS_PROXY");
}
httpsProxyAddress = httpsProxyAddress?.Trim();
var noProxyList = Environment.GetEnvironmentVariable("no_proxy");
if (string.IsNullOrEmpty(noProxyList))
{
noProxyList = Environment.GetEnvironmentVariable("NO_PROXY");
}
if (string.IsNullOrEmpty(httpProxyAddress) && string.IsNullOrEmpty(httpsProxyAddress))
{
return;
}
if (!string.IsNullOrEmpty(httpProxyAddress) && Uri.TryCreate(httpProxyAddress, UriKind.Absolute, out var proxyHttpUri))
{
_httpProxyAddress = proxyHttpUri.AbsoluteUri;
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
var userInfo = Uri.UnescapeDataString(proxyHttpUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (userInfo.Length == 2)
{
_httpProxyUsername = userInfo[0];
_httpProxyPassword = userInfo[1];
}
else if (userInfo.Length == 1)
{
_httpProxyUsername = userInfo[0];
}
if (!string.IsNullOrEmpty(_httpProxyUsername) || !string.IsNullOrEmpty(_httpProxyPassword))
{
var credentials = new NetworkCredential(_httpProxyUsername, _httpProxyPassword);
// Replace the entry in the credential cache if it exists
(Credentials as CredentialCache).Remove(proxyHttpUri, "Basic");
(Credentials as CredentialCache).Add(proxyHttpUri, "Basic", credentials);
}
}
if (!string.IsNullOrEmpty(httpsProxyAddress) && Uri.TryCreate(httpsProxyAddress, UriKind.Absolute, out var proxyHttpsUri))
{
_httpsProxyAddress = proxyHttpsUri.AbsoluteUri;
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
var userInfo = Uri.UnescapeDataString(proxyHttpsUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (userInfo.Length == 2)
{
_httpsProxyUsername = userInfo[0];
_httpsProxyPassword = userInfo[1];
}
else if (userInfo.Length == 1)
{
_httpsProxyUsername = userInfo[0];
}
if (!string.IsNullOrEmpty(_httpsProxyUsername) || !string.IsNullOrEmpty(_httpsProxyPassword))
{
var credentials = new NetworkCredential(_httpsProxyUsername, _httpsProxyPassword);
// Replace the entry in the credential cache if it exists
(Credentials as CredentialCache).Remove(proxyHttpsUri, "Basic");
(Credentials as CredentialCache).Add(proxyHttpsUri, "Basic", credentials);
}
}
if (!string.IsNullOrEmpty(noProxyList))
{
var noProxyListSplit = noProxyList.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (string noProxy in noProxyListSplit)
{
var noProxyTrim = noProxy.Trim();
if (string.IsNullOrEmpty(noProxyTrim))
{
continue;
}
else if (_noProxyUnique.Add(noProxyTrim))
{
var noProxyInfo = new ByPassInfo();
var noProxyHostPort = noProxyTrim.Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (noProxyHostPort.Length == 1)
{
noProxyInfo.Host = noProxyHostPort[0];
}
else if (noProxyHostPort.Length == 2)
{
noProxyInfo.Host = noProxyHostPort[0];
noProxyInfo.Port = noProxyHostPort[1];
}
// We don't support IP address for no_proxy
if (_validIpRegex.IsMatch(noProxyInfo.Host))
{
continue;
}
_noProxyList.Add(noProxyInfo);
}
}
}
}
public Uri GetProxy(Uri destination)
{
if (IsBypassed(destination))
{
return null;
}
if (destination.Scheme == Uri.UriSchemeHttps)
{
return new Uri(_httpsProxyAddress);
}
else
{
return new Uri(_httpProxyAddress);
}
}
public bool IsBypassed(Uri uri)
{
if (uri.Scheme == Uri.UriSchemeHttps && string.IsNullOrEmpty(_httpsProxyAddress))
{
return true;
}
if (uri.Scheme == Uri.UriSchemeHttp && string.IsNullOrEmpty(_httpProxyAddress))
{
return true;
}
return uri.IsLoopback || IsUriInBypassList(uri);
}
private bool IsUriInBypassList(Uri input)
{
foreach (var noProxy in _noProxyList)
{
var matchHost = false;
var matchPort = false;
if (string.IsNullOrEmpty(noProxy.Port))
{
matchPort = true;
}
else
{
matchPort = string.Equals(noProxy.Port, input.Port.ToString());
}
if (noProxy.Host.StartsWith('.'))
{
matchHost = input.Host.EndsWith(noProxy.Host, StringComparison.OrdinalIgnoreCase);
}
else
{
matchHost = string.Equals(input.Host, noProxy.Host, StringComparison.OrdinalIgnoreCase) || input.Host.EndsWith($".{noProxy.Host}", StringComparison.OrdinalIgnoreCase);
}
if (matchHost && matchPort)
{
return true;
}
}
return false;
}
}
}

View File

@@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
namespace GitHub.Runner.Sdk
{
public class RunnerWebProxySettings
{
public string ProxyAddress { get; set; }
public string ProxyUsername { get; set; }
public string ProxyPassword { get; set; }
public List<string> ProxyBypassList { get; set; }
public IWebProxy WebProxy { get; set; }
}
public class RunnerWebProxyCore : IWebProxy
{
private string _proxyAddress;
private readonly List<Regex> _regExBypassList = new List<Regex>();
public ICredentials Credentials { get; set; }
public RunnerWebProxyCore()
{
}
public RunnerWebProxyCore(string proxyAddress, string proxyUsername, string proxyPassword, List<string> proxyBypassList)
{
Update(proxyAddress, proxyUsername, proxyPassword, proxyBypassList);
}
public void Update(string proxyAddress, string proxyUsername, string proxyPassword, List<string> proxyBypassList)
{
_proxyAddress = proxyAddress?.Trim();
if (string.IsNullOrEmpty(proxyUsername) || string.IsNullOrEmpty(proxyPassword))
{
Credentials = CredentialCache.DefaultNetworkCredentials;
}
else
{
Credentials = new NetworkCredential(proxyUsername, proxyPassword);
}
if (proxyBypassList != null)
{
foreach (string bypass in proxyBypassList)
{
if (string.IsNullOrWhiteSpace(bypass))
{
continue;
}
else
{
try
{
Regex bypassRegex = new Regex(bypass.Trim(), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ECMAScript);
_regExBypassList.Add(bypassRegex);
}
catch (Exception)
{
// eat all exceptions
}
}
}
}
}
public Uri GetProxy(Uri destination)
{
if (IsBypassed(destination))
{
return destination;
}
else
{
return new Uri(_proxyAddress);
}
}
public bool IsBypassed(Uri uri)
{
return string.IsNullOrEmpty(_proxyAddress) || uri.IsLoopback || IsMatchInBypassList(uri);
}
private bool IsMatchInBypassList(Uri input)
{
string matchUriString = input.IsDefaultPort ?
input.Scheme + "://" + input.Host :
input.Scheme + "://" + input.Host + ":" + input.Port.ToString();
foreach (Regex r in _regExBypassList)
{
if (r.IsMatch(matchUriString))
{
return true;
}
}
return false;
}
}
}

View File

@@ -616,29 +616,6 @@ namespace GitHub.Runner.Worker
// PostJobSteps for job ExecutionContext
PostJobSteps = new Stack<IStep>();
// Proxy variables
// var agentWebProxy = HostContext.GetService<IRunnerWebProxy>();
// if (!string.IsNullOrEmpty(agentWebProxy.ProxyAddress))
// {
// SetRunnerContext("proxyurl", agentWebProxy.ProxyAddress);
// if (!string.IsNullOrEmpty(agentWebProxy.ProxyUsername))
// {
// SetRunnerContext("proxyusername", agentWebProxy.ProxyUsername);
// }
// if (!string.IsNullOrEmpty(agentWebProxy.ProxyPassword))
// {
// HostContext.SecretMasker.AddValue(agentWebProxy.ProxyPassword);
// SetRunnerContext("proxypassword", agentWebProxy.ProxyPassword);
// }
// if (agentWebProxy.ProxyBypassList.Count > 0)
// {
// SetRunnerContext("proxybypasslist", JsonUtility.ToString(agentWebProxy.ProxyBypassList));
// }
// }
// // Certificate variables
// var agentCert = HostContext.GetService<IRunnerCertificateManager>();
// if (agentCert.SkipServerCertificateValidation)

View File

@@ -55,10 +55,13 @@ namespace GitHub.Runner.Worker
context.Debug($"Primary repository: {repoFullName}");
// Print proxy setting information for better diagnostic experience
var runnerWebProxy = HostContext.GetService<IRunnerWebProxy>();
if (!string.IsNullOrEmpty(runnerWebProxy.ProxyAddress))
if (!string.IsNullOrEmpty(HostContext.WebProxy.HttpProxyAddress))
{
context.Output($"Runner is running behind proxy server: '{runnerWebProxy.ProxyAddress}'");
context.Output($"Runner is running behind proxy server '{HostContext.WebProxy.HttpProxyAddress}' for all HTTP requests.");
}
if (!string.IsNullOrEmpty(HostContext.WebProxy.HttpsProxyAddress))
{
context.Output($"Runner is running behind proxy server '{HostContext.WebProxy.HttpsProxyAddress}' for all HTTPS requests.");
}
// Prepare the workflow directory

View File

@@ -40,9 +40,8 @@ namespace GitHub.Runner.Worker
// Validate args.
ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn));
ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut));
var runnerWebProxy = HostContext.GetService<IRunnerWebProxy>();
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, runnerWebProxy.WebProxy, runnerCertManager.VssClientCertificateManager);
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy, runnerCertManager.VssClientCertificateManager);
var jobRunner = HostContext.CreateService<IJobRunner>();
using (var channel = HostContext.CreateService<IProcessChannel>())

View File

@@ -107,6 +107,35 @@ namespace GitHub.Runner.Common.Tests
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void SecretMaskerForProxy()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://user:password123@127.0.0.1:8888");
// Arrange.
Setup();
// Assert.
var logFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"trace_{nameof(HostContextL0)}_{nameof(SecretMaskerForProxy)}.log");
var tempFile = Path.GetTempFileName();
File.Delete(tempFile);
File.Copy(logFile, tempFile);
var content = File.ReadAllText(tempFile);
Assert.DoesNotContain("password123", content);
Assert.Contains("http://user:***@127.0.0.1:8888", content);
}
finally
{
Environment.SetEnvironmentVariable("http_proxy", null);
// Cleanup.
Teardown();
}
}
private void Setup([CallerMemberName] string testName = "")
{
_tokenSource = new CancellationTokenSource();

View File

@@ -26,8 +26,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
private Mock<IPromptManager> _promptManager;
private Mock<IConfigurationStore> _store;
private Mock<IExtensionManager> _extnMgr;
// private Mock<IDeploymentGroupServer> _machineGroupServer;
private Mock<IRunnerWebProxy> _runnerWebProxy;
private Mock<IRunnerCertificateManager> _cert;
#if OS_WINDOWS
@@ -59,8 +57,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
_store = new Mock<IConfigurationStore>();
_extnMgr = new Mock<IExtensionManager>();
_rsaKeyManager = new Mock<IRSAKeyManager>();
// _machineGroupServer = new Mock<IDeploymentGroupServer>();
_runnerWebProxy = new Mock<IRunnerWebProxy>();
_cert = new Mock<IRunnerCertificateManager>();
#if OS_WINDOWS
@@ -134,7 +130,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
tc.SetSingleton<IExtensionManager>(_extnMgr.Object);
tc.SetSingleton<IRunnerServer>(_agentServer.Object);
tc.SetSingleton<ILocationServer>(_locationServer.Object);
tc.SetSingleton<IRunnerWebProxy>(_runnerWebProxy.Object);
tc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
#if OS_WINDOWS

View File

@@ -1,116 +0,0 @@
using GitHub.Runner.Common.Util;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Xunit;
using System;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public sealed class ProxyConfigL0
{
private static readonly Regex NewHttpClientHandlerRegex = new Regex("New\\s+HttpClientHandler\\s*\\(", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex NewHttpClientRegex = new Regex("New\\s+HttpClient\\s*\\(\\s*\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly List<string> SkippedFiles = new List<string>()
{
"Runner.Common\\HostContext.cs",
"Runner.Common/HostContext.cs"
};
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void IsNotUseRawHttpClientHandler()
{
List<string> sourceFiles = Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Common"),
"*.cs",
SearchOption.AllDirectories).ToList();
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Listener"),
"*.cs",
SearchOption.AllDirectories));
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Worker"),
"*.cs",
SearchOption.AllDirectories));
List<string> badCode = new List<string>();
foreach (string sourceFile in sourceFiles)
{
// Skip skipped files.
if (SkippedFiles.Any(s => sourceFile.Contains(s)))
{
continue;
}
// Skip files in the obj directory.
if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
{
continue;
}
int lineCount = 0;
foreach (string line in File.ReadAllLines(sourceFile))
{
lineCount++;
if (NewHttpClientHandlerRegex.IsMatch(line))
{
badCode.Add($"{sourceFile} (line {lineCount})");
}
}
}
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClientHandler() which will not follow the proxy setting agent have. Please use HostContext.CreateHttpClientHandler() instead.\n {string.Join("\n", badCode)}");
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void IsNotUseRawHttpClient()
{
List<string> sourceFiles = Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Common"),
"*.cs",
SearchOption.AllDirectories).ToList();
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Listener"),
"*.cs",
SearchOption.AllDirectories));
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Worker"),
"*.cs",
SearchOption.AllDirectories));
List<string> badCode = new List<string>();
foreach (string sourceFile in sourceFiles)
{
// Skip skipped files.
if (SkippedFiles.Any(s => sourceFile.Contains(s)))
{
continue;
}
// Skip files in the obj directory.
if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
{
continue;
}
int lineCount = 0;
foreach (string line in File.ReadAllLines(sourceFile))
{
lineCount++;
if (NewHttpClientRegex.IsMatch(line))
{
badCode.Add($"{sourceFile} (line {lineCount})");
}
}
}
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClient() which will not follow the proxy setting agent have. Please use New HttpClient(HostContext.CreateHttpClientHandler()) instead.\n {string.Join("\n", badCode)}");
}
}
}

View File

@@ -0,0 +1,416 @@
using GitHub.Runner.Common.Util;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Xunit;
using System;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public sealed class RunnerWebProxyL0
{
private static readonly Regex NewHttpClientHandlerRegex = new Regex("New\\s+HttpClientHandler\\s*\\(", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex NewHttpClientRegex = new Regex("New\\s+HttpClient\\s*\\(\\s*\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly List<string> SkippedFiles = new List<string>()
{
"Runner.Common\\HostContext.cs",
"Runner.Common/HostContext.cs"
};
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void IsNotUseRawHttpClientHandler()
{
List<string> sourceFiles = Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Common"),
"*.cs",
SearchOption.AllDirectories).ToList();
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Listener"),
"*.cs",
SearchOption.AllDirectories));
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Worker"),
"*.cs",
SearchOption.AllDirectories));
List<string> badCode = new List<string>();
foreach (string sourceFile in sourceFiles)
{
// Skip skipped files.
if (SkippedFiles.Any(s => sourceFile.Contains(s)))
{
continue;
}
// Skip files in the obj directory.
if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
{
continue;
}
int lineCount = 0;
foreach (string line in File.ReadAllLines(sourceFile))
{
lineCount++;
if (NewHttpClientHandlerRegex.IsMatch(line))
{
badCode.Add($"{sourceFile} (line {lineCount})");
}
}
}
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClientHandler() which will not follow the proxy setting agent have. Please use HostContext.CreateHttpClientHandler() instead.\n {string.Join("\n", badCode)}");
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void IsNotUseRawHttpClient()
{
List<string> sourceFiles = Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Common"),
"*.cs",
SearchOption.AllDirectories).ToList();
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Listener"),
"*.cs",
SearchOption.AllDirectories));
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Worker"),
"*.cs",
SearchOption.AllDirectories));
List<string> badCode = new List<string>();
foreach (string sourceFile in sourceFiles)
{
// Skip skipped files.
if (SkippedFiles.Any(s => sourceFile.Contains(s)))
{
continue;
}
// Skip files in the obj directory.
if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
{
continue;
}
int lineCount = 0;
foreach (string line in File.ReadAllLines(sourceFile))
{
lineCount++;
if (NewHttpClientRegex.IsMatch(line))
{
badCode.Add($"{sourceFile} (line {lineCount})");
}
}
}
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClient() which will not follow the proxy setting agent have. Please use New HttpClient(HostContext.CreateHttpClientHandler()) instead.\n {string.Join("\n", badCode)}");
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariables()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:8888");
Environment.SetEnvironmentVariable("https_proxy", "http://user:pass@127.0.0.1:9999");
Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
var proxy = new RunnerWebProxy();
Assert.Equal("http://127.0.0.1:8888/", proxy.HttpProxyAddress);
Assert.Null(proxy.HttpProxyUsername);
Assert.Null(proxy.HttpProxyPassword);
Assert.Equal("http://user:pass@127.0.0.1:9999/", proxy.HttpsProxyAddress);
Assert.Equal("user", proxy.HttpsProxyUsername);
Assert.Equal("pass", proxy.HttpsProxyPassword);
Assert.Equal(2, proxy.NoProxyList.Count);
Assert.Equal("github.com", proxy.NoProxyList[0].Host);
Assert.Equal("google.com", proxy.NoProxyList[1].Host);
}
finally
{
CleanProxyEnv();
}
}
#if !OS_WINDOWS
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesPreferLowerCase()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:7777");
Environment.SetEnvironmentVariable("HTTP_PROXY", "http://127.0.0.1:8888");
Environment.SetEnvironmentVariable("https_proxy", "http://user:pass@127.0.0.1:8888");
Environment.SetEnvironmentVariable("HTTPS_PROXY", "http://user:pass@127.0.0.1:9999");
Environment.SetEnvironmentVariable("no_proxy", "github.com, github.com ");
Environment.SetEnvironmentVariable("NO_PROXY", "github.com, google.com,");
var proxy = new RunnerWebProxy();
Assert.Equal("http://127.0.0.1:7777/", proxy.HttpProxyAddress);
Assert.Null(proxy.HttpProxyUsername);
Assert.Null(proxy.HttpProxyPassword);
Assert.Equal("http://user:pass@127.0.0.1:8888/", proxy.HttpsProxyAddress);
Assert.Equal("user", proxy.HttpsProxyUsername);
Assert.Equal("pass", proxy.HttpsProxyPassword);
Assert.Equal(1, proxy.NoProxyList.Count);
Assert.Equal("github.com", proxy.NoProxyList[0].Host);
}
finally
{
CleanProxyEnv();
}
}
#endif
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesInvalidString()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "127.0.0.1:7777");
Environment.SetEnvironmentVariable("https_proxy", "127.0.0.1");
var proxy = new RunnerWebProxy();
Assert.Null(proxy.HttpProxyAddress);
Assert.Null(proxy.HttpProxyUsername);
Assert.Null(proxy.HttpProxyPassword);
Assert.Null(proxy.HttpsProxyAddress);
Assert.Null(proxy.HttpsProxyUsername);
Assert.Null(proxy.HttpsProxyPassword);
Assert.Equal(0, proxy.NoProxyList.Count);
}
finally
{
CleanProxyEnv();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesProxyCredentials()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://user1@127.0.0.1:8888");
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass@127.0.0.1:9999");
Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
var proxy = new RunnerWebProxy();
Assert.Equal("http://user1@127.0.0.1:8888/", proxy.HttpProxyAddress);
Assert.Equal("user1", proxy.HttpProxyUsername);
Assert.Null(proxy.HttpProxyPassword);
var cred = proxy.Credentials.GetCredential(new Uri("http://user1@127.0.0.1:8888/"), "Basic");
Assert.Equal("user1", cred.UserName);
Assert.Equal(string.Empty, cred.Password);
Assert.Equal("http://user2:pass@127.0.0.1:9999/", proxy.HttpsProxyAddress);
Assert.Equal("user2", proxy.HttpsProxyUsername);
Assert.Equal("pass", proxy.HttpsProxyPassword);
cred = proxy.Credentials.GetCredential(new Uri("http://user2:pass@127.0.0.1:9999/"), "Basic");
Assert.Equal("user2", cred.UserName);
Assert.Equal("pass", cred.Password);
Assert.Equal(2, proxy.NoProxyList.Count);
Assert.Equal("github.com", proxy.NoProxyList[0].Host);
Assert.Equal("google.com", proxy.NoProxyList[1].Host);
}
finally
{
CleanProxyEnv();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesProxyCredentialsEncoding()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
var proxy = new RunnerWebProxy();
Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.HttpProxyAddress);
Assert.Equal("user1", proxy.HttpProxyUsername);
Assert.Equal("pass1@", proxy.HttpProxyPassword);
var cred = proxy.Credentials.GetCredential(new Uri("http://user1:pass1%40@127.0.0.1:8888/"), "Basic");
Assert.Equal("user1", cred.UserName);
Assert.Equal("pass1@", cred.Password);
Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.HttpsProxyAddress);
Assert.Equal("user2", proxy.HttpsProxyUsername);
Assert.Equal("pass2@", proxy.HttpsProxyPassword);
cred = proxy.Credentials.GetCredential(new Uri("http://user2:pass2%40@127.0.0.1:9999/"), "Basic");
Assert.Equal("user2", cred.UserName);
Assert.Equal("pass2@", cred.Password);
Assert.Equal(2, proxy.NoProxyList.Count);
Assert.Equal("github.com", proxy.NoProxyList[0].Host);
Assert.Equal("google.com", proxy.NoProxyList[1].Host);
}
finally
{
CleanProxyEnv();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesByPassEmptyProxy()
{
var proxy = new RunnerWebProxy();
Assert.True(proxy.IsBypassed(new Uri("https://github.com")));
Assert.True(proxy.IsBypassed(new Uri("https://github.com")));
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesGetProxyEmptyHttpProxy()
{
try
{
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
var proxy = new RunnerWebProxy();
Assert.Null(proxy.GetProxy(new Uri("http://github.com")));
Assert.Null(proxy.GetProxy(new Uri("http://example.com:444")));
Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://something.com")).AbsoluteUri);
Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://www.something2.com")).AbsoluteUri);
}
finally
{
CleanProxyEnv();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesGetProxyEmptyHttpsProxy()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
var proxy = new RunnerWebProxy();
Assert.Null(proxy.GetProxy(new Uri("https://github.com/owner/repo")));
Assert.Null(proxy.GetProxy(new Uri("https://mails.google.com")));
Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://something.com")).AbsoluteUri);
Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://www.something2.com")).AbsoluteUri);
}
finally
{
CleanProxyEnv();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesNoProxy()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
Environment.SetEnvironmentVariable("no_proxy", "github.com, .google.com, example.com:444, 192.168.0.123:123, 192.168.1.123");
var proxy = new RunnerWebProxy();
Assert.False(proxy.IsBypassed(new Uri("https://actions.com")));
Assert.False(proxy.IsBypassed(new Uri("https://ggithub.com")));
Assert.False(proxy.IsBypassed(new Uri("https://github.comm")));
Assert.False(proxy.IsBypassed(new Uri("https://google.com")));
Assert.False(proxy.IsBypassed(new Uri("https://example.com")));
Assert.False(proxy.IsBypassed(new Uri("http://example.com:333")));
Assert.False(proxy.IsBypassed(new Uri("http://192.168.0.123:123")));
Assert.False(proxy.IsBypassed(new Uri("http://192.168.1.123/home")));
Assert.True(proxy.IsBypassed(new Uri("https://github.com")));
Assert.True(proxy.IsBypassed(new Uri("https://GITHUB.COM")));
Assert.True(proxy.IsBypassed(new Uri("https://github.com/owner/repo")));
Assert.True(proxy.IsBypassed(new Uri("https://actions.github.com")));
Assert.True(proxy.IsBypassed(new Uri("https://mails.google.com")));
Assert.True(proxy.IsBypassed(new Uri("https://MAILS.GOOGLE.com")));
Assert.True(proxy.IsBypassed(new Uri("https://mails.v2.google.com")));
Assert.True(proxy.IsBypassed(new Uri("http://mails.v2.v3.google.com/inbox")));
Assert.True(proxy.IsBypassed(new Uri("https://example.com:444")));
Assert.True(proxy.IsBypassed(new Uri("http://example.com:444")));
Assert.True(proxy.IsBypassed(new Uri("http://example.COM:444")));
}
finally
{
CleanProxyEnv();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WebProxyFromEnvironmentVariablesGetProxy()
{
try
{
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
Environment.SetEnvironmentVariable("no_proxy", "github.com, .google.com, example.com:444");
var proxy = new RunnerWebProxy();
Assert.Null(proxy.GetProxy(new Uri("http://github.com")));
Assert.Null(proxy.GetProxy(new Uri("https://github.com/owner/repo")));
Assert.Null(proxy.GetProxy(new Uri("https://mails.google.com")));
Assert.Null(proxy.GetProxy(new Uri("http://example.com:444")));
Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://something.com")).AbsoluteUri);
Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://www.something2.com")).AbsoluteUri);
Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://something.com")).AbsoluteUri);
Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://www.something2.com")).AbsoluteUri);
}
finally
{
CleanProxyEnv();
}
}
private void CleanProxyEnv()
{
Environment.SetEnvironmentVariable("http_proxy", null);
Environment.SetEnvironmentVariable("https_proxy", null);
Environment.SetEnvironmentVariable("HTTP_PROXY", null);
Environment.SetEnvironmentVariable("HTTPS_PROXY", null);
Environment.SetEnvironmentVariable("no_proxy", null);
Environment.SetEnvironmentVariable("NO_PROXY", null);
}
}
}

View File

@@ -90,6 +90,8 @@ namespace GitHub.Runner.Common.Tests
public ProductInfoHeaderValue UserAgent => new ProductInfoHeaderValue("L0Test", "0.0");
public RunnerWebProxy WebProxy => new RunnerWebProxy();
public async Task Delay(TimeSpan delay, CancellationToken token)
{
await Task.Delay(TimeSpan.Zero);
@@ -274,24 +276,6 @@ namespace GitHub.Runner.Common.Tests
".certificates");
break;
case WellKnownConfigFile.Proxy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxy");
break;
case WellKnownConfigFile.ProxyCredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxycredentials");
break;
case WellKnownConfigFile.ProxyBypass:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxybypass");
break;
case WellKnownConfigFile.Options:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),

View File

@@ -1688,10 +1688,6 @@ runs:
_hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object);
_hc.SetSingleton<IActionManifestManager>(actionManifest);
var proxy = new RunnerWebProxy();
proxy.Initialize(_hc);
_hc.SetSingleton<IRunnerWebProxy>(proxy);
_configurationStore = new Mock<IConfigurationStore>();
_configurationStore
.Setup(x => x.GetSettings())

View File

@@ -256,10 +256,6 @@ namespace GitHub.Runner.Common.Tests.Worker
configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings());
hc.SetSingleton(configurationStore.Object);
// Arrange: Setup the proxy configation.
var proxy = new Mock<IRunnerWebProxy>();
hc.SetSingleton(proxy.Object);
// Arrange: Setup the cert configation.
var cert = new Mock<IRunnerCertificateManager>();
hc.SetSingleton(cert.Object);

View File

@@ -22,7 +22,6 @@ namespace GitHub.Runner.Common.Tests.Worker
private CancellationTokenSource _tokenSource;
private Mock<IJobServer> _jobServer;
private Mock<IJobServerQueue> _jobServerQueue;
private Mock<IRunnerWebProxy> _proxyConfig;
private Mock<IRunnerCertificateManager> _cert;
private Mock<IConfigurationStore> _config;
private Mock<IExtensionManager> _extensions;
@@ -43,7 +42,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_jobExtension = new Mock<IJobExtension>();
_jobServer = new Mock<IJobServer>();
_jobServerQueue = new Mock<IJobServerQueue>();
_proxyConfig = new Mock<IRunnerWebProxy>();
_cert = new Mock<IRunnerCertificateManager>();
_stepRunner = new Mock<IStepsRunner>();
_logger = new Mock<IPagingLogger>();
@@ -95,9 +93,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_jobExtension.Setup(x => x.InitializeJob(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.AgentJobRequestMessage>())).
Returns(Task.FromResult(_initResult));
_proxyConfig.Setup(x => x.ProxyAddress)
.Returns(string.Empty);
var settings = new RunnerSettings
{
AgentId = 1,
@@ -114,7 +109,6 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.SetSingleton(_config.Object);
hc.SetSingleton(_jobServer.Object);
hc.SetSingleton(_jobServerQueue.Object);
hc.SetSingleton(_proxyConfig.Object);
hc.SetSingleton(_cert.Object);
hc.SetSingleton(_stepRunner.Object);
hc.SetSingleton(_extensions.Object);

View File

@@ -16,14 +16,12 @@ namespace GitHub.Runner.Common.Tests.Worker
{
private Mock<IProcessChannel> _processChannel;
private Mock<IJobRunner> _jobRunner;
private Mock<IRunnerWebProxy> _proxy;
private Mock<IRunnerCertificateManager> _cert;
public WorkerL0()
{
_processChannel = new Mock<IProcessChannel>();
_jobRunner = new Mock<IJobRunner>();
_proxy = new Mock<IRunnerWebProxy>();
_cert = new Mock<IRunnerCertificateManager>();
}
@@ -92,7 +90,6 @@ namespace GitHub.Runner.Common.Tests.Worker
var worker = new GitHub.Runner.Worker.Worker();
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
hc.EnqueueInstance<IJobRunner>(_jobRunner.Object);
hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
worker.Initialize(hc);
var jobMessage = CreateJobRequestMessage("job1");
@@ -145,7 +142,6 @@ namespace GitHub.Runner.Common.Tests.Worker
var worker = new GitHub.Runner.Worker.Worker();
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
hc.EnqueueInstance<IJobRunner>(_jobRunner.Object);
hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
worker.Initialize(hc);
var jobMessage = CreateJobRequestMessage("job1");