Skip to content

Commit 4a9883d

Browse files
authored
Merge pull request #822 from dwnusbaum/dynamic-installation-access-token-restrictions
Add new options to GitHub App credentials to allow dynamic restrictions of the repositories and permissions available to installation access tokens in some contexts
2 parents 68f4865 + b8aefa2 commit 4a9883d

39 files changed

+1646
-125
lines changed

docs/github-app.adoc

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ Fill out the form:
8787
- App ID: the github app ID, it can be found in the 'About' section of your GitHub app in the general tab.
8888
- API endpoint (optional, only required for GitHub enterprise this will only show up if a GitHub enterprise server is configured).
8989
- Key: click add, paste the contents of the converted private key
90-
- Advanced: (optional) If you've installed your same GitHub app on multiple organizations you need the next step
91-
* Owner: the name of the organisation or user, i.e. jenkinsci for https://github.com/jenkinsci
90+
- Advanced: (optional):
91+
* Repository access strategy: Controls what GitHub repositories will be accessible to these credentials in untrusted contexts (see below for details)
92+
* Default permissions strategy: Controls what GitHub permissions will be accessible to these credentials in untrusted contexts (see below for details)
9293
- Click OK
9394

9495
=== link:https://github.com/jenkinsci/configuration-as-code-plugin[Configuration as Code Plugin]
@@ -135,6 +136,42 @@ Verify at the bottom of the scan log it says:
135136
Finished: SUCCESS
136137
----
137138

139+
=== Enhancing security using repository access strategies and default permissions strategies
140+
141+
GitHub App Credentials offer advanced configuration options that can provide additional security in some scenarios.
142+
In particular, when GitHub App Credentials are used by an Organization Folder or Multibranch Pipeline, these strategies may dynamically restrict the accessible repositories and permissions available to the credentials when they are accessed in untrusted contexts, such as when they are accessed by a `withCredentials` step in one of the individual Pipeline jobs.
143+
See link:https://www.jenkins.io/doc/book/security/securing-org-folders-and-multibranch-pipelines/[this documentation] for additional information on why it may be beneficial to limit credentials in this way.
144+
These strategies do not apply when using the credentials in trusted contexts, such as during organization folder scans and branch indexing.
145+
Note also that Jenkins users who have Job/Configure permission in a context where the credentials are available are considered trusted and can bypass these strategies by configuring jobs as desired.
146+
In trusted contexts, the generated access tokens will have the same access as configured for the app installation in GitHub.
147+
148+
The following repository access strategies are available:
149+
* **Infer owner and allow access to all owned repositories** (Default)
150+
* The credentials may only be used in contexts where a GitHub organization can be inferred, such as Organization Folders and Multibranch Pipelines
151+
* The access tokens generated in untrusted contexts will be able to access all repositories in the inferred GitHub organization that are accessible to the GitHub App installation.
152+
* **Infer accessible repository**
153+
* The credentials may only be used in contexts where a GitHub organization and repository can be inferred, such as Organization Folders and Multibranch Pipelines
154+
* The access tokens generated in untrusted contexts will only be able to access the inferred repository
155+
* **Specify accessible repositories**
156+
* The access tokens generated in untrusted contexts will be able to access the repositories specified statically in the credential configuration
157+
* If the GitHub app is installed in a single organization, the owner field may be left blank empty, in which case that organization will be accessed automatically
158+
* Leaving the repositories field empty will result in all repositories accessible to the configured owner being accessible
159+
160+
The following default permissions strategies are available:
161+
* Read-only access to repository contents
162+
* The access tokens generated in untrusted contexts will only be able to read the repository contents
163+
* Read and write access to repository contents
164+
* The access tokens generated in untrusted contexts will only be able to read and write the repository contents
165+
* All permissions available to the app installation (default)
166+
* The access tokens generated in untrusted contexts will have the same permissions as the app installation in GitHub
167+
168+
==== Repository access strategies and Pipeline libraries
169+
170+
Repository inference for GitHub App Credentials does not work when checking out Pipeline libraries.
171+
If you have a GitHub App Credential for an Organization Folder or Multibranch Pipeline whose individual Pipeline jobs access a Pipeline library, the contextually inferred repository for the library checkout will be the repository for the Pipeline job rather than the library.
172+
This means that the library will be inaccessible if you use an inference-based repository access strategy which only provides access to a single contextually-inferred repository, or if the Pipeline library is in a different GitHub organization than the repository being built.
173+
For now, in this case, you either need to use a less restrictive strategy for the GitHub App credential, such as "Infer owner and allow access to all owned repositories", or you can define a second credential specifically for the Pipeline library which uses "Specify accessible repositories" and only allows access to the repository for the Pipeline library.
174+
138175
=== Help?
139176

140177
Raise an issue on link:https://issues.jenkins-ci.org/[Jenkins jira]

src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public static FormValidation checkScanCredentials(
227227
} finally {
228228
Connector.release(connector);
229229
}
230-
} catch (IllegalArgumentException | InvalidPrivateKeyException e) {
230+
} catch (IllegalArgumentException | IllegalStateException | InvalidPrivateKeyException e) {
231231
String msg = "Exception validating credentials " + CredentialsNameProvider.name(credentials);
232232
LOGGER.log(Level.WARNING, msg, e);
233233
return FormValidation.error(e, msg);
@@ -261,8 +261,7 @@ public static StandardCredentials lookupScanCredentials(
261261
}
262262

263263
/**
264-
* Resolves the specified scan credentials in the specified context for use against the specified
265-
* API endpoint.
264+
* Retained for binary compatibility only.
266265
*
267266
* @param context the context.
268267
* @param apiUri the API endpoint.
@@ -281,6 +280,9 @@ public static StandardCredentials lookupScanCredentials(
281280
* Resolves the specified scan credentials in the specified context for use against the specified
282281
* API endpoint.
283282
*
283+
* <p>Callers of this method must not expose the credentials to unprivileged users for
284+
* uncontrolled usage.
285+
*
284286
* @param context the context.
285287
* @param apiUri the API endpoint.
286288
* @param scanCredentialsId the credentials to resolve.
@@ -306,9 +308,20 @@ public static StandardCredentials lookupScanCredentials(
306308
githubDomainRequirements(apiUri)),
307309
CredentialsMatchers.allOf(
308310
CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher()));
309-
310311
if (c instanceof GitHubAppCredentials && repoOwner != null) {
311-
c = ((GitHubAppCredentials) c).withOwner(repoOwner);
312+
// Note: We considered adding an overload so that all existing callers in this plugin could
313+
// specify an exact repository and granular permission, but decided against it. This method
314+
// should only be called in contexts where the credential could not be exposed to users
315+
// other than those who were able to create/configure whatever is using the credential in
316+
// the first place. Those users would be able to steal the GitHub App refresh JWT, which
317+
// they can then use to generate their own credentials, so dynamic limitations in this
318+
// context have no benefits, and would unnecessarily increase the size of the connection
319+
// cache because the cache keys are distinct for every context.
320+
final var usageContext = GitHubAppUsageContext.builder()
321+
.inferredOwner(repoOwner)
322+
.trust()
323+
.build();
324+
return ((GitHubAppCredentials) c).contextualize(usageContext);
312325
}
313326
return c;
314327
}
@@ -368,12 +381,15 @@ public static ListBoxModel listCheckoutCredentials(@CheckForNull Item context, S
368381
password = null;
369382
gitHubAppCredentials = (GitHubAppCredentials) credentials;
370383
hash = Util.getDigestOf(gitHubAppCredentials.getAppID()
371-
+ gitHubAppCredentials.getOwner()
384+
+ gitHubAppCredentials.getAccessibleRepositories()
385+
+ gitHubAppCredentials.getPermissions()
372386
+ gitHubAppCredentials.getPrivateKey().getPlainText()
373387
+ SALT); // want to ensure pooling by credential
374388
authHash = Util.getDigestOf(gitHubAppCredentials.getAppID()
375389
+ "::"
376-
+ gitHubAppCredentials.getOwner()
390+
+ gitHubAppCredentials.getAccessibleRepositories()
391+
+ "::"
392+
+ gitHubAppCredentials.getPermissions()
377393
+ "::"
378394
+ gitHubAppCredentials.getPrivateKey().getPlainText()
379395
+ "::"

0 commit comments

Comments
 (0)