Skip to content

Instantly share code, notes, and snippets.

@rajbos
Last active July 11, 2024 20:58
Show Gist options
  • Save rajbos/8581083586b537029fe8ab796506bec3 to your computer and use it in GitHub Desktop.
Save rajbos/8581083586b537029fe8ab796506bec3 to your computer and use it in GitHub Desktop.
Load jwt token from GitHub App for authentication
#!/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}"
#!/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
# 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"
}
#!/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"
#!/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 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)]"
}
# 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
@rajbos
Copy link
Author

rajbos commented Jan 28, 2022

Learn more on GitHub Tokens and when and how to use which one in this blogpost: devopsjournal.io.

@BrianSidebotham
Copy link

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!

@rajbos
Copy link
Author

rajbos commented Apr 7, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment