Skip to content

Conversation

@nrayapati
Copy link
Member

@nrayapati nrayapati commented Aug 1, 2025

Description

Problem Statement

The current GitHub branch source plugin requires users to create separate GitHub App credentials for each organization where their GitHub App is installed, leading to credential duplication and maintenance overhead. While pipeline scripts can use credential bindings with standard username/password credentials, teams must constantly switch credential IDs based on the target organization, creating complex pipeline logic and maintenance burden.

What

Adds Multi-Organization GitHub App Credentials that work across multiple organizations where a GitHub App is installed, plus pipeline binding support.

Why

Eliminates the need to create separate GitHub App credentials for each organization, reducing maintenance overhead for teams working across multiple GitHub organizations.

How

  • MultiOrgGitHubAppCredentials: Automatically discovers organizations where the GitHub App is installed
  • MultiOrgGitHubAppCredentialsBinding: Pipeline binding with automatic and manual modes
  • Backward compatible with existing GitHub App credentials
  • Comprehensive test coverage and documentation included

See the included documentation for detailed usage examples, Configuration as Code setup, and migration guidance.

Related to #375 but takes a different approach to avoid breaking changes.

JENKINS-64662 for further information.

Submitter checklist

  • Link to JIRA ticket in description, if appropriate.
  • Change is code complete and matches issue description
  • Automated tests have been added to exercise the changes
  • Reviewer's manual test instructions provided in PR description. See Reviewer's first task below.

Reviewer checklist

  • Run the changes and verify that the change matches the issue description
  • Reviewed the code
  • Verified that the appropriate tests have been written or valid explanation given

Documentation changes

  • Link to jenkins.io PR, or an explanation for why no doc changes are needed

Users/aliases to notify

@nrayapati nrayapati requested a review from a team as a code owner August 1, 2025 16:43
}

@POST
public FormValidation doTestMultiOrgConnection(

Check warning

Code scanning / Jenkins Security Scan

Stapler: Missing permission check Warning

Potential missing permission check in DescriptorImpl#doTestMultiOrgConnection
* The variable name for the GitHub token (optional)
*/
@CheckForNull
private final String tokenVariable;

Check warning

Code scanning / Jenkins Security Scan

Jenkins: Plaintext password storage Warning

Field should be reviewed whether it stores a password and is serialized to disk: tokenVariable
* @return form validation result
*/
@POST
public FormValidation doCheckTokenVariable(@QueryParameter String value, @QueryParameter String orgName) {

Check warning

Code scanning / Jenkins Security Scan

Stapler: Missing permission check Warning

Potential missing permission check in DescriptorImpl#doCheckTokenVariable
* @return form validation result
*/
@POST
public FormValidation doCheckOrgName(@QueryParameter String value, @QueryParameter String tokenVariable) {

Check warning

Code scanning / Jenkins Security Scan

Stapler: Missing permission check Warning

Potential missing permission check in DescriptorImpl#doCheckOrgName
@jglick
Copy link
Member

jglick commented Aug 1, 2025

It remains unclear to me what concrete problem this is solving. The existing GitHub App credentials type can already be used across organizations. Perhaps there is some specific use case which is not implemented yet; is this just about withCredentials binding? From a job which is associated (via multibranch with github-branch-source) with a specific organization, or a standalone pipeline?

@nrayapati
Copy link
Member Author

It remains unclear to me what concrete problem this is solving. The existing GitHub App credentials type can already be used across organizations. Perhaps there is some specific use case which is not implemented yet; is this just about withCredentials binding? From a job which is associated (via multibranch with github-branch-source) with a specific organization, or a standalone pipeline?

The existing Jenkins GitHub App credentials have a 1:1 relationship between credential and organization, forcing users to create separate credential entries for each organization where their GitHub App is installed. This creates credential explosion, manual configuration overhead, and makes it impossible to dynamically discover new organizations.

The MultiOrgGitHubAppCredentials class solves this by providing a single credential that automatically discovers all organizations where the GitHub App is installed via the GitHub API, caches organization-specific tokens efficiently, and generates tokens for each organization.

The companion MultiOrgGitHubAppCredentialsBinding enables this in Jenkins pipelines through a withCredentials binding that can either automatically provide tokens for all discovered organizations (e.g., GITHUB_TOKEN_ORG1, GITHUB_TOKEN_ORG2) or target a specific organization with a custom variable name. This is particularly critical for enterprise environments where pipelines need to checkout code across multiple organizations, commit automated updates back to repositories in different orgs, and handle GitHub submodules that span across organizational boundaries - all common scenarios in GitHub setups with multiple organizations where managing separate credentials for each org would be operationally complex and error-prone, making this multi-org binding essential for seamless cross-organizational repository operations and automated workflows.

@nrayapati
Copy link
Member Author

It remains unclear to me what concrete problem this is solving. The existing GitHub App credentials type can already be used across organizations. Perhaps there is some specific use case which is not implemented yet; is this just about withCredentials binding? From a job which is associated (via multibranch with github-branch-source) with a specific organization, or a standalone pipeline?

Example: Currently, you cannot use the same GitHub App credentials to configure multiple organization folders, multibranch pipelines, or regular pipeline jobs across different organizations - even when the credentials are configured with an empty owner field, Jenkins still falls back to using the first GitHub App installation it discovers, effectively limiting the credential to that single organization. This forces administrators to create duplicate credential entries for each organization (e.g., "github-app-frontend-org", "github-app-backend-org", "github-app-devops-org") just to use the same underlying GitHub App across different Jenkins job contexts. With MultiOrgGitHubAppCredentials, a single credential can be dynamically scoped to the appropriate organization based on the job context, eliminating this duplication and enabling true multi-organizational GitHub App usage within Jenkins job configurations.

@jglick
Copy link
Member

jglick commented Aug 1, 2025

even when the credentials are configured with an empty owner field, Jenkins still falls back to using the first GitHub App installation it discovers

Can you clarify? Jenkins will select the appropriate installation for the organization it detects from the context. For example, you can create multiple organization folders targeted distinct organizations, all using a single App credentials item with a blank owned field, and everything should just work automatically. If there is some situation where that logic is broken, it would be treated as a bugfix.

@nrayapati
Copy link
Member Author

nrayapati commented Aug 1, 2025

even when the credentials are configured with an empty owner field, Jenkins still falls back to using the first GitHub App installation it discovers

Can you clarify? Jenkins will select the appropriate installation for the organization it detects from the context. For example, you can create multiple organization folders targeted distinct organizations, all using a single App credentials item with a blank owned field, and everything should just work automatically. If there is some situation where that logic is broken, it would be treated as a bugfix.

I'll double-check, but we're likely on a old version, which may be causing some issues. The same app credentials aren't working across orgs, and we also don't have a binding that provides an org-specific token for use in the pipeline.

Instead of modifying the existing app, we created multi-org credentials and have been using them for a while to avoid conflicts while rebasing from remote mainly — thought it might be useful to contribute back.

A while ago, I created PR #375, which aimed to add a binding to support owner-specific tokens. The default binding only provides a token scoped to a single org, but as described above, we're checking out repos across multiple orgs in the pipeline—including submodules in the same repo. So having org-specific tokens is essential, which is why the custom binding was needed.

@jglick
Copy link
Member

jglick commented Aug 1, 2025

a binding to support owner-specific tokens

I do not see any reason why you could not create a new custom binding type for withCredentials without a new credentials implementation, e.g.

withCredentials([gitHubAppForOrg(credentialsId: 'the-existing-App-credentials', owner: 'myorg23')]) {
  sh '''
    echo "$GH_TOKEN should be masked here"
    gh repo clone myorg23/somerepo
    gh auth setup-git
    cd somerepo
    git fetch --all --tags
  '''
}

@nrayapati
Copy link
Member Author

nrayapati commented Aug 1, 2025

a binding to support owner-specific tokens

I do not see any reason why you could not create a new custom binding type for withCredentials without a new credentials implementation, e.g.

withCredentials([gitHubAppForOrg(credentialsId: 'the-existing-App-credentials', owner: 'myorg23')]) {
  sh '''
    echo "$GH_TOKEN should be masked here"
    gh repo clone myorg23/somerepo
    gh auth setup-git
    cd somerepo
    git fetch --all --tags
  '''
}

You're absolutely right that a custom binding like gitHubAppForOrg(credentialsId: 'existing-app', owner: 'myorg23') would be an elegant solution for single-organization targeting!

We actually started with that approach originally, but ran into a couple of enterprise use cases that led us to create MultiOrgGitHubAppCredentials. When we tried modifying the existing implementation, it required significant architectural changes that would make maintaining our fork much harder - especially for rebasing and pulling upstream bug fixes or Jenkins version updates.

The core limitation we hit is that the existing GitHubAppCredentials can only provide a token for one organization at a time. Even with the withOwner() method, each credential instance is locked to a single organization, and getPassword() returns only one token. This makes it impossible to access multiple organizations simultaneously within a single pipeline execution.

Our main use cases that drove this:

  • Cross-org submodules - repositories with submodules from different organizations
// Currently impossible - git needs different tokens per org for submodules
withCredentials([multiOrgGitHubApp(credentialsId: 'app')]) {
    sh '''
        git config url."https://[email protected]/org1/".insteadOf "https://github.com/org1/"
        git config url."https://[email protected]/org2/".insteadOf "https://github.com/org2/"
        git clone --recursive https://github.com/main-org/project-with-cross-org-submodules.git
    '''
}
  • Enterprise monorepo deployments - code spread across multiple GitHub organizations
// Deploy from repos across multiple orgs in one pipeline
withCredentials([multiOrgGitHubApp(credentialsId: 'app')]) {
    parallel {
        'frontend': { sh 'git clone https://[email protected]/frontend-org/app.git' },
        'backend': { sh 'git clone https://[email protected]/backend-org/api.git' },
        'infra': { sh 'git clone https://[email protected]/devops-org/terraform.git' }
    }
}
  • We also use GitHub App credentials beyond standard Jenkins checkout operations. Some Jenkins pipelines monitor GitHub App installations across organizations and automatically apply settings to those orgs/repos based on installation status. We also have automation pipelines that report unauthorized app uninstalls/installs, track which orgs have specific apps installed, and collect GitHub metrics using gh commands.

Beyond concurrent access, MultiOrgGitHubAppCredentials provides:

  • Automatic organization discovery - no manual maintenance when apps are installed to new orgs.
  • Token caching with TTL and memory management.
  • Rate limiting protection to prevent API abuse.

Both Approaches Have Value:

Your suggested gitHubAppForOrg binding would be perfect for simple, single-org scenarios. MultiOrgGitHubAppCredentials enables complex enterprise workflows. I believe we could absolutely support both! :) Thank you for your help reviewing and maintaining this awesome plugin.

@jglick
Copy link
Member

jglick commented Aug 7, 2025

the existing GitHubAppCredentials can only provide a token for one organization at a time. Even with the withOwner() method, each credential instance is locked to a single organization, and getPassword() returns only one token. This makes it impossible to access multiple organizations simultaneously

I am not following.

return byOwner.computeIfAbsent(owner, k -> {
GitHubAppCredentials clone =
new GitHubAppCredentials(getScope(), getId(), getDescription(), getAppID(), getPrivateKey());
clone.apiUri = getApiUri();
clone.owner = owner;
return clone;
});
creates distinct instances.

At any rate, it feels like it would be better if complex features like this could live in their own plugins. It seems like that should be possible with a little API refinement in Connector to refactor the instanceof GitHubAppCredentials into an appropriate interface, just as jenkinsci/credentials-plugin#293 allowed an owner to be inferred for the basic cases.

Note that use of GitHubAppCredentials as a UsernamePasswordCredentials for use in a stock credentials binding (withCredentials) is a convenience but not really necessary: you can also just keep a private key as generic file credentials and then follow the GH docs to authenticate to an App however you like from your own scripts (Python, etc.) without any specific functionality from Jenkins.

No strong opinion here; perhaps someone maintaining this plugin (I do not really maintain it much, though I note incoming PRs) wants to review, test, and commit to maintaining this implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants