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 ProxyBypassList { get; } IWebProxy WebProxy { get; } } public class RunnerWebProxy : RunnerService, IRunnerWebProxy { private readonly List _regExBypassList = new List(); private readonly List _bypassList = new List(); 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 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(); 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(); 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(); 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."); } } } }