From 37a9d99ebddde7caf8ff2b63a177eb507ef03103 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sat, 27 Apr 2024 17:07:36 +0000 Subject: [PATCH] Update repository workflows and Dockerfile --- .devcontainer/Dockerfile | 5 ++ .devcontainer/devcontainer.json | 38 ++++++++++++++ .github/workflows/library.ps1 | 87 ++++++++++++++++----------------- .github/workflows/repo-info.yml | 2 +- .github/workflows/repoInfo.ps1 | 2 +- github-app-jwt.sh | 51 +++++++++++++++++++ local-run.ps1 | 15 ++++-- 7 files changed, 150 insertions(+), 50 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100755 github-app-jwt.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..3207b78d8 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +FROM mcr.microsoft.com/devcontainers/base:focal + +# Install Node.js using apt-get +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends nodejs npm \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..a7612328c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,38 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "devcontainer", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "dockerFile": "Dockerfile", + "features": { + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/azure-cli:1": {}, + "ghcr.io/devcontainers-contrib/features/powershell:1": {}, + }, + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot", + "ms-vscode.azurecli", + "ms-vscode.PowerShell" + ], + "settings": { + "terminal.integrated.shell.linux": "/usr/local/lib/pwsh", + "powershell.powerShellAdditionalExePaths": { + "pwsh": "/usr/local/lib/pwsh/pwsh" + }, + "editor.trimAutoWhitespace": true, + "files.trimTrailingWhitespace": true + } + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pwsh -Command { ./devcontainer/postCreateCommand.ps1; }" + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/workflows/library.ps1 b/.github/workflows/library.ps1 index 7aa1918ec..ac8768ba2 100644 --- a/.github/workflows/library.ps1 +++ b/.github/workflows/library.ps1 @@ -302,7 +302,7 @@ function GetRateLimitInfo { if ($access_token -ne $access_token_destination) { # check the ratelimit for the destination token as well: $response2 = ApiCall -method GET -url $url -access_token $access_token_destination - Write-Message -message "Access token destination ratelimit info: $($response2.rate | ConvertTo-Json)" -logToSummary $true + Write-Message -message "Access token destination ratelimit info: $($response2.rate | ConvertTo-Json -Depth 5)" -logToSummary $true } if ($response.rate.limit -eq 60) { @@ -867,55 +867,52 @@ function GetForkedActionRepos { return ($status, $failedForks) } -function Get-GitHubAppToken { + +<# + .DESCRIPTION + Get-TokenFromApp uses the paramsas credentials + for the GitHub App to load an aceess token with. Be aware that this token is only valid for an hour. + Note: this token has only access to the repositories that the App has been installed to. + We cannot use this token to create new repositories or install the app in a repo. +#> +function Get-TokenFromApp { param ( [string] $appId, [string] $installationId, [string] $pemKey ) - $jwt = New-Jwt -appId $appId -pemKey $pemKey - $token = Get-AppToken -jwt $jwt -installationId $installationId - return $token -} + # get a temporary jwt token from the key file and app id (hardcoded in the file:) + $generated_jwt = $(bash ./github-app-jwt.sh $appId $pemKey) + $github_api_url = "https://api.github.com/app" + + #Write-Host "Loaded jwt token: [$($generated_jwt)]" + $github_api_url="https://api.github.com/app/installations" + Write-Debug "Calling [${github_api_url}]" + $installationId = "" + try { + $response = Invoke-RestMethod -Uri $github_api_url -Headers @{Authorization = "Bearer $generated_jwt" } -ContentType "application/json" -Method Get -function New-Jwt { - param ( - [string] $appId, - [string] $pemKey - ) - $now = [System.DateTime]::UtcNow - $payload = @{ - iat = [math]::floor($now.Subtract((New-Object DateTime 1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind::Utc)).TotalSeconds) - exp = [math]::floor($now.AddMinutes(10).Subtract((New-Object DateTime 1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind::Utc)).TotalSeconds) - iss = $appId - } - $header = @{ - alg = "RS256" - typ = "JWT" - } - $headerJson = $header | ConvertTo-Json -Compress - $payloadJson = $payload | ConvertTo-Json -Compress - $headerBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerJson)) - $payloadBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadJson)) - $data = "$headerBase64.$payloadBase64" - $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider - $rsa.ImportParameters((New-Object System.Security.Cryptography.RSAParameters)) - $rsa.FromXmlString($pemKey) - $signature = $rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($data), "SHA256") - $signatureBase64 = [Convert]::ToBase64String($signature) - return "$data.$signatureBase64" -} + Write-Debug "Found installationId: [$($response[0].id)]" + $installationId = $response[0].id + } + catch + { + Write-Error "Error in finding the app installations: $($_)" + } -function Get-AppToken { - param ( - [string] $jwt, - [string] $installationId - ) - $uri = "https://api.github.com/app/installations/$installationId/access_tokens" - $headers = @{ - "Authorization" = "Bearer $jwt" - "Accept" = "application/vnd.github.v3+json" + $github_api_url="https://api.github.com/app/installations/$installationId/access_tokens" + Write-Host "Calling [${github_api_url}]" + $token = "" + try { + $response = Invoke-RestMethod -Uri $github_api_url -Headers @{Authorization = "Bearer $generated_jwt" } -ContentType "application/json" -Method POST -Body "{}" + $token = $response.token + Write-Host "Got an access token that will expire at: [$($response.expires_at)]" } - $response = Invoke-RestMethod -Uri $uri -Method POST -Headers $headers - return $response.token -} \ No newline at end of file + catch + { + Write-Error "Error in getting an access token: $($_)" + } + + Write-Host "Found token with [$($token.length)]" + return $token +} diff --git a/.github/workflows/repo-info.yml b/.github/workflows/repo-info.yml index 565ce457d..5232e4bdd 100644 --- a/.github/workflows/repo-info.yml +++ b/.github/workflows/repo-info.yml @@ -17,7 +17,7 @@ on: workflow_dispatch: env: - numberOfReposToDo: 1000 + numberOfReposToDo: 100 jobs: get-repo-information: diff --git a/.github/workflows/repoInfo.ps1 b/.github/workflows/repoInfo.ps1 index c26b1cbe9..000186ece 100644 --- a/.github/workflows/repoInfo.ps1 +++ b/.github/workflows/repoInfo.ps1 @@ -14,7 +14,7 @@ if ($env:APP_PEM_KEY) { $env:APP_ID = 264650 $env:INSTALLATION_ID = 31486141 # get a token to use from the app - $accessToken = Get-GitHubAppToken -appId $env:APP_ID -installationId $env:INSTALLATION_ID -pemKey $env:APP_PEM_KEY + $accessToken = Get-TokenFromApp -appId $env:APP_ID -installationId $env:INSTALLATION_ID -pemKey $env:APP_PEM_KEY } Test-AccessTokens -accessToken $accessToken -access_token_destination $access_token_destination -numberOfReposToDo $numberOfReposToDo diff --git a/github-app-jwt.sh b/github-app-jwt.sh new file mode 100755 index 000000000..d44ed168b --- /dev/null +++ b/github-app-jwt.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Generate JWT for Github App +# +# Found at https://gist.github.com/carestad/bed9cb8140d28fe05e67e15f667d98ad from Alexander Karlstad: +# +# Inspired by implementation by Will Haley at: +# http://willhaley.com/blog/generate-jwt-with-bash/ +# From: +# https://stackoverflow.com/questions/46657001/how-do-you-create-an-rs256-jwt-assertion-with-bash-shell-scripting + +thisdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +set -o pipefail + +# Change these variables: +app_id=$1 +app_private_key="$2" + +# Shared content to use as template +header='{ + "alg": "RS256", + "typ": "JWT" +}' +payload_template='{}' + +build_payload() { + jq -c \ + --arg iat_str "$(date +%s)" \ + --arg app_id "${app_id}" \ + ' + ($iat_str | tonumber) as $iat + | .iat = $iat + | .exp = ($iat + 300) + | .iss = ($app_id | tonumber) + ' <<< "${payload_template}" | tr -d '\n' +} + +b64enc() { openssl enc -base64 -A | tr '+/' '-_' | tr -d '='; } +json() { jq -c . | LC_CTYPE=C tr -d '\n'; } +rs256_sign() { openssl dgst -binary -sha256 -sign <(printf '%s\n' "$1"); } + +sign() { + local algo payload sig + algo=${1:-RS256}; algo=${algo^^} + payload=$(build_payload) || return + signed_content="$(json <<<"$header" | b64enc).$(json <<<"$payload" | b64enc)" + sig=$(printf %s "$signed_content" | rs256_sign "$app_private_key" | b64enc) + printf '%s.%s\n' "${signed_content}" "${sig}" +} + +sign \ No newline at end of file diff --git a/local-run.ps1 b/local-run.ps1 index cc1bc05cc..8daa63bcd 100644 --- a/local-run.ps1 +++ b/local-run.ps1 @@ -3,6 +3,15 @@ GetRateLimitInfo -access_token $env:GITHUB_TOKEN +if ($null -ne $env:APP_PEM_KEY) { + Write-Host "GitHub App information found, using GitHub App" + # todo: move into codespace variable + $env:APP_ID = 264650 + $env:INSTALLATION_ID = 31486141 + # get a token to use from the app + $accessToken = Get-TokenFromApp -appId $env:APP_ID -installationId $env:INSTALLATION_ID -pemKey $env:APP_PEM_KEY +} + # to add: how to refresh the actions.json from the storage account? $actionsFile = "actions.json" @@ -15,7 +24,7 @@ else { $numberofReposToDo = 10 #./.github/workflows/functions.ps1 -actions $actions -numberofReposToDo $numberofReposToDo -#./.github/workflows/repoInfo.ps1 -actions $actions -numberofReposToDo $numberofReposToDo +./.github/workflows/repoInfo.ps1 -actions $actions -numberofReposToDo $numberofReposToDo -access_token $accessToken -access_token_destination $accessToken $statusFile = "status.json" if ((Test-Path $statusFile)) { @@ -30,6 +39,6 @@ else { #./.github/workflows/dependabot-updates.ps1 -actions $status -numberOfReposToDo $numberofReposToDo #./.github/workflows/ossf-scan.ps1 -actions $actions -numberofReposToDo $numberofReposToDo -. ./.github/workflows/dependents.ps1 +#. ./.github/workflows/dependents.ps1 #GetDependentsForRepo -owner "pozil" -repo "auto-assign-issue" -GetDependentsForRepo -owner "devops-action" -repo "get-tag" \ No newline at end of file +#GetDependentsForRepo -owner "devops-action" -repo "get-tag" \ No newline at end of file