-
-
Notifications
You must be signed in to change notification settings - Fork 296
feat: add GitHub Apps authentication support #459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9334e78
845ea88
299e6a5
1b1c45c
9e765db
8668a54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -260,3 +260,39 @@ load_git_crypt_key() { | |
| cat $git_crypt_tmp_key_path | tr ' ' '\n' | base64 -d > $GIT_CRYPT_KEY_PATH | ||
| fi | ||
| } | ||
|
|
||
| setup_github_app_credentials() { | ||
| github_app_id=$(jq -r '.source.github_app_id // ""' <<< "$payload") | ||
| github_app_private_key=$(jq -r '.source.github_app_private_key // ""' <<< "$payload") | ||
| github_app_installation_id=$(jq -r '.source.github_app_installation_id // ""' <<< "$payload") | ||
|
|
||
| if [ -n "$github_app_id" ] && [ -n "$github_app_private_key" ] && [ -n "$github_app_installation_id" ]; then | ||
| # Generate JWT for GitHub App | ||
| now=$(date +%s) | ||
| iat=$((now - 60)) | ||
| exp=$((now + 600)) # 10 minutes expiration | ||
| jwt_header=$(echo -n '{"alg":"RS256","typ":"JWT"}' | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\r\n') | ||
| jwt_payload=$(echo -n "{\"iat\":$iat,\"exp\":$exp,\"iss\":\"$github_app_id\"}" | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\r\n') | ||
| jwt_signature=$(echo -n "$jwt_header.$jwt_payload" | openssl dgst -sha256 -sign <(echo -n "$github_app_private_key") | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\r\n') | ||
| jwt_token="$jwt_header.$jwt_payload.$jwt_signature" | ||
|
|
||
| # Get installation access token | ||
| access_token=$(curl -s -X POST -H "Authorization: Bearer $jwt_token" -H "Accept: application/vnd.github.v3+json" \ | ||
| "https://api.github.com/app/installations/$github_app_installation_id/access_tokens" | jq -r '.token') | ||
|
Comment on lines
+280
to
+281
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing this We can do two things here to actually test this curl call:
Basic example of using netcat to mock a server response: Another idea! In the test suite, you could add a curl() {
// test and check passed in args
}
export -f curlThere are options, we can get this code properly tested :) |
||
|
|
||
| if [ -z "$access_token" ]; then | ||
| echo "Failed to generate GitHub App access token" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Configure Git to use the token for the repository host | ||
| git config --global credential.helper "!f() { echo \"username=x-access-token\"; echo \"password=$access_token\"; }; f" | ||
| git config --global credential.https://github.com.username "x-access-token" | ||
| git config --global credential.https://github.com.password "$access_token" | ||
|
|
||
| # Add this block to switch to HTTPS URI if it's an SSH URI | ||
| if [[ $uri == git@github.com:* ]]; then | ||
| uri="https://github.com/${uri#git@github.com:}" | ||
| fi | ||
| fi | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,5 +25,8 @@ | |
| "https_tunnel": "", | ||
| "commit_filter": "", | ||
| "version_depth": "", | ||
| "search_remote_refs": "" | ||
| "search_remote_refs": "", | ||
| "github_app_id": "", | ||
| "github_app_installation_id": "", | ||
| "github_app_private_key": "" | ||
|
Comment on lines
+29
to
+31
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These new fields should be documented in the README please. |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,186 @@ | ||||||||
| #!/bin/bash | ||||||||
|
|
||||||||
| set -e | ||||||||
|
|
||||||||
| source "$(dirname "$0")/helpers.sh" | ||||||||
| if [ -d /opt/resource ]; then | ||||||||
| source /opt/resource/common.sh | ||||||||
| else | ||||||||
| source "$(dirname "$0")/../assets/common.sh" | ||||||||
| fi | ||||||||
|
|
||||||||
| # Test case 1: setup_github_app_credentials without GitHub App credentials | ||||||||
| it_skips_setup_when_github_app_credentials_not_provided() { | ||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # This should not error and should not modify git config | ||||||||
| setup_github_app_credentials | ||||||||
|
|
||||||||
| # Verify that git config was not modified with credential helper | ||||||||
| ! git config --global credential.helper | grep -q "x-access-token" || true | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 2: setup_github_app_credentials with incomplete credentials (missing private key) | ||||||||
| it_skips_setup_when_github_app_credentials_incomplete() { | ||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git", | ||||||||
| "github_app_id": "123456" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # This should not error and should not modify git config | ||||||||
| setup_github_app_credentials | ||||||||
|
|
||||||||
| # Verify that git config was not modified | ||||||||
| ! git config --global credential.helper | grep -q "x-access-token" || true | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 3: setup_github_app_credentials extracts credentials correctly | ||||||||
| it_extracts_github_app_credentials() { | ||||||||
| local test_key="test-private-key-content" | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can use ssh-keygen to generate keys. We do this in the check tests: Lines 101 to 103 in 6e7bbd3
These keys end up in ED25519 openssh format though, which I mentioned openssl won't be able to read.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use jq -n --arg key "$key" '{
"source": {
"uri": "https://github.com/test/repo.git",
"github_app_id": "123456",
"github_app_private_key": $key,
"github_app_installation_id": "789012"
}
}' |
||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git", | ||||||||
| "github_app_id": "123456", | ||||||||
| "github_app_private_key": "'"$test_key"'", | ||||||||
| "github_app_installation_id": "789012" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # Extract the values to verify they are extracted correctly | ||||||||
| github_app_id=$(jq -r '.source.github_app_id // ""' <<< "$payload") | ||||||||
| github_app_private_key=$(jq -r '.source.github_app_private_key // ""' <<< "$payload") | ||||||||
| github_app_installation_id=$(jq -r '.source.github_app_installation_id // ""' <<< "$payload") | ||||||||
|
Comment on lines
+58
to
+61
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also not testing the code in You could export all of these vars and then call |
||||||||
|
|
||||||||
| test "$github_app_id" = "123456" | ||||||||
| test "$github_app_installation_id" = "789012" | ||||||||
| test "$github_app_private_key" = "$test_key" | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 4: setup_github_app_credentials converts SSH URI to HTTPS | ||||||||
| it_converts_ssh_uri_to_https() { | ||||||||
| export uri="git@github.com:test/repo.git" | ||||||||
|
|
||||||||
| # Simulate the URI conversion logic | ||||||||
| if [[ $uri == git@github.com:* ]]; then | ||||||||
| uri="https://github.com/${uri#git@github.com:}" | ||||||||
| fi | ||||||||
|
Comment on lines
+72
to
+75
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't actually test the code that gets used in Why not put the code from |
||||||||
|
|
||||||||
| test "$uri" = "https://github.com/test/repo.git" | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 5: setup_github_app_credentials with empty github_app_id | ||||||||
| it_skips_when_github_app_id_is_empty() { | ||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git", | ||||||||
| "github_app_id": "", | ||||||||
| "github_app_private_key": "test-key", | ||||||||
| "github_app_installation_id": "789012" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # This should not error | ||||||||
| setup_github_app_credentials | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 6: setup_github_app_credentials with empty github_app_private_key | ||||||||
| it_skips_when_github_app_private_key_is_empty() { | ||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git", | ||||||||
| "github_app_id": "123456", | ||||||||
| "github_app_private_key": "", | ||||||||
| "github_app_installation_id": "789012" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # This should not error | ||||||||
| setup_github_app_credentials | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 7: setup_github_app_credentials with empty github_app_installation_id | ||||||||
| it_skips_when_github_app_installation_id_is_empty() { | ||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git", | ||||||||
| "github_app_id": "123456", | ||||||||
| "github_app_private_key": "test-key", | ||||||||
| "github_app_installation_id": "" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # This should not error | ||||||||
| setup_github_app_credentials | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 8: Verify that the function handles JSON parsing correctly | ||||||||
| it_handles_missing_json_fields() { | ||||||||
| export payload='{ | ||||||||
| "source": { | ||||||||
| "uri": "https://github.com/test/repo.git" | ||||||||
| } | ||||||||
| }' | ||||||||
| export uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # Extract values when fields are missing | ||||||||
| github_app_id=$(jq -r '.source.github_app_id // ""' <<< "$payload") | ||||||||
| github_app_private_key=$(jq -r '.source.github_app_private_key // ""' <<< "$payload") | ||||||||
| github_app_installation_id=$(jq -r '.source.github_app_installation_id // ""' <<< "$payload") | ||||||||
|
|
||||||||
| # Should return empty strings when fields are missing | ||||||||
| test -z "$github_app_id" | ||||||||
| test -z "$github_app_private_key" | ||||||||
| test -z "$github_app_installation_id" | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 9: Verify SSH URI patterns are converted correctly | ||||||||
| it_converts_various_ssh_uri_formats() { | ||||||||
| # Test standard SSH format | ||||||||
| uri="git@github.com:user/repo.git" | ||||||||
| if [[ $uri == git@github.com:* ]]; then | ||||||||
| uri="https://github.com/${uri#git@github.com:}" | ||||||||
| fi | ||||||||
| test "$uri" = "https://github.com/user/repo.git" | ||||||||
|
|
||||||||
| # Test without .git extension | ||||||||
| uri="git@github.com:user/repo" | ||||||||
| if [[ $uri == git@github.com:* ]]; then | ||||||||
| uri="https://github.com/${uri#git@github.com:}" | ||||||||
| fi | ||||||||
| test "$uri" = "https://github.com/user/repo" | ||||||||
| } | ||||||||
|
|
||||||||
| # Test case 10: Verify HTTPS URIs are not modified | ||||||||
| it_does_not_modify_https_uris() { | ||||||||
| uri="https://github.com/test/repo.git" | ||||||||
|
|
||||||||
| # Should not modify HTTPS URIs | ||||||||
| if [[ $uri == git@github.com:* ]]; then | ||||||||
| uri="https://github.com/${uri#git@github.com:}" | ||||||||
| fi | ||||||||
|
Comment on lines
+151
to
+172
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as previous comment from |
||||||||
|
|
||||||||
| test "$uri" = "https://github.com/test/repo.git" | ||||||||
| } | ||||||||
|
|
||||||||
| run it_skips_setup_when_github_app_credentials_not_provided | ||||||||
| run it_skips_setup_when_github_app_credentials_incomplete | ||||||||
| run it_extracts_github_app_credentials | ||||||||
| run it_converts_ssh_uri_to_https | ||||||||
| run it_skips_when_github_app_id_is_empty | ||||||||
| run it_skips_when_github_app_private_key_is_empty | ||||||||
| run it_skips_when_github_app_installation_id_is_empty | ||||||||
| run it_handles_missing_json_fields | ||||||||
| run it_converts_various_ssh_uri_formats | ||||||||
| run it_does_not_modify_https_uris | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
openssl only supports private keys in PEM format. If the key is not in PEM format you get this error:
PEM formatted keys start with:
Newer openssh keys, which openssl cannot read, start with:
This should be documented in the README and maybe even detected by the function here and errored out to the user.
I did some digging to see if it's possible to convert ED25519 keys (the default from
ssh-keygen) to PEM format and couldn't turn anything up indicating that it's possible.