Skip to content

Support GitHub App installation tokens for the GitHub forge#6806

Open
slayerjain wants to merge 5 commits into
woodpecker-ci:mainfrom
slayerjain:github-app-support
Open

Support GitHub App installation tokens for the GitHub forge#6806
slayerjain wants to merge 5 commits into
woodpecker-ci:mainfrom
slayerjain:github-app-support

Conversation

@slayerjain

Copy link
Copy Markdown

This adds first-class GitHub App support to the GitHub forge: when WOODPECKER_GITHUB_APP_ID and WOODPECKER_GITHUB_APP_PRIVATE_KEY(_FILE) are configured, Woodpecker authenticates repo-scoped server-side operations and clone credentials with installation access tokens instead of the repo owner's personal OAuth token. User login stays on the OAuth flow, and repositories the app is not installed on transparently fall back to the current user-token behavior.

Why

Today every server-side API call (commit statuses, pipeline config fetching, changed-file lookups) and every clone runs on the OAuth token of the user who enabled the repository. For larger organizations this has three structural problems:

Installation tokens get per-installation rate limits that scale with repository count, survive user departure, and post statuses as the app's bot account. This also allows removing the long-stale docs warning against GitHub Apps (added in #2499, obsolete since #3811 introduced refresh-token support).

What changed

  • server/forge/github/app.go: authenticates as the app (RS256 JWT via the already-vendored golang-jwt/jwt/v5; client id or app id as issuer, backdated iat per GitHub's guidance) and mints installation access tokens through the existing go-github dependency — no new module needed. Installation lookups and tokens are cached with validity margins (5 min for API calls, 45 min for clone credentials); "not installed" results are negative-cached and uninstall/suspension invalidates the cache and falls back gracefully.
  • Token selection wired into Netrc (x-access-token), Status, File/Dir, Branches, BranchHead, PullRequests and the webhook changed-files/tag loaders. The loaders skip the repo-owner DB lookup entirely when the app covers the repo. Tokens are installation-wide on purpose so same-org submodule clones keep working.
  • Config plumbing: new env vars → AdditionalOptions["app-id"/"app-private-key"] (same channel as the bitbucket-dc machine account) → github.Opts. No DB migration needed. Private keys are accepted as PEM or base64-encoded PEM, including keys whose newlines were mangled by env files or input fields.
  • The private key is write-only in the API/UI (like oauth_client_secret): responses replace it with an app-private-key-set marker, and updates that omit it keep the stored key. Clearing the app id disables app auth again.
  • Validation: invalid app credentials are rejected at save time in the forge API (HTTP 400) and fail fast at boot for env-managed forges, instead of breaking every forge operation at runtime.
  • New admin endpoint GET /forges/{id}/app-health plus a "Test GitHub App" button in the admin forge form so operators can verify the credentials actually work.
  • Admin UI fields (app id + multi-line private key), docs rewrite of the GitHub App section (required permissions, webhook note, token-expiry caveat), fixture-based unit tests for the token flow (JWT signature verification, caching, fallback, PEM variants) and API tests for redaction/merge/health.

Notes

  • Webhook handling is unchanged: Woodpecker keeps creating per-repo webhooks, the app's own webhook stays disabled. Org-level auto-enrollment via app installation events (Handle Github organizational webhooks to autoenable new repos #71) would be a natural follow-up but is out of scope here.
  • A token cached before an app is suspended/uninstalled can be served until its validity margin passes (bounded by the 10-minute forge cache); this matches the behavior of ghinstallation-based tools (Atlantis, ArgoCD).

When WOODPECKER_GITHUB_APP_ID and WOODPECKER_GITHUB_APP_PRIVATE_KEY(_FILE)
are configured, the GitHub forge authenticates repo-scoped server-side
operations (commit statuses, pipeline config fetching, changed-file lookups,
branch/PR queries) and clone credentials with GitHub App installation access
tokens instead of the repo owner's OAuth token, falling back to user OAuth
tokens for repositories the app is not installed on.

This raises API rate limits for large organizations (per-installation
limits that scale with repository count instead of a single user's
5,000 req/h), keeps pipelines working when the enabling user loses access,
and posts commit statuses under the app's bot identity.

Includes: app JWT signing (client id or app id as issuer), cached
installation lookups and tokens with validity margins, write-only private
key handling in the forge API/UI, save-time and boot-time validation of
app credentials, an admin app-health check endpoint with a UI test button,
admin form fields, docs, and tests.

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
@6543 6543 added forge/github github forge related feature add new functionality labels Jul 2, 2026
@woodpecker-bot

woodpecker-bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Surge PR preview deployment succeeded. View it at https://woodpecker-ci-woodpecker-pr-6806.surge.sh

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.35443% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.31%. Comparing base (adbc365) to head (3bed999).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
server/forge/github/app.go 92.68% 6 Missing and 3 partials ⚠️
server/forge/github/github.go 91.66% 5 Missing and 2 partials ⚠️
server/api/forge.go 94.93% 3 Missing and 1 partial ⚠️
server/router/api.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6806      +/-   ##
==========================================
+ Coverage   47.30%   48.31%   +1.00%     
==========================================
  Files         440      441       +1     
  Lines       29726    30007     +281     
==========================================
+ Hits        14062    14498     +436     
+ Misses      14496    14313     -183     
- Partials     1168     1196      +28     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds tests for the forge API handlers (redaction, secret merge, save-time
validation, app health), the installation token error and cache-invalidation
branches, PEM repair edge cases, repo-scoped forge methods using installation
tokens, RedactSecrets, setupGitHub option coercion, and the env boot path
validation. Also removes an unreachable nil-map guard in
restoreSecretOptions.

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
@slayerjain

Copy link
Copy Markdown
Author

This seems to be working fine in Keploy on-prem CI. Please let me know if any changes needed before merge.


## GitHub App

In addition to the OAuth login, Woodpecker can authenticate as a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) using installation access tokens for server-side API calls (commit statuses, pipeline configuration fetching, changed-file lookups) and for cloning repositories.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

... for cloning repos

this could have security implications and need to be looked at

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Makes sense. I have narrowed the minted token to just the job's repo and with read-only content permissions.

@slayerjain slayerjain Jul 7, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Please resolve this if you think it's fine.

Comment thread docs/docs/30-administration/10-configuration/12-forges/20-github.md Outdated
Comment thread server/api/forge.go Outdated
Comment thread server/api/forge.go Outdated
Comment thread server/api/forge.go Outdated
Comment thread server/forge/setup/setup.go Outdated
Comment thread server/router/api.go Outdated
- scope clone credentials to the repository being built with read-only
  contents access by default; WOODPECKER_GITHUB_APP_CLONE_TOKEN_SCOPE
  (or the admin UI) can widen them to the whole app installation for
  private git submodules and similar cross-repository clones
- centralize the additional-option keys as constants and make the
  secret option redaction and restore logic generic per forge type
- validate all forge types (except addons) when saving via the API and
  reject non-string github app options instead of coercing them
- relax the bitbucket datacenter admin-scope option to an optional
  boolean, it is not exposed by the admin UI
- group the forge id routes in the router
- document the clone token scope and reference woodpecker-ci#2851 for the queued
  token expiry limitation

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature add new functionality forge/github github forge related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants