From 9334e78e0b5b24c0711e6d818c685e6d41212e95 Mon Sep 17 00:00:00 2001 From: Hippo Lin Date: Mon, 13 Oct 2025 11:15:29 +0800 Subject: [PATCH 1/6] feat(github): add GitHub Apps authentication support Extract new variables from payload for GitHub Apps (app ID, private key, and installation ID). Generate JWT token and use it to fetch an access token via API. Configure Git credentials to use the token for repository access, enhancing authentication options in CI/CD workflows. Signed-off-by: Hippo Lin --- assets/check | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/assets/check b/assets/check index 3eb43b4c..08b11ffe 100755 --- a/assets/check +++ b/assets/check @@ -26,6 +26,12 @@ configure_https_tunnel "$payload" configure_git_ssl_verification "$payload" configure_credentials "$payload" +# Add new variables for GitHub Apps support after existing jq extractions +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") + + uri=$(jq -r '.source.uri // ""' <<< "$payload") uri=${uri# } branch=$(jq -r '.source.branch // ""' <<< "$payload") @@ -54,6 +60,32 @@ fi configure_git_global "${git_config_payload}" +# Add GitHub Apps token generation and credential setup (integrate into or after configure_credentials if needed) +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') + + 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" +fi + destination=$TMPDIR/git-resource-repo-cache # Optimization when last commit only is checked and skip ci is disabled From 845ea88260e595be5a1752e014a2d945feaadfa6 Mon Sep 17 00:00:00 2001 From: Hippo Lin Date: Mon, 13 Oct 2025 11:38:41 +0800 Subject: [PATCH 2/6] feat(source_schema): add GitHub app configuration fields This introduces new options for GitHub App integration, enabling secure authentication via app ID, installation ID, and private key. These fields enhance support for automated Git operations in the source schema. Signed-off-by: Hippo Lin --- assets/source_schema.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/source_schema.json b/assets/source_schema.json index a1ae86d2..b0b2cb72 100644 --- a/assets/source_schema.json +++ b/assets/source_schema.json @@ -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": "" } From 299e6a51e9c1be571014c70ea4fbf895019d0a2c Mon Sep 17 00:00:00 2001 From: Hippo Lin Date: Mon, 13 Oct 2025 11:43:15 +0800 Subject: [PATCH 3/6] chore(dockerfile): add openssl and curl dependencies This update installs openssl and curl to ensure the image includes necessary tools for secure communications and HTTP requests, which may be required for enhanced functionality in the build environment. - Specifically, add these packages to the apk install command to support potential encryption and network operations. Signed-off-by: Hippo Lin --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 19bba186..edf07776 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,9 @@ RUN apk --no-cache add \ gpg \ gpg-agent \ jq \ - openssh-client + openssh-client \ + openssl \ + curl RUN git config --global user.email "git@localhost" RUN git config --global user.name "git" From 1b1c45c057d123e905fcb41613f0bb22637453c6 Mon Sep 17 00:00:00 2001 From: Hippo Lin Date: Mon, 13 Oct 2025 11:58:23 +0800 Subject: [PATCH 4/6] feat(github): add GitHub Apps authentication support Extract and modularize GitHub Apps token generation into a new setup_github_app_credentials function in common.sh for reuse. This change moves the logic from assets/check to assets/common.sh, and integrates it into assets/in and assets/out. It enhances modularity, reduces duplication, and enables secure authentication for Git operations using GitHub Apps. - Remove redundant debugging code in assets/check - Add function to handle JWT generation and credential setup Signed-off-by: Hippo Lin (+1 squashed commit) Squashed commits: [bfe3a76] chore(check): add debug logging for payload This introduces a debug statement to write the incoming payload to /tmp/debug.json. This helps in troubleshooting issues by allowing easy inspection of the payload content during script execution. - The change is non-intrusive and only activates for debugging purposes. Signed-off-by: Hippo Lin --- assets/check | 32 +------------------------------- assets/common.sh | 36 ++++++++++++++++++++++++++++++++++++ assets/in | 2 ++ assets/out | 2 ++ 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/assets/check b/assets/check index 08b11ffe..9df2bc72 100755 --- a/assets/check +++ b/assets/check @@ -26,12 +26,6 @@ configure_https_tunnel "$payload" configure_git_ssl_verification "$payload" configure_credentials "$payload" -# Add new variables for GitHub Apps support after existing jq extractions -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") - - uri=$(jq -r '.source.uri // ""' <<< "$payload") uri=${uri# } branch=$(jq -r '.source.branch // ""' <<< "$payload") @@ -60,31 +54,7 @@ fi configure_git_global "${git_config_payload}" -# Add GitHub Apps token generation and credential setup (integrate into or after configure_credentials if needed) -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') - - 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" -fi +setup_github_app_credentials destination=$TMPDIR/git-resource-repo-cache diff --git a/assets/common.sh b/assets/common.sh index d2fa1d13..b0cc49c8 100644 --- a/assets/common.sh +++ b/assets/common.sh @@ -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') + + 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 +} diff --git a/assets/in b/assets/in index 59c29a9b..090741f2 100755 --- a/assets/in +++ b/assets/in @@ -76,6 +76,8 @@ if [ -z "$uri" ]; then exit 1 fi +setup_github_app_credentials + branchflag="" if [ -n "$branch" ]; then branchflag="--branch $branch" diff --git a/assets/out b/assets/out index 5d3b9d67..9bcdf0ca 100755 --- a/assets/out +++ b/assets/out @@ -57,6 +57,8 @@ if [ -z "$uri" ]; then exit 1 fi +setup_github_app_credentials + if [ -z "$branch" ] && [ "$only_tag" != "true" ] && [ -z "$override_branch" ]; then echo "invalid payload (missing branch)" exit 1 From 9e765dbfc24316a329f7e26db788ec5323fc2114 Mon Sep 17 00:00:00 2001 From: Hippo Lin Date: Thu, 30 Oct 2025 14:45:35 +0800 Subject: [PATCH 5/6] test(github-app): add unit tests for setup_github_app_credentials function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive unit tests for the new setup_github_app_credentials function that was added for GitHub Apps authentication support. Tests cover: - Skipping setup when credentials are not provided - Handling incomplete credentials - Credential extraction and parsing - SSH to HTTPS URI conversion - Empty field handling - JSON parsing edge cases 🤖 Generated with Claude Code Co-Authored-By: Claude Signed-off-by: Hippo Lin --- test/all.sh | 1 + test/github_app.sh | 186 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 test/github_app.sh diff --git a/test/all.sh b/test/all.sh index 43a34736..987e48f6 100755 --- a/test/all.sh +++ b/test/all.sh @@ -8,5 +8,6 @@ $(dirname $0)/common.sh $(dirname $0)/get.sh $(dirname $0)/put.sh $(dirname $0)/lfs.sh +$(dirname $0)/github_app.sh echo -e '\e[32mall tests passed!\e[0m' diff --git a/test/github_app.sh b/test/github_app.sh new file mode 100644 index 00000000..07540e53 --- /dev/null +++ b/test/github_app.sh @@ -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" + 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") + + 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 + + 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 + + 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 From 8668a54d4882026f1b8adb0497a1ebfc8a3bc687 Mon Sep 17 00:00:00 2001 From: Hippo Lin Date: Thu, 30 Oct 2025 16:56:24 +0800 Subject: [PATCH 6/6] chore(test): make github_app.sh executable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Claude Code Co-Authored-By: Claude Signed-off-by: Hippo Lin --- test/github_app.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 test/github_app.sh diff --git a/test/github_app.sh b/test/github_app.sh old mode 100644 new mode 100755