A cross-platform no-dependency GIT_ASKPASS trampoline for GitHub Apps, written in Go.
This is inspired by https://github.com/desktop/askpass-trampoline project.
If you already know what GitHub Apps is - you probably also know common challenges to work with them, namely - it's a bit of work to convert app credentials into a username+token pair that could be used by a regular Git clients such as Git CLI and various Git SDKs.
If you also know about Git AskPass Credentials Helpers - your already know that there is actually an easy solution to these challenges. You can do something like git config --global credential.helper "/usr/bin/my-helper" and that way you can easily delegate to external program the process to generate JWT and request a temporary token with it. With a slight problem - you have to create that external program.
Yeah, that was a bit of a bummer. Almighty GitHub did not provide us with and "official" one, but oh well - that is not a rocket science and we can do it ourselves. This project is exactly that - an open source community-maintained configurable helper that can use your SSH Private Key to generate JWT and then request a temporary token with it. You don't have to create it - just configure it and make your Git client to use it.
It can also be used as a standalone CLI app so it can be embedded into workflows as a little helper to request temp credentials.
If you happen to write your workflows in Go - you could also use packages from this repository as dependencies to build your own.
TBD
TBD
# To use as a helper, configure your Git Client as follows
git config --global credential.useHttpPath true
git config --global credential.helper "/path/to/github-apps-trampoline -c /path/to/config.json"
# Configurable via JSON as AskPass Helper
# Using full JSON config will always supersede any CLI arguments
cat << EOF > config.json
{
"github\\.com/foo/bar": {
"key": "private.key",
"app": 1,
"permissions": {"contents": "write"},
"current_repo": true
},
"github\\.com/foo/.*": {
"key": "private.key",
"app": 1,
"permissions": {"contents": "read"}
},
".*": {
"key": "private.key",
"app": 1,
"installation_id": "<numeric ID of the installation - if no provided will automatically infer from the current repo>",
"installation": "<alternatively - installation path such as github.com/foo>",
"repository_ids": "<optional XXX,YYY>",
"repositories": "<optional foo,bar>",
"permissions": {"contents": "read"}
}
}
EOF
github-apps-trampoline -c config.json
GITHUB_APPS_TRAMPOLINE="$(cat config.json)" github-apps-trampoline
GITHUB_APPS_TRAMPOLINE_CONFIG="config.json" github-apps-trampoline
# Somewhat configurable via CLI as AskPass Helper
# This will generate config.json in-memory with a single key
# Some of these examples are not secure to use:
# missing --permissions will assume all permissions from the app scope
# missing --repository-ids, --repositories and --current-repo will assume access to all repositories in the current installation
github-apps-trampoline --key private.key --app 1 --filter 'github\.com/foo/bar' --current-repo=true --permissions '{"contents": "write"}'
github-apps-trampoline --key private.key --app 1 --filter 'github\.com/foo/.*' --permissions '{"contents": "read"}'
github-apps-trampoline --key private.key --app 1 # using no --filter is the same as using --filter '.*'
github-apps-trampoline --key private.key --app 1 --permissions '{"contents": "read"}'
github-apps-trampoline --key private.key --app 1 --current-owner=true
github-apps-trampoline --key private.key --app 1 --repository-ids 'XXX,YYY'
github-apps-trampoline --key private.key --app 1 --repositories 'bar,baz'
github-apps-trampoline --key private.key --app 1 --installation-id 'XXX'
github-apps-trampoline --key private.key --app 1 --installation 'github.com/foo'
# As a standalone CLI
# It will spit out JSON in STDOUT
# It will not read STDIN and will not use config.json - it will only use the CLI input
github-apps-trampoline --cli --key private.key --app 1 --installation 'github.com/foo' --repositories 'bar,baz' --permissions '{"contents": "read"}'Enabling verbose mode will print credentials in STDERR - use with caution.
You can route logs to a file and optionally tee to stderr:
github-apps-trampoline --log-file /tmp/trampoline.log --log-tee-stderr --verboseEnvironment variables (prefixed with GITHUB_APPS_TRAMPOLINE_) are supported:
export GITHUB_APPS_TRAMPOLINE_LOG_FILE=/tmp/trampoline.log
export GITHUB_APPS_TRAMPOLINE_LOG_TEE_STDERR=true
export GITHUB_APPS_TRAMPOLINE_TOKEN_FINGERPRINT=trueWhen token-fingerprint is enabled, the helper logs a correlation line with a timestamp, repo path, and a short token fingerprint prefix to aid troubleshooting without printing full secrets.
Caching is disabled by default and must be explicitly enabled.
github-apps-trampoline --cache --cache-dir /tmp/trampoline-cacheCache TTLs and locking can be tuned:
github-apps-trampoline \
--cache \
--cache-ttl-installations 5m \
--cache-ttl-installation-map 5m \
--cache-ttl-token 10m \
--cache-lock-timeout 30s \
--cache-lock-poll 200msEnvironment variables (prefixed with GITHUB_APPS_TRAMPOLINE_) are supported:
export GITHUB_APPS_TRAMPOLINE_CACHE=true
export GITHUB_APPS_TRAMPOLINE_CACHE_DIR=/tmp/trampoline-cache
export GITHUB_APPS_TRAMPOLINE_CACHE_TTL_INSTALLATIONS=5m
export GITHUB_APPS_TRAMPOLINE_CACHE_TTL_INSTALLATION_MAP=5m
export GITHUB_APPS_TRAMPOLINE_CACHE_TTL_TOKEN=10m
export GITHUB_APPS_TRAMPOLINE_CACHE_LOCK_TIMEOUT=30s
export GITHUB_APPS_TRAMPOLINE_CACHE_LOCK_POLL=200msCache logging emits hit/miss/refresh/lock events. File logs may include sensitive data; stderr logs are sanitized by the caller.
To request installation-wide tokens (all repositories in the owner installation), use current-owner. This conflicts with current-repo.
github-apps-trampoline --current-ownerEnvironment variable:
export GITHUB_APPS_TRAMPOLINE_CURRENT_OWNER=trueJSON config:
{
"github\\.com/foo/.*": {
"key": "private.key",
"app": 1,
"current_owner": true,
"permissions": {"contents": "read"}
}
}