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

@@ -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;
}
}
}