Skip to content

Commit

Permalink
prototype auth with GitHub. (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
TingluoHuang authored Sep 18, 2019
1 parent 68893b3 commit 7c835a5
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 14 deletions.
3 changes: 3 additions & 0 deletions src/Runner.Common/ConfigurationStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public sealed class RunnerSettings
[DataMember(EmitDefaultValue = false)]
public string ServerUrl { get; set; }

[DataMember(EmitDefaultValue = false)]
public string GitHubUrl { get; set; }

[DataMember(EmitDefaultValue = false)]
public string WorkFolder { get; set; }

Expand Down
4 changes: 2 additions & 2 deletions src/Runner.Listener/CommandSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public string GetToken()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "personal access token",
description: "GitHub PAT",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
Expand All @@ -230,7 +230,7 @@ public string GetUrl(bool suppressPromptIfEmpty = false)

return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Url,
description: "GitHub organization URL",
description: "GitHub Repository URL",
defaultValue: string.Empty,
validator: Validators.ServerUrlValidator);
}
Expand Down
96 changes: 88 additions & 8 deletions src/Runner.Listener/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
using System.Runtime.InteropServices;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

namespace GitHub.Runner.Listener.Configuration
{
Expand Down Expand Up @@ -67,6 +70,29 @@ public RunnerSettings LoadSettings()

public async Task ConfigureAsync(CommandSettings command)
{
#if !OS_WINDOWS
_term.WriteLine("\x1b[1;97m┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ ____ _ _ _ _ _ _ _ _ ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ / ___(_) |_| | | |_ _| |__ / \\ ___| |_(_) ___ _ __ ___ ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ | | _| | __| |_| | | | | '_ \\ / _ \\ / __| __| |/ _ \\| '_ \\/ __| ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ | |_| | | |_| _ | |_| | |_) | / ___ \\ (__| |_| | (_) | | | \\__ \\\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ \\____|_|\\__|_| |_|\\__,_|_.__/ /_/ \\_\\___|\\__|_|\\___/|_| |_|___/ ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ \x1b[1;34mSelf-hosted runner registration\x1b[1;97m ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┃ ┃\x1b[0m");
_term.WriteLine("\x1b[1;97m┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\x1b[0m");
#else
_term.WriteLine("--------------------------------------------------------------------------------");
_term.WriteLine("| ____ _ _ _ _ _ _ _ _ |");
_term.WriteLine("| / ___(_) |_| | | |_ _| |__ / \\ ___| |_(_) ___ _ __ ___ |");
_term.WriteLine("| | | _| | __| |_| | | | | '_ \\ / _ \\ / __| __| |/ _ \\| '_ \\/ __| |");
_term.WriteLine("| | |_| | | |_| _ | |_| | |_) | / ___ \\ (__| |_| | (_) | | | \\__ \\ |");
_term.WriteLine("| \\____|_|\\__|_| |_|\\__,_|_.__/ /_/ \\_\\___|\\__|_|\\___/|_| |_|___/ |");
_term.WriteLine("| |");
_term.WriteLine("| Self-hosted runner registration |");
_term.WriteLine("| |");
_term.WriteLine("--------------------------------------------------------------------------------");
#endif
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info(nameof(ConfigureAsync));
if (IsConfigured())
Expand Down Expand Up @@ -150,12 +176,25 @@ public async Task ConfigureAsync(CommandSettings command)
while (true)
{
// Get the URL
runnerSettings.ServerUrl = command.GetUrl();
var inputUrl = command.GetUrl();
if (!inputUrl.Contains("github.com", StringComparison.OrdinalIgnoreCase))
{
runnerSettings.ServerUrl = inputUrl;
// Get the credentials
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("legacy vss cred retrieved");
}
else
{
runnerSettings.GitHubUrl = inputUrl;
var githubToken = command.GetToken();
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, githubToken);
runnerSettings.ServerUrl = authResult.TenantUrl;
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
}

// Get the credentials
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("cred retrieved");
try
{
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
Expand Down Expand Up @@ -440,9 +479,20 @@ public async Task UnconfigureAsync(CommandSettings command)
var credentialManager = HostContext.GetService<ICredentialManager>();

// Get the credentials
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
VssCredentials creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("cred retrieved");
VssCredentials creds = null;
if (string.IsNullOrEmpty(settings.GitHubUrl))
{
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("legacy vss cred retrieved");
}
else
{
var githubToken = command.GetToken();
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken);
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
}

// Determine the service deployment type based on connection data. (Hosted/OnPremises)
bool isHostedServer = await IsHostedServer(settings.ServerUrl, creds);
Expand Down Expand Up @@ -613,5 +663,35 @@ private async Task<bool> IsHostedServer(string serverUrl, VssCredentials credent
return true;
}
}

private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken)
{
var gitHubUrl = new UriBuilder(githubUrl);
var githubApiUrl = $"https://api.github.com/repos/{gitHubUrl.Path.Trim('/')}/actions-runners/registration";
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler))
{
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"github:{githubToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.shuri-preview+json"));
var response = await httpClient.PostAsync(githubApiUrl, new StringContent("", null, "application/json"));

if (response.IsSuccessStatusCode)
{
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubAuthResult>(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;
}
}
}
}
}
31 changes: 31 additions & 0 deletions src/Runner.Listener/Configuration/CredentialManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.OAuth;

namespace GitHub.Runner.Listener.Configuration
{
Expand Down Expand Up @@ -61,4 +64,32 @@ public VssCredentials LoadCredentials()
return creds;
}
}

[DataContract]
public sealed class GitHubAuthResult
{
[DataMember(Name = "url")]
public string TenantUrl { get; set; }

[DataMember(Name = "token_schema")]
public string TokenSchema { get; set; }

[DataMember(Name = "token")]
public string Token { get; set; }

public VssCredentials ToVssCredentials()
{
ArgUtil.NotNullOrEmpty(TokenSchema, nameof(TokenSchema));
ArgUtil.NotNullOrEmpty(Token, nameof(Token));

if (string.Equals(TokenSchema, "OAuthAccessToken", StringComparison.OrdinalIgnoreCase))
{
return new VssCredentials(null, new VssOAuthAccessTokenCredential(Token), CredentialPromptType.DoNotPrompt);
}
else
{
throw new NotSupportedException($"Not supported token schema: {TokenSchema}");
}
}
}
}
8 changes: 4 additions & 4 deletions src/Test/L0/Listener/CommandSettingsL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ public void PromptsForToken()
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Token, // argName
"personal access token", // description
"GitHub PAT", // description
true, // secret
string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator
Expand All @@ -527,7 +527,7 @@ public void PromptsForUrl()
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Url, // argName
"GitHub organization URL", // description
"GitHub Repository URL", // description
false, // secret
string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator
Expand Down Expand Up @@ -665,7 +665,7 @@ public void PromptsWhenEmpty()
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Url, // argName
"GitHub organization URL", // description
"GitHub Repository URL", // description
false, // secret
string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator
Expand Down Expand Up @@ -694,7 +694,7 @@ public void PromptsWhenInvalid()
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Url, // argName
"GitHub organization URL", // description
"GitHub Repository URL", // description
false, // secret
string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator
Expand Down

0 comments on commit 7c835a5

Please sign in to comment.