diff --git a/src/Runner.Common/GitHubServer.cs b/src/Runner.Common/GitHubServer.cs new file mode 100644 index 000000000..1b765fd5c --- /dev/null +++ b/src/Runner.Common/GitHubServer.cs @@ -0,0 +1,67 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Services.Common; + +namespace GitHub.Runner.Common +{ + + public class GitHubResult + { + public HttpStatusCode StatusCode { get; set; } + public String Message { get; set; } + public HttpResponseHeaders Headers { get; set; } + } + + + [ServiceLocator(Default = typeof(GitHubServer))] + public interface IGitHubServer : IRunnerService + { + Task RevokeInstallationToken(string GithubApiUrl, string AccessToken); + } + + public class GitHubServer : RunnerService, IGitHubServer + { + public async Task RevokeInstallationToken(string GithubApiUrl, string AccessToken) + { + var result = new GitHubResult(); + var requestUrl = new UriBuilder(GithubApiUrl); + requestUrl.Path = requestUrl.Path.TrimEnd('/') + "/installation/token"; + + using (var httpClientHandler = HostContext.CreateHttpClientHandler()) + using (var httpClient = HttpClientFactory.Create(httpClientHandler, new VssHttpRetryMessageHandler(3))) + { + httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent); + var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{AccessToken}")); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken); + var count = 1; + while (true) + { + try + { + using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Delete, requestUrl.Uri)) + { + requestMessage.Headers.Add("Accept", "application/vnd.github.gambit-preview+json"); + var response = await httpClient.SendAsync(requestMessage, CancellationToken.None); + result.StatusCode = response.StatusCode; + result.Headers = response.Headers; + result.Message = await response.Content.ReadAsStringAsync(); + return result; + } + } + catch (Exception ex) when (count++ < 3) + { + Trace.Error("Fail to revoke GITHUB_TOKEN, will try again later"); + Trace.Error(ex); + var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15)); + await Task.Delay(backoff); + } + } + } + } + } +} diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 232ec18cf..2140004a9 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -33,7 +34,7 @@ namespace GitHub.Runner.Worker public interface IJobExtension : IRunnerService { Task> InitializeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message); - void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc); + Task FinalizeJobAsync(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc); } public sealed class JobExtension : RunnerService, IJobExtension @@ -311,7 +312,7 @@ namespace GitHub.Runner.Worker } } - public void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) + public async Task FinalizeJobAsync(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) { Trace.Entering(); ArgUtil.NotNull(jobContext, nameof(jobContext)); @@ -325,6 +326,39 @@ namespace GitHub.Runner.Worker context.Start(); context.Debug("Starting: Complete job"); + // Revoke GITHUB_TOKEN + context.Debug("Revoking GITHUB_TOKEN"); + var githubApiUrl = context.GetGitHubContext("api_url"); + if (string.IsNullOrEmpty(githubApiUrl)) + { + githubApiUrl = "https://api.github.com"; + } + + var githubToken = context.GetGitHubContext("token"); + try + { + var githubServer = HostContext.GetService(); + var result = await githubServer.RevokeInstallationToken(githubApiUrl, githubToken); + if (result.StatusCode == HttpStatusCode.NoContent) + { + context.Debug("GITHUB_TOKEN revoked"); + } + else if (result.StatusCode == HttpStatusCode.Unauthorized) + { + context.Debug("GITHUB_TOKEN already expired"); + } + else + { + Trace.Error("Fail to revoke GITHUB_TOKEN"); + Trace.Error(result.Message); + } + } + catch (Exception ex) + { + Trace.Error("Fail to revoke GITHUB_TOKEN"); + Trace.Error(ex); + } + // Evaluate job outputs if (message.JobOutputs != null && message.JobOutputs.Type != TokenType.Null) { diff --git a/src/Runner.Worker/JobRunner.cs b/src/Runner.Worker/JobRunner.cs index a94d7dd3d..49b98a8ed 100644 --- a/src/Runner.Worker/JobRunner.cs +++ b/src/Runner.Worker/JobRunner.cs @@ -184,7 +184,7 @@ namespace GitHub.Runner.Worker finally { Trace.Info("Finalize job."); - jobExtension.FinalizeJob(jobContext, message, jobStartTimeUtc); + await jobExtension.FinalizeJobAsync(jobContext, message, jobStartTimeUtc); } Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}"); diff --git a/src/Test/L0/Worker/JobExtensionL0.cs b/src/Test/L0/Worker/JobExtensionL0.cs index a8c4573e6..353abfa1e 100644 --- a/src/Test/L0/Worker/JobExtensionL0.cs +++ b/src/Test/L0/Worker/JobExtensionL0.cs @@ -207,7 +207,7 @@ namespace GitHub.Runner.Common.Tests.Worker [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] - public void UploadDiganosticLogIfEnvironmentVariableSet() + public async Task UploadDiganosticLogIfEnvironmentVariableSet() { using (TestHostContext hc = CreateTestContext()) { @@ -220,7 +220,7 @@ namespace GitHub.Runner.Common.Tests.Worker _jobEc.Initialize(hc); _jobEc.InitializeJob(_message, _tokenSource.Token); - jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow); + await jobExtension.FinalizeJobAsync(_jobEc, _message, DateTime.UtcNow); _diagnosticLogManager.Verify(x => x.UploadDiagnosticLogs( @@ -235,7 +235,7 @@ namespace GitHub.Runner.Common.Tests.Worker [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] - public void DontUploadDiagnosticLogIfEnvironmentVariableFalse() + public async Task DontUploadDiagnosticLogIfEnvironmentVariableFalse() { using (TestHostContext hc = CreateTestContext()) { @@ -248,7 +248,7 @@ namespace GitHub.Runner.Common.Tests.Worker _jobEc.Initialize(hc); _jobEc.InitializeJob(_message, _tokenSource.Token); - jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow); + await jobExtension.FinalizeJobAsync(_jobEc, _message, DateTime.UtcNow); _diagnosticLogManager.Verify(x => x.UploadDiagnosticLogs( @@ -263,14 +263,14 @@ namespace GitHub.Runner.Common.Tests.Worker [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] - public void DontUploadDiagnosticLogIfEnvironmentVariableMissing() + public async Task DontUploadDiagnosticLogIfEnvironmentVariableMissing() { using (TestHostContext hc = CreateTestContext()) { var jobExtension = new JobExtension(); jobExtension.Initialize(hc); - jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow); + await jobExtension.FinalizeJobAsync(_jobEc, _message, DateTime.UtcNow); _diagnosticLogManager.Verify(x => x.UploadDiagnosticLogs(