mirror of
https://github.com/actions/runner.git
synced 2026-08-03 09:52:46 +08:00
Compare commits
16 Commits
luketomlin
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7fd7da153 | ||
|
|
ed0bf12a66 | ||
|
|
ec6b92b5dc | ||
|
|
34ef7f24f8 | ||
|
|
b8dd9a3e3c | ||
|
|
cc01a0d090 | ||
|
|
03a0707cbe | ||
|
|
e1d1844725 | ||
|
|
44b796f3c8 | ||
|
|
6bb3178b59 | ||
|
|
148a3bb616 | ||
|
|
e18d1a9845 | ||
|
|
14de40d73a | ||
|
|
8bd0a334d7 | ||
|
|
f898ef14a5 | ||
|
|
de4c2885af |
@@ -4,7 +4,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "8.0.422"
|
||||
"version": "8.0.423"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
|
||||
2
.github/workflows/close-bugs-bot.yml
vendored
2
.github/workflows/close-bugs-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
close-issue-message: "This issue does not seem to be a problem with the runner application, it concerns the GitHub actions platform more generally. Could you please post your feedback on the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃"
|
||||
exempt-issue-labels: "keep"
|
||||
|
||||
2
.github/workflows/close-features-bot.yml
vendored
2
.github/workflows/close-features-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
close-issue-message: "Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please [read our contribution guidelines](https://github.com/actions/runner#contribute). 😃"
|
||||
exempt-issue-labels: "keep"
|
||||
|
||||
2
.github/workflows/dependency-check.yml
vendored
2
.github/workflows/dependency-check.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
|
||||
2
.github/workflows/npm-audit-typescript.yml
vendored
2
.github/workflows/npm-audit-typescript.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: NPM install and audit fix with TypeScript auto-repair
|
||||
|
||||
2
.github/workflows/npm-audit.yml
vendored
2
.github/workflows/npm-audit.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
|
||||
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
stale-issue-message: "This issue is stale because it has been open 365 days with no activity. Remove stale label or comment or this will be closed in 15 days."
|
||||
close-issue-message: "This issue was closed because it has been stalled for 15 days with no activity."
|
||||
|
||||
@@ -1,40 +1,32 @@
|
||||
## What's Changed
|
||||
* Bump System.ServiceProcess.ServiceController from 10.0.6 to 10.0.7 by @dependabot[bot] in https://github.com/actions/runner/pull/4370
|
||||
* Bump @actions/glob from 0.6.1 to 0.7.0 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4367
|
||||
* feat: propagate actions dependencies by @nodeselector in https://github.com/actions/runner/pull/4372
|
||||
* Not retry and report action download 403. by @TingluoHuang in https://github.com/actions/runner/pull/4391
|
||||
* Update setup job starting logs by @GitPaulo in https://github.com/actions/runner/pull/4383
|
||||
* fix: expand commit hash regex to support SHA-256 (64-char) hashes by @yaananth in https://github.com/actions/runner/pull/4347
|
||||
* Move dap setup to setup job step by @rentziass in https://github.com/actions/runner/pull/4403
|
||||
* Add support for Ubuntu 26.04 (liblttng-ust1t64, libicu77-80) by @dvaldivia in https://github.com/actions/runner/pull/4394
|
||||
* Update dotnet sdk to latest version @8.0.421 by @github-actions[bot] in https://github.com/actions/runner/pull/4428
|
||||
* Update Docker to v29.5.0 and Buildx to v0.34.0 by @github-actions[bot] in https://github.com/actions/runner/pull/4425
|
||||
* Execute debugger REPL commands inside job container by @rentziass in https://github.com/actions/runner/pull/4420
|
||||
* Send welcome message in debugger console on connect by @rentziass in https://github.com/actions/runner/pull/4419
|
||||
* Update snapshot-if context and functions by @drielenr in https://github.com/actions/runner/pull/4443
|
||||
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4452
|
||||
* Allow disable node v8 maglev jit compiler on node24. by @TingluoHuang in https://github.com/actions/runner/pull/4447
|
||||
* Update Node 24 default date to June 16th, 2026 by @salmanmkc in https://github.com/actions/runner/pull/4462
|
||||
* Populate telemetry for non-action post-job steps by @drielenr in https://github.com/actions/runner/pull/4463
|
||||
* Add SDK types and results plumbing for background step control by @lokesh755 in https://github.com/actions/runner/pull/4472
|
||||
* Add job execution view model by @rentziass in https://github.com/actions/runner/pull/4470
|
||||
* Add thread-safety locks to StepsContext by @lokesh755 in https://github.com/actions/runner/pull/4475
|
||||
* Add background step deferral infrastructure and metadata plumbing by @lokesh755 in https://github.com/actions/runner/pull/4479
|
||||
* Wire job execution view into DAP by @rentziass in https://github.com/actions/runner/pull/4471
|
||||
* Background steps execution engine by @lokesh755 in https://github.com/actions/runner/pull/4476
|
||||
* Update Docker to v29.5.2 and Buildx to v0.34.1 by @github-actions[bot] in https://github.com/actions/runner/pull/4451
|
||||
* BrokerServer should not retry on 401. by @TingluoHuang in https://github.com/actions/runner/pull/4445
|
||||
* Add new env var to allow single-prefix multiline logs on stdout by @nuclearpidgeon in https://github.com/actions/runner/pull/4424
|
||||
* Bump Microsoft.DevTunnels.Connections from 1.3.39 to 1.3.48 by @dependabot[bot] in https://github.com/actions/runner/pull/4441
|
||||
* Bump System.Formats.Asn1 and System.Security.Cryptography.Pkcs by @dependabot[bot] in https://github.com/actions/runner/pull/4369
|
||||
* Canceled background steps should not impact job result by @lokesh755 in https://github.com/actions/runner/pull/4482
|
||||
* Report actions archive size in telemetry. by @TingluoHuang in https://github.com/actions/runner/pull/4509
|
||||
* Bump actions/checkout from 6 to 7 by @dependabot[bot] in https://github.com/actions/runner/pull/4511
|
||||
* Update Docker to v29.6.0 and Buildx to v0.35.0 by @github-actions[bot] in https://github.com/actions/runner/pull/4516
|
||||
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4519
|
||||
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4530
|
||||
* feat: enhance telemetry for action download resolution and failures by @TingluoHuang in https://github.com/actions/runner/pull/4536
|
||||
* Update Docker version to 29.6.1 by @AllanGuigou in https://github.com/actions/runner/pull/4539
|
||||
* feat: add self-repository action reference syntax by @nodeselector in https://github.com/actions/runner/pull/4457
|
||||
* Update dotnet sdk to latest version @8.0.422 by @github-actions[bot] in https://github.com/actions/runner/pull/4504
|
||||
* Link config.sh and installdependencies.sh in docs by @Wuodan in https://github.com/actions/runner/pull/4526
|
||||
* Add support for $GITHUB_ARTIFACTS environment files by @bdehamer in https://github.com/actions/runner/pull/4527
|
||||
* feat: expose effective cache-mode to steps via ACTIONS_CACHE_MODE by @philip-gai in https://github.com/actions/runner/pull/4538
|
||||
* Setup Job: announce when running with locked dependencies by @nodeselector in https://github.com/actions/runner/pull/4546
|
||||
* Setup Job: reword locked-dependencies log line to use lockfile language by @nodeselector in https://github.com/actions/runner/pull/4550
|
||||
* Wait for worker to finish during cancel by @TingluoHuang in https://github.com/actions/runner/pull/4553
|
||||
* do not cap migrated setting retry is exception is session conflict by @aiqiaoy in https://github.com/actions/runner/pull/4557
|
||||
* Allow checking DNS with api.gihub.com. by @TingluoHuang in https://github.com/actions/runner/pull/4547
|
||||
* Exit ephemeral runners on broker acknowledge job-not-found by @rentziass in https://github.com/actions/runner/pull/4540
|
||||
* Cleanup session files on get message or session deleted error by @nikola-jokic in https://github.com/actions/runner/pull/4551
|
||||
* Recreate session on RunnerSessionInvalid from broker by @luketomlinson in https://github.com/actions/runner/pull/4556
|
||||
|
||||
## New Contributors
|
||||
* @GitPaulo made their first contribution in https://github.com/actions/runner/pull/4383
|
||||
* @dvaldivia made their first contribution in https://github.com/actions/runner/pull/4394
|
||||
* @drielenr made their first contribution in https://github.com/actions/runner/pull/4443
|
||||
* @nuclearpidgeon made their first contribution in https://github.com/actions/runner/pull/4424
|
||||
* @Wuodan made their first contribution in https://github.com/actions/runner/pull/4526
|
||||
* @bdehamer made their first contribution in https://github.com/actions/runner/pull/4527
|
||||
* @philip-gai made their first contribution in https://github.com/actions/runner/pull/4538
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.334.0...v2.335.0
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.335.0...v2.336.0
|
||||
|
||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
|
||||
6134
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
6134
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,13 +38,13 @@
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.0",
|
||||
"@typescript-eslint/parser": "^8.59.0",
|
||||
"@typescript-eslint/parser": "^8.65.0",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"lint-staged": "^17.2.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace GitHub.Runner.Common
|
||||
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
|
||||
{
|
||||
_liveConsoleFeedUrl = liveConsoleFeedUrl;
|
||||
InitializeWebsocketClient(liveConsoleFeedUrl, token, TimeSpan.Zero, retryConnection: true);
|
||||
InitializeWebsocketClient(liveConsoleFeedUrl, TimeSpan.Zero, retryConnection: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +164,9 @@ namespace GitHub.Runner.Common
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private void InitializeWebsocketClient(string liveConsoleFeedUrl, string accessToken, TimeSpan delay, bool retryConnection = false)
|
||||
private void InitializeWebsocketClient(string liveConsoleFeedUrl, TimeSpan delay, bool retryConnection = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(accessToken))
|
||||
if (string.IsNullOrEmpty(_token))
|
||||
{
|
||||
Trace.Info($"No access token from server");
|
||||
return;
|
||||
@@ -179,12 +179,7 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
Trace.Info($"Creating websocket client ..." + liveConsoleFeedUrl);
|
||||
this._websocketClient = new ClientWebSocket();
|
||||
this._websocketClient.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}");
|
||||
var userAgentValues = new List<ProductInfoHeaderValue>();
|
||||
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
|
||||
userAgentValues.AddRange(HostContext.UserAgents);
|
||||
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x => x.ToString())));
|
||||
this._websocketClient = CreateWebSocketClient();
|
||||
|
||||
// during initialization, retry upto 3 times to setup connection
|
||||
this._websocketConnectTask = ConnectWebSocketClient(liveConsoleFeedUrl, delay, retryConnection);
|
||||
@@ -201,8 +196,15 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
Trace.Info($"Attempting to start websocket client with delay {delay}.");
|
||||
await Task.Delay(delay);
|
||||
using var connectTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
||||
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), connectTimeoutTokenSource.Token);
|
||||
using (var connectTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
if (this._websocketClient == null)
|
||||
{
|
||||
this._websocketClient = CreateWebSocketClient();
|
||||
}
|
||||
|
||||
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), connectTimeoutTokenSource.Token);
|
||||
}
|
||||
Trace.Info($"Successfully started websocket client.");
|
||||
connected = true;
|
||||
}
|
||||
@@ -211,6 +213,7 @@ namespace GitHub.Runner.Common
|
||||
Trace.Info("Exception caught during websocket client connect, retry connection.");
|
||||
Trace.Error(ex);
|
||||
retries++;
|
||||
this._websocketClient?.Dispose();
|
||||
this._websocketClient = null;
|
||||
_lastConnectionFailure = DateTime.Now;
|
||||
}
|
||||
@@ -259,7 +262,7 @@ namespace GitHub.Runner.Common
|
||||
Trace.Info($"Websocket is not open, let's attempt to connect back again with random backoff {delay} ms.");
|
||||
Trace.Verbose(ex.ToString());
|
||||
retries++;
|
||||
InitializeWebsocketClient(_liveConsoleFeedUrl, _token, delay);
|
||||
InitializeWebsocketClient(_liveConsoleFeedUrl, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,13 +277,24 @@ namespace GitHub.Runner.Common
|
||||
if (_lastConnectionFailure.HasValue && DateTime.Now > _lastConnectionFailure.Value.AddMinutes(10))
|
||||
{
|
||||
// Some minutes passed since we retried last time, try connection again
|
||||
InitializeWebsocketClient(_liveConsoleFeedUrl, _token, TimeSpan.Zero);
|
||||
InitializeWebsocketClient(_liveConsoleFeedUrl, TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
return delivered;
|
||||
}
|
||||
|
||||
private ClientWebSocket CreateWebSocketClient()
|
||||
{
|
||||
var client = new ClientWebSocket();
|
||||
client.Options.SetRequestHeader("Authorization", $"Bearer {_token}");
|
||||
var userAgentValues = new List<ProductInfoHeaderValue>();
|
||||
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
|
||||
userAgentValues.AddRange(HostContext.UserAgents);
|
||||
client.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x => x.ToString())));
|
||||
return client;
|
||||
}
|
||||
|
||||
private void CloseWebSocket(WebSocketCloseStatus closeStatus, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
|
||||
232
src/Runner.Common/VSockSecretNotifier.cs
Normal file
232
src/Runner.Common/VSockSecretNotifier.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Logging;
|
||||
using GitHub.Runner.Sdk;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
|
||||
[ServiceLocator(Default = typeof(VSockSecretNotifier))]
|
||||
public interface IVSockSecretNotifier : IRunnerService, IAsyncDisposable
|
||||
{
|
||||
bool TryStartNotifier();
|
||||
|
||||
void NotifyNewSecret(NewSecretEventArgs newSecret);
|
||||
}
|
||||
|
||||
public sealed class VSockSecretNotifier : RunnerService, IVSockSecretNotifier
|
||||
{
|
||||
private Socket _vsock = null;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource = null;
|
||||
|
||||
private Task _secretNotificationTask = null;
|
||||
|
||||
private Channel<byte[]> _channel = Channel.CreateUnbounded<byte[]>(new UnboundedChannelOptions() { SingleReader = true });
|
||||
|
||||
public bool TryStartNotifier()
|
||||
{
|
||||
if (_vsock != null)
|
||||
{
|
||||
Trace.Verbose("VSocket is already connected.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// `GITHUB_ACTIONS_RUNNER_VSOCK_CID_PORT` is expected to be in the format "CID:PORT", e.g. "2:9999".
|
||||
string vsockCidPort = Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_VSOCK_CID_PORT");
|
||||
if (string.IsNullOrEmpty(vsockCidPort))
|
||||
{
|
||||
Trace.Verbose("VSocket CID/Port environment variable is not set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] parts = vsockCidPort.Split(':', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
Trace.Verbose("VSocket CID/Port environment variable is not in the correct format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint cid, port;
|
||||
if (!uint.TryParse(parts[0], out cid) || !uint.TryParse(parts[1], out port))
|
||||
{
|
||||
Trace.Verbose("VSocket CID/Port environment variable contains invalid numbers.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Trace.Info($"Attempting to start VSocket secret notifier with CID: {cid}, Port: {port}.");
|
||||
try
|
||||
{
|
||||
SafeSocketHandle nativeSocket = NativeSocket((int)(AddressFamily)40, (int)SocketType.Stream, 0);
|
||||
if (nativeSocket.IsInvalid)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
nativeSocket.Dispose();
|
||||
throw new SocketException(error);
|
||||
}
|
||||
|
||||
_vsock = new Socket(nativeSocket);
|
||||
_vsock.Connect(new HostVsockEndPoint(cid, port));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to create and connect VSocket: {ex}");
|
||||
_vsock?.Dispose();
|
||||
_vsock = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken);
|
||||
_secretNotificationTask = ProcessSecretChannel();
|
||||
Trace.Info($"VSocket secret notifier started successfully.");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void NotifyNewSecret(NewSecretEventArgs newSecret)
|
||||
{
|
||||
if (_vsock == null)
|
||||
{
|
||||
Trace.Verbose("VSocket is not connected, skipping secret notification.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] payloadBytes = Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(new { RunnerSecrets = newSecret }, Formatting.None));
|
||||
byte[] lengthPrefix = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(payloadBytes.Length));
|
||||
byte[] fullPayload = new byte[lengthPrefix.Length + payloadBytes.Length];
|
||||
Buffer.BlockCopy(lengthPrefix, 0, fullPayload, 0, lengthPrefix.Length);
|
||||
Buffer.BlockCopy(payloadBytes, 0, fullPayload, lengthPrefix.Length, payloadBytes.Length);
|
||||
|
||||
// we don't need to check return since unbounded channel will always accept the item.
|
||||
_channel.Writer.TryWrite(fullPayload);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_vsock != null && _secretNotificationTask != null)
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
try
|
||||
{
|
||||
await _secretNotificationTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Secret notification task finished with error: {ex}");
|
||||
}
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
_vsock?.Dispose();
|
||||
_vsock = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessSecretChannel()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_cancellationTokenSource.Token.IsCancellationRequested &&
|
||||
await _channel.Reader.WaitToReadAsync(_cancellationTokenSource.Token))
|
||||
{
|
||||
while (_channel.Reader.TryRead(out var payload))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Socket.SendAsync on a stream socket may send fewer bytes than requested,
|
||||
// so keep sending until the entire payload has been written.
|
||||
int totalSent = 0;
|
||||
while (totalSent < payload.Length)
|
||||
{
|
||||
int bytesSent = await _vsock.SendAsync(payload.AsMemory(totalSent), SocketFlags.None, _cancellationTokenSource.Token);
|
||||
if (bytesSent == 0)
|
||||
{
|
||||
throw new SocketException((int)SocketError.ConnectionReset);
|
||||
}
|
||||
|
||||
totalSent += bytesSent;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Trace.Info("Secret notification task was canceled.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to notify new secret over VSocket: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Trace.Info("Secret notification task was canceled.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to process secret channel: {ex}");
|
||||
}
|
||||
|
||||
_channel.Writer.TryComplete();
|
||||
}
|
||||
|
||||
[DllImport("libc", SetLastError = true, EntryPoint = "socket")]
|
||||
private static extern SafeSocketHandle NativeSocket(int domain, int type, int protocol);
|
||||
}
|
||||
|
||||
internal sealed class HostVsockEndPoint : EndPoint
|
||||
{
|
||||
private const int SocketAddressSize = 16;
|
||||
private readonly uint _cid;
|
||||
private readonly uint _port;
|
||||
|
||||
public HostVsockEndPoint(uint cid, uint port)
|
||||
{
|
||||
_cid = cid;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
public override AddressFamily AddressFamily => (AddressFamily)40;
|
||||
|
||||
public override SocketAddress Serialize()
|
||||
{
|
||||
SocketAddress socketAddress = new SocketAddress(AddressFamily.Unspecified, SocketAddressSize);
|
||||
// sockaddr_vm layout: family(0-1), reserved1(2-3), port(4-7), cid(8-11)
|
||||
ushort family = (ushort)AddressFamily;
|
||||
socketAddress[0] = (byte)(family & 0xFF);
|
||||
socketAddress[1] = (byte)((family >> 8) & 0xFF);
|
||||
socketAddress[2] = 0;
|
||||
socketAddress[3] = 0;
|
||||
socketAddress[4] = (byte)(_port & 0xFF);
|
||||
socketAddress[5] = (byte)((_port >> 8) & 0xFF);
|
||||
socketAddress[6] = (byte)((_port >> 16) & 0xFF);
|
||||
socketAddress[7] = (byte)((_port >> 24) & 0xFF);
|
||||
socketAddress[8] = (byte)(_cid & 0xFF);
|
||||
socketAddress[9] = (byte)((_cid >> 8) & 0xFF);
|
||||
socketAddress[10] = (byte)((_cid >> 16) & 0xFF);
|
||||
socketAddress[11] = (byte)((_cid >> 24) & 0xFF);
|
||||
return socketAddress;
|
||||
}
|
||||
|
||||
public override EndPoint Create(SocketAddress socketAddress)
|
||||
{
|
||||
uint port = (uint)socketAddress[4]
|
||||
| ((uint)socketAddress[5] << 8)
|
||||
| ((uint)socketAddress[6] << 16)
|
||||
| ((uint)socketAddress[7] << 24);
|
||||
|
||||
uint cid = (uint)socketAddress[8]
|
||||
| ((uint)socketAddress[9] << 8)
|
||||
| ((uint)socketAddress[10] << 16)
|
||||
| ((uint)socketAddress[11] << 24);
|
||||
|
||||
return new HostVsockEndPoint(cid, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1026,7 +1026,9 @@ namespace GitHub.Runner.Worker.Dap
|
||||
{
|
||||
if (!string.IsNullOrEmpty(debuggerConfig.WelcomeMessage))
|
||||
{
|
||||
SendOutput("console", debuggerConfig.WelcomeMessage);
|
||||
// The welcome message is server-supplied and never rendered verbatim:
|
||||
// mask secrets and strip control characters before it reaches the console.
|
||||
SendOutput("console", SanitizeConsoleText(MaskUserVisibleText(debuggerConfig.WelcomeMessage)));
|
||||
Trace.Info("Sent custom welcome message");
|
||||
}
|
||||
else
|
||||
@@ -1772,6 +1774,30 @@ namespace GitHub.Runner.Worker.Dap
|
||||
return HostContext?.SecretMasker?.MaskSecrets(value) ?? value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes C0/C1 control characters (except tab, carriage return and line feed) so
|
||||
/// server-supplied text cannot inject ANSI escape sequences or terminal control codes
|
||||
/// into the DAP console.
|
||||
/// </summary>
|
||||
internal static string SanitizeConsoleText(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(value.Length);
|
||||
foreach (var character in value)
|
||||
{
|
||||
if (!char.IsControl(character) || character == '\t' || character == '\r' || character == '\n')
|
||||
{
|
||||
builder.Append(character);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a DAP response with common fields pre-populated.
|
||||
/// </summary>
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace GitHub.Runner.Worker.Dap
|
||||
/// Optional welcome message content for the debugger console. Only used when
|
||||
/// <see cref="OverrideWelcomeMessage"/> is true.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Server-supplied and treated as untrusted: it is masked with the runner's
|
||||
/// secret masker and stripped of control characters before being written to
|
||||
/// the debugger console.
|
||||
/// </remarks>
|
||||
public string WelcomeMessage { get; }
|
||||
|
||||
/// <summary>Whether the tunnel configuration is complete and valid.</summary>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.3" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.3" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.DevTunnels.Connections" Version="1.3.48" />
|
||||
<PackageReference Include="Microsoft.DevTunnels.Connections" Version="1.3.50" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
{
|
||||
@@ -46,6 +45,7 @@ namespace GitHub.Runner.Worker
|
||||
var jobRunner = HostContext.CreateService<IJobRunner>();
|
||||
var terminal = HostContext.GetService<ITerminal>();
|
||||
|
||||
await using (var secretNotifier = HostContext.GetService<IVSockSecretNotifier>())
|
||||
using (var channel = HostContext.CreateService<IProcessChannel>())
|
||||
using (var jobRequestCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken))
|
||||
using (var channelTokenSource = new CancellationTokenSource())
|
||||
@@ -86,6 +86,14 @@ namespace GitHub.Runner.Worker
|
||||
HostContext.WritePerfCounter($"WorkerJobMessageReceived_{jobMessage.RequestId.ToString()}");
|
||||
|
||||
// Initialize the secret masker and set the thread culture.
|
||||
if (Constants.Runner.Platform == Constants.OSPlatform.Linux &&
|
||||
secretNotifier.TryStartNotifier())
|
||||
{
|
||||
HostContext.SecretMasker.NewSecretAdded += (sender, e) =>
|
||||
{
|
||||
secretNotifier.NotifyNewSecret(e);
|
||||
};
|
||||
}
|
||||
InitializeSecretMasker(jobMessage);
|
||||
SetCulture(jobMessage);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.Logging
|
||||
{
|
||||
@@ -11,5 +13,41 @@ namespace GitHub.DistributedTask.Logging
|
||||
void AddValueEncoder(ValueEncoder encoder);
|
||||
ISecretMasker Clone();
|
||||
String MaskSecrets(String input);
|
||||
public event EventHandler<NewSecretEventArgs> NewSecretAdded;
|
||||
}
|
||||
|
||||
public abstract class NewSecretEventArgs : EventArgs
|
||||
{
|
||||
public abstract String Type { get; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public sealed class NewRegexSecretEventArgs : NewSecretEventArgs
|
||||
{
|
||||
[DataMember]
|
||||
public override String Type => "regex";
|
||||
|
||||
public NewRegexSecretEventArgs(String pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public String Pattern { get; private set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public sealed class NewVariableSecretEventArgs : NewSecretEventArgs
|
||||
{
|
||||
[DataMember]
|
||||
public override String Type => "variable";
|
||||
|
||||
public NewVariableSecretEventArgs(List<string> values)
|
||||
{
|
||||
Values.AddRange(values);
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public List<string> Values { get; private set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace GitHub.DistributedTask.Logging
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class SecretMasker : ISecretMasker, IDisposable
|
||||
{
|
||||
public event EventHandler<NewSecretEventArgs> NewSecretAdded;
|
||||
|
||||
public SecretMasker()
|
||||
{
|
||||
m_originalValueSecrets = new HashSet<ValueSecret>();
|
||||
@@ -66,6 +68,8 @@ namespace GitHub.DistributedTask.Logging
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
NewSecretAdded?.Invoke(this, new NewRegexSecretEventArgs(pattern));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,6 +137,9 @@ namespace GitHub.DistributedTask.Logging
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
// valueSecrets contains all the values run through the encoders.
|
||||
NewSecretAdded?.Invoke(this, new NewVariableSecretEventArgs(valueSecrets.Select(x => x.m_value).ToList()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
/// Optional welcome message shown in the debugger console when a client connects.
|
||||
/// Only used when the <c>actions_runner_override_debugger_welcome_message</c>
|
||||
/// feature flag is set to <c>true</c> in the job variables. With the flag set,
|
||||
/// a non-empty value is shown as-is and a null or empty value suppresses the
|
||||
/// a non-empty value is shown and a null or empty value suppresses the
|
||||
/// default welcome message. When the flag is not set, the runner shows its
|
||||
/// built-in help text and this field is ignored.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.27.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.29.1" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.7" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.10" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="10.0.7" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="10.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1422,6 +1422,96 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task WelcomeMessageMasksSecrets()
|
||||
{
|
||||
using (var hc = CreateTestContext())
|
||||
{
|
||||
hc.SecretMasker.AddValue("super-secret-token");
|
||||
|
||||
var port = GetFreePort();
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port,
|
||||
overrideWelcomeMessage: true,
|
||||
welcomeMessage: "Welcome! Token: super-secret-token");
|
||||
await _debugger.StartAsync(jobContext.Object);
|
||||
|
||||
using var client = await ConnectClientAsync(port);
|
||||
var stream = client.GetStream();
|
||||
|
||||
await SendRequestAsync(stream, new Request
|
||||
{
|
||||
Seq = 1,
|
||||
Type = "request",
|
||||
Command = "configurationDone"
|
||||
});
|
||||
|
||||
var configDoneResponse = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("\"command\":\"configurationDone\"", configDoneResponse);
|
||||
|
||||
var welcomeMsg = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("\"event\":\"output\"", welcomeMsg);
|
||||
Assert.DoesNotContain("super-secret-token", welcomeMsg);
|
||||
Assert.Contains("***", welcomeMsg);
|
||||
|
||||
await _debugger.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task WelcomeMessageStripsControlCharacters()
|
||||
{
|
||||
using (CreateTestContext())
|
||||
{
|
||||
var port = GetFreePort();
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port,
|
||||
overrideWelcomeMessage: true,
|
||||
welcomeMessage: "Wel\u001b[31mcome\u0007\u0000\u009bhere\nnext\tline");
|
||||
await _debugger.StartAsync(jobContext.Object);
|
||||
|
||||
using var client = await ConnectClientAsync(port);
|
||||
var stream = client.GetStream();
|
||||
|
||||
await SendRequestAsync(stream, new Request
|
||||
{
|
||||
Seq = 1,
|
||||
Type = "request",
|
||||
Command = "configurationDone"
|
||||
});
|
||||
|
||||
var configDoneResponse = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("\"command\":\"configurationDone\"", configDoneResponse);
|
||||
|
||||
var welcomeMsg = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("\"event\":\"output\"", welcomeMsg);
|
||||
|
||||
var output = JObject.Parse(welcomeMsg)["body"]["output"].ToString();
|
||||
Assert.Equal("Wel[31mcomehere\nnext\tline", output);
|
||||
|
||||
await _debugger.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData(null, "")]
|
||||
[InlineData("", "")]
|
||||
[InlineData("plain text", "plain text")]
|
||||
[InlineData("keep\r\nnewlines\tand tabs", "keep\r\nnewlines\tand tabs")]
|
||||
[InlineData("esc\u001bape", "escape")]
|
||||
[InlineData("bell\u0007null\u0000del\u007f", "bellnulldel")]
|
||||
[InlineData("c1\u0080\u009fchars", "c1chars")]
|
||||
public void SanitizeConsoleTextRemovesControlCharacters(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, DapDebugger.SanitizeConsoleText(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
|
||||
@@ -16,11 +16,13 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
private Mock<IProcessChannel> _processChannel;
|
||||
private Mock<IJobRunner> _jobRunner;
|
||||
private Mock<IVSockSecretNotifier> _vsockSecretNotifier;
|
||||
|
||||
public WorkerL0()
|
||||
{
|
||||
_processChannel = new Mock<IProcessChannel>();
|
||||
_jobRunner = new Mock<IJobRunner>();
|
||||
_vsockSecretNotifier = new Mock<IVSockSecretNotifier>();
|
||||
}
|
||||
|
||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
||||
@@ -88,6 +90,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
var worker = new GitHub.Runner.Worker.Worker();
|
||||
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
|
||||
hc.EnqueueInstance<IJobRunner>(_jobRunner.Object);
|
||||
hc.SetSingleton<IVSockSecretNotifier>(_vsockSecretNotifier.Object);
|
||||
worker.Initialize(hc);
|
||||
var jobMessage = CreateJobRequestMessage("job1");
|
||||
var arWorkerMessages = new WorkerMessage[]
|
||||
@@ -139,6 +142,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
var worker = new GitHub.Runner.Worker.Worker();
|
||||
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
|
||||
hc.EnqueueInstance<IJobRunner>(_jobRunner.Object);
|
||||
hc.SetSingleton<IVSockSecretNotifier>(_vsockSecretNotifier.Object);
|
||||
worker.Initialize(hc);
|
||||
var jobMessage = CreateJobRequestMessage("job1");
|
||||
var cancelMessage = CreateJobCancelMessage(jobMessage.JobId);
|
||||
|
||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="8.0.422"
|
||||
DOTNETSDK_VERSION="8.0.423"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.422"
|
||||
"version": "8.0.423"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.335.0
|
||||
2.336.0
|
||||
|
||||
Reference in New Issue
Block a user