mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9987a8d024 | ||
|
|
1326d12e25 | ||
|
|
c3219ccd9c | ||
|
|
d815be5264 | ||
|
|
dd945096d0 |
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -2,7 +2,6 @@ name: "Code Scanning - Action"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0'
|
- cron: '0 0 * * 0'
|
||||||
|
|
||||||
|
|||||||
335
.github/workflows/e2etest.yml
vendored
335
.github/workflows/e2etest.yml
vendored
@@ -1,335 +0,0 @@
|
|||||||
name: Runner E2E Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- releases/*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
init:
|
|
||||||
name: Initialize workflow ☕
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
unique_runner_label: ${{steps.generator.outputs.runner_label}}
|
|
||||||
steps:
|
|
||||||
- name: Delete all runners
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
with:
|
|
||||||
debug: true
|
|
||||||
script: |
|
|
||||||
var runnersResp = await github.actions.listSelfHostedRunnersForRepo({
|
|
||||||
owner: 'actions',
|
|
||||||
repo: 'runner',
|
|
||||||
per_page: '100'
|
|
||||||
});
|
|
||||||
for(var i=0; i<runnersResp.data.total_count; i++){
|
|
||||||
core.debug(JSON.stringify(runnersResp.data.runners[i]))
|
|
||||||
await github.actions.deleteSelfHostedRunnerFromRepo({
|
|
||||||
owner: 'actions',
|
|
||||||
repo: 'runner',
|
|
||||||
runner_id: runnersResp.data.runners[i].id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
github-token: ${{secrets.PAT}}
|
|
||||||
- name: Generate Unique Runner label
|
|
||||||
id: generator
|
|
||||||
run: |
|
|
||||||
label=$(openssl rand -hex 16)
|
|
||||||
echo ::set-output name=runner_label::$label
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build runner packages 🏗 📦
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
|
|
||||||
include:
|
|
||||||
- runtime: linux-x64
|
|
||||||
os: ubuntu-latest
|
|
||||||
devScript: ./dev.sh
|
|
||||||
|
|
||||||
- runtime: linux-arm64
|
|
||||||
os: ubuntu-latest
|
|
||||||
devScript: ./dev.sh
|
|
||||||
|
|
||||||
- runtime: linux-arm
|
|
||||||
os: ubuntu-latest
|
|
||||||
devScript: ./dev.sh
|
|
||||||
|
|
||||||
- runtime: osx-x64
|
|
||||||
os: macOS-latest
|
|
||||||
devScript: ./dev.sh
|
|
||||||
|
|
||||||
- runtime: win-x64
|
|
||||||
os: windows-latest
|
|
||||||
devScript: ./dev
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
|
|
||||||
# Build runner layout
|
|
||||||
- name: Build & Layout Release
|
|
||||||
run: |
|
|
||||||
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
|
|
||||||
working-directory: src
|
|
||||||
|
|
||||||
# Create runner package tar.gz/zip
|
|
||||||
- name: Package Release
|
|
||||||
run: |
|
|
||||||
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
|
|
||||||
working-directory: src
|
|
||||||
|
|
||||||
# Upload runner package tar.gz/zip as artifact
|
|
||||||
- name: Publish Artifact
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
with:
|
|
||||||
name: runner-package-${{ matrix.runtime }}
|
|
||||||
path: _package
|
|
||||||
|
|
||||||
dispatch_workflow:
|
|
||||||
name: Dispatch workflow to runners 🚨
|
|
||||||
needs: [init, build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Dispatch workflow
|
|
||||||
timeout-minutes: 10
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
with:
|
|
||||||
debug: true
|
|
||||||
script: |
|
|
||||||
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
|
|
||||||
async function dispatchWorkflow(runner) {
|
|
||||||
await github.actions.createWorkflowDispatch({
|
|
||||||
owner: 'actions',
|
|
||||||
repo: 'runner',
|
|
||||||
workflow_id: 'runner-basic-e2e-test-case.yml',
|
|
||||||
ref: 'main',
|
|
||||||
inputs: {target_runner: runner}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var runWin64 = false, runLinux64 = false, runOsx64 = false, runLinuxARM64 = false;
|
|
||||||
while (true) {
|
|
||||||
core.info(`------------- Waiting for runners to be configured --------------`)
|
|
||||||
await sleep(10000);
|
|
||||||
var runnersResp = await github.actions.listSelfHostedRunnersForRepo({owner: 'actions', repo: 'runner', per_page: '100'});
|
|
||||||
for (var i = 0; i < runnersResp.data.total_count; i++) {
|
|
||||||
core.debug(JSON.stringify(runnersResp.data.runners[i]))
|
|
||||||
var labels = runnersResp.data.runners[i].labels;
|
|
||||||
for (var j = 0; j < labels.length; j++) {
|
|
||||||
core.debug(`Comparing: ${labels[j].name} to win-x64/linux-x64/osx-x64/linux-arm64-${{ needs.init.outputs.unique_runner_label }}`)
|
|
||||||
if (labels[j].name == 'win-x64-${{needs.init.outputs.unique_runner_label}}' && runWin64 == false) {
|
|
||||||
core.info(`------------------- Windows runner is configured, queue Windows Run -------------------------`)
|
|
||||||
runWin64 = true;
|
|
||||||
await dispatchWorkflow('win-x64-${{needs.init.outputs.unique_runner_label}}');
|
|
||||||
break;
|
|
||||||
} else if (labels[j].name == 'linux-x64-${{needs.init.outputs.unique_runner_label}}' && runLinux64 == false) {
|
|
||||||
core.info(`------------------- Linux runner is configured, queue Linux Run -------------------------`)
|
|
||||||
runLinux64 = true;
|
|
||||||
await dispatchWorkflow('linux-x64-${{needs.init.outputs.unique_runner_label}}');
|
|
||||||
break;
|
|
||||||
} else if (labels[j].name == 'osx-x64-${{needs.init.outputs.unique_runner_label}}' && runOsx64 == false) {
|
|
||||||
core.info(`------------------- macOS runner is configured, queue macOS Run -------------------------`)
|
|
||||||
runOsx64 = true;
|
|
||||||
await dispatchWorkflow('osx-x64-${{needs.init.outputs.unique_runner_label}}');
|
|
||||||
break;
|
|
||||||
} else if (labels[j].name == 'linux-arm64-${{needs.init.outputs.unique_runner_label}}' && runLinuxARM64 == false) {
|
|
||||||
core.info(`------------------- Linux ARM64 runner is configured, queue Linux ARM64 Run-------------------------`)
|
|
||||||
runLinuxARM64 = true;
|
|
||||||
await dispatchWorkflow('linux-arm64-${{needs.init.outputs.unique_runner_label}}');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (runWin64 && runLinux64 && runOsx64 && runLinuxARM64) {
|
|
||||||
core.info(`--------------------- ALL runner are running jobs --------------------------`)
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
core.info(`---------- Windows running: ${runWin64} -- Linux running: ${runLinux64} -- macOS running: ${runOsx64} -- Linux ARM64 running: ${runLinuxARM64} -----------`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
github-token: ${{secrets.PAT}}
|
|
||||||
|
|
||||||
LinuxE2E:
|
|
||||||
needs: [build, init]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download Runner
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: runner-package-linux-x64
|
|
||||||
- name: Unzip Runner Package
|
|
||||||
run: |
|
|
||||||
tar -xzf *.tar.gz
|
|
||||||
- name: Configure Runner
|
|
||||||
env:
|
|
||||||
unique_runner_name: linux-x64-${{needs.init.outputs.unique_runner_label}}
|
|
||||||
run: |
|
|
||||||
./config.sh --url ${{github.event.repository.html_url}} --unattended --name $unique_runner_name --pat ${{secrets.PAT}} --labels $unique_runner_name --replace
|
|
||||||
- name: Start Runner and Wait for Job
|
|
||||||
timeout-minutes: 5
|
|
||||||
run: |
|
|
||||||
./run.sh --once
|
|
||||||
- name: Remove Runner
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
./config.sh remove --pat ${{secrets.PAT}}
|
|
||||||
- name: Upload Runner Logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: linux_x64_logs
|
|
||||||
path: _diag
|
|
||||||
macOSE2E:
|
|
||||||
needs: [build, init]
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Download Runner
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: runner-package-osx-x64
|
|
||||||
- name: Unzip Runner Package
|
|
||||||
run: |
|
|
||||||
tar -xzf *.tar.gz
|
|
||||||
- name: Configure Runner
|
|
||||||
env:
|
|
||||||
unique_runner_name: osx-x64-${{needs.init.outputs.unique_runner_label}}
|
|
||||||
run: |
|
|
||||||
./config.sh --url ${{github.event.repository.html_url}} --unattended --name $unique_runner_name --pat ${{secrets.PAT}} --labels $unique_runner_name --replace
|
|
||||||
- name: Start Runner and Wait for Job
|
|
||||||
timeout-minutes: 5
|
|
||||||
run: |
|
|
||||||
./run.sh --once
|
|
||||||
- name: Remove Runner
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
./config.sh remove --pat ${{secrets.PAT}}
|
|
||||||
- name: Upload Runner Logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: osx_x64_logs
|
|
||||||
path: _diag
|
|
||||||
|
|
||||||
ARM64E2E:
|
|
||||||
needs: [build, init]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download Runner
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: runner-package-linux-arm64
|
|
||||||
- name: Unzip Runner Package
|
|
||||||
run: |
|
|
||||||
tar -xzf *.tar.gz
|
|
||||||
- name: Prepare QEMU
|
|
||||||
run: |
|
|
||||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
|
||||||
- name: Configure Runner
|
|
||||||
uses: docker://multiarch/ubuntu-core:arm64-bionic
|
|
||||||
with:
|
|
||||||
args: 'bash -c "apt-get update && apt-get install -y curl && ./bin/installdependencies.sh && ./config.sh --unattended --name $unique_runner_name --url ${{github.event.repository.html_url}} --pat ${{secrets.PAT}} --labels $unique_runner_name --replace"'
|
|
||||||
env:
|
|
||||||
RUNNER_ALLOW_RUNASROOT: 1
|
|
||||||
unique_runner_name: linux-arm64-${{needs.init.outputs.unique_runner_label}}
|
|
||||||
|
|
||||||
- name: Start Runner and Wait for Job
|
|
||||||
timeout-minutes: 5
|
|
||||||
uses: docker://multiarch/ubuntu-core:arm64-bionic
|
|
||||||
with:
|
|
||||||
args: 'bash -c "apt-get update && apt-get install -y curl git && ./bin/installdependencies.sh && ./run.sh --once"'
|
|
||||||
env:
|
|
||||||
RUNNER_ALLOW_RUNASROOT: 1
|
|
||||||
|
|
||||||
- name: Remove Runner
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
uses: docker://multiarch/ubuntu-core:arm64-bionic
|
|
||||||
with:
|
|
||||||
args: 'bash -c "apt-get update && apt-get install -y curl && ./bin/installdependencies.sh && ./config.sh remove --pat ${{secrets.PAT}}"'
|
|
||||||
env:
|
|
||||||
RUNNER_ALLOW_RUNASROOT: 1
|
|
||||||
|
|
||||||
- name: Upload Runner Logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: linux_arm64_logs
|
|
||||||
path: _diag
|
|
||||||
|
|
||||||
WindowsE2E:
|
|
||||||
needs: [build, init]
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Download Runner
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: runner-package-win-x64
|
|
||||||
- name: Unzip Runner Package
|
|
||||||
run: |
|
|
||||||
Get-ChildItem *.zip | Expand-Archive -DestinationPath $PWD
|
|
||||||
- name: Configure Runner
|
|
||||||
shell: cmd
|
|
||||||
run: |
|
|
||||||
config.cmd --unattended --url ${{github.event.repository.html_url}} --name %unique_runner_name% --pat ${{secrets.PAT}} --labels %unique_runner_name% --replace
|
|
||||||
env:
|
|
||||||
unique_runner_name: win-x64-${{needs.init.outputs.unique_runner_label}}
|
|
||||||
|
|
||||||
- name: Start Runner and Wait for Job
|
|
||||||
shell: cmd
|
|
||||||
timeout-minutes: 5
|
|
||||||
run: |
|
|
||||||
run.cmd --once
|
|
||||||
- name: Remove Runner
|
|
||||||
shell: cmd
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
config.cmd remove --pat ${{secrets.PAT}}
|
|
||||||
- name: Upload Runner Logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: win_x64_logs
|
|
||||||
path: _diag
|
|
||||||
|
|
||||||
check:
|
|
||||||
name: Check runner logs 🕵️♂️
|
|
||||||
needs: [WindowsE2E, LinuxE2E, macOSE2E, ARM64E2E]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download Linux Runner Logs
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: linux_x64_logs
|
|
||||||
path: linux_x64_logs
|
|
||||||
- name: Download macOS Runner Logs
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: osx_x64_logs
|
|
||||||
path: osx_x64_logs
|
|
||||||
- name: Download Linux ARM64 Runner Logs
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: linux_arm64_logs
|
|
||||||
path: linux_arm64_logs
|
|
||||||
- name: Download Windows Runner Logs
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: win_x64_logs
|
|
||||||
path: win_x64_logs
|
|
||||||
- name: Check Runner Logs
|
|
||||||
run: |
|
|
||||||
function failed()
|
|
||||||
{
|
|
||||||
local error=${1:-Undefined error}
|
|
||||||
echo "Failed: $error" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
grep -R "completed with result: Succeeded" ./win_x64_logs || failed "Windows Runner fail to run the job, please check logs"
|
|
||||||
grep -R "completed with result: Succeeded" ./linux_x64_logs || failed "Linux Runner fail to run the job, please check logs"
|
|
||||||
grep -R "completed with result: Succeeded" ./osx_x64_logs || failed "macOS Runner fail to run the job, please check logs"
|
|
||||||
grep -R "completed with result: Succeeded" ./linux_arm64_logs || failed "Linux ARM64 Runner fail to run the job, please check logs"
|
|
||||||
31
.github/workflows/runner-basic-e2e-test-case.yml
vendored
31
.github/workflows/runner-basic-e2e-test-case.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
name: Runner Basics Test Case
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
target_runner:
|
|
||||||
description: 'Self-hosted runner will run the job'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on:
|
|
||||||
- self-hosted
|
|
||||||
- ${{github.event.inputs.target_runner}}
|
|
||||||
|
|
||||||
name: Runner Basic Test 🛠
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Run a one-line script
|
|
||||||
run: echo Hello, world!
|
|
||||||
- name: Run a multi-line script
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
printenv|sort
|
|
||||||
cat $GITHUB_EVENT_PATH
|
|
||||||
- name: Validate GitHub Context
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
declare -a context_vars=("GITHUB_ACTION" "GITHUB_ACTIONS" "GITHUB_REPOSITORY" "GITHUB_WORKSPACE" "GITHUB_SHA" "GITHUB_RUN_ID" "GITHUB_RUN_NUMBER")
|
|
||||||
for var in ${context_vars[@]};
|
|
||||||
do [ -z "${!var}" ] && echo "##[error]$var not found" && exit 1 || echo "$var: ${!var}"; done
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
* @actions/actions-runtime
|
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
# GitHub Actions Runner
|
# GitHub Actions Runner
|
||||||
|
|
||||||
[](https://github.com/actions/runner/actions)
|
[](https://github.com/actions/runner/actions)
|
||||||
[](https://github.com/actions/runner/actions)
|
|
||||||
|
|
||||||
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.
|
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
|
|
||||||
# Actions Connection Check
|
|
||||||
|
|
||||||
## What is this check for?
|
|
||||||
|
|
||||||
Make sure the runner has access to actions service for GitHub.com or GitHub Enterprise Server
|
|
||||||
|
|
||||||
- For GitHub.com
|
|
||||||
- The runner needs to access https://api.github.com for downloading actions.
|
|
||||||
- The runner needs to access https://vstoken.actions.githubusercontent.com/_apis/.../ for requesting an access token.
|
|
||||||
- The runner needs to access https://pipelines.actions.githubusercontent.com/_apis/.../ for receiving workflow jobs.
|
|
||||||
- For GitHub Enterprise Server
|
|
||||||
- The runner needs to access https://myGHES.com/api/v3 for downloading actions.
|
|
||||||
- The runner needs to access https://myGHES.com/_services/vstoken/_apis/.../ for requesting an access token.
|
|
||||||
- The runner needs to access https://myGHES.com/_services/pipelines/_apis/.../ for receiving workflow jobs.
|
|
||||||
|
|
||||||
## What is checked?
|
|
||||||
|
|
||||||
- DNS lookup for api.github.com or myGHES.com using dotnet
|
|
||||||
- Ping api.github.com or myGHES.com using dotnet
|
|
||||||
- Make HTTP GET to https://api.github.com or https://myGHES.com/api/v3 using dotnet, check response headers contains `X-GitHub-Request-Id`
|
|
||||||
---
|
|
||||||
- DNS lookup for vstoken.actions.githubusercontent.com using dotnet
|
|
||||||
- Ping vstoken.actions.githubusercontent.com using dotnet
|
|
||||||
- Make HTTP GET to https://vstoken.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/vstoken/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
|
||||||
---
|
|
||||||
- DNS lookup for pipelines.actions.githubusercontent.com using dotnet
|
|
||||||
- Ping pipelines.actions.githubusercontent.com using dotnet
|
|
||||||
- Make HTTP GET to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
|
||||||
|
|
||||||
## How to fix the issue?
|
|
||||||
|
|
||||||
### 1. Check the common network issue
|
|
||||||
|
|
||||||
> Please check the [network doc](./network.md)
|
|
||||||
|
|
||||||
### 2. SSL certificate related issue
|
|
||||||
|
|
||||||
If you are seeing `System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.` in the log, it means the runner can't connect to Actions service due to SSL handshake failure.
|
|
||||||
> Please check the [SSL cert doc](./sslcert.md)
|
|
||||||
|
|
||||||
## Still not working?
|
|
||||||
|
|
||||||
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Git Connection Check
|
|
||||||
|
|
||||||
## What is this check for?
|
|
||||||
|
|
||||||
Make sure `git` can access GitHub.com or your GitHub Enterprise Server.
|
|
||||||
|
|
||||||
|
|
||||||
## What is checked?
|
|
||||||
|
|
||||||
The test is done by executing
|
|
||||||
```bash
|
|
||||||
# For GitHub.com
|
|
||||||
git ls-remote --exit-code https://github.com/actions/checkout HEAD
|
|
||||||
|
|
||||||
# For GitHub Enterprise Server
|
|
||||||
git ls-remote --exit-code https://ghes.me/actions/checkout HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
The test also set environment variable `GIT_TRACE=1` and `GIT_CURL_VERBOSE=1` before running `git ls-remote`, this will make `git` to produce debug log for better debug any potential issues.
|
|
||||||
|
|
||||||
## How to fix the issue?
|
|
||||||
|
|
||||||
### 1. Check the common network issue
|
|
||||||
|
|
||||||
> Please check the [network doc](./network.md)
|
|
||||||
|
|
||||||
### 2. SSL certificate related issue
|
|
||||||
|
|
||||||
If you are seeing `SSL Certificate problem:` in the log, it means the `git` can't connect to the GitHub server due to SSL handshake failure.
|
|
||||||
> Please check the [SSL cert doc](./sslcert.md)
|
|
||||||
|
|
||||||
## Still not working?
|
|
||||||
|
|
||||||
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Internet Connection Check
|
|
||||||
|
|
||||||
## What is this check for?
|
|
||||||
|
|
||||||
Make sure the runner has access to https://api.github.com
|
|
||||||
|
|
||||||
The runner needs to access https://api.github.com to download any actions from the marketplace.
|
|
||||||
|
|
||||||
Even the runner is configured to GitHub Enterprise Server, the runner can still download actions from GitHub.com with [GitHub Connect](https://docs.github.com/en/enterprise-server@2.22/admin/github-actions/enabling-automatic-access-to-githubcom-actions-using-github-connect)
|
|
||||||
|
|
||||||
|
|
||||||
## What is checked?
|
|
||||||
|
|
||||||
- DNS lookup for api.github.com using dotnet
|
|
||||||
- Ping api.github.com using dotnet
|
|
||||||
- Make HTTP GET to https://api.github.com using dotnet, check response headers contains `X-GitHub-Request-Id`
|
|
||||||
|
|
||||||
## How to fix the issue?
|
|
||||||
|
|
||||||
### 1. Check the common network issue
|
|
||||||
|
|
||||||
> Please check the [network doc](./network.md)
|
|
||||||
|
|
||||||
## Still not working?
|
|
||||||
|
|
||||||
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
## Common Network Related Issues
|
|
||||||
|
|
||||||
### Common things that can cause the runner to not working properly
|
|
||||||
|
|
||||||
- Bug in the runner or the dotnet framework that causes actions runner can't make Http request in a certain network environment.
|
|
||||||
|
|
||||||
- Proxy/Firewall block certain HTTP method, like it block all POST and PUT calls which the runner will use to upload logs.
|
|
||||||
|
|
||||||
- Proxy/Firewall only allows requests with certain user-agent to pass through and the actions runner user-agent is not in the allow list.
|
|
||||||
|
|
||||||
- Proxy try to decrypt and exam HTTPS traffic for security purpose but cause the actions-runner to fail to finish SSL handshake due to the lack of trusting proxy's CA.
|
|
||||||
|
|
||||||
- Firewall rules that block action runner from accessing certain hosts, ex: `*.github.com`, `*.actions.githubusercontent.com`, etc.
|
|
||||||
|
|
||||||
|
|
||||||
### Identify and solve these problems
|
|
||||||
|
|
||||||
The key is to figure out where is the problem, the network environment, or the actions runner?
|
|
||||||
|
|
||||||
Use a 3rd party tool to make the same requests as the runner did would be a good start point.
|
|
||||||
|
|
||||||
- Use `nslookup` to check DNS
|
|
||||||
- Use `ping` to check Ping
|
|
||||||
- Use `curl -v` to check the network stack, good for verifying default certificate/proxy settings.
|
|
||||||
- Use `Invoke-WebRequest` from `pwsh` (`PowerShell Core`) to check the dotnet network stack, good for verifying bugs in the dotnet framework.
|
|
||||||
|
|
||||||
If the 3rd party tool is also experiencing the same error as the runner does, then you might want to contact your network administrator for help.
|
|
||||||
|
|
||||||
Otherwise, contact GitHub customer support or log an issue at https://github.com/actions/runner
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Node.js Connection Check
|
|
||||||
|
|
||||||
## What is this check for?
|
|
||||||
|
|
||||||
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
|
||||||
|
|
||||||
The runner carries it's own copy of node.js executable under `<runner_root>/externals/node12/`.
|
|
||||||
|
|
||||||
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node12/`.
|
|
||||||
|
|
||||||
> Not the `node` from `$PATH`
|
|
||||||
|
|
||||||
## What is checked?
|
|
||||||
|
|
||||||
- Make HTTPS GET to https://api.github.com or https://myGHES.com/api/v3 using node.js, make sure it gets 200 response code.
|
|
||||||
|
|
||||||
## How to fix the issue?
|
|
||||||
|
|
||||||
### 1. Check the common network issue
|
|
||||||
|
|
||||||
> Please check the [network doc](./network.md)
|
|
||||||
|
|
||||||
### 2. SSL certificate related issue
|
|
||||||
|
|
||||||
If you are seeing `Https request failed due to SSL cert issue` in the log, it means the `node.js` can't connect to the GitHub server due to SSL handshake failure.
|
|
||||||
> Please check the [SSL cert doc](./sslcert.md)
|
|
||||||
|
|
||||||
## Still not working?
|
|
||||||
|
|
||||||
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
## SSL Certificate Related Issues
|
|
||||||
|
|
||||||
You might run into an SSL certificate error when your GitHub Enterprise Server is using a self-signed SSL server certificate or a web proxy within your network is decrypting HTTPS traffic for a security audit.
|
|
||||||
|
|
||||||
As long as your certificate is generated properly, most of the issues should be fixed after your trust the certificate properly on the runner machine.
|
|
||||||
|
|
||||||
> Different OS might have extra requirements on SSL certificate,
|
|
||||||
> Ex: macOS requires `ExtendedKeyUsage` https://support.apple.com/en-us/HT210176
|
|
||||||
|
|
||||||
### Don't skip SSL cert validation
|
|
||||||
|
|
||||||
> !!! DO NOT SKIP SSL CERT VALIDATION !!!
|
|
||||||
> !!! IT IS A BAD SECURITY PRACTICE !!!
|
|
||||||
|
|
||||||
### Download SSL certificate chain
|
|
||||||
|
|
||||||
Depends on how your SSL server certificate gets configured, you might need to download the whole certificate chain from a machine that has trusted the SSL certificate's CA.
|
|
||||||
|
|
||||||
- Approach 1: Download certificate chain using a browser (Chrome, Firefox, IT), you can google for more example, [here is what I found](https://medium.com/@menakajain/export-download-ssl-certificate-from-server-site-url-bcfc41ea46a2)
|
|
||||||
|
|
||||||
- Approach 2: Download certificate chain using OpenSSL, you can google for more example, [here is what I found](https://superuser.com/a/176721)
|
|
||||||
|
|
||||||
- Approach 3: Ask your network administrator or the owner of the CA certificate to send you a copy of it
|
|
||||||
|
|
||||||
### Trust CA certificate for the Runner
|
|
||||||
|
|
||||||
The actions runner is a dotnet core application which will follow how dotnet load SSL CA certificates on each OS.
|
|
||||||
|
|
||||||
You can get full details documentation at [here](https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography#x509store)
|
|
||||||
|
|
||||||
In short:
|
|
||||||
- Windows: Load from Windows certificate store.
|
|
||||||
- Linux: Load from OpenSSL CA cert bundle.
|
|
||||||
- macOS: Load from macOS KeyChain.
|
|
||||||
|
|
||||||
To let the runner trusts your CA certificate, you will need to:
|
|
||||||
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
|
|
||||||
2. Use `OpenSSL` to convert `.pem` file to a proper format for different OS, here is some [doc with sample commands](https://www.sslshopper.com/ssl-converter.html)
|
|
||||||
3. Trust CA on different OS:
|
|
||||||
- Windows: https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate
|
|
||||||
- macOS: 
|
|
||||||
- Linux: Refer to the distribution documentation
|
|
||||||
1. RedHat: https://www.redhat.com/sysadmin/ca-certificates-cli
|
|
||||||
2. Ubuntu: http://manpages.ubuntu.com/manpages/focal/man8/update-ca-certificates.8.html
|
|
||||||
3. Google search: "trust ca certificate on [linux distribution]"
|
|
||||||
4. If all approaches failed, set environment variable `SSL_CERT_FILE` to the CA bundle `.pem` file we get.
|
|
||||||
> To verity cert gets installed properly on Linux, you can try use `curl -v https://sitewithsslissue.com` and `pwsh -Command \"Invoke-WebRequest -Uri https://sitewithsslissue.com\"`
|
|
||||||
|
|
||||||
### Trust CA certificate for Git CLI
|
|
||||||
|
|
||||||
Git uses various CA bundle file depends on your operation system.
|
|
||||||
- Git packaged the CA bundle file within the Git installation on Windows
|
|
||||||
- Git use OpenSSL certificate CA bundle file on Linux and macOS
|
|
||||||
|
|
||||||
You can check where Git check CA file by running:
|
|
||||||
```bash
|
|
||||||
export GIT_CURL_VERBOSE=1
|
|
||||||
git ls-remote https://github.com/actions/runner HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see something like:
|
|
||||||
```
|
|
||||||
* Couldn't find host github.com in the .netrc file; using defaults
|
|
||||||
* Trying 140.82.114.4...
|
|
||||||
* TCP_NODELAY set
|
|
||||||
* Connected to github.com (140.82.114.4) port 443 (#0)
|
|
||||||
* ALPN, offering h2
|
|
||||||
* ALPN, offering http/1.1
|
|
||||||
* successfully set certificate verify locations:
|
|
||||||
* CAfile: /etc/ssl/cert.pem
|
|
||||||
CApath: none
|
|
||||||
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
|
|
||||||
```
|
|
||||||
This tells me `/etc/ssl/cert.pem` is where it read trusted CA certificates.
|
|
||||||
|
|
||||||
To let Git trusts your CA certificate, you will need to:
|
|
||||||
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
|
|
||||||
2. Set `http.sslCAInfo` Git config or `GIT_SSL_CAINFO` environment variable to the full path of the `.pem` file [Git Doc](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslCAInfo)
|
|
||||||
> I would recommend using `http.sslCAInfo` since it can be scope to certain hosts that need the extra trusted CA.
|
|
||||||
> Ex: `git config --global http.https://myghes.com/.sslCAInfo /extra/ca/cert.pem`
|
|
||||||
> This will make Git use the `/extra/ca/cert.pem` only when communicates with `https://myghes.com` and keep using the default CA bundle with others.
|
|
||||||
|
|
||||||
### Trust CA certificate for Node.js
|
|
||||||
|
|
||||||
Node.js has compiled a snapshot of the Mozilla CA store that is fixed at each version of Node.js' release time.
|
|
||||||
|
|
||||||
To let Node.js trusts your CA certificate, you will need to:
|
|
||||||
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
|
|
||||||
2. Set environment variable `NODE_EXTRA_CA_CERTS` which point to the file. ex: `export NODE_EXTRA_CA_CERTS=/full/path/to/cacert.pem` or `set NODE_EXTRA_CA_CERTS=C:\full\path\to\cacert.pem`
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 MiB |
@@ -15,16 +15,16 @@ x64
|
|||||||
- openSUSE 15+
|
- openSUSE 15+
|
||||||
- SUSE Enterprise Linux (SLES) 12 SP2+
|
- SUSE Enterprise Linux (SLES) 12 SP2+
|
||||||
|
|
||||||
## Install .Net Core 5 Linux Dependencies
|
## Install .Net Core 3.x Linux Dependencies
|
||||||
|
|
||||||
The `./config.sh` will check .Net Core 5 dependencies during runner configuration.
|
The `./config.sh` will check .Net Core 3.x dependencies during runner configuration.
|
||||||
You might see something like this which indicate a dependency's missing.
|
You might see something like this which indicate a dependency's missing.
|
||||||
```bash
|
```bash
|
||||||
./config.sh
|
./config.sh
|
||||||
libunwind.so.8 => not found
|
libunwind.so.8 => not found
|
||||||
libunwind-x86_64.so.8 => not found
|
libunwind-x86_64.so.8 => not found
|
||||||
Dependencies is missing for Dotnet 5
|
Dependencies is missing for Dotnet Core 3.0
|
||||||
Execute ./bin/installdependencies.sh to install any missing Dotnet 5 dependencies.
|
Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies.
|
||||||
```
|
```
|
||||||
You can easily correct the problem by executing `./bin/installdependencies.sh`.
|
You can easily correct the problem by executing `./bin/installdependencies.sh`.
|
||||||
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions
|
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
## Features
|
## Features
|
||||||
- Support config runner via GitHub PAT. (#874)
|
- N/A
|
||||||
- Update runner to .NET 5 (#799)
|
|
||||||
- Add new ANDROID_SDK_ROOT environment variable (#892)
|
|
||||||
- Add warning when running out of disk. (#873)
|
|
||||||
- Always use FIPS Cryptography (#896)
|
|
||||||
- Add `--check` to run a serials network test against GitHub or GHES. (#900)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Ignore certain scenarios so they are not counted as infra failures (#889)
|
- N/A
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Add runner e2e test workflow (#885)
|
- Disabled add-path and set-env runner commands (#779)
|
||||||
- Add on: pull_request trigger to CodeQL workflow (#907)
|
- Updated dotnet install scripts (#779)
|
||||||
|
|
||||||
## 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.276.0
|
2.274.2
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ set -e
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# export RUNNER_CFG_PAT=<yourPAT>
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
# ./create-latest-svc scope [ghe_domain] [name] [user] [labels]
|
# ./create-latest-svc scope [ghe_domain] [name] [user]
|
||||||
#
|
#
|
||||||
# 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
|
||||||
@@ -31,7 +30,6 @@ 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
|
||||||
@@ -132,8 +130,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 --labels $labels"
|
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name"
|
||||||
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name --labels $labels
|
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name
|
||||||
|
|
||||||
#---------------------------------------
|
#---------------------------------------
|
||||||
# Configuring as a service
|
# Configuring as a service
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
const https = require('https')
|
|
||||||
const fs = require('fs')
|
|
||||||
const http = require('http')
|
|
||||||
const hostname = process.env['HOSTNAME'] || ''
|
|
||||||
const port = process.env['PORT'] || ''
|
|
||||||
const path = process.env['PATH'] || ''
|
|
||||||
const pat = process.env['PAT'] || ''
|
|
||||||
const proxyHost = process.env['PROXYHOST'] || ''
|
|
||||||
const proxyPort = process.env['PROXYPORT'] || ''
|
|
||||||
const proxyUsername = process.env['PROXYUSERNAME'] || ''
|
|
||||||
const proxyPassword = process.env['PROXYPASSWORD'] || ''
|
|
||||||
|
|
||||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'
|
|
||||||
|
|
||||||
if (proxyHost === '') {
|
|
||||||
const options = {
|
|
||||||
hostname: hostname,
|
|
||||||
port: port,
|
|
||||||
path: path,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
|
||||||
'Authorization': `token ${pat}`
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const req = https.request(options, res => {
|
|
||||||
console.log(`statusCode: ${res.statusCode}`)
|
|
||||||
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
|
||||||
let cert = socket.getPeerCertificate(true)
|
|
||||||
let certPEM = ''
|
|
||||||
let fingerprints = {}
|
|
||||||
while (cert != null && fingerprints[cert.fingerprint] != '1') {
|
|
||||||
fingerprints[cert.fingerprint] = '1'
|
|
||||||
certPEM = certPEM + '-----BEGIN CERTIFICATE-----\n'
|
|
||||||
let certEncoded = cert.raw.toString('base64')
|
|
||||||
for (let i = 0; i < certEncoded.length; i++) {
|
|
||||||
certPEM = certPEM + certEncoded[i]
|
|
||||||
if (i != certEncoded.length - 1 && (i + 1) % 64 == 0) {
|
|
||||||
certPEM = certPEM + '\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
certPEM = certPEM + '\n-----END CERTIFICATE-----\n'
|
|
||||||
cert = cert.issuerCertificate
|
|
||||||
}
|
|
||||||
console.log(certPEM)
|
|
||||||
fs.writeFileSync('./download_ca_cert.pem', certPEM)
|
|
||||||
res.on('data', d => {
|
|
||||||
process.stdout.write(d)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
req.on('error', error => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
req.end()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const auth = 'Basic ' + Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64')
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
host: proxyHost,
|
|
||||||
port: proxyPort,
|
|
||||||
method: 'CONNECT',
|
|
||||||
path: `${hostname}:${port}`,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxyUsername != '' || proxyPassword != '') {
|
|
||||||
options.headers = {
|
|
||||||
'Proxy-Authorization': auth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.request(options).on('connect', (res, socket) => {
|
|
||||||
if (res.statusCode != 200) {
|
|
||||||
throw new Error(`Proxy returns code: ${res.statusCode}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
https.get({
|
|
||||||
host: hostname,
|
|
||||||
port: port,
|
|
||||||
socket: socket,
|
|
||||||
agent: false,
|
|
||||||
path: '/',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
|
||||||
'Authorization': `token ${pat}`
|
|
||||||
}
|
|
||||||
}, (res) => {
|
|
||||||
let cert = res.socket.getPeerCertificate(true)
|
|
||||||
let certPEM = ''
|
|
||||||
let fingerprints = {}
|
|
||||||
while (cert != null && fingerprints[cert.fingerprint] != '1') {
|
|
||||||
fingerprints[cert.fingerprint] = '1'
|
|
||||||
certPEM = certPEM + '-----BEGIN CERTIFICATE-----\n'
|
|
||||||
let certEncoded = cert.raw.toString('base64')
|
|
||||||
for (let i = 0; i < certEncoded.length; i++) {
|
|
||||||
certPEM = certPEM + certEncoded[i]
|
|
||||||
if (i != certEncoded.length - 1 && (i + 1) % 64 == 0) {
|
|
||||||
certPEM = certPEM + '\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
certPEM = certPEM + '\n-----END CERTIFICATE-----\n'
|
|
||||||
cert = cert.issuerCertificate
|
|
||||||
}
|
|
||||||
console.log(certPEM)
|
|
||||||
fs.writeFileSync('./download_ca_cert.pem', certPEM)
|
|
||||||
console.log(`statusCode: ${res.statusCode}`)
|
|
||||||
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
|
||||||
res.on('data', d => {
|
|
||||||
process.stdout.write(d)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}).on('error', (err) => {
|
|
||||||
console.error('error', err)
|
|
||||||
}).end()
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
const https = require('https')
|
|
||||||
const http = require('http')
|
|
||||||
const hostname = process.env['HOSTNAME'] || ''
|
|
||||||
const port = process.env['PORT'] || ''
|
|
||||||
const path = process.env['PATH'] || ''
|
|
||||||
const pat = process.env['PAT'] || ''
|
|
||||||
const proxyHost = process.env['PROXYHOST'] || ''
|
|
||||||
const proxyPort = process.env['PROXYPORT'] || ''
|
|
||||||
const proxyUsername = process.env['PROXYUSERNAME'] || ''
|
|
||||||
const proxyPassword = process.env['PROXYPASSWORD'] || ''
|
|
||||||
|
|
||||||
if (proxyHost === '') {
|
|
||||||
const options = {
|
|
||||||
hostname: hostname,
|
|
||||||
port: port,
|
|
||||||
path: path,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
|
||||||
'Authorization': `token ${pat}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const req = https.request(options, res => {
|
|
||||||
console.log(`statusCode: ${res.statusCode}`)
|
|
||||||
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
|
||||||
|
|
||||||
res.on('data', d => {
|
|
||||||
process.stdout.write(d)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
req.on('error', error => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
req.end()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const proxyAuth = 'Basic ' + Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64')
|
|
||||||
const options = {
|
|
||||||
hostname: proxyHost,
|
|
||||||
port: proxyPort,
|
|
||||||
method: 'CONNECT',
|
|
||||||
path: `${hostname}:${port}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxyUsername != '' || proxyPassword != '') {
|
|
||||||
options.headers = {
|
|
||||||
'Proxy-Authorization': proxyAuth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.request(options).on('connect', (res, socket) => {
|
|
||||||
if (res.statusCode != 200) {
|
|
||||||
throw new Error(`Proxy returns code: ${res.statusCode}`)
|
|
||||||
}
|
|
||||||
https.get({
|
|
||||||
host: hostname,
|
|
||||||
port: port,
|
|
||||||
socket: socket,
|
|
||||||
agent: false,
|
|
||||||
path: path,
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
|
||||||
'Authorization': `token ${pat}`,
|
|
||||||
}
|
|
||||||
}, (res) => {
|
|
||||||
console.log(`statusCode: ${res.statusCode}`)
|
|
||||||
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
|
||||||
|
|
||||||
res.on('data', d => {
|
|
||||||
process.stdout.write(d)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}).on('error', (err) => {
|
|
||||||
console.error('error', err)
|
|
||||||
}).end()
|
|
||||||
}
|
|
||||||
@@ -14,14 +14,14 @@ fi
|
|||||||
|
|
||||||
function print_errormessage()
|
function print_errormessage()
|
||||||
{
|
{
|
||||||
echo "Can't install dotnet 5 dependencies."
|
echo "Can't install dotnet core dependencies."
|
||||||
echo "You can manually install all required dependencies based on following documentation"
|
echo "You can manually install all required dependencies based on following documentation"
|
||||||
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
||||||
}
|
}
|
||||||
|
|
||||||
function print_rhel6message()
|
function print_rhel6message()
|
||||||
{
|
{
|
||||||
echo "We did our best effort to install dotnet 5 dependencies"
|
echo "We did our best effort to install dotnet core dependencies"
|
||||||
echo "However, there are some dependencies which require manual installation"
|
echo "However, there are some dependencies which require manual installation"
|
||||||
echo "You can install all remaining required dependencies based on the following documentation"
|
echo "You can install all remaining required dependencies based on the following documentation"
|
||||||
echo "https://github.com/dotnet/core/blob/master/Documentation/build-and-install-rhel6-prerequisites.md"
|
echo "https://github.com/dotnet/core/blob/master/Documentation/build-and-install-rhel6-prerequisites.md"
|
||||||
@@ -29,7 +29,7 @@ function print_rhel6message()
|
|||||||
|
|
||||||
function print_rhel6errormessage()
|
function print_rhel6errormessage()
|
||||||
{
|
{
|
||||||
echo "We couldn't install dotnet 5 dependencies"
|
echo "We couldn't install dotnet core dependencies"
|
||||||
echo "You can manually install all required dependencies based on following documentation"
|
echo "You can manually install all required dependencies based on following documentation"
|
||||||
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
||||||
echo "In addition, there are some dependencies which require manual installation. Please follow this documentation"
|
echo "In addition, there are some dependencies which require manual installation. Please follow this documentation"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check dotnet 5 dependencies for Linux
|
# Check dotnet core 3.0 dependencies for Linux
|
||||||
if [[ (`uname` == "Linux") ]]
|
if [[ (`uname` == "Linux") ]]
|
||||||
then
|
then
|
||||||
command -v ldd > /dev/null
|
command -v ldd > /dev/null
|
||||||
@@ -18,25 +18,25 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
message="Execute sudo ./bin/installdependencies.sh to install any missing Dotnet 5 dependencies."
|
message="Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies."
|
||||||
|
|
||||||
ldd ./bin/libcoreclr.so | grep 'not found'
|
ldd ./bin/libcoreclr.so | grep 'not found'
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Dependencies is missing for Dotnet 5"
|
echo "Dependencies is missing for Dotnet Core 3.0"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ldd ./bin/libSystem.Security.Cryptography.Native.OpenSsl.so | grep 'not found'
|
ldd ./bin/System.Security.Cryptography.Native.OpenSsl.so | grep 'not found'
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Dependencies is missing for Dotnet 5"
|
echo "Dependencies is missing for Dotnet Core 3.0"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ldd ./bin/libSystem.IO.Compression.Native.so | grep 'not found'
|
ldd ./bin/System.IO.Compression.Native.so | grep 'not found'
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Dependencies is missing for Dotnet 5"
|
echo "Dependencies is missing for Dotnet Core 3.0"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -54,7 +54,7 @@ then
|
|||||||
libpath=${LD_LIBRARY_PATH:-}
|
libpath=${LD_LIBRARY_PATH:-}
|
||||||
$LDCONFIG_COMMAND -NXv ${libpath//:/ } 2>&1 | grep libicu >/dev/null 2>&1
|
$LDCONFIG_COMMAND -NXv ${libpath//:/ } 2>&1 | grep libicu >/dev/null 2>&1
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Libicu's dependencies is missing for Dotnet 5"
|
echo "Libicu's dependencies is missing for Dotnet Core 3.0"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ varCheckList=(
|
|||||||
'ANT_HOME'
|
'ANT_HOME'
|
||||||
'M2_HOME'
|
'M2_HOME'
|
||||||
'ANDROID_HOME'
|
'ANDROID_HOME'
|
||||||
'ANDROID_SDK_ROOT'
|
|
||||||
'GRADLE_HOME'
|
'GRADLE_HOME'
|
||||||
'NVM_BIN'
|
'NVM_BIN'
|
||||||
'NVM_PATH'
|
'NVM_PATH'
|
||||||
|
|||||||
@@ -99,11 +99,9 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
// Secret args. Must be added to the "Secrets" getter as well.
|
// Secret args. Must be added to the "Secrets" getter as well.
|
||||||
public static readonly string Token = "token";
|
public static readonly string Token = "token";
|
||||||
public static readonly string PAT = "pat";
|
|
||||||
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
||||||
public static string[] Secrets => new[]
|
public static string[] Secrets => new[]
|
||||||
{
|
{
|
||||||
PAT,
|
|
||||||
Token,
|
Token,
|
||||||
WindowsLogonPassword,
|
WindowsLogonPassword,
|
||||||
};
|
};
|
||||||
@@ -121,7 +119,6 @@ namespace GitHub.Runner.Common
|
|||||||
//validFlags array as well present in the CommandSettings.cs
|
//validFlags array as well present in the CommandSettings.cs
|
||||||
public static class Flags
|
public static class Flags
|
||||||
{
|
{
|
||||||
public static readonly string Check = "check";
|
|
||||||
public static readonly string Commit = "commit";
|
public static readonly string Commit = "commit";
|
||||||
public static readonly string Help = "help";
|
public static readonly string Help = "help";
|
||||||
public static readonly string Replace = "replace";
|
public static readonly string Replace = "replace";
|
||||||
@@ -141,14 +138,8 @@ namespace GitHub.Runner.Common
|
|||||||
public const int RunOnceRunnerUpdating = 4;
|
public const int RunOnceRunnerUpdating = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Features
|
|
||||||
{
|
|
||||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||||
public static readonly string WorkerCrash = "WORKER_CRASH";
|
public static readonly string WorkerCrash = "WORKER_CRASH";
|
||||||
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
|
|
||||||
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
||||||
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,6 @@ namespace GitHub.Runner.Common
|
|||||||
Add<T>(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker");
|
||||||
break;
|
break;
|
||||||
case "GitHub.Runner.Listener.Check.ICheckExtension":
|
|
||||||
Add<T>(extensions, "GitHub.Runner.Listener.Check.InternetCheck, Runner.Listener");
|
|
||||||
Add<T>(extensions, "GitHub.Runner.Listener.Check.ActionsCheck, Runner.Listener");
|
|
||||||
Add<T>(extensions, "GitHub.Runner.Listener.Check.GitCheck, Runner.Listener");
|
|
||||||
Add<T>(extensions, "GitHub.Runner.Listener.Check.NodeJsCheck, Runner.Listener");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
throw new NotSupportedException($"Unexpected extension type: '{typeof(T).FullName}'");
|
throw new NotSupportedException($"Unexpected extension type: '{typeof(T).FullName}'");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Check
|
|
||||||
{
|
|
||||||
public sealed class ActionsCheck : RunnerService, ICheckExtension
|
|
||||||
{
|
|
||||||
private string _logFile = null;
|
|
||||||
|
|
||||||
public int Order => 2;
|
|
||||||
|
|
||||||
public string CheckName => "GitHub Actions Connection";
|
|
||||||
|
|
||||||
public string CheckDescription => "Make sure the actions runner have access to the GitHub Actions Service.";
|
|
||||||
|
|
||||||
public string CheckLog => _logFile;
|
|
||||||
|
|
||||||
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/actions.md";
|
|
||||||
|
|
||||||
public Type ExtensionType => typeof(ICheckExtension);
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(ActionsCheck), DateTime.UtcNow));
|
|
||||||
}
|
|
||||||
|
|
||||||
// runner access to actions service
|
|
||||||
public async Task<bool> RunCheck(string url, string pat)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
|
||||||
|
|
||||||
var checkTasks = new List<Task<CheckResult>>();
|
|
||||||
string githubApiUrl = null;
|
|
||||||
string actionsTokenServiceUrl = null;
|
|
||||||
string actionsPipelinesServiceUrl = null;
|
|
||||||
var urlBuilder = new UriBuilder(url);
|
|
||||||
if (UrlUtil.IsHostedServer(urlBuilder))
|
|
||||||
{
|
|
||||||
urlBuilder.Host = $"api.{urlBuilder.Host}";
|
|
||||||
urlBuilder.Path = "";
|
|
||||||
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
|
|
||||||
actionsTokenServiceUrl = "https://vstoken.actions.githubusercontent.com/_apis/health";
|
|
||||||
actionsPipelinesServiceUrl = "https://pipelines.actions.githubusercontent.com/_apis/health";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
urlBuilder.Path = "api/v3";
|
|
||||||
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
|
|
||||||
urlBuilder.Path = "_services/vstoken/_apis/health";
|
|
||||||
actionsTokenServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
|
||||||
urlBuilder.Path = "_services/pipelines/_apis/health";
|
|
||||||
actionsPipelinesServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check github api
|
|
||||||
checkTasks.Add(CheckUtil.CheckDns(githubApiUrl));
|
|
||||||
checkTasks.Add(CheckUtil.CheckPing(githubApiUrl));
|
|
||||||
checkTasks.Add(HostContext.CheckHttpsRequests(githubApiUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
|
|
||||||
|
|
||||||
// check actions token service
|
|
||||||
checkTasks.Add(CheckUtil.CheckDns(actionsTokenServiceUrl));
|
|
||||||
checkTasks.Add(CheckUtil.CheckPing(actionsTokenServiceUrl));
|
|
||||||
checkTasks.Add(HostContext.CheckHttpsRequests(actionsTokenServiceUrl, pat, expectedHeader: "x-vss-e2eid"));
|
|
||||||
|
|
||||||
// check actions pipelines service
|
|
||||||
checkTasks.Add(CheckUtil.CheckDns(actionsPipelinesServiceUrl));
|
|
||||||
checkTasks.Add(CheckUtil.CheckPing(actionsPipelinesServiceUrl));
|
|
||||||
checkTasks.Add(HostContext.CheckHttpsRequests(actionsPipelinesServiceUrl, pat, expectedHeader: "x-vss-e2eid"));
|
|
||||||
|
|
||||||
var result = true;
|
|
||||||
while (checkTasks.Count > 0)
|
|
||||||
{
|
|
||||||
var finishedCheckTask = await Task.WhenAny<CheckResult>(checkTasks);
|
|
||||||
var finishedCheck = await finishedCheckTask;
|
|
||||||
result = result && finishedCheck.Pass;
|
|
||||||
await File.AppendAllLinesAsync(_logFile, finishedCheck.Logs);
|
|
||||||
checkTasks.Remove(finishedCheckTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.WhenAll(checkTasks);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.Tracing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.NetworkInformation;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Check
|
|
||||||
{
|
|
||||||
public static class CheckUtil
|
|
||||||
{
|
|
||||||
public static List<string> WarnLog(this IHostContext hostContext)
|
|
||||||
{
|
|
||||||
var logs = new List<string>();
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** !!! WARNING !!! ");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** DO NOT share the log in public place! The log may contains secrets in plain text. ");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** !!! WARNING !!! ");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<string> CheckProxy(this IHostContext hostContext)
|
|
||||||
{
|
|
||||||
var logs = new List<string>();
|
|
||||||
if (!string.IsNullOrEmpty(hostContext.WebProxy.HttpProxyAddress) ||
|
|
||||||
!string.IsNullOrEmpty(hostContext.WebProxy.HttpsProxyAddress))
|
|
||||||
{
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** Runner is behind web proxy {hostContext.WebProxy.HttpsProxyAddress ?? hostContext.WebProxy.HttpProxyAddress} ");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<CheckResult> CheckDns(string targetUrl)
|
|
||||||
{
|
|
||||||
var result = new CheckResult();
|
|
||||||
var url = new Uri(targetUrl);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Try DNS lookup for {url.Host} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
IPHostEntry host = await Dns.GetHostEntryAsync(url.Host);
|
|
||||||
foreach (var address in host.AddressList)
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Resolved DNS for {url.Host} to '{address}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Pass = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Resolved DNS for {url.Host} failed with error: {ex}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<CheckResult> CheckPing(string targetUrl)
|
|
||||||
{
|
|
||||||
var result = new CheckResult();
|
|
||||||
var url = new Uri(targetUrl);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Try ping {url.Host} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
using (var ping = new Ping())
|
|
||||||
{
|
|
||||||
var reply = await ping.SendPingAsync(url.Host);
|
|
||||||
if (reply.Status == IPStatus.Success)
|
|
||||||
{
|
|
||||||
result.Pass = true;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Ping {url.Host} ({reply.Address}) succeed within to '{reply.RoundtripTime} ms'");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Ping {url.Host} ({reply.Address}) failed with '{reply.Status}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Ping api.github.com failed with error: {ex}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<CheckResult> CheckHttpsRequests(this IHostContext hostContext, string url, string pat, string expectedHeader)
|
|
||||||
{
|
|
||||||
var result = new CheckResult();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Send HTTPS Request to {url} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
using (var _ = new HttpEventSourceListener(result.Logs))
|
|
||||||
using (var httpClientHandler = hostContext.CreateHttpClientHandler())
|
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(hostContext.UserAgents);
|
|
||||||
if (!string.IsNullOrEmpty(pat))
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", pat);
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await httpClient.GetAsync(url);
|
|
||||||
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http status code: {response.StatusCode}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http response headers: {response.Headers}");
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync();
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http response body: {responseContent}");
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
if (response.Headers.Contains(expectedHeader))
|
|
||||||
{
|
|
||||||
result.Pass = true;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http request 'GET' to {url} succeed");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http request 'GET' to {url} succeed but doesn't have expected HTTP Header.");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http request 'GET' to {url} failed with {response.StatusCode}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Https request 'GET' to {url} failed with error: {ex}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<CheckResult> DownloadExtraCA(this IHostContext hostContext, string url, string pat)
|
|
||||||
{
|
|
||||||
var result = new CheckResult();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Download SSL Certificate from {url} ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
|
|
||||||
var uri = new Uri(url);
|
|
||||||
var env = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
{ "HOSTNAME", uri.Host },
|
|
||||||
{ "PORT", uri.IsDefaultPort ? (uri.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : uri.Port.ToString() },
|
|
||||||
{ "PATH", uri.AbsolutePath },
|
|
||||||
{ "PAT", pat }
|
|
||||||
};
|
|
||||||
|
|
||||||
var proxy = hostContext.WebProxy.GetProxy(uri);
|
|
||||||
if (proxy != null)
|
|
||||||
{
|
|
||||||
env["PROXYHOST"] = proxy.Host;
|
|
||||||
env["PROXYPORT"] = proxy.IsDefaultPort ? (proxy.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : proxy.Port.ToString();
|
|
||||||
if (hostContext.WebProxy.HttpProxyUsername != null ||
|
|
||||||
hostContext.WebProxy.HttpsProxyUsername != null)
|
|
||||||
{
|
|
||||||
env["PROXYUSERNAME"] = hostContext.WebProxy.HttpProxyUsername ?? hostContext.WebProxy.HttpsProxyUsername;
|
|
||||||
env["PROXYPASSWORD"] = hostContext.WebProxy.HttpProxyPassword ?? hostContext.WebProxy.HttpsProxyPassword;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
env["PROXYUSERNAME"] = "";
|
|
||||||
env["PROXYPASSWORD"] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
env["PROXYHOST"] = "";
|
|
||||||
env["PROXYPORT"] = "";
|
|
||||||
env["PROXYUSERNAME"] = "";
|
|
||||||
env["PROXYPASSWORD"] = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var processInvoker = hostContext.CreateService<IProcessInvoker>())
|
|
||||||
{
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDOUT] {args.Data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDERR] {args.Data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert");
|
|
||||||
var node12 = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node12} \"{downloadCertScript}\"' ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
|
|
||||||
await processInvoker.ExecuteAsync(
|
|
||||||
hostContext.GetDirectory(WellKnownDirectory.Root),
|
|
||||||
node12,
|
|
||||||
$"\"{downloadCertScript}\"",
|
|
||||||
env,
|
|
||||||
true,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Pass = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Download SSL Certificate from '{url}' failed with error: {ex}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventSource listener for dotnet debug trace for HTTP and SSL
|
|
||||||
public sealed class HttpEventSourceListener : EventListener
|
|
||||||
{
|
|
||||||
private readonly List<string> _logs;
|
|
||||||
private readonly object _lock = new object();
|
|
||||||
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new Dictionary<string, HashSet<string>>
|
|
||||||
{
|
|
||||||
{
|
|
||||||
"Private.InternalDiagnostics.System.Net.Http",
|
|
||||||
new HashSet<string>
|
|
||||||
{
|
|
||||||
"Info",
|
|
||||||
"Associate"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Private.InternalDiagnostics.System.Net.Security",
|
|
||||||
new HashSet<string>
|
|
||||||
{
|
|
||||||
"Info",
|
|
||||||
"SslStreamCtor",
|
|
||||||
"SecureChannelCtor",
|
|
||||||
"NoDelegateNoClientCert",
|
|
||||||
"CertsAfterFiltering",
|
|
||||||
"UsingCachedCredential",
|
|
||||||
"SspiSelectedCipherSuite"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public HttpEventSourceListener(List<string> logs)
|
|
||||||
{
|
|
||||||
_logs = logs;
|
|
||||||
if (Environment.GetEnvironmentVariable("ACTIONS_RUNNER_TRACE_ALL_HTTP_EVENT") == "1")
|
|
||||||
{
|
|
||||||
_ignoredEvent.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnEventSourceCreated(EventSource eventSource)
|
|
||||||
{
|
|
||||||
base.OnEventSourceCreated(eventSource);
|
|
||||||
|
|
||||||
if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http" ||
|
|
||||||
eventSource.Name == "Private.InternalDiagnostics.System.Net.Security")
|
|
||||||
{
|
|
||||||
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnEventWritten(EventWrittenEventArgs eventData)
|
|
||||||
{
|
|
||||||
base.OnEventWritten(eventData);
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_ignoredEvent.TryGetValue(eventData.EventSource.Name, out var ignored) &&
|
|
||||||
ignored.Contains(eventData.EventName))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logs.Add($"{DateTime.UtcNow.ToString("O")} [START {eventData.EventSource.Name} - {eventData.EventName}]");
|
|
||||||
_logs.AddRange(eventData.Payload.Select(x => string.Join(Environment.NewLine, x.ToString().Split(Environment.NewLine).Select(y => $"{DateTime.UtcNow.ToString("O")} {y}"))));
|
|
||||||
_logs.Add($"{DateTime.UtcNow.ToString("O")} [END {eventData.EventSource.Name} - {eventData.EventName}]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Check
|
|
||||||
{
|
|
||||||
public sealed class GitCheck : RunnerService, ICheckExtension
|
|
||||||
{
|
|
||||||
private string _logFile = null;
|
|
||||||
private string _gitPath = null;
|
|
||||||
|
|
||||||
public int Order => 3;
|
|
||||||
|
|
||||||
public string CheckName => "Git Certificate/Proxy Validation";
|
|
||||||
|
|
||||||
public string CheckDescription => "Make sure the git cli can access to GitHub.com or the GitHub Enterprise Server.";
|
|
||||||
|
|
||||||
public string CheckLog => _logFile;
|
|
||||||
|
|
||||||
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/git.md";
|
|
||||||
|
|
||||||
public Type ExtensionType => typeof(ICheckExtension);
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(GitCheck), DateTime.UtcNow));
|
|
||||||
_gitPath = WhichUtil.Which("git");
|
|
||||||
}
|
|
||||||
|
|
||||||
// git access to ghes/gh
|
|
||||||
public async Task<bool> RunCheck(string url, string pat)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_gitPath))
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Can't verify git with GitHub.com or GitHub Enterprise Server since git is not installed." });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkGit = await CheckGit(url, pat);
|
|
||||||
var result = checkGit.Pass;
|
|
||||||
await File.AppendAllLinesAsync(_logFile, checkGit.Logs);
|
|
||||||
|
|
||||||
// try fix SSL error by providing extra CA certificate.
|
|
||||||
if (checkGit.SslError)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Try fix SSL error by providing extra CA certificate." });
|
|
||||||
var downloadCert = await HostContext.DownloadExtraCA(url, pat);
|
|
||||||
await File.AppendAllLinesAsync(_logFile, downloadCert.Logs);
|
|
||||||
|
|
||||||
if (downloadCert.Pass)
|
|
||||||
{
|
|
||||||
var recheckGit = await CheckGit(url, pat, extraCA: true);
|
|
||||||
await File.AppendAllLinesAsync(_logFile, recheckGit.Logs);
|
|
||||||
if (recheckGit.Pass)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Fixed SSL error by providing extra CA certs." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<CheckResult> CheckGit(string url, string pat, bool extraCA = false)
|
|
||||||
{
|
|
||||||
var result = new CheckResult();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Validate server cert and proxy configuration with Git ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
var repoUrlBuilder = new UriBuilder(url);
|
|
||||||
repoUrlBuilder.Path = "actions/checkout";
|
|
||||||
repoUrlBuilder.UserName = "gh";
|
|
||||||
repoUrlBuilder.Password = pat;
|
|
||||||
|
|
||||||
var gitProxy = "";
|
|
||||||
var proxy = HostContext.WebProxy.GetProxy(repoUrlBuilder.Uri);
|
|
||||||
if (proxy != null)
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Runner is behind http proxy '{proxy.AbsoluteUri}'");
|
|
||||||
if (HostContext.WebProxy.HttpProxyUsername != null ||
|
|
||||||
HostContext.WebProxy.HttpsProxyUsername != null)
|
|
||||||
{
|
|
||||||
var proxyUrlWithCred = UrlUtil.GetCredentialEmbeddedUrl(
|
|
||||||
proxy,
|
|
||||||
HostContext.WebProxy.HttpProxyUsername ?? HostContext.WebProxy.HttpsProxyUsername,
|
|
||||||
HostContext.WebProxy.HttpProxyPassword ?? HostContext.WebProxy.HttpsProxyPassword);
|
|
||||||
gitProxy = $"-c http.proxy={proxyUrlWithCred}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gitProxy = $"-c http.proxy={proxy.AbsoluteUri}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
|
||||||
{
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {args.Data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {args.Data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var gitArgs = $"{gitProxy} ls-remote --exit-code {repoUrlBuilder.Uri.AbsoluteUri} HEAD";
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run 'git {gitArgs}' ");
|
|
||||||
|
|
||||||
var env = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "GIT_TRACE", "1" },
|
|
||||||
{ "GIT_CURL_VERBOSE", "1" }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (extraCA)
|
|
||||||
{
|
|
||||||
env["GIT_SSL_CAINFO"] = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), "download_ca_cert.pem");
|
|
||||||
}
|
|
||||||
|
|
||||||
await processInvoker.ExecuteAsync(
|
|
||||||
HostContext.GetDirectory(WellKnownDirectory.Root),
|
|
||||||
_gitPath,
|
|
||||||
gitArgs,
|
|
||||||
env,
|
|
||||||
true,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Pass = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** git ls-remote failed with error: {ex}");
|
|
||||||
if (result.Logs.Any(x => x.Contains("SSL Certificate problem", StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** git ls-remote failed due to SSL cert issue.");
|
|
||||||
result.SslError = true;
|
|
||||||
}
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Check
|
|
||||||
{
|
|
||||||
public interface ICheckExtension : IExtension
|
|
||||||
{
|
|
||||||
int Order { get; }
|
|
||||||
string CheckName { get; }
|
|
||||||
string CheckDescription { get; }
|
|
||||||
string CheckLog { get; }
|
|
||||||
string HelpLink { get; }
|
|
||||||
Task<bool> RunCheck(string url, string pat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CheckResult
|
|
||||||
{
|
|
||||||
public CheckResult()
|
|
||||||
{
|
|
||||||
Logs = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Pass { get; set; }
|
|
||||||
|
|
||||||
public bool SslError { get; set; }
|
|
||||||
|
|
||||||
public List<string> Logs { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Check
|
|
||||||
{
|
|
||||||
public sealed class InternetCheck : RunnerService, ICheckExtension
|
|
||||||
{
|
|
||||||
private string _logFile = null;
|
|
||||||
|
|
||||||
public int Order => 1;
|
|
||||||
|
|
||||||
public string CheckName => "Internet Connection";
|
|
||||||
|
|
||||||
public string CheckDescription => "Make sure the actions runner have access to public internet.";
|
|
||||||
|
|
||||||
public string CheckLog => _logFile;
|
|
||||||
|
|
||||||
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/internet.md";
|
|
||||||
|
|
||||||
public Type ExtensionType => typeof(ICheckExtension);
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(InternetCheck), DateTime.UtcNow));
|
|
||||||
}
|
|
||||||
|
|
||||||
// check runner access to api.github.com
|
|
||||||
public async Task<bool> RunCheck(string url, string pat)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
|
||||||
|
|
||||||
var checkTasks = new List<Task<CheckResult>>();
|
|
||||||
checkTasks.Add(CheckUtil.CheckDns("https://api.github.com"));
|
|
||||||
checkTasks.Add(CheckUtil.CheckPing("https://api.github.com"));
|
|
||||||
|
|
||||||
// We don't need to pass a PAT since it might be a token for GHES.
|
|
||||||
checkTasks.Add(HostContext.CheckHttpsRequests("https://api.github.com", pat: null, expectedHeader: "X-GitHub-Request-Id"));
|
|
||||||
|
|
||||||
var result = true;
|
|
||||||
while (checkTasks.Count > 0)
|
|
||||||
{
|
|
||||||
var finishedCheckTask = await Task.WhenAny<CheckResult>(checkTasks);
|
|
||||||
var finishedCheck = await finishedCheckTask;
|
|
||||||
result = result && finishedCheck.Pass;
|
|
||||||
await File.AppendAllLinesAsync(_logFile, finishedCheck.Logs);
|
|
||||||
checkTasks.Remove(finishedCheckTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.WhenAll(checkTasks);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Check
|
|
||||||
{
|
|
||||||
public sealed class NodeJsCheck : RunnerService, ICheckExtension
|
|
||||||
{
|
|
||||||
private string _logFile = null;
|
|
||||||
|
|
||||||
public int Order => 4;
|
|
||||||
|
|
||||||
public string CheckName => "Node.js Certificate/Proxy Validation";
|
|
||||||
|
|
||||||
public string CheckDescription => "Make sure the node.js have access to GitHub.com or the GitHub Enterprise Server.";
|
|
||||||
|
|
||||||
public string CheckLog => _logFile;
|
|
||||||
|
|
||||||
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/nodejs.md";
|
|
||||||
|
|
||||||
public Type ExtensionType => typeof(ICheckExtension);
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(NodeJsCheck), DateTime.UtcNow));
|
|
||||||
}
|
|
||||||
|
|
||||||
// node access to ghes/gh
|
|
||||||
public async Task<bool> RunCheck(string url, string pat)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
|
||||||
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
|
||||||
|
|
||||||
// Request to github.com or ghes server
|
|
||||||
var urlBuilder = new UriBuilder(url);
|
|
||||||
if (UrlUtil.IsHostedServer(urlBuilder))
|
|
||||||
{
|
|
||||||
urlBuilder.Host = $"api.{urlBuilder.Host}";
|
|
||||||
urlBuilder.Path = "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
urlBuilder.Path = "api/v3";
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkNode = await CheckNodeJs(urlBuilder.Uri.AbsoluteUri, pat);
|
|
||||||
var result = checkNode.Pass;
|
|
||||||
await File.AppendAllLinesAsync(_logFile, checkNode.Logs);
|
|
||||||
|
|
||||||
// try fix SSL error by providing extra CA certificate.
|
|
||||||
if (checkNode.SslError)
|
|
||||||
{
|
|
||||||
var downloadCert = await HostContext.DownloadExtraCA(urlBuilder.Uri.AbsoluteUri, pat);
|
|
||||||
await File.AppendAllLinesAsync(_logFile, downloadCert.Logs);
|
|
||||||
|
|
||||||
if (downloadCert.Pass)
|
|
||||||
{
|
|
||||||
var recheckNode = await CheckNodeJs(urlBuilder.Uri.AbsoluteUri, pat, extraCA: true);
|
|
||||||
await File.AppendAllLinesAsync(_logFile, recheckNode.Logs);
|
|
||||||
if (recheckNode.Pass)
|
|
||||||
{
|
|
||||||
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Fixed SSL error by providing extra CA certs." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<CheckResult> CheckNodeJs(string url, string pat, bool extraCA = false)
|
|
||||||
{
|
|
||||||
var result = new CheckResult();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Make Http request to {url} using node.js ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
|
|
||||||
// Request to github.com or ghes server
|
|
||||||
Uri requestUrl = new Uri(url);
|
|
||||||
var env = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
{ "HOSTNAME", requestUrl.Host },
|
|
||||||
{ "PORT", requestUrl.IsDefaultPort ? (requestUrl.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : requestUrl.Port.ToString() },
|
|
||||||
{ "PATH", requestUrl.AbsolutePath },
|
|
||||||
{ "PAT", pat }
|
|
||||||
};
|
|
||||||
|
|
||||||
var proxy = HostContext.WebProxy.GetProxy(requestUrl);
|
|
||||||
if (proxy != null)
|
|
||||||
{
|
|
||||||
env["PROXYHOST"] = proxy.Host;
|
|
||||||
env["PROXYPORT"] = proxy.IsDefaultPort ? (proxy.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : proxy.Port.ToString();
|
|
||||||
if (HostContext.WebProxy.HttpProxyUsername != null ||
|
|
||||||
HostContext.WebProxy.HttpsProxyUsername != null)
|
|
||||||
{
|
|
||||||
env["PROXYUSERNAME"] = HostContext.WebProxy.HttpProxyUsername ?? HostContext.WebProxy.HttpsProxyUsername;
|
|
||||||
env["PROXYPASSWORD"] = HostContext.WebProxy.HttpProxyPassword ?? HostContext.WebProxy.HttpsProxyPassword;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
env["PROXYUSERNAME"] = "";
|
|
||||||
env["PROXYPASSWORD"] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
env["PROXYHOST"] = "";
|
|
||||||
env["PROXYPORT"] = "";
|
|
||||||
env["PROXYUSERNAME"] = "";
|
|
||||||
env["PROXYPASSWORD"] = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraCA)
|
|
||||||
{
|
|
||||||
env["NODE_EXTRA_CA_CERTS"] = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), "download_ca_cert.pem");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
|
||||||
{
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDOUT] {args.Data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDERR] {args.Data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js");
|
|
||||||
var node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node12} \"{makeWebRequestScript}\"' ");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
|
|
||||||
await processInvoker.ExecuteAsync(
|
|
||||||
HostContext.GetDirectory(WellKnownDirectory.Root),
|
|
||||||
node12,
|
|
||||||
$"\"{makeWebRequestScript}\"",
|
|
||||||
env,
|
|
||||||
true,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Pass = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
result.Pass = false;
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Make https request to {url} using node.js failed with error: {ex}");
|
|
||||||
if (result.Logs.Any(x => x.Contains("UNABLE_TO_VERIFY_LEAF_SIGNATURE") ||
|
|
||||||
x.Contains("UNABLE_TO_GET_ISSUER_CERT_LOCALLY") ||
|
|
||||||
x.Contains("SELF_SIGNED_CERT_IN_CHAIN")))
|
|
||||||
{
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Https request failed due to SSL cert issue.");
|
|
||||||
result.SslError = true;
|
|
||||||
}
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
|
||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,6 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private readonly string[] validFlags =
|
private readonly string[] validFlags =
|
||||||
{
|
{
|
||||||
Constants.Runner.CommandLine.Flags.Check,
|
|
||||||
Constants.Runner.CommandLine.Flags.Commit,
|
Constants.Runner.CommandLine.Flags.Commit,
|
||||||
Constants.Runner.CommandLine.Flags.Help,
|
Constants.Runner.CommandLine.Flags.Help,
|
||||||
Constants.Runner.CommandLine.Flags.Replace,
|
Constants.Runner.CommandLine.Flags.Replace,
|
||||||
@@ -43,7 +42,6 @@ namespace GitHub.Runner.Listener
|
|||||||
Constants.Runner.CommandLine.Args.Labels,
|
Constants.Runner.CommandLine.Args.Labels,
|
||||||
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
||||||
Constants.Runner.CommandLine.Args.Name,
|
Constants.Runner.CommandLine.Args.Name,
|
||||||
Constants.Runner.CommandLine.Args.PAT,
|
|
||||||
Constants.Runner.CommandLine.Args.RunnerGroup,
|
Constants.Runner.CommandLine.Args.RunnerGroup,
|
||||||
Constants.Runner.CommandLine.Args.StartupType,
|
Constants.Runner.CommandLine.Args.StartupType,
|
||||||
Constants.Runner.CommandLine.Args.Token,
|
Constants.Runner.CommandLine.Args.Token,
|
||||||
@@ -61,7 +59,6 @@ namespace GitHub.Runner.Listener
|
|||||||
public bool Warmup => TestCommand(Constants.Runner.CommandLine.Commands.Warmup);
|
public bool Warmup => TestCommand(Constants.Runner.CommandLine.Commands.Warmup);
|
||||||
|
|
||||||
// Flags.
|
// Flags.
|
||||||
public bool Check => TestFlag(Constants.Runner.CommandLine.Flags.Check);
|
|
||||||
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
||||||
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
||||||
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
||||||
@@ -190,22 +187,6 @@ namespace GitHub.Runner.Listener
|
|||||||
validator: Validators.NonEmptyValidator);
|
validator: Validators.NonEmptyValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetGitHubPersonalAccessToken(bool required = false)
|
|
||||||
{
|
|
||||||
if (required)
|
|
||||||
{
|
|
||||||
return GetArgOrPrompt(
|
|
||||||
name: Constants.Runner.CommandLine.Args.PAT,
|
|
||||||
description: "What is your GitHub personal access token?",
|
|
||||||
defaultValue: string.Empty,
|
|
||||||
validator: Validators.NonEmptyValidator);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return GetArg(name: Constants.Runner.CommandLine.Args.PAT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetRunnerRegisterToken()
|
public string GetRunnerRegisterToken()
|
||||||
{
|
{
|
||||||
return GetArgOrPrompt(
|
return GetArgOrPrompt(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using GitHub.Runner.Common.Util;
|
|||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -11,7 +12,6 @@ using System.Net.Http;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Configuration
|
namespace GitHub.Runner.Listener.Configuration
|
||||||
@@ -107,8 +107,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
runnerSettings.GitHubUrl = inputUrl;
|
runnerSettings.GitHubUrl = inputUrl;
|
||||||
var registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
|
var githubToken = command.GetRunnerRegisterToken();
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, githubToken, Constants.RunnerEvent.Register);
|
||||||
runnerSettings.ServerUrl = authResult.TenantUrl;
|
runnerSettings.ServerUrl = authResult.TenantUrl;
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
@@ -117,7 +117,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || UrlUtil.IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl));
|
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl));
|
||||||
|
|
||||||
// Warn if the Actions server url and GHES server url has different Host
|
// Warn if the Actions server url and GHES server url has different Host
|
||||||
if (!runnerSettings.IsHostedServer)
|
if (!runnerSettings.IsHostedServer)
|
||||||
@@ -373,8 +373,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
var githubToken = command.GetRunnerDeletionToken();
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
|
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken, Constants.RunnerEvent.Remove);
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
}
|
}
|
||||||
@@ -508,107 +508,18 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetRunnerTokenAsync(CommandSettings command, string githubUrl, string tokenType)
|
private bool IsHostedServer(UriBuilder gitHubUrl)
|
||||||
{
|
{
|
||||||
var githubPAT = command.GetGitHubPersonalAccessToken();
|
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
var runnerToken = string.Empty;
|
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
if (!string.IsNullOrEmpty(githubPAT))
|
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
||||||
{
|
|
||||||
Trace.Info($"Retriving runner {tokenType} token using GitHub PAT.");
|
|
||||||
var jitToken = await GetJITRunnerTokenAsync(githubUrl, githubPAT, tokenType);
|
|
||||||
Trace.Info($"Retrived runner {tokenType} token is good to {jitToken.ExpiresAt}.");
|
|
||||||
HostContext.SecretMasker.AddValue(jitToken.Token);
|
|
||||||
runnerToken = jitToken.Token;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(runnerToken))
|
|
||||||
{
|
|
||||||
if (string.Equals("registration", tokenType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
runnerToken = command.GetRunnerRegisterToken();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
runnerToken = command.GetRunnerDeletionToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return runnerToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<GitHubRunnerRegisterToken> GetJITRunnerTokenAsync(string githubUrl, string githubToken, string tokenType)
|
|
||||||
{
|
|
||||||
var githubApiUrl = "";
|
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
||||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (path.Length == 1)
|
|
||||||
{
|
|
||||||
// org runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners/{tokenType}-token";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners/{tokenType}-token";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (path.Length == 2)
|
|
||||||
{
|
|
||||||
// repo or enterprise runner.
|
|
||||||
var repoScope = "repos/";
|
|
||||||
if (string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
repoScope = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{repoScope}{path[0]}/{path[1]}/actions/runners/{tokenType}-token";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{repoScope}{path[0]}/{path[1]}/actions/runners/{tokenType}-token";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"'{githubUrl}' should point to an org or repository.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
|
||||||
{
|
|
||||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"github:{githubToken}"));
|
|
||||||
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
|
||||||
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
|
|
||||||
|
|
||||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
|
||||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
|
||||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
_term.WriteError(errorResponse);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
||||||
{
|
{
|
||||||
var githubApiUrl = "";
|
var githubApiUrl = "";
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
if (IsHostedServer(gitHubUrlBuilder))
|
||||||
{
|
{
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,16 +71,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public sealed class GitHubRunnerRegisterToken
|
|
||||||
{
|
|
||||||
[DataMember(Name = "token")]
|
|
||||||
public string Token { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "expires_at")]
|
|
||||||
public string ExpiresAt { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public sealed class GitHubAuthResult
|
public sealed class GitHubAuthResult
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
RSA CreateKey();
|
RSACryptoServiceProvider 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>
|
||||||
RSA GetKey();
|
RSACryptoServiceProvider 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(), requireFipsCryptography: true);
|
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
||||||
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 RSA CreateKey()
|
public RSACryptoServiceProvider CreateKey()
|
||||||
{
|
{
|
||||||
RSA rsa = null;
|
RSACryptoServiceProvider 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 = RSA.Create(2048);
|
rsa = new RSACryptoServiceProvider(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 = RSA.Create();
|
rsa = new RSACryptoServiceProvider();
|
||||||
rsa.ImportParameters(LoadParameters());
|
rsa.ImportParameters(LoadParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RSA GetKey()
|
public RSACryptoServiceProvider 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 = RSA.Create();
|
var rsa = new RSACryptoServiceProvider();
|
||||||
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 RSA CreateKey()
|
public RSACryptoServiceProvider CreateKey()
|
||||||
{
|
{
|
||||||
RSA rsa = null;
|
RSACryptoServiceProvider 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 = RSA.Create(2048);
|
rsa = new RSACryptoServiceProvider(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 = RSA.Create();
|
rsa = new RSACryptoServiceProvider();
|
||||||
rsa.ImportParameters(IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters);
|
rsa.ImportParameters(IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RSA GetKey()
|
public RSACryptoServiceProvider 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 = RSA.Create();
|
var rsa = new RSACryptoServiceProvider();
|
||||||
rsa.ImportParameters(parameters);
|
rsa.ImportParameters(parameters);
|
||||||
return rsa;
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,8 +319,7 @@ namespace GitHub.Runner.Listener
|
|||||||
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
using (var rsa = keyManager.GetKey())
|
using (var rsa = keyManager.GetKey())
|
||||||
{
|
{
|
||||||
var padding = _session.UseFipsEncryption ? RSAEncryptionPadding.OaepSHA256 : RSAEncryptionPadding.OaepSHA1;
|
return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, RSAEncryptionPadding.OaepSHA1), message.IV);
|
||||||
return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, padding), message.IV);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -10,8 +11,6 @@ using System.Reflection;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using System.Linq;
|
|
||||||
using GitHub.Runner.Listener.Check;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -73,46 +72,6 @@ namespace GitHub.Runner.Listener
|
|||||||
return Constants.Runner.ReturnCode.Success;
|
return Constants.Runner.ReturnCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.Check)
|
|
||||||
{
|
|
||||||
var url = command.GetUrl();
|
|
||||||
var pat = command.GetGitHubPersonalAccessToken(required: true);
|
|
||||||
var checkExtensions = HostContext.GetService<IExtensionManager>().GetExtensions<ICheckExtension>();
|
|
||||||
var sortedChecks = checkExtensions.OrderBy(x => x.Order);
|
|
||||||
foreach (var check in sortedChecks)
|
|
||||||
{
|
|
||||||
_term.WriteLine($"**********************************************************************************************************************");
|
|
||||||
_term.WriteLine($"** Check: {check.CheckName}");
|
|
||||||
_term.WriteLine($"** Description: {check.CheckDescription}");
|
|
||||||
_term.WriteLine($"**********************************************************************************************************************");
|
|
||||||
var result = await check.RunCheck(url, pat);
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
_term.WriteLine($"** **");
|
|
||||||
_term.WriteLine($"** F A I L **");
|
|
||||||
_term.WriteLine($"** **");
|
|
||||||
_term.WriteLine($"**********************************************************************************************************************");
|
|
||||||
_term.WriteLine($"** Log: {check.CheckLog}");
|
|
||||||
_term.WriteLine($"** Help Doc: {check.HelpLink}");
|
|
||||||
_term.WriteLine($"**********************************************************************************************************************");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteLine($"** **");
|
|
||||||
_term.WriteLine($"** P A S S **");
|
|
||||||
_term.WriteLine($"** **");
|
|
||||||
_term.WriteLine($"**********************************************************************************************************************");
|
|
||||||
_term.WriteLine($"** Log: {check.CheckLog}");
|
|
||||||
_term.WriteLine($"**********************************************************************************************************************");
|
|
||||||
}
|
|
||||||
|
|
||||||
_term.WriteLine();
|
|
||||||
_term.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Constants.Runner.ReturnCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure runner prompt for args if not supplied
|
// Configure runner prompt for args if not supplied
|
||||||
// Unattended configure mode will not prompt for args if not supplied and error on any missing or invalid value.
|
// Unattended configure mode will not prompt for args if not supplied and error on any missing or invalid value.
|
||||||
if (command.Configure)
|
if (command.Configure)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
{
|
{
|
||||||
// We should never
|
// We should never
|
||||||
context.Error($"Error '{ex.Message}' when downloading file '{fileToDownload}'. (Downloader {downloaderId})");
|
context.Error($"Error '{ex.Message}' when downloading file '{fileToDownload}'. (Downloader {downloaderId})");
|
||||||
throw;
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +528,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
context.Output($"File error '{ex.Message}' when uploading file '{fileToUpload}'.");
|
context.Output($"File error '{ex.Message}' when uploading file '{fileToUpload}'.");
|
||||||
throw;
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,4 +682,4 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
: base(message, inner)
|
: base(message, inner)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -4,13 +4,6 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
public static class UrlUtil
|
public static class UrlUtil
|
||||||
{
|
{
|
||||||
public static bool IsHostedServer(UriBuilder gitHubUrl)
|
|
||||||
{
|
|
||||||
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(baseUrl, nameof(baseUrl));
|
ArgUtil.NotNull(baseUrl, nameof(baseUrl));
|
||||||
|
|||||||
@@ -594,33 +594,15 @@ 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 (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled.
|
catch (Exception ex) when (attempt < 3)
|
||||||
{
|
{
|
||||||
if (attempt < 3)
|
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
|
executionContext.Debug(ex.ToString());
|
||||||
|
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||||
{
|
{
|
||||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||||
executionContext.Debug(ex.ToString());
|
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
|
||||||
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
await Task.Delay(backoff);
|
||||||
{
|
|
||||||
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
|
||||||
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
|
|
||||||
await Task.Delay(backoff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Some possible cases are:
|
|
||||||
// * Repo is rate limited
|
|
||||||
// * Repo or tag doesn't exist, or isn't public
|
|
||||||
if (ex is WebApi.UnresolvableActionDownloadInfoException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This exception will be traced as an infrastructure failure
|
|
||||||
throw new WebApi.FailedToResolveActionDownloadInfoException("Failed to resolve action download info.", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,11 +142,6 @@ 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)
|
||||||
@@ -255,11 +250,11 @@ namespace GitHub.Runner.Worker
|
|||||||
handler.PrintActionDetails(Stage);
|
handler.PrintActionDetails(Stage);
|
||||||
|
|
||||||
// Run the task.
|
// Run the task.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await handler.RunAsync(Stage);
|
await handler.RunAsync(Stage);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
fileCommandManager.ProcessFiles(ExecutionContext, ExecutionContext.Global.Container);
|
fileCommandManager.ProcessFiles(ExecutionContext, ExecutionContext.Global.Container);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ 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,12 +918,6 @@ 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(HostContext)
|
var container = new ContainerInfo()
|
||||||
{
|
{
|
||||||
ContainerImage = Data.Image,
|
ContainerImage = Data.Image,
|
||||||
ContainerName = ExecutionContext.Id.ToString("N"),
|
ContainerName = ExecutionContext.Id.ToString("N"),
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
@@ -42,8 +41,6 @@ namespace GitHub.Runner.Worker
|
|||||||
private readonly HashSet<string> _existingProcesses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _existingProcesses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
private bool _processCleanup;
|
private bool _processCleanup;
|
||||||
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
||||||
private CancellationTokenSource _diskSpaceCheckToken = new CancellationTokenSource();
|
|
||||||
private Task _diskSpaceCheckTask = null;
|
|
||||||
|
|
||||||
// Download all required actions.
|
// Download all required actions.
|
||||||
// Make sure all condition inputs are valid.
|
// Make sure all condition inputs are valid.
|
||||||
@@ -328,12 +325,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jobContext.Global.EnvironmentVariables.TryGetValue(Constants.Runner.Features.DiskSpaceWarning, out var enableWarning);
|
|
||||||
if (StringUtil.ConvertToBoolean(enableWarning, defaultValue: true))
|
|
||||||
{
|
|
||||||
_diskSpaceCheckTask = CheckDiskSpaceAsync(context, _diskSpaceCheckToken.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps;
|
return steps;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
|
||||||
@@ -344,14 +335,6 @@ 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.
|
||||||
@@ -538,11 +521,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_diskSpaceCheckTask != null)
|
|
||||||
{
|
|
||||||
_diskSpaceCheckToken.Cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -558,39 +536,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckDiskSpaceAsync(IExecutionContext context, CancellationToken token)
|
|
||||||
{
|
|
||||||
while (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// Add warning when disk is lower than system.runner.lowdiskspacethreshold from service (default to 100 MB on service side)
|
|
||||||
var lowDiskSpaceThreshold = context.Global.Variables.GetInt(WellKnownDistributedTaskVariables.RunnerLowDiskspaceThreshold);
|
|
||||||
if (lowDiskSpaceThreshold == null)
|
|
||||||
{
|
|
||||||
Trace.Info($"Low diskspace warning is not enabled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var workDirRoot = Directory.GetDirectoryRoot(HostContext.GetDirectory(WellKnownDirectory.Work));
|
|
||||||
var driveInfo = new DriveInfo(workDirRoot);
|
|
||||||
var freeSpaceInMB = driveInfo.AvailableFreeSpace / 1024 / 1024;
|
|
||||||
if (freeSpaceInMB < lowDiskSpaceThreshold)
|
|
||||||
{
|
|
||||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
|
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
|
|
||||||
context.AddIssue(issue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(10 * 1000, token);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<int, Process> SnapshotProcesses()
|
private Dictionary<int, Process> SnapshotProcesses()
|
||||||
{
|
{
|
||||||
Dictionary<int, Process> snapshot = new Dictionary<int, Process>();
|
Dictionary<int, Process> snapshot = new Dictionary<int, Process>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -12,9 +12,29 @@ namespace GitHub.Services.Common
|
|||||||
m_request = request;
|
m_request = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHttpHeaders Headers => this;
|
public IHttpHeaders Headers
|
||||||
public Uri RequestUri => m_request.RequestUri;
|
{
|
||||||
public IDictionary<string,object> Properties => m_request.Options;
|
get
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri RequestUri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_request.RequestUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDictionary<string, object> Properties
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_request.Properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerable<String> IHttpHeaders.GetValues(String name)
|
IEnumerable<String> IHttpHeaders.GetValues(String name)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Services.Common.Diagnostics
|
|||||||
public static VssTraceActivity GetActivity(this HttpRequestMessage message)
|
public static VssTraceActivity GetActivity(this HttpRequestMessage message)
|
||||||
{
|
{
|
||||||
Object traceActivity;
|
Object traceActivity;
|
||||||
if (!message.Options.TryGetValue(VssTraceActivity.PropertyName, out traceActivity))
|
if (!message.Properties.TryGetValue(VssTraceActivity.PropertyName, out traceActivity))
|
||||||
{
|
{
|
||||||
return VssTraceActivity.Empty;
|
return VssTraceActivity.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ namespace GitHub.Services.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add ourselves to the message so the underlying token issuers may use it if necessary
|
// Add ourselves to the message so the underlying token issuers may use it if necessary
|
||||||
request.Options.Set(new HttpRequestOptionsKey<VssHttpMessageHandler>(VssHttpMessageHandler.PropertyName), this);
|
request.Properties[VssHttpMessageHandler.PropertyName] = this;
|
||||||
|
|
||||||
Boolean succeeded = false;
|
Boolean succeeded = false;
|
||||||
Boolean lastResponseDemandedProxyAuth = false;
|
Boolean lastResponseDemandedProxyAuth = false;
|
||||||
@@ -409,7 +409,7 @@ namespace GitHub.Services.Common
|
|||||||
// Read the completion option provided by the caller. If we don't find the property then we
|
// Read the completion option provided by the caller. If we don't find the property then we
|
||||||
// assume it is OK to buffer by default.
|
// assume it is OK to buffer by default.
|
||||||
HttpCompletionOption completionOption;
|
HttpCompletionOption completionOption;
|
||||||
if (!request.Options.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
|
if (!request.Properties.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
|
||||||
{
|
{
|
||||||
completionOption = HttpCompletionOption.ResponseContentRead;
|
completionOption = HttpCompletionOption.ResponseContentRead;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ namespace GitHub.Services.Common
|
|||||||
public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo)
|
public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo)
|
||||||
{
|
{
|
||||||
object existingTraceInfo;
|
object existingTraceInfo;
|
||||||
if (!message.Options.TryGetValue(TfsTraceInfoKey, out existingTraceInfo))
|
if (!message.Properties.TryGetValue(TfsTraceInfoKey, out existingTraceInfo))
|
||||||
{
|
{
|
||||||
message.Options.Set(new HttpRequestOptionsKey<VssHttpMessageHandlerTraceInfo>(TfsTraceInfoKey), traceInfo);
|
message.Properties.Add(TfsTraceInfoKey, traceInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ namespace GitHub.Services.Common
|
|||||||
{
|
{
|
||||||
VssHttpMessageHandlerTraceInfo traceInfo = null;
|
VssHttpMessageHandlerTraceInfo traceInfo = null;
|
||||||
|
|
||||||
if (message.Options.TryGetValue(TfsTraceInfoKey, out object traceInfoObject))
|
if (message.Properties.TryGetValue(TfsTraceInfoKey, out object traceInfoObject))
|
||||||
{
|
{
|
||||||
traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo;
|
traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,12 +291,12 @@ namespace GitHub.Services.Common
|
|||||||
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
|
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
// Make sure we only apply the settings to the request once
|
// Make sure we only apply the settings to the request once
|
||||||
if (request.Options.TryGetValue(new HttpRequestOptionsKey<VssHttpRequestSettings>(PropertyName), out _))
|
if (request.Properties.ContainsKey(PropertyName))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Options.Set(new HttpRequestOptionsKey<VssHttpRequestSettings>(PropertyName), this);
|
request.Properties.Add(PropertyName, this);
|
||||||
|
|
||||||
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
|
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace GitHub.Services.Common
|
|||||||
// Allow overriding default retry options per request
|
// Allow overriding default retry options per request
|
||||||
VssHttpRetryOptions retryOptions = m_retryOptions;
|
VssHttpRetryOptions retryOptions = m_retryOptions;
|
||||||
object retryOptionsObject;
|
object retryOptionsObject;
|
||||||
if (request.Options.TryGetValue(HttpRetryOptionsKey, out retryOptionsObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
if (request.Properties.TryGetValue(HttpRetryOptionsKey, out retryOptionsObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
||||||
{
|
{
|
||||||
// Fallback to default options if object of unexpected type was passed
|
// Fallback to default options if object of unexpected type was passed
|
||||||
retryOptions = retryOptionsObject as VssHttpRetryOptions ?? m_retryOptions;
|
retryOptions = retryOptionsObject as VssHttpRetryOptions ?? m_retryOptions;
|
||||||
@@ -66,7 +66,7 @@ namespace GitHub.Services.Common
|
|||||||
|
|
||||||
IVssHttpRetryInfo retryInfo = null;
|
IVssHttpRetryInfo retryInfo = null;
|
||||||
object retryInfoObject;
|
object retryInfoObject;
|
||||||
if (request.Options.TryGetValue(HttpRetryInfoKey, out retryInfoObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
if (request.Properties.TryGetValue(HttpRetryInfoKey, out retryInfoObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
||||||
{
|
{
|
||||||
retryInfo = retryInfoObject as IVssHttpRetryInfo;
|
retryInfo = retryInfoObject as IVssHttpRetryInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2458,42 +2458,4 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class UnresolvableActionDownloadInfoException : DistributedTaskException
|
|
||||||
{
|
|
||||||
public UnresolvableActionDownloadInfoException(String message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnresolvableActionDownloadInfoException(String message, Exception innerException)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected UnresolvableActionDownloadInfoException(SerializationInfo info, StreamingContext context)
|
|
||||||
: base(info, context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[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,7 +17,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -49,13 +48,6 @@ 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,15 +65,5 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,5 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
|
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
Completed,
|
Completed,
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
Delayed,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public static class WellKnownDistributedTaskVariables
|
public static class WellKnownDistributedTaskVariables
|
||||||
{
|
{
|
||||||
public static readonly String JobId = "system.jobId";
|
public static readonly String JobId = "system.jobId";
|
||||||
public static readonly String RunnerLowDiskspaceThreshold = "system.runner.lowdiskspacethreshold";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -130,6 +130,55 @@ 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));
|
||||||
@@ -192,6 +241,59 @@ 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));
|
||||||
|
|||||||
@@ -833,7 +833,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
if (userState != null)
|
if (userState != null)
|
||||||
{
|
{
|
||||||
message.Options.Set(new HttpRequestOptionsKey<object>(UserStatePropertyName), userState);
|
message.Properties[UserStatePropertyName] = userState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message.Headers.Contains(Common.Internal.HttpHeaders.VssE2EID))
|
if (!message.Headers.Contains(Common.Internal.HttpHeaders.VssE2EID))
|
||||||
@@ -842,11 +842,11 @@ namespace GitHub.Services.WebApi
|
|||||||
}
|
}
|
||||||
VssHttpEventSource.Log.HttpRequestStart(traceActivity, message);
|
VssHttpEventSource.Log.HttpRequestStart(traceActivity, message);
|
||||||
message.Trace();
|
message.Trace();
|
||||||
message.Options.Set(new HttpRequestOptionsKey<VssTraceActivity>(VssTraceActivity.PropertyName), traceActivity);
|
message.Properties[VssTraceActivity.PropertyName] = traceActivity;
|
||||||
|
|
||||||
// Send the completion option to the inner handler stack so we know when it's safe to buffer
|
// Send the completion option to the inner handler stack so we know when it's safe to buffer
|
||||||
// and when we should avoid buffering.
|
// and when we should avoid buffering.
|
||||||
message.Options.Set(new HttpRequestOptionsKey<HttpCompletionOption>(VssHttpRequestSettings.HttpCompletionOptionPropertyName), completionOption);
|
message.Properties[VssHttpRequestSettings.HttpCompletionOptionPropertyName] = completionOption;
|
||||||
|
|
||||||
//ConfigureAwait(false) enables the continuation to be run outside
|
//ConfigureAwait(false) enables the continuation to be run outside
|
||||||
//any captured SyncronizationContext (such as ASP.NET's) which keeps things
|
//any captured SyncronizationContext (such as ASP.NET's) which keeps things
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
Object tracerObj = null;
|
Object tracerObj = null;
|
||||||
VssRequestTimerTrace tracer = null;
|
VssRequestTimerTrace tracer = null;
|
||||||
if (request.Options.TryGetValue(tracerKey, out tracerObj))
|
if (request.Properties.TryGetValue(tracerKey, out tracerObj))
|
||||||
{
|
{
|
||||||
tracer = tracerObj as VssRequestTimerTrace;
|
tracer = tracerObj as VssRequestTimerTrace;
|
||||||
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
||||||
@@ -26,7 +26,7 @@ namespace GitHub.Services.WebApi
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
tracer = new VssRequestTimerTrace();
|
tracer = new VssRequestTimerTrace();
|
||||||
request.Options.Set(new HttpRequestOptionsKey<VssRequestTimerTrace>(tracerKey), tracer);
|
request.Properties[tracerKey] = tracer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tracer != null)
|
if (tracer != null)
|
||||||
@@ -39,7 +39,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
Object tracerObj = null;
|
Object tracerObj = null;
|
||||||
VssRequestTimerTrace tracer = null;
|
VssRequestTimerTrace tracer = null;
|
||||||
if (response.RequestMessage.Options.TryGetValue(tracerKey, out tracerObj))
|
if (response.RequestMessage.Properties.TryGetValue(tracerKey, out tracerObj))
|
||||||
{
|
{
|
||||||
tracer = tracerObj as VssRequestTimerTrace;
|
tracer = tracerObj as VssRequestTimerTrace;
|
||||||
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetSignature(input);
|
return GetSignature(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +86,48 @@ 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, bool requireFipsCryptography)
|
public static VssSigningCredentials Create(Func<RSA> factory)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(factory, nameof(factory));
|
ArgumentUtility.CheckForNull(factory, nameof(factory));
|
||||||
|
|
||||||
@@ -106,19 +143,80 @@ namespace GitHub.Services.WebApi
|
|||||||
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requireFipsCryptography)
|
return new RSASigningToken(factory, rsa.KeySize);
|
||||||
{
|
|
||||||
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pss);
|
|
||||||
}
|
|
||||||
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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));
|
||||||
|
|
||||||
|
// Probably should have validation here, but there was none previously
|
||||||
|
return new SymmetricKeySigningToken(key);
|
||||||
|
}
|
||||||
|
|
||||||
private const Int32 c_minKeySize = 2048;
|
private const Int32 c_minKeySize = 2048;
|
||||||
private readonly DateTime m_effectiveDate;
|
private readonly DateTime m_effectiveDate;
|
||||||
|
|
||||||
#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();
|
||||||
@@ -146,14 +244,70 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -170,7 +324,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
using (var rsa = m_factory())
|
using (var rsa = m_factory())
|
||||||
{
|
{
|
||||||
return rsa.SignData(input, HashAlgorithmName.SHA256, m_signaturePadding);
|
return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +335,7 @@ namespace GitHub.Services.WebApi
|
|||||||
// As unfortunate as this is, there is no way to tell from an RSA implementation, based on querying
|
// As unfortunate as this is, there is no way to tell from an RSA implementation, based on querying
|
||||||
// properties alone, if it supports signature operations or has a private key. This is a one-time
|
// properties alone, if it supports signature operations or has a private key. This is a one-time
|
||||||
// hit for the signing credentials implementation, so it shouldn't be a huge deal.
|
// hit for the signing credentials implementation, so it shouldn't be a huge deal.
|
||||||
GetSignature(new Byte[1] { 1 });
|
GetSignature(new Byte[1] { 1 });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (CryptographicException)
|
catch (CryptographicException)
|
||||||
@@ -190,9 +344,18 @@ 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
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using GitHub.Runner.Listener;
|
using GitHub.Runner.Listener;
|
||||||
using GitHub.Runner.Listener.Check;
|
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
using GitHub.Runner.Worker.Handlers;
|
using GitHub.Runner.Worker.Handlers;
|
||||||
@@ -22,8 +21,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
// Otherwise, the interface needs to whitelisted.
|
// Otherwise, the interface needs to whitelisted.
|
||||||
var whitelist = new[]
|
var whitelist = new[]
|
||||||
{
|
{
|
||||||
typeof(ICredentialProvider),
|
typeof(ICredentialProvider)
|
||||||
typeof(ICheckExtension),
|
|
||||||
};
|
};
|
||||||
Validate(
|
Validate(
|
||||||
assembly: typeof(IMessageListener).GetTypeInfo().Assembly,
|
assembly: typeof(IMessageListener).GetTypeInfo().Assembly,
|
||||||
@@ -87,8 +85,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interfaceTypeInfo.FullName.Contains("IConverter"))
|
if (interfaceTypeInfo.FullName.Contains("IConverter")){
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using var stream = File.OpenRead(archiveFile);
|
using var stream = File.OpenRead(archiveFile);
|
||||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(expectedArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(expectedArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
||||||
|
|
||||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||||
@@ -205,9 +205,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using var stream = File.OpenRead(archiveFile);
|
using var stream = File.OpenRead(archiveFile);
|
||||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(builtInArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(builtInArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.NotFound));
|
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(dotcomArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(dotcomArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
||||||
|
|
||||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||||
@@ -265,7 +265,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using var stream = File.OpenRead(archiveFile);
|
using var stream = File.OpenRead(archiveFile);
|
||||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.NotFound));
|
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||||
|
|
||||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||||
|
|||||||
@@ -333,66 +333,6 @@ 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,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
|
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="5.0.100"
|
DOTNETSDK_VERSION="3.1.302"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "5.0.100"
|
"version": "3.1.302"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.276.0
|
2.274.2
|
||||||
|
|||||||
Reference in New Issue
Block a user