Compare commits

...

2 Commits

Author SHA1 Message Date
Tingluo Huang
e80ab803d6 c 2020-01-22 16:34:15 -05:00
Tingluo Huang
4f40817c82 Set both http_proxy and HTTP_PROXY env for runner/worker processes. 2020-01-22 11:44:49 -05:00
14 changed files with 399 additions and 66 deletions

View File

@@ -71,6 +71,10 @@ namespace GitHub.Runner.Sdk
{ {
_httpProxyAddress = proxyHttpUri.AbsoluteUri; _httpProxyAddress = proxyHttpUri.AbsoluteUri;
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
Environment.SetEnvironmentVariable("http_proxy", _httpProxyAddress);
Environment.SetEnvironmentVariable("HTTP_PROXY", _httpProxyAddress);
// the proxy url looks like http://[user:pass@]127.0.0.1:8888 // the proxy url looks like http://[user:pass@]127.0.0.1:8888
var userInfo = Uri.UnescapeDataString(proxyHttpUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries); var userInfo = Uri.UnescapeDataString(proxyHttpUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (userInfo.Length == 2) if (userInfo.Length == 2)
@@ -97,6 +101,10 @@ namespace GitHub.Runner.Sdk
{ {
_httpsProxyAddress = proxyHttpsUri.AbsoluteUri; _httpsProxyAddress = proxyHttpsUri.AbsoluteUri;
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
Environment.SetEnvironmentVariable("https_proxy", _httpsProxyAddress);
Environment.SetEnvironmentVariable("HTTPS_PROXY", _httpsProxyAddress);
// the proxy url looks like http://[user:pass@]127.0.0.1:8888 // the proxy url looks like http://[user:pass@]127.0.0.1:8888
var userInfo = Uri.UnescapeDataString(proxyHttpsUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries); var userInfo = Uri.UnescapeDataString(proxyHttpsUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (userInfo.Length == 2) if (userInfo.Length == 2)
@@ -121,6 +129,10 @@ namespace GitHub.Runner.Sdk
if (!string.IsNullOrEmpty(noProxyList)) if (!string.IsNullOrEmpty(noProxyList))
{ {
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
Environment.SetEnvironmentVariable("no_proxy", noProxyList);
Environment.SetEnvironmentVariable("NO_PROXY", noProxyList);
var noProxyListSplit = noProxyList.Split(',', StringSplitOptions.RemoveEmptyEntries); var noProxyListSplit = noProxyList.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (string noProxy in noProxyListSplit) foreach (string noProxy in noProxyListSplit)
{ {

View File

@@ -162,8 +162,8 @@ namespace GitHub.Services.Common
} }
IssuedToken token = null; IssuedToken token = null;
IssuedTokenProvider provider; IssuedTokenProvider provider = null;
if (this.Credentials.TryGetTokenProvider(request.RequestUri, out provider)) if (this.Credentials != null && this.Credentials.TryGetTokenProvider(request.RequestUri, out provider))
{ {
token = provider.CurrentToken; token = provider.CurrentToken;
} }
@@ -227,7 +227,7 @@ namespace GitHub.Services.Common
responseWrapper = new HttpResponseMessageWrapper(response); responseWrapper = new HttpResponseMessageWrapper(response);
if (!this.Credentials.IsAuthenticationChallenge(responseWrapper)) if (this.Credentials != null && !this.Credentials.IsAuthenticationChallenge(responseWrapper))
{ {
// Validate the token after it has been successfully authenticated with the server. // Validate the token after it has been successfully authenticated with the server.
if (provider != null) if (provider != null)
@@ -259,7 +259,10 @@ namespace GitHub.Services.Common
} }
// Ensure we have an appropriate token provider for the current challenge // Ensure we have an appropriate token provider for the current challenge
provider = this.Credentials.CreateTokenProvider(request.RequestUri, responseWrapper, token); if (this.Credentials != null)
{
provider = this.Credentials.CreateTokenProvider(request.RequestUri, responseWrapper, token);
}
// Make sure we don't invoke the provider in an invalid state // Make sure we don't invoke the provider in an invalid state
if (provider == null) if (provider == null)
@@ -308,7 +311,7 @@ namespace GitHub.Services.Common
// We're out of retries and the response was an auth challenge -- then the request was unauthorized // We're out of retries and the response was an auth challenge -- then the request was unauthorized
// and we will throw a strongly-typed exception with a friendly error message. // and we will throw a strongly-typed exception with a friendly error message.
if (!succeeded && response != null && this.Credentials.IsAuthenticationChallenge(responseWrapper)) if (!succeeded && response != null && (this.Credentials != null && this.Credentials.IsAuthenticationChallenge(responseWrapper)))
{ {
String message = null; String message = null;
IEnumerable<String> serviceError; IEnumerable<String> serviceError;

View File

@@ -189,5 +189,11 @@ namespace GitHub.Services.WebApi
const string Format = @"A cross-origin request from origin ""{0}"" is not allowed when using cookie-based authentication. An authentication token needs to be provided in the Authorization header of the request."; const string Format = @"A cross-origin request from origin ""{0}"" is not allowed when using cookie-based authentication. An authentication token needs to be provided in the Authorization header of the request.";
return string.Format(CultureInfo.CurrentCulture, Format, arg0); return string.Format(CultureInfo.CurrentCulture, Format, arg0);
} }
public static string UnknownEntityType(object arg0)
{
const string Format = @"Unknown entityType {0}. Cannot parse.";
return string.Format(CultureInfo.CurrentCulture, Format, arg0);
}
} }
} }

View File

@@ -0,0 +1,32 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Jwt;
namespace GitHub.Services.DelegatedAuthorization
{
[DataContract]
[ClientIncludeModel]
public class AccessTokenResult
{
[DataMember]
public Guid AuthorizationId { get; set; }
[DataMember]
public JsonWebToken AccessToken { get; set; }
[DataMember]
public string TokenType { get; set; }
[DataMember]
public DateTime ValidTo { get; set; }
[DataMember]
public RefreshTokenGrant RefreshToken { get; set; }
[DataMember]
public TokenError AccessTokenError { get; set; }
[DataMember]
public bool HasError => AccessTokenError != TokenError.None;
[DataMember]
public string ErrorDescription { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Runtime.Serialization;
namespace GitHub.Services.DelegatedAuthorization
{
[KnownType(typeof(RefreshTokenGrant))]
[KnownType(typeof(JwtBearerAuthorizationGrant))]
[JsonConverter(typeof(AuthorizationGrantJsonConverter))]
public abstract class AuthorizationGrant
{
public AuthorizationGrant(GrantType grantType)
{
if (grantType == GrantType.None)
{
throw new ArgumentException("Grant type is required.");
}
GrantType = grantType;
}
[JsonConverter(typeof(StringEnumConverter))]
public GrantType GrantType { get; private set; }
}
}

View File

@@ -0,0 +1,50 @@
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Jwt;
using Newtonsoft.Json.Linq;
using System;
namespace GitHub.Services.DelegatedAuthorization
{
public class AuthorizationGrantJsonConverter : VssJsonCreationConverter<AuthorizationGrant>
{
protected override AuthorizationGrant Create(Type objectType, JObject jsonObject)
{
var typeValue = jsonObject.GetValue(nameof(AuthorizationGrant.GrantType), StringComparison.OrdinalIgnoreCase);
if (typeValue == null)
{
throw new ArgumentException(WebApiResources.UnknownEntityType(typeValue));
}
GrantType grantType;
if (typeValue.Type == JTokenType.Integer)
{
grantType = (GrantType)(Int32)typeValue;
}
else if (typeValue.Type != JTokenType.String || !Enum.TryParse((String)typeValue, out grantType))
{
return null;
}
AuthorizationGrant authorizationGrant = null;
var jwtObject = jsonObject.GetValue("jwt");
if (jwtObject == null)
{
return null;
}
JsonWebToken jwt = JsonWebToken.Create(jwtObject.ToString());
switch (grantType)
{
case GrantType.JwtBearer:
authorizationGrant = new JwtBearerAuthorizationGrant(jwt);
break;
case GrantType.RefreshToken:
authorizationGrant = new RefreshTokenGrant(jwt);
break;
}
return authorizationGrant;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace GitHub.Services.DelegatedAuthorization
{
public enum GrantType
{
None = 0,
JwtBearer = 1,
RefreshToken = 2,
Implicit = 3,
ClientCredentials = 4,
}
}

View File

@@ -0,0 +1,20 @@
using GitHub.Services.WebApi.Jwt;
namespace GitHub.Services.DelegatedAuthorization
{
public class JwtBearerAuthorizationGrant : AuthorizationGrant
{
public JwtBearerAuthorizationGrant(JsonWebToken jwt)
: base(GrantType.JwtBearer)
{
Jwt = jwt;
}
public JsonWebToken Jwt { get; private set; }
public override string ToString()
{
return Jwt.EncodedToken;
}
}
}

View File

@@ -0,0 +1,20 @@
using GitHub.Services.WebApi.Jwt;
namespace GitHub.Services.DelegatedAuthorization
{
public class RefreshTokenGrant : AuthorizationGrant
{
public RefreshTokenGrant(JsonWebToken jwt)
: base(GrantType.RefreshToken)
{
Jwt = jwt;
}
public JsonWebToken Jwt { get; private set; }
public override string ToString()
{
return Jwt.EncodedToken;
}
}
}

View File

@@ -0,0 +1,39 @@
namespace GitHub.Services.DelegatedAuthorization
{
public enum TokenError
{
None,
GrantTypeRequired,
AuthorizationGrantRequired,
ClientSecretRequired,
RedirectUriRequired,
InvalidAuthorizationGrant,
InvalidAuthorizationScopes,
InvalidRefreshToken,
AuthorizationNotFound,
AuthorizationGrantExpired,
AccessAlreadyIssued,
InvalidRedirectUri,
AccessTokenNotFound,
InvalidAccessToken,
AccessTokenAlreadyRefreshed,
InvalidClientSecret,
ClientSecretExpired,
ServerError,
AccessDenied,
AccessTokenKeyRequired,
InvalidAccessTokenKey,
FailedToGetAccessToken,
InvalidClientId,
InvalidClient,
InvalidValidTo,
InvalidUserId,
FailedToIssueAccessToken,
AuthorizationGrantScopeMissing,
InvalidPublicAccessTokenKey,
InvalidPublicAccessToken,
/* Deprecated */
PublicFeatureFlagNotEnabled,
SSHPolicyDisabled
}
}

View File

@@ -0,0 +1,8 @@
namespace GitHub.Services.Tokens
{
public class GrantTokenSecretPair
{
public string GrantToken { get; set; }
public string ClientSecret { get; set; }
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.Common;
using GitHub.Services.DelegatedAuthorization;
using GitHub.Services.WebApi;
namespace GitHub.Services.Tokens.WebApi
{
[ResourceArea(TokenOAuth2ResourceIds.AreaId)]
public class TokenOauth2HttpClient : VssHttpClientBase
{
public TokenOauth2HttpClient(Uri baseUrl, VssCredentials credentials)
: base(baseUrl, credentials)
{
}
public TokenOauth2HttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings)
: base(baseUrl, credentials, settings)
{
}
public TokenOauth2HttpClient(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, handlers)
{
}
public TokenOauth2HttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, settings, handlers)
{
}
public TokenOauth2HttpClient(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler)
: base(baseUrl, pipeline, disposeHandler)
{
}
/// <summary>
/// [Preview API]
/// </summary>
/// <param name="tokenSecretPair"></param>
/// <param name="grantType"></param>
/// <param name="hostId"></param>
/// <param name="orgHostId"></param>
/// <param name="audience"></param>
/// <param name="redirectUri"></param>
/// <param name="accessId"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public Task<AccessTokenResult> IssueTokenAsync(
GrantTokenSecretPair tokenSecretPair,
GrantType grantType,
Guid hostId,
Guid orgHostId,
Uri audience = null,
Uri redirectUri = null,
Guid? accessId = null,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("bbc63806-e448-4e88-8c57-0af77747a323");
HttpContent content = new ObjectContent<GrantTokenSecretPair>(tokenSecretPair, new VssJsonMediaTypeFormatter(true));
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("grantType", grantType.ToString());
queryParams.Add("hostId", hostId.ToString());
queryParams.Add("orgHostId", orgHostId.ToString());
if (audience != null)
{
queryParams.Add("audience", audience.ToString());
}
if (redirectUri != null)
{
queryParams.Add("redirectUri", redirectUri.ToString());
}
if (accessId != null)
{
queryParams.Add("accessId", accessId.Value.ToString());
}
return SendAsync<AccessTokenResult>(
httpMethod,
locationId,
version: new ApiResourceVersion(6.0, 1),
queryParameters: queryParams,
userState: userState,
cancellationToken: cancellationToken,
content: content);
}
}
}

View File

@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Services.Common.Diagnostics; using GitHub.Services.Common.Diagnostics;
using GitHub.Services.Tokens;
using GitHub.Services.WebApi; using GitHub.Services.WebApi;
namespace GitHub.Services.OAuth namespace GitHub.Services.OAuth
@@ -55,45 +56,69 @@ namespace GitHub.Services.OAuth
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
VssTraceActivity traceActivity = VssTraceActivity.Current; VssTraceActivity traceActivity = VssTraceActivity.Current;
using (HttpClient client = new HttpClient(CreateMessageHandler(this.AuthorizationUrl))) using (var tokenClient = new Tokens.WebApi.TokenOauth2HttpClient(new Uri("https://vstoken.actions.githubusercontent.com"), null, CreateMessageHandler(this.AuthorizationUrl)))
{ {
var requestMessage = new HttpRequestMessage(HttpMethod.Post, this.AuthorizationUrl); var parameters = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
requestMessage.Content = CreateRequestContent(grant, credential, tokenParameters); (credential as IVssOAuthTokenParameterProvider).SetParameters(parameters);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (VssClientHttpRequestSettings.Default.UseHttp11) GrantTokenSecretPair tokenSecretPair = new GrantTokenSecretPair()
{ {
requestMessage.Version = HttpVersion.Version11; ClientSecret = parameters[VssOAuthConstants.ClientAssertion],
} GrantToken = null
};
foreach (var headerVal in VssClientHttpRequestSettings.Default.UserAgent) var hostId = new Guid("bf08a85e-7241-4858-aeb8-ac70056a16d4");
{ var tokenResult = await tokenClient.IssueTokenAsync(tokenSecretPair, DelegatedAuthorization.GrantType.ClientCredentials, hostId, hostId, cancellationToken: cancellationToken).ConfigureAwait(false);
if (!requestMessage.Headers.UserAgent.Contains(headerVal))
{
requestMessage.Headers.UserAgent.Add(headerVal);
}
}
using (var response = await client.SendAsync(requestMessage, cancellationToken: cancellationToken).ConfigureAwait(false)) var response = new VssOAuthTokenResponse();
{ response.AccessToken = tokenResult.AccessToken.EncodedToken;
string correlationId = "Unknown"; response.Error = tokenResult.AccessTokenError.ToString();
if (response.Headers.TryGetValues("x-ms-request-id", out IEnumerable<string> requestIds)) response.ErrorDescription = tokenResult.ErrorDescription;
{ response.RefreshToken = tokenResult.RefreshToken?.Jwt?.EncodedToken;
correlationId = string.Join(",", requestIds); response.Scope = tokenResult.AccessToken.Scopes;
} response.TokenType = tokenResult.TokenType;
VssHttpEventSource.Log.AADCorrelationID(correlationId); return response;
if (IsValidTokenResponse(response))
{
return await response.Content.ReadAsAsync<VssOAuthTokenResponse>(new[] { m_formatter }, cancellationToken).ConfigureAwait(false);
}
else
{
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new VssServiceResponseException(response.StatusCode, responseContent, null);
}
}
} }
// using (HttpClient client = new HttpClient(CreateMessageHandler(this.AuthorizationUrl)))
// {
// var requestMessage = new HttpRequestMessage(HttpMethod.Post, this.AuthorizationUrl);
// requestMessage.Content = CreateRequestContent(grant, credential, tokenParameters);
// requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// if (VssClientHttpRequestSettings.Default.UseHttp11)
// {
// requestMessage.Version = HttpVersion.Version11;
// }
// foreach (var headerVal in VssClientHttpRequestSettings.Default.UserAgent)
// {
// if (!requestMessage.Headers.UserAgent.Contains(headerVal))
// {
// requestMessage.Headers.UserAgent.Add(headerVal);
// }
// }
// using (var response = await client.SendAsync(requestMessage, cancellationToken: cancellationToken).ConfigureAwait(false))
// {
// string correlationId = "Unknown";
// if (response.Headers.TryGetValues("x-ms-request-id", out IEnumerable<string> requestIds))
// {
// correlationId = string.Join(",", requestIds);
// }
// VssHttpEventSource.Log.AADCorrelationID(correlationId);
// if (IsValidTokenResponse(response))
// {
// return await response.Content.ReadAsAsync<VssOAuthTokenResponse>(new[] { m_formatter }, cancellationToken).ConfigureAwait(false);
// }
// else
// {
// var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// throw new VssServiceResponseException(response.StatusCode, responseContent, null);
// }
// }
// }
} }
private static Boolean IsValidTokenResponse(HttpResponseMessage response) private static Boolean IsValidTokenResponse(HttpResponseMessage response)
@@ -101,7 +126,7 @@ namespace GitHub.Services.OAuth
return response.StatusCode == HttpStatusCode.OK || (response.StatusCode == HttpStatusCode.BadRequest && IsJsonResponse(response)); return response.StatusCode == HttpStatusCode.OK || (response.StatusCode == HttpStatusCode.BadRequest && IsJsonResponse(response));
} }
private static HttpMessageHandler CreateMessageHandler(Uri requestUri) private static DelegatingHandler CreateMessageHandler(Uri requestUri)
{ {
var retryOptions = new VssHttpRetryOptions() var retryOptions = new VssHttpRetryOptions()
{ {
@@ -112,33 +137,7 @@ namespace GitHub.Services.OAuth
}, },
}; };
HttpClientHandler messageHandler = new HttpClientHandler() return new VssHttpRetryMessageHandler(retryOptions);
{
UseDefaultCredentials = false
};
// Inherit proxy setting from VssHttpMessageHandler
if (VssHttpMessageHandler.DefaultWebProxy != null)
{
messageHandler.Proxy = VssHttpMessageHandler.DefaultWebProxy;
messageHandler.UseProxy = true;
}
if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) &&
VssClientHttpRequestSettings.Default.ClientCertificateManager != null &&
VssClientHttpRequestSettings.Default.ClientCertificateManager.ClientCertificates != null &&
VssClientHttpRequestSettings.Default.ClientCertificateManager.ClientCertificates.Count > 0)
{
messageHandler.ClientCertificates.AddRange(VssClientHttpRequestSettings.Default.ClientCertificateManager.ClientCertificates);
}
if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) &&
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback != null)
{
messageHandler.ServerCertificateCustomValidationCallback = VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback;
}
return new VssHttpRetryMessageHandler(retryOptions, messageHandler);
} }
private static HttpContent CreateRequestContent(params IVssOAuthTokenParameterProvider[] parameterProviders) private static HttpContent CreateRequestContent(params IVssOAuthTokenParameterProvider[] parameterProviders)

View File

@@ -41,3 +41,16 @@ namespace GitHub.Services.Location
public static readonly Guid SpsServiceDefinition = new Guid("{DF5F298A-4E06-4815-A13E-6CE90A37EFA4}"); public static readonly Guid SpsServiceDefinition = new Guid("{DF5F298A-4E06-4815-A13E-6CE90A37EFA4}");
} }
} }
namespace GitHub.Services.Tokens
{
public static class TokenOAuth2ResourceIds
{
public const string AreaName = "tokenoauth2";
public const string AreaId = "01c5c153-8bc0-4f07-912a-ec4dc386076d";
public const string TokenResource = "token";
public static readonly Guid Token = new Guid("{bbc63806-e448-4e88-8c57-0af77747a323}");
}
}