mirror of
https://github.com/actions/runner.git
synced 2025-12-13 00:36:29 +00:00
Add Proxy Support for self-hosted runner. (#206)
This commit is contained in:
@@ -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)
|
||||
|
||||
224
src/Runner.Sdk/RunnerWebProxy.cs
Normal file
224
src/Runner.Sdk/RunnerWebProxy.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user