From 9f778b814d356321d894209a440a71fb15ff0228 Mon Sep 17 00:00:00 2001 From: Luke Tomlinson Date: Tue, 28 Mar 2023 15:45:00 -0400 Subject: [PATCH] Register Runners against V2 servers (#2505) * Parse runners and send publicKey * wip * Fix conflicts * Cleanup * Cleanup * fix test * fix test * Add trace for broker message listener * Feedback * refactor * remove dead code * Remove old comment --- src/Runner.Common/RunnerDotcomServer.cs | 10 ++-- src/Runner.Listener/BrokerMessageListener.cs | 5 ++ .../Configuration/ConfigurationManager.cs | 50 +++++++++++------- src/Runner.Listener/Runner.cs | 1 + .../DTWebApi/WebApi/ListRunnersResponse.cs | 2 - src/Sdk/DTWebApi/WebApi/Runner.cs | 51 +++++++++++++++++-- .../Configuration/ConfigurationManagerL0.cs | 9 +++- 7 files changed, 99 insertions(+), 29 deletions(-) diff --git a/src/Runner.Common/RunnerDotcomServer.cs b/src/Runner.Common/RunnerDotcomServer.cs index 510877e2e..41cc0816d 100644 --- a/src/Runner.Common/RunnerDotcomServer.cs +++ b/src/Runner.Common/RunnerDotcomServer.cs @@ -17,7 +17,7 @@ namespace GitHub.Runner.Common { Task> GetRunnersAsync(int runnerGroupId, string githubUrl, string githubToken, string agentName); - Task AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken); + Task AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey); Task> GetRunnerGroupsAsync(string githubUrl, string githubToken); string GetGitHubRequestId(HttpResponseHeaders headers); @@ -136,7 +136,7 @@ namespace GitHub.Runner.Common return agentPools?.ToAgentPoolList(); } - public async Task AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken) + public async Task AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey) { var gitHubUrlBuilder = new UriBuilder(githubUrl); var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries); @@ -159,12 +159,12 @@ namespace GitHub.Runner.Common {"updates_disabled", agent.DisableUpdate}, {"ephemeral", agent.Ephemeral}, {"labels", agent.Labels}, + {"public_key", publicKey} }; var body = new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"); - var responseAgent = await RetryRequest(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body); - agent.Id = responseAgent.Id; - return agent; + + return await RetryRequest(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body); } private async Task RetryRequest(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null) diff --git a/src/Runner.Listener/BrokerMessageListener.cs b/src/Runner.Listener/BrokerMessageListener.cs index 93259c8e5..aa3e81886 100644 --- a/src/Runner.Listener/BrokerMessageListener.cs +++ b/src/Runner.Listener/BrokerMessageListener.cs @@ -196,6 +196,11 @@ namespace GitHub.Runner.Listener var configManager = HostContext.GetService(); _settings = configManager.LoadSettings(); + if (_settings.ServerUrlV2 == null) + { + throw new InvalidOperationException("ServerUrlV2 is not set"); + } + var credMgr = HostContext.GetService(); VssCredentials creds = credMgr.LoadCredentials(); await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds); diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 492bea042..0c835504a 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -181,9 +181,11 @@ namespace GitHub.Runner.Listener.Configuration // We want to use the native CSP of the platform for storage, so we use the RSACSP directly RSAParameters publicKey; var keyManager = HostContext.GetService(); + string publicKeyXML; using (var rsa = keyManager.CreateKey()) { publicKey = rsa.ExportParameters(false); + publicKeyXML = rsa.ToXmlString(includePrivateParameters: false); } _term.WriteSection("Runner Registration"); @@ -297,7 +299,15 @@ namespace GitHub.Runner.Listener.Configuration { if (runnerSettings.UseV2Flow) { - agent = await _dotcomServer.AddRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken); + var runner = await _dotcomServer.AddRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML); + runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl; + + agent.Id = runner.Id; + agent.Authorization = new TaskAgentAuthorization() + { + AuthorizationUrl = runner.RunnerAuthorization.AuthorizationUrl, + ClientId = new Guid(runner.RunnerAuthorization.ClientId) + }; } else { @@ -354,24 +364,28 @@ namespace GitHub.Runner.Listener.Configuration } // Testing agent connection, detect any potential connection issue, like local clock skew that cause OAuth token expired. - var credMgr = HostContext.GetService(); - VssCredentials credential = credMgr.LoadCredentials(); - try + + if (!runnerSettings.UseV2Flow) { - await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential); - // ConnectAsync() hits _apis/connectionData which is an anonymous endpoint - // Need to hit an authenticate endpoint to trigger OAuth token exchange. - await _runnerServer.GetAgentPoolsAsync(); - _term.WriteSuccessMessage("Runner connection is good"); - } - catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is")) - { - // there are two exception messages server send that indicate clock skew. - // 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}. - // 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}. - Trace.Error("Catch exception during test agent connection."); - Trace.Error(ex); - throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again."); + var credMgr = HostContext.GetService(); + VssCredentials credential = credMgr.LoadCredentials(); + try + { + await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential); + // ConnectAsync() hits _apis/connectionData which is an anonymous endpoint + // Need to hit an authenticate endpoint to trigger OAuth token exchange. + await _runnerServer.GetAgentPoolsAsync(); + _term.WriteSuccessMessage("Runner connection is good"); + } + catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is")) + { + // there are two exception messages server send that indicate clock skew. + // 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}. + // 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}. + Trace.Error("Catch exception during test agent connection."); + Trace.Error(ex); + throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again."); + } } _term.WriteSection("Runner settings"); diff --git a/src/Runner.Listener/Runner.cs b/src/Runner.Listener/Runner.cs index e95379ad3..865c596a9 100644 --- a/src/Runner.Listener/Runner.cs +++ b/src/Runner.Listener/Runner.cs @@ -343,6 +343,7 @@ namespace GitHub.Runner.Listener { if (settings.UseV2Flow) { + Trace.Info($"Using BrokerMessageListener"); var brokerListener = new BrokerMessageListener(); brokerListener.Initialize(HostContext); return brokerListener; diff --git a/src/Sdk/DTWebApi/WebApi/ListRunnersResponse.cs b/src/Sdk/DTWebApi/WebApi/ListRunnersResponse.cs index 292b8d668..ddc83813f 100644 --- a/src/Sdk/DTWebApi/WebApi/ListRunnersResponse.cs +++ b/src/Sdk/DTWebApi/WebApi/ListRunnersResponse.cs @@ -41,8 +41,6 @@ namespace GitHub.DistributedTask.WebApi public List ToTaskAgents() { - List taskAgents = new List(); - return Runners.Select(runner => new TaskAgent() { Name = runner.Name }).ToList(); } } diff --git a/src/Sdk/DTWebApi/WebApi/Runner.cs b/src/Sdk/DTWebApi/WebApi/Runner.cs index 2103bf3a2..dc871c1ca 100644 --- a/src/Sdk/DTWebApi/WebApi/Runner.cs +++ b/src/Sdk/DTWebApi/WebApi/Runner.cs @@ -1,12 +1,44 @@ +using System; using Newtonsoft.Json; namespace GitHub.DistributedTask.WebApi { public class Runner { - /// - /// Name of the agent - /// + + public class Authorization + { + /// + /// The url to refresh tokens + /// + [JsonProperty("authorization_url")] + public Uri AuthorizationUrl + { + get; + internal set; + } + + /// + /// The url to connect to to poll for messages + /// + [JsonProperty("server_url")] + public string ServerUrl + { + get; + internal set; + } + + /// + /// The client id to use when connecting to the authorization_url + /// + [JsonProperty("client_id")] + public string ClientId + { + get; + internal set; + } + } + [JsonProperty("name")] public string Name { @@ -14,5 +46,18 @@ namespace GitHub.DistributedTask.WebApi internal set; } + [JsonProperty("id")] + public Int32 Id + { + get; + internal set; + } + + [JsonProperty("authorization")] + public Authorization RunnerAuthorization + { + get; + internal set; + } } } diff --git a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs index 19bf0250c..129183900 100644 --- a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs +++ b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs @@ -73,6 +73,13 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration AuthorizationUrl = new Uri("http://localhost:8080/pipelines"), }; + var expectedRunner = new GitHub.DistributedTask.WebApi.Runner() { Name = expectedAgent.Name, Id = 1 }; + expectedRunner.RunnerAuthorization = new GitHub.DistributedTask.WebApi.Runner.Authorization + { + ClientId = expectedAgent.Authorization.ClientId.ToString(), + AuthorizationUrl = new Uri("http://localhost:8080/pipelines"), + }; + var connectionData = new ConnectionData() { InstanceId = Guid.NewGuid(), @@ -110,7 +117,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration _dotcomServer.Setup(x => x.GetRunnersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedAgents)); _dotcomServer.Setup(x => x.GetRunnerGroupsAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedPools)); - _dotcomServer.Setup(x => x.AddRunnerAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedAgent)); + _dotcomServer.Setup(x => x.AddRunnerAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedRunner)); rsa = new RSACryptoServiceProvider(2048);