diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index 9ccfe0200..e1ba00284 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -99,9 +99,11 @@ namespace GitHub.Runner.Common // Secret args. Must be added to the "Secrets" getter as well. public static readonly string Token = "token"; + public static readonly string PAT = "pat"; public static readonly string WindowsLogonPassword = "windowslogonpassword"; public static string[] Secrets => new[] { + PAT, Token, WindowsLogonPassword, }; diff --git a/src/Runner.Listener/CommandSettings.cs b/src/Runner.Listener/CommandSettings.cs index c7ad6b3f4..e13569fd4 100644 --- a/src/Runner.Listener/CommandSettings.cs +++ b/src/Runner.Listener/CommandSettings.cs @@ -177,6 +177,11 @@ namespace GitHub.Runner.Listener validator: Validators.NonEmptyValidator); } + public string GetGitHubPersonalAccessToken() + { + return GetArg(name: Constants.Runner.CommandLine.Args.PAT); + } + public string GetRunnerRegisterToken() { return GetArgOrPrompt( diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 7e05ae373..f42e19c9d 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -7,11 +7,13 @@ using GitHub.Services.OAuth; using GitHub.Services.WebApi; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.InteropServices; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace GitHub.Runner.Listener.Configuration @@ -107,8 +109,21 @@ namespace GitHub.Runner.Listener.Configuration else { runnerSettings.GitHubUrl = inputUrl; - var githubToken = command.GetRunnerRegisterToken(); - GitHubAuthResult authResult = await GetTenantCredential(inputUrl, githubToken, Constants.RunnerEvent.Register); + var githubPAT = command.GetGitHubPersonalAccessToken(); + var registerToken = string.Empty; + if (!string.IsNullOrEmpty(githubPAT)) + { + Trace.Info("Retriving runner register token using GitHub PAT."); + var jitToken = await GetJITRunnerRegisterTokenAsync(inputUrl, githubPAT); + Trace.Info($"Retrived runner register token is good to {jitToken.ExpiresAt}."); + HostContext.SecretMasker.AddValue(jitToken.Token); + registerToken = jitToken.Token; + } + if (string.IsNullOrEmpty(registerToken)) + { + registerToken = command.GetRunnerRegisterToken(); + } + GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register); runnerSettings.ServerUrl = authResult.TenantUrl; creds = authResult.ToVssCredentials(); Trace.Info("cred retrieved via GitHub auth"); @@ -502,6 +517,66 @@ namespace GitHub.Runner.Listener.Configuration string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase); } + private async Task GetJITRunnerRegisterTokenAsync(string githubUrl, string githubToken) + { + var githubApiUrl = ""; + var gitHubUrlBuilder = new UriBuilder(githubUrl); + var path = gitHubUrlBuilder.Path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + if (path.Length == 1) + { + if (IsHostedServer(gitHubUrlBuilder)) + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners/registration-token"; + } + else + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners/registration-token"; + } + } + if (path.Length == 2) + { + if (IsHostedServer(gitHubUrlBuilder)) + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runners/registration-token"; + } + else + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runners/registration-token"; + } + } + else + { + throw new ArgumentException($"'{githubUrl}' should point to an org or repository."); + } + + using (var httpClientHandler = HostContext.CreateHttpClientHandler()) + using (var httpClient = new HttpClient(httpClientHandler)) + { + var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"github:{githubToken}")); + HostContext.SecretMasker.AddValue(base64EncodingToken); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken); + httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); + httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json"); + + var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty)); + + if (response.IsSuccessStatusCode) + { + Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'"); + var jsonResponse = await response.Content.ReadAsStringAsync(); + return StringUtil.ConvertFromJson(jsonResponse); + } + else + { + _term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'"); + var errorResponse = await response.Content.ReadAsStringAsync(); + _term.WriteError(errorResponse); + response.EnsureSuccessStatusCode(); + return null; + } + } + } + private async Task GetTenantCredential(string githubUrl, string githubToken, string runnerEvent) { var githubApiUrl = ""; diff --git a/src/Runner.Listener/Configuration/CredentialManager.cs b/src/Runner.Listener/Configuration/CredentialManager.cs index ee459abe7..e13d29510 100644 --- a/src/Runner.Listener/Configuration/CredentialManager.cs +++ b/src/Runner.Listener/Configuration/CredentialManager.cs @@ -71,6 +71,16 @@ namespace GitHub.Runner.Listener.Configuration } } + [DataContract] + public sealed class GitHubRunnerRegisterToken + { + [DataMember(Name = "token")] + public string Token { get; set; } + + [DataMember(Name = "expires_at")] + public string ExpiresAt { get; set; } + } + [DataContract] public sealed class GitHubAuthResult {