Last active
July 11, 2024 20:58
-
-
Save rajbos/8581083586b537029fe8ab796506bec3 to your computer and use it in GitHub Desktop.
Load jwt token from GitHub App for authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Purpose | |
# grab the jwt token | |
# make API calls as the GitHub App used | |
# get a temporary jwt token from the key file and app id (hardcoded in the file:) | |
generated_jwt=$(./github-app-jwt.sh) | |
github_api_url="https://api.github.com/app" | |
installation_id=21043970 | |
org="devops-actions" | |
# show the jwt during testing | |
echo "Generated jwt:" | |
echo "${generated_jwt}" | |
echo "" | |
# call the urls with it | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.machine-man-preview+json" \ | |
"${github_api_url}" | |
github_api_url="https://api.github.com/app/installations" | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" | |
# get the token by POSTING to the url: | |
github_api_url="https://api.github.com/app/installations/$installation_id/access_tokens" | |
echo "Calling [${github_api_url}], result:" | |
tokens=$(curl -s -X POST \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" ) | |
echo "Token info: $tokens" | |
# extract the token, more information about expiry for example is present as well: | |
token=$(echo "$tokens" | jq -r '.token') | |
echo "Token: $token" | |
# from now until the token expires, you can use the token to make authenticated requests to the GitHub API: | |
# get the repositories this token has access to | |
github_api_url="https://api.github.com/installation/repositories" | |
echo "Calling [${github_api_url}], result:" | |
curl -s GET \ | |
-H "Authorization: Bearer ${token}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" | |
# get the runner information for a repo | |
github_api_url="https://api.github.com/repos/rajbos/dotnetcore-webapp/actions/runners" | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${token}" \ | |
-H "Accept: application/vnd.github.machine-man-preview+json" \ | |
"${github_api_url}" | |
# load the files in a directory | |
github_api_url="https://api.github.com/repos/rajbos/dotnetcore-webapp/contents/.github/workflows" | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${token}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" | |
# load a file in a directory | |
github_api_url="https://api.github.com/repos/rajbos/dotnetcore-webapp/contents/README.md" | |
echo "Calling [${github_api_url}], result:" | |
curl -i -X GET \ | |
-H "Authorization: Bearer ${token}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Purpose: | |
# grab the jwt token | |
# get a normal token from it | |
# git clone with that token | |
# use git config globally to always use this token, even instead of ssh | |
# git clone sshRepo with git config setting | |
# get a temporary jwt token from the key file and app id (hardcoded in the file:) | |
generated_jwt=$(./github-app-jwt.sh) | |
github_api_url="https://api.github.com/app" | |
installation_id=21043970 | |
owner="devops-actions" | |
repo="load-used-actions" | |
sshRepo="load-available-actions" | |
# show the jwt during testing | |
echo "Generated jwt:" | |
echo "${generated_jwt}" | |
echo "" | |
# call the urls with it | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.machine-man-preview+json" \ | |
"${github_api_url}" | |
github_api_url="https://api.github.com/app/installations" | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" | |
# get the token by POSTING to the url: | |
github_api_url="https://api.github.com/app/installations/$installation_id/access_tokens" | |
echo "Calling [${github_api_url}], result:" | |
tokens=$(curl -s -X POST \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" ) | |
echo "Token info: $tokens" | |
# extract the token, more information about expiry for example is present as well: | |
token=$(echo "$tokens" | jq -r '.token') | |
echo "Token: $token" | |
# this token can be used to call the API's or used in a Git clone call using https | |
# clone with https: | |
rm -rf tempGitClone | |
mkdir tempGitClone | |
cd tempGitClone | |
git clone "https://x-access-token:$token@github.com/$owner/$repo.git" | |
cd $repo | |
ls -la | |
echo "setting up git config" | |
git config --global url."https://x-access-token:$token@github.com/".insteadOf "git@github.com:" | |
# clone different repo by using ssh call | |
git clone "git@github.com:$owner/$sshRepo.git" | |
cd $sshRepo | |
ls -la | |
echo "Cleanup folders" | |
cd ../../../ | |
rm -rf tempGitClone | |
# clean up global configuration so we don't leave the token around | |
git config --global --remove-section url."https://x-access-token:$token@github.com/" | |
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# code to load the JWT token in PowerShell | |
# Call teh Get-JWTToken method with the App ID and the App Private Key (normal string from env var or file contents, make sure there is no extra line ending at the end of it!). | |
function Build-Payload { | |
Param ( | |
[string] $app_id | |
) | |
$iat = [Math]::Floor([decimal](Get-Date(Get-Date) -UFormat %s)) | |
$payload = @{ | |
"iat" = [int]$iat # issues at = now | |
"exp" = [int]($iat + 300) # expire this short lived token 5 minutes from now | |
"iss" = [int]$app_id # GitHub ApplicationId that we are signing for | |
} | |
return $payload | ConvertTo-Json -Compress | |
} | |
function B64Enc { | |
param ( | |
[Parameter(ValueFromPipeline=$true)] | |
[string] $InputObject | |
) | |
$output = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($InputObject)) -replace '\+/', '-_' -replace '=' | |
return $output | |
} | |
function Json { | |
param ( | |
[Parameter(ValueFromPipeline=$true)] | |
[string] $InputObject | |
) | |
$output = $InputObject | ConvertFrom-Json | ConvertTo-Json -Compress | |
return $output | |
} | |
function RS256_Sign { | |
param ( | |
[string] $InputObject, | |
[string] $PrivateKey | |
) | |
$rsaProvider = [System.Security.Cryptography.RSA]::Create() | |
$rsaProvider.ImportFromPem($PrivateKey) | |
$hashAlgorithmName = [System.Security.Cryptography.HashAlgorithmName]::SHA256 | |
$rsaSignaturePadding = [System.Security.Cryptography.RSASignaturePadding]::Pkcs1 | |
$signedBytes = $rsaProvider.SignData([Text.Encoding]::UTF8.GetBytes($InputObject), $hashAlgorithmName, $rsaSignaturePadding) | |
$output = [Convert]::ToBase64String($signedBytes) -replace '\+/', '-_' -replace '=' | |
return $output | |
} | |
function Get-JWTToken { | |
Param ( | |
[string] $app_id, | |
[string] $app_private_key | |
) | |
# build the JWT payload for the GitHub application we are using | |
$payload = Build-Payload -app_id $app_id | |
$header = @{ | |
"alg" = "RS256" | |
"typ" = "JWT" | |
} | ConvertTo-Json | |
# sign the JWT content payload | |
$signed_content = "$($header | B64Enc).$($payload | B64Enc)" | |
# sign the JWT key payload with the GitHub application's private key | |
$sig = RS256_Sign -InputObject $signed_content -PrivateKey $app_private_key | |
# return the signed content and the signature as JWT | |
return "$signed_content.$sig" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Purpose | |
# grab the jwt token | |
# call the api with that token to retrieve an access token | |
# get an installation token to use for registring a runner | |
# get a temporary jwt token from the key file and app id (hardcoded in the file:) | |
generated_jwt=$(./github-app-jwt.sh) | |
github_api_url="https://api.github.com/app" | |
installation_id=21043970 | |
org="devops-actions" | |
# show the jwt during testing | |
echo "Generated jwt:" | |
echo "${generated_jwt}" | |
echo "" | |
# call the urls with it | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.machine-man-preview+json" \ | |
"${github_api_url}" | |
github_api_url="https://api.github.com/app/installations" | |
echo "Calling [${github_api_url}], result:" | |
curl -s \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" | |
# get the token by POSTING to the url: | |
github_api_url="https://api.github.com/app/installations/$installation_id/access_tokens" | |
echo "Calling [${github_api_url}], result:" | |
tokens=$(curl -s -X POST \ | |
-H "Authorization: Bearer ${generated_jwt}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}" ) | |
echo "Token info: $tokens" | |
# extract the token, more information about expiry for example is present as well: | |
token=$(echo "$tokens" | jq -r '.token') | |
echo "Token: $token" | |
# this token can be used for calling the API or Git clone over https | |
encoded_token=$(echo -n "x:$token" | base64) | |
echo "Encoded token: $encoded_token" | |
github_api_url="https://api.github.com/orgs/$org/actions/runners/registration-token" | |
echo "Calling [${github_api_url}], result:" | |
runner_installation_token_result=$(curl -s -X POST \ | |
-H "Authorization: Basic ${encoded_token}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"${github_api_url}") | |
# this token can be used to register a runner with, only valid for 1 hour (and probably 1 installation) | |
runner_installation_token=$(echo "$runner_installation_token_result" | jq -r '.token') | |
echo "Runner installation token: $runner_installation_token" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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=148825 | |
app_private_key="$(< /mnt/c/Users/RobBos/Downloads/path_to_file.pem)" | |
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# this file is an example how to call the github-app-jwt.sh file from pwsh (PowerShell Core running on Linux / WSL) so that we do not have to rewrite the entire jwt logic. | |
<# | |
.DESCRIPTION | |
Get-TokenFromApp uses the environment settings 'GH_APPID' and 'GH_PEM' as 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 { | |
# get a temporary jwt token from the key file and app id (hardcoded in the file:) | |
$generated_jwt = $(bash ./github-app-jwt.sh) | |
$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 | |
Write-Debug "Found installationId: [$($response[0].id)]" | |
$installationId = $response[0].id | |
} | |
catch | |
{ | |
Write-Error "Error in finding the app installations: $($_)" | |
} | |
$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)]" | |
} | |
catch | |
{ | |
Write-Error "Error in getting an access token: $($_)" | |
} | |
# set token as env var so the gh cli can pick it up | |
$env:GH_TOKEN = $token | |
Write-Host "Found token with [$($token.length)]" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# with this setup you can configure git to dynamically rewrite all setups with git@your.server.com | |
# to https://x-access-token:your-token-here@serverUrl | |
# this will help with moving existing tooling over | |
serverurl="your.server.com" | |
https_repo_pattern="https://x-access-token:${token}@${serverurl}/" | |
ssh_repo_pattern='git@${serverurl}:' | |
# Validating if the redirection already exists with a different token value | |
current_value=$(git config --global --name-only --get-regexp 'url.https://x-access-token:.*') | |
if [[ -z $current_value ]]; then | |
echo "Adding URL translation" | |
git config --global url."${https_repo_pattern}".insteadOf "${ssh_repo_pattern}" | |
else | |
echo "Updating token in the URL translation" | |
git config --global --rename-section "${current_value%.insteadof}" "url.https://x-access-token:${token}@${serverurl}/" | |
fi |
I think these scripts should put you into DevOps legendary status! They are really great and very nicely implemented. Definitely appreciative of your efforts here!
Thanks @BrianSidebotham!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Learn more on GitHub Tokens and when and how to use which one in this blogpost: devopsjournal.io.