Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions assets/check
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ fi

configure_git_global "${git_config_payload}"

setup_github_app_credentials

destination=$TMPDIR/git-resource-repo-cache

# Optimization when last commit only is checked and skip ci is disabled
Expand Down
36 changes: 36 additions & 0 deletions assets/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Copy link
Member

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:

Could not find private key from /dev/fd/63
20602F81FFFF0000:error:1608010C:STORE routines:ossl_store_handle_load_result:unsupported:crypto/store/store_result.c:162:provider=default

PEM formatted keys start with:

-----BEGIN RSA PRIVATE KEY-----

Newer openssh keys, which openssl cannot read, start with:

-----BEGIN OPENSSH PRIVATE KEY-----

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.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this curl call is what stopped you from directly testing this function and resulted in the copy-pasted tests.

We can do two things here to actually test this curl call:

  1. Make the api endpoint https://api.github.com configurable
  2. Use netcat nc to mock the server response

Basic example of using netcat to mock a server response:

echo -e "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"token\":\"some-token\"}" | nc -l 8080 &
curl http://localhost:8080

Another idea! In the test suite, you could add a curl() function that gets called instead of real curl.

curl() {
  // test and check passed in args
}

export -f curl

There 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
}
2 changes: 2 additions & 0 deletions assets/in
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ if [ -z "$uri" ]; then
exit 1
fi

setup_github_app_credentials

branchflag=""
if [ -n "$branch" ]; then
branchflag="--branch $branch"
Expand Down
2 changes: 2 additions & 0 deletions assets/out
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion assets/source_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new fields should be documented in the README please.

}
1 change: 1 addition & 0 deletions test/all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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'
186 changes: 186 additions & 0 deletions test/github_app.sh
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"
Copy link
Member

Choose a reason for hiding this comment

The 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:

git-resource/test/check.sh

Lines 101 to 103 in 6e7bbd3

local key=$TMPDIR/key-no-passphrase
ssh-keygen -f $key

These keys end up in ED25519 openssh format though, which I mentioned openssl won't be able to read.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use jq -n --arg key "$key" to have jq properly escape the key in the json payload.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also not testing the code in common.sh Why not move it out into a function so it can be tested?

You could export all of these vars and then call setup_github_app_credentials.


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually test the code that gets used in common.sh.

Why not put the code from common.sh into a function and then test the function?


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment from it_converts_ssh_uri_to_https()


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