mirror of
https://github.com/actions/runner.git
synced 2025-12-12 15:13:30 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fe3c90573 | ||
|
|
510fadf71a | ||
|
|
007ac8138b | ||
|
|
1e12b8909a | ||
|
|
9ceb3d481a | ||
|
|
3bce2eb09c | ||
|
|
80bf68db81 | ||
|
|
a2e32170fd | ||
|
|
35dda19491 |
@@ -1,12 +1,13 @@
|
|||||||
## Features
|
## Features
|
||||||
- N/A
|
- Add labels in the script that register runner (#844)
|
||||||
|
- Add proxy support for container actions (#840)
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- N/A
|
- Unset GTIHUB_ACTION_REPOSITORY and GITHUB_ACTION_REF for non-repo based actions #804
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Disabled add-path and set-env runner commands (#779)
|
- Crypto cleanup and enable usage of FIPS compliant crypto when required (#806)
|
||||||
- Updated dotnet install scripts (#779)
|
- Count actions resolve failures as infra failures (#851)
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.274.2
|
2.275.0
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ set -e
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# export RUNNER_CFG_PAT=<yourPAT>
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
# ./create-latest-svc scope [ghe_domain] [name] [user]
|
# ./create-latest-svc scope [ghe_domain] [name] [user] [labels]
|
||||||
#
|
#
|
||||||
# scope required repo (:owner/:repo) or org (:organization)
|
# scope required repo (:owner/:repo) or org (:organization)
|
||||||
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
|
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
|
||||||
# name optional defaults to hostname
|
# name optional defaults to hostname
|
||||||
# user optional user svc will run as. defaults to current
|
# user optional user svc will run as. defaults to current
|
||||||
|
# labels optional list of labels (split by comma) applied on the runner
|
||||||
#
|
#
|
||||||
# Notes:
|
# Notes:
|
||||||
# PATS over envvars are more secure
|
# PATS over envvars are more secure
|
||||||
@@ -30,6 +31,7 @@ runner_scope=${1}
|
|||||||
ghe_hostname=${2}
|
ghe_hostname=${2}
|
||||||
runner_name=${3:-$(hostname)}
|
runner_name=${3:-$(hostname)}
|
||||||
svc_user=${4:-$USER}
|
svc_user=${4:-$USER}
|
||||||
|
labels=${5}
|
||||||
|
|
||||||
echo "Configuring runner @ ${runner_scope}"
|
echo "Configuring runner @ ${runner_scope}"
|
||||||
sudo echo
|
sudo echo
|
||||||
@@ -130,8 +132,8 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Configuring ${runner_name} @ $runner_url"
|
echo "Configuring ${runner_name} @ $runner_url"
|
||||||
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name"
|
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name --labels $labels"
|
||||||
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name
|
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name --labels $labels
|
||||||
|
|
||||||
#---------------------------------------
|
#---------------------------------------
|
||||||
# Configuring as a service
|
# Configuring as a service
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
||||||
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
||||||
|
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
/// key is returned to the caller.
|
/// key is returned to the caller.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
||||||
RSACryptoServiceProvider CreateKey();
|
RSA CreateKey();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the RSA key managed by the key manager.
|
/// Deletes the RSA key managed by the key manager.
|
||||||
@@ -32,7 +32,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
||||||
/// <exception cref="CryptographicException">No key exists in the store</exception>
|
/// <exception cref="CryptographicException">No key exists in the store</exception>
|
||||||
RSACryptoServiceProvider GetKey();
|
RSA GetKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newtonsoft 10 is not working properly with dotnet RSAParameters class
|
// Newtonsoft 10 is not working properly with dotnet RSAParameters class
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// We expect the key to be in the machine store at this point. Configuration should have set all of
|
// We expect the key to be in the machine store at this point. Configuration should have set all of
|
||||||
// this up correctly so we can use the key to generate access tokens.
|
// this up correctly so we can use the key to generate access tokens.
|
||||||
var keyManager = context.GetService<IRSAKeyManager>();
|
var keyManager = context.GetService<IRSAKeyManager>();
|
||||||
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey(), StringUtil.ConvertToBoolean(CredentialData.Data.GetValueOrDefault("requireFipsCryptography"), false));
|
||||||
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
||||||
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
private string _keyFile;
|
private string _keyFile;
|
||||||
private IHostContext _context;
|
private IHostContext _context;
|
||||||
|
|
||||||
public RSACryptoServiceProvider CreateKey()
|
public RSA CreateKey()
|
||||||
{
|
{
|
||||||
RSACryptoServiceProvider rsa = null;
|
RSA rsa = null;
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
Trace.Info("Creating new RSA key using 2048-bit key length");
|
Trace.Info("Creating new RSA key using 2048-bit key length");
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider(2048);
|
rsa = RSA.Create(2048);
|
||||||
|
|
||||||
// Now write the parameters to disk
|
// Now write the parameters to disk
|
||||||
SaveParameters(rsa.ExportParameters(true));
|
SaveParameters(rsa.ExportParameters(true));
|
||||||
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider();
|
rsa = RSA.Create();
|
||||||
rsa.ImportParameters(LoadParameters());
|
rsa.ImportParameters(LoadParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RSACryptoServiceProvider GetKey()
|
public RSA GetKey()
|
||||||
{
|
{
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
||||||
|
|
||||||
var rsa = new RSACryptoServiceProvider();
|
var rsa = RSA.Create();
|
||||||
rsa.ImportParameters(LoadParameters());
|
rsa.ImportParameters(LoadParameters());
|
||||||
return rsa;
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
private string _keyFile;
|
private string _keyFile;
|
||||||
private IHostContext _context;
|
private IHostContext _context;
|
||||||
|
|
||||||
public RSACryptoServiceProvider CreateKey()
|
public RSA CreateKey()
|
||||||
{
|
{
|
||||||
RSACryptoServiceProvider rsa = null;
|
RSA rsa = null;
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
Trace.Info("Creating new RSA key using 2048-bit key length");
|
Trace.Info("Creating new RSA key using 2048-bit key length");
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider(2048);
|
rsa = RSA.Create(2048);
|
||||||
|
|
||||||
// Now write the parameters to disk
|
// Now write the parameters to disk
|
||||||
IOUtil.SaveObject(new RSAParametersSerializable(rsa.ExportParameters(true)), _keyFile);
|
IOUtil.SaveObject(new RSAParametersSerializable(rsa.ExportParameters(true)), _keyFile);
|
||||||
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider();
|
rsa = RSA.Create();
|
||||||
rsa.ImportParameters(IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters);
|
rsa.ImportParameters(IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RSACryptoServiceProvider GetKey()
|
public RSA GetKey()
|
||||||
{
|
{
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
@@ -80,7 +80,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
||||||
|
|
||||||
var parameters = IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters;
|
var parameters = IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters;
|
||||||
var rsa = new RSACryptoServiceProvider();
|
var rsa = RSA.Create();
|
||||||
rsa.ImportParameters(parameters);
|
rsa.ImportParameters(parameters);
|
||||||
return rsa;
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,7 +319,8 @@ namespace GitHub.Runner.Listener
|
|||||||
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
using (var rsa = keyManager.GetKey())
|
using (var rsa = keyManager.GetKey())
|
||||||
{
|
{
|
||||||
return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, RSAEncryptionPadding.OaepSHA1), message.IV);
|
var padding = _session.UseFipsEncryption ? RSAEncryptionPadding.OaepSHA256 : RSAEncryptionPadding.OaepSHA1;
|
||||||
|
return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, padding), message.IV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -594,7 +594,9 @@ namespace GitHub.Runner.Worker
|
|||||||
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (attempt < 3)
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (attempt < 3)
|
||||||
{
|
{
|
||||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
executionContext.Debug(ex.ToString());
|
executionContext.Debug(ex.ToString());
|
||||||
@@ -605,6 +607,11 @@ namespace GitHub.Runner.Worker
|
|||||||
await Task.Delay(backoff);
|
await Task.Delay(backoff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new WebApi.FailedToResolveActionDownloadInfoException("Failed to resolve action download info.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
|
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ namespace GitHub.Runner.Worker
|
|||||||
ExecutionContext.SetGitHubContext("action_repository", repoPathReferenceAction.Name);
|
ExecutionContext.SetGitHubContext("action_repository", repoPathReferenceAction.Name);
|
||||||
ExecutionContext.SetGitHubContext("action_ref", repoPathReferenceAction.Ref);
|
ExecutionContext.SetGitHubContext("action_ref", repoPathReferenceAction.Ref);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecutionContext.SetGitHubContext("action_repository", null);
|
||||||
|
ExecutionContext.SetGitHubContext("action_ref", null);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup container stephost for running inside the container.
|
// Setup container stephost for running inside the container.
|
||||||
if (ExecutionContext.Global.Container != null)
|
if (ExecutionContext.Global.Container != null)
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ContainerInfo(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
UpdateWebProxyEnv(hostContext.WebProxy);
|
||||||
|
}
|
||||||
|
|
||||||
public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container, bool isJobContainer = true, string networkAlias = null)
|
public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container, bool isJobContainer = true, string networkAlias = null)
|
||||||
{
|
{
|
||||||
this.ContainerName = container.Alias;
|
this.ContainerName = container.Alias;
|
||||||
|
|||||||
@@ -918,6 +918,12 @@ namespace GitHub.Runner.Worker
|
|||||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
|
public static void InfrastructureError(this IExecutionContext context, string message)
|
||||||
|
{
|
||||||
|
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true});
|
||||||
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void Warning(this IExecutionContext context, string message)
|
public static void Warning(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run container
|
// run container
|
||||||
var container = new ContainerInfo()
|
var container = new ContainerInfo(HostContext)
|
||||||
{
|
{
|
||||||
ContainerImage = Data.Image,
|
ContainerImage = Data.Image,
|
||||||
ContainerName = ExecutionContext.Id.ToString("N"),
|
ContainerName = ExecutionContext.Id.ToString("N"),
|
||||||
|
|||||||
@@ -335,6 +335,14 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Result = TaskResult.Canceled;
|
context.Result = TaskResult.Canceled;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
catch (FailedToResolveActionDownloadInfoException ex)
|
||||||
|
{
|
||||||
|
// Log the error and fail the JobExtension Initialization.
|
||||||
|
Trace.Error($"Caught exception from JobExtenion Initialization: {ex}");
|
||||||
|
context.InfrastructureError(ex.Message);
|
||||||
|
context.Result = TaskResult.Failed;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Log the error and fail the JobExtension Initialization.
|
// Log the error and fail the JobExtension Initialization.
|
||||||
|
|||||||
@@ -2458,4 +2458,23 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class FailedToResolveActionDownloadInfoException : DistributedTaskException
|
||||||
|
{
|
||||||
|
public FailedToResolveActionDownloadInfoException(String message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FailedToResolveActionDownloadInfoException(String message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private FailedToResolveActionDownloadInfoException(SerializationInfo info, StreamingContext context)
|
||||||
|
: base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
this.Type = issueToBeCloned.Type;
|
this.Type = issueToBeCloned.Type;
|
||||||
this.Category = issueToBeCloned.Category;
|
this.Category = issueToBeCloned.Category;
|
||||||
this.Message = issueToBeCloned.Message;
|
this.Message = issueToBeCloned.Message;
|
||||||
|
this.IsInfrastructureIssue = issueToBeCloned.IsInfrastructureIssue;
|
||||||
|
|
||||||
if (issueToBeCloned.m_data != null)
|
if (issueToBeCloned.m_data != null)
|
||||||
{
|
{
|
||||||
@@ -48,6 +49,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(Order = 4)]
|
||||||
|
public bool? IsInfrastructureIssue
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
public IDictionary<String, String> Data
|
public IDictionary<String, String> Data
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -65,5 +65,15 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether to use FIPS compliant encryption scheme for job message key
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public bool UseFipsEncryption
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,55 +130,6 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
return credentials.SignatureAlgorithm;
|
return credentials.SignatureAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClaimsPrincipal ValidateToken(this JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
|
||||||
ArgumentUtility.CheckForNull(parameters, nameof(parameters));
|
|
||||||
|
|
||||||
ClaimsIdentity actorIdentity = ValidateActor(token, parameters);
|
|
||||||
ValidateLifetime(token, parameters);
|
|
||||||
ValidateAudience(token, parameters);
|
|
||||||
ValidateSignature(token, parameters);
|
|
||||||
ValidateIssuer(token, parameters);
|
|
||||||
|
|
||||||
ClaimsIdentity identity = new ClaimsIdentity("Federation", parameters.IdentityNameClaimType, ClaimTypes.Role);
|
|
||||||
|
|
||||||
if (actorIdentity != null)
|
|
||||||
{
|
|
||||||
identity.Actor = actorIdentity;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<Claim> claims = token.ExtractClaims();
|
|
||||||
|
|
||||||
foreach (Claim claim in claims)
|
|
||||||
{
|
|
||||||
identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, token.Issuer));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClaimsPrincipal(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClaimsIdentity ValidateActor(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
|
||||||
ArgumentUtility.CheckForNull(parameters, nameof(parameters));
|
|
||||||
|
|
||||||
if (!parameters.ValidateActor)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//this recursive call with check the parameters
|
|
||||||
ClaimsPrincipal principal = token.Actor.ValidateToken(parameters.ActorValidationParameters);
|
|
||||||
|
|
||||||
if (!(principal?.Identity is ClaimsIdentity))
|
|
||||||
{
|
|
||||||
throw new ActorValidationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (ClaimsIdentity)principal.Identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateLifetime(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
private static void ValidateLifetime(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
ArgumentUtility.CheckForNull(token, nameof(token));
|
||||||
@@ -241,59 +192,6 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
throw new InvalidAudienceException(); //validation exception;
|
throw new InvalidAudienceException(); //validation exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateSignature(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
|
||||||
ArgumentUtility.CheckForNull(parameters, nameof(parameters));
|
|
||||||
|
|
||||||
if (!parameters.ValidateSignature)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string encodedData = token.EncodedToken;
|
|
||||||
|
|
||||||
string[] parts = encodedData.Split('.');
|
|
||||||
|
|
||||||
if (parts.Length != 3)
|
|
||||||
{
|
|
||||||
throw new InvalidTokenException(JwtResources.EncodedTokenDataMalformed()); //validation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(parts[2]))
|
|
||||||
{
|
|
||||||
throw new InvalidTokenException(JwtResources.SignatureNotFound()); //validation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.Algorithm == JWTAlgorithm.None)
|
|
||||||
{
|
|
||||||
throw new InvalidTokenException(JwtResources.InvalidSignatureAlgorithm()); //validation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgumentUtility.CheckForNull(parameters.SigningCredentials, nameof(parameters.SigningCredentials));
|
|
||||||
|
|
||||||
//ArgumentUtility.CheckEnumerableForNullOrEmpty(parameters.SigningToken.SecurityKeys, nameof(parameters.SigningToken.SecurityKeys));
|
|
||||||
|
|
||||||
byte[] sourceInput = Encoding.UTF8.GetBytes(string.Format("{0}.{1}", parts[0], parts[1]));
|
|
||||||
|
|
||||||
byte[] sourceSignature = parts[2].FromBase64StringNoPadding();
|
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (parameters.SigningCredentials.VerifySignature(sourceInput, sourceSignature))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
//swallow exceptions here, we'll throw if nothing works...
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SignatureValidationException(); //valiation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateIssuer(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
private static void ValidateIssuer(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
ArgumentUtility.CheckForNull(token, nameof(token));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi.Jwt;
|
using GitHub.Services.WebApi.Jwt;
|
||||||
|
|
||||||
@@ -75,7 +74,6 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetSignature(input);
|
return GetSignature(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,48 +84,13 @@ namespace GitHub.Services.WebApi
|
|||||||
/// <returns>A blob of data representing the signature of the input data</returns>
|
/// <returns>A blob of data representing the signature of the input data</returns>
|
||||||
protected abstract Byte[] GetSignature(Byte[] input);
|
protected abstract Byte[] GetSignature(Byte[] input);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifies the signature of the input data, returning true if the signature is valid.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">The data which should be signed</param>
|
|
||||||
/// <param name="signature">The signature which should be verified</param>
|
|
||||||
/// <returns>True if the provided signature matches the current signing token; otherwise, false</returns>
|
|
||||||
public abstract Boolean VerifySignature(Byte[] input, Byte[] signature);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="certificate"/> instance
|
|
||||||
/// as the signing key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificate">The certificate which contains the key used for signing and verification</param>
|
|
||||||
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified certificate for signing</returns>
|
|
||||||
public static VssSigningCredentials Create(X509Certificate2 certificate)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(certificate, nameof(certificate));
|
|
||||||
|
|
||||||
if (certificate.HasPrivateKey)
|
|
||||||
{
|
|
||||||
var rsa = certificate.GetRSAPrivateKey();
|
|
||||||
if (rsa == null)
|
|
||||||
{
|
|
||||||
throw new SignatureAlgorithmUnsupportedException(certificate.SignatureAlgorithm.FriendlyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rsa.KeySize < c_minKeySize)
|
|
||||||
{
|
|
||||||
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new X509Certificate2SigningToken(certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="factory"/>
|
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="factory"/>
|
||||||
/// callback function to retrieve the signing key.
|
/// callback function to retrieve the signing key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="factory">The factory which creates <c>RSA</c> keys used for signing and verification</param>
|
/// <param name="factory">The factory which creates <c>RSA</c> keys used for signing and verification</param>
|
||||||
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified provider for signing</returns>
|
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified provider for signing</returns>
|
||||||
public static VssSigningCredentials Create(Func<RSA> factory)
|
public static VssSigningCredentials Create(Func<RSA> factory, bool requireFipsCryptography)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(factory, nameof(factory));
|
ArgumentUtility.CheckForNull(factory, nameof(factory));
|
||||||
|
|
||||||
@@ -143,22 +106,12 @@ namespace GitHub.Services.WebApi
|
|||||||
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RSASigningToken(factory, rsa.KeySize);
|
if (requireFipsCryptography)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="key"/> as the signing
|
|
||||||
/// key. The returned signing token performs symmetric key signing and verification.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rsa">The key used for signing and verification</param>
|
|
||||||
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified key for signing</returns>
|
|
||||||
public static VssSigningCredentials Create(Byte[] key)
|
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(key, nameof(key));
|
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pss);
|
||||||
|
}
|
||||||
// Probably should have validation here, but there was none previously
|
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pkcs1);
|
||||||
return new SymmetricKeySigningToken(key);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const Int32 c_minKeySize = 2048;
|
private const Int32 c_minKeySize = 2048;
|
||||||
@@ -166,57 +119,6 @@ namespace GitHub.Services.WebApi
|
|||||||
|
|
||||||
#region Concrete Implementations
|
#region Concrete Implementations
|
||||||
|
|
||||||
private class SymmetricKeySigningToken : VssSigningCredentials
|
|
||||||
{
|
|
||||||
public SymmetricKeySigningToken(Byte[] key)
|
|
||||||
{
|
|
||||||
m_key = new Byte[key.Length];
|
|
||||||
Buffer.BlockCopy(key, 0, m_key, 0, m_key.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean CanSignData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Int32 KeySize
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_key.Length * 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override JWTAlgorithm SignatureAlgorithm
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return JWTAlgorithm.HS256;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Byte[] GetSignature(Byte[] input)
|
|
||||||
{
|
|
||||||
using (var hash = new HMACSHA256(m_key))
|
|
||||||
{
|
|
||||||
return hash.ComputeHash(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean VerifySignature(
|
|
||||||
Byte[] input,
|
|
||||||
Byte[] signature)
|
|
||||||
{
|
|
||||||
var computedSignature = SignData(input);
|
|
||||||
return SecureCompare.TimeInvariantEquals(computedSignature, signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Byte[] m_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class AsymmetricKeySigningToken : VssSigningCredentials
|
private abstract class AsymmetricKeySigningToken : VssSigningCredentials
|
||||||
{
|
{
|
||||||
protected abstract Boolean HasPrivateKey();
|
protected abstract Boolean HasPrivateKey();
|
||||||
@@ -244,70 +146,14 @@ namespace GitHub.Services.WebApi
|
|||||||
private Boolean? m_hasPrivateKey;
|
private Boolean? m_hasPrivateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class X509Certificate2SigningToken : AsymmetricKeySigningToken, IJsonWebTokenHeaderProvider
|
|
||||||
{
|
|
||||||
public X509Certificate2SigningToken(X509Certificate2 certificate)
|
|
||||||
{
|
|
||||||
m_certificate = certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Int32 KeySize
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_certificate.GetRSAPublicKey().KeySize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override DateTime ValidFrom
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_certificate.NotBefore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override DateTime ValidTo
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_certificate.NotAfter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean VerifySignature(
|
|
||||||
Byte[] input,
|
|
||||||
Byte[] signature)
|
|
||||||
{
|
|
||||||
var rsa = m_certificate.GetRSAPublicKey();
|
|
||||||
return rsa.VerifyData(input, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Byte[] GetSignature(Byte[] input)
|
|
||||||
{
|
|
||||||
var rsa = m_certificate.GetRSAPrivateKey();
|
|
||||||
return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Boolean HasPrivateKey()
|
|
||||||
{
|
|
||||||
return m_certificate.HasPrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IJsonWebTokenHeaderProvider.SetHeaders(IDictionary<String, Object> headers)
|
|
||||||
{
|
|
||||||
headers[JsonWebTokenHeaderParameters.X509CertificateThumbprint] = m_certificate.GetCertHash().ToBase64StringNoPadding();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly X509Certificate2 m_certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RSASigningToken : AsymmetricKeySigningToken
|
private class RSASigningToken : AsymmetricKeySigningToken
|
||||||
{
|
{
|
||||||
public RSASigningToken(
|
public RSASigningToken(
|
||||||
Func<RSA> factory,
|
Func<RSA> factory,
|
||||||
Int32 keySize)
|
Int32 keySize,
|
||||||
|
RSASignaturePadding signaturePadding)
|
||||||
{
|
{
|
||||||
|
m_signaturePadding = signaturePadding;
|
||||||
m_keySize = keySize;
|
m_keySize = keySize;
|
||||||
m_factory = factory;
|
m_factory = factory;
|
||||||
}
|
}
|
||||||
@@ -324,7 +170,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
using (var rsa = m_factory())
|
using (var rsa = m_factory())
|
||||||
{
|
{
|
||||||
return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
return rsa.SignData(input, HashAlgorithmName.SHA256, m_signaturePadding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,18 +190,9 @@ namespace GitHub.Services.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Boolean VerifySignature(
|
|
||||||
Byte[] input,
|
|
||||||
Byte[] signature)
|
|
||||||
{
|
|
||||||
using (var rsa = m_factory())
|
|
||||||
{
|
|
||||||
return rsa.VerifyData(input, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Int32 m_keySize;
|
private readonly Int32 m_keySize;
|
||||||
private readonly Func<RSA> m_factory;
|
private readonly Func<RSA> m_factory;
|
||||||
|
private readonly RSASignaturePadding m_signaturePadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -333,6 +333,66 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async void SetGitHubContextActionRepoRef()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var actionInputs = new MappingToken(null, null, null);
|
||||||
|
actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "test1"));
|
||||||
|
actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "test2"));
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "actions/test",
|
||||||
|
Ref = "master"
|
||||||
|
},
|
||||||
|
Inputs = actionInputs
|
||||||
|
};
|
||||||
|
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
Dictionary<string, string> finialInputs = new Dictionary<string, string>();
|
||||||
|
_handlerFactory.Setup(x => x.Create(It.IsAny<IExecutionContext>(), It.IsAny<ActionStepDefinitionReference>(), It.IsAny<IStepHost>(), It.IsAny<ActionExecutionData>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Variables>(), It.IsAny<string>()))
|
||||||
|
.Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary<string, string> inputs, Dictionary<string, string> environment, Variables runtimeVariables, string taskDirectory) =>
|
||||||
|
{
|
||||||
|
finialInputs = inputs;
|
||||||
|
})
|
||||||
|
.Returns(new Mock<IHandler>().Object);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
await _actionRunner.RunAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_repository", "actions/test"), Times.Once);
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_ref", "master"), Times.Once);
|
||||||
|
|
||||||
|
action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.ScriptReference(),
|
||||||
|
Inputs = actionInputs
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
_hc.EnqueueInstance<IDefaultStepHost>(_defaultStepHost.Object);
|
||||||
|
_hc.EnqueueInstance(_fileCommandManager.Object);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
await _actionRunner.RunAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_repository", null), Times.Once);
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_ref", null), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
private void Setup([CallerMemberName] string name = "")
|
private void Setup([CallerMemberName] string name = "")
|
||||||
{
|
{
|
||||||
_ecTokenSource?.Dispose();
|
_ecTokenSource?.Dispose();
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.274.2
|
2.275.0
|
||||||
|
|||||||
Reference in New Issue
Block a user