Skip to content

Commit b42dd6b

Browse files
authored
Merge pull request #269 from timja/github-app-support
Add support for GitHub app authentication
2 parents 51a0e7f + d33000a commit b42dd6b

File tree

18 files changed

+747
-9
lines changed

18 files changed

+747
-9
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ The GitHub Branch Source plugin allows you to create a new project based on the
1010
GitHub users or organizations. Complete documentation is
1111
[hosted by CloudBees](https://docs.cloudbees.com/docs/admin-resources/latest/plugins/github-branch-source).
1212

13+
### Guides
14+
15+
* [GitHub App authentication](docs/github-app.adoc)
16+
* [Extension points provided by this plugin](docs/implementation.adoc)
17+
18+
## Extension plugins
19+
20+
* [github-scm-trait-notification-context](https://github.com/jenkinsci/github-scm-trait-notification-context-plugin) -
21+
allows overriding the `continuous-integration/jenkins/<context>` commit status name.
22+
1323
## Version History
1424

1525
See [the changelog](CHANGELOG.md).

docs/github-app.adoc

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
= GitHub app authentication guide
2+
3+
This guide is targeted to users who want to use a link:https://developer.github.com/v3/apps/[GitHub app]
4+
to authenticate to Jenkins.
5+
6+
== Why?
7+
8+
- the link:https://developer.github.com/apps/building-github-apps/understanding-rate-limits-for-github-apps/[rate limit]
9+
for a GitHub app scales with your organization size, whereas a user based token has a limit of 5000 regardless of
10+
how many repositories you have,
11+
- for organization's that have 2fa enforced - no need to manage 2fa tokens for a 'bot' user
12+
- to improve and tighten security: the Jenkins GitHub app requires a minimum, controlled set of privileges compared to a service user and its personal access token which has a much wider set of privileges
13+
14+
== Getting started
15+
16+
Before you get started make sure you have the required permissions:
17+
18+
=== GitHub
19+
20+
You'll need the permission to create a GitHub app, if you're creating it on a personal account then you can skip this section,
21+
otherwise:
22+
23+
- organization owner
24+
25+
or
26+
27+
- permission to manage GitHub apps has been
28+
link:https://help.github.com/en/github/setting-up-and-managing-organizations-and-teams/adding-github-app-managers-in-your-organization[delegated to you].
29+
30+
=== Jenkins
31+
32+
You'll need the permission to create a new credential and update job configuration, the specific permissions are:
33+
34+
- Credentials/Create
35+
- Job/Configure
36+
37+
== Creating the GitHub app
38+
39+
link:https://developer.github.com/apps/building-github-apps/creating-a-github-app/[Follow the GitHub guide for creating an app]
40+
41+
The only fields you need to fill out (currently) are:
42+
43+
- Github App name - i.e. `Jenkins - <team name>`
44+
- Homepage URL - your company domain or a github repository
45+
- Webhook URL - your jenkins instance, i.e. `https://<jenkins-host>/github-webhook/`
46+
47+
Permissions this plugin uses:
48+
49+
- Commit statuses - Read and Write
50+
- Contents: Read-only (to read the `Jenkinsfile` and the repository content during `git fetch`). You may need "Read & write" to update the repository such as tagging releases
51+
- Metadata: Read-only
52+
- Pull requests: Read-only
53+
- Webhooks (optional) - If you want the plugin to manage webhooks for you, Read and Write
54+
55+
56+
Click 'Create GitHub app'
57+
58+
You now need to generate a private key authenticating to the GitHub app
59+
60+
Click the 'generate a private key' option.
61+
62+
After a couple of seconds the key will be downloaded to your downloads folder.
63+
64+
Now you need to convert the key into a different format that Jenkins can use:
65+
66+
[source,shell]
67+
----
68+
openssl pkcs8 -topk8 -inform PEM -outform PEM -in key-in-your-downloads-folder.pem -out converted-github-app.pem -nocrypt
69+
----
70+
71+
== Install the GitHub app
72+
73+
- From the install app section of newly created app, install the app to your organization.
74+
75+
== Adding the Jenkins credential
76+
77+
=== UI
78+
79+
- From the Jenkins main page click 'Credentials'
80+
- Pick your credential store, normally `(global)`
81+
- Click 'Add credentials'
82+
83+
Fill out the form:
84+
85+
- Kind: GitHub app
86+
- ID: i.e. github-app-<team-name>
87+
- App ID: the github app ID, it can be found in the 'About' section of your GitHub app in the general tab.
88+
- API endpoint (optional, only required for GitHub enterprise this will only show up if a GitHub enterprise server is configured).
89+
- Key: click add, paste the contents of the converted private key
90+
- Passphrase: do not fill this field, it will be ignored
91+
- Click OK
92+
93+
=== link:https://github.com/jenkinsci/configuration-as-code-plugin[Configuration as Code Plugin]
94+
95+
[source,yaml]
96+
----
97+
credentials:
98+
system:
99+
domainCredentials:
100+
- credentials:
101+
- gitHubApp:
102+
appID: "1111"
103+
description: "GitHub app"
104+
id: "github-app"
105+
# apiUri: https://my-custom-github-enterprise.com/api/v3 # optional only required for GitHub enterprise
106+
privateKey: "${GITHUB_APP_KEY}"
107+
----
108+
109+
== Configuring the github organization folder
110+
111+
See the link:https://docs.cloudbees.com/docs/admin-resources/latest/plugins/github-branch-source[main documentation]
112+
for how to create a GitHub folder.
113+
114+
- Load the folders configuration page
115+
- Select the GitHub app credentials in the 'Credentials field drop down
116+
- If you are using GitHub enterprise make sure the API url is set to your server,
117+
(note you currently need to set the API url on both the credential and the job).
118+
119+
After selecting the credential you should see:
120+
121+
[quote]
122+
----
123+
GHApp verified, remaining rate limit: 5000
124+
----
125+
126+
- Click save
127+
- Click 'Scan organization now'
128+
- Click 'Scan organisation log'
129+
130+
Verify at the bottom of the scan log it says:
131+
132+
[quote]
133+
----
134+
Finished: SUCCESS
135+
----
136+
137+
=== Help?
138+
139+
Raise an issue on link:https://issues.jenkins-ci.org/[Jenkins jira]
140+
setting the 'component' to be `github-brance-source-plugin`

docs/implementation.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ explicitly apply a `DefaultGitHubNotificationStrategy` to the source context in
4545
Duplicate (by equality) strategies are ignored when applied to the source context.
4646

4747
==== Implementations:
48-
https://github.com/steven-foster/github-scm-trait-notification-context[github-scm-trait-notification-context]
48+
https://github.com/jenkinsci/github-scm-trait-notification-context-plugin[github-scm-trait-notification-context]
4949

5050

pom.xml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<hamcrest.version>2.2</hamcrest.version>
3131
<useBeta>true</useBeta>
3232
<jcasc.version>1.35</jcasc.version>
33+
<jjwt.version>0.10.5</jjwt.version>
3334
</properties>
3435

3536
<scm>
@@ -67,7 +68,13 @@
6768
<dependency>
6869
<groupId>org.jenkins-ci.plugins</groupId>
6970
<artifactId>credentials</artifactId>
70-
<version>2.1.18</version>
71+
<version>2.2.0</version>
72+
</dependency>
73+
<!-- TODO: after upgrading jenkins.version >= 2.171, migrate dependency -->
74+
<dependency>
75+
<groupId>io.jenkins.temp.jelly</groupId>
76+
<artifactId>multiline-secrets-ui</artifactId>
77+
<version>1.0</version>
7178
</dependency>
7279
<dependency>
7380
<groupId>com.coravy.hudson.plugins.github</groupId>
@@ -79,6 +86,25 @@
7986
<artifactId>display-url-api</artifactId>
8087
<version>2.0</version>
8188
</dependency>
89+
90+
<dependency>
91+
<groupId>io.jsonwebtoken</groupId>
92+
<artifactId>jjwt-api</artifactId>
93+
<version>${jjwt.version}</version>
94+
</dependency>
95+
<dependency>
96+
<groupId>io.jsonwebtoken</groupId>
97+
<artifactId>jjwt-impl</artifactId>
98+
<version>${jjwt.version}</version>
99+
<scope>runtime</scope>
100+
</dependency>
101+
<dependency>
102+
<groupId>io.jsonwebtoken</groupId>
103+
<artifactId>jjwt-jackson</artifactId>
104+
<version>${jjwt.version}</version>
105+
<scope>runtime</scope>
106+
</dependency>
107+
82108
<!-- Currently just here for interactive testing via hpi:run: -->
83109
<dependency>
84110
<groupId>org.jenkins-ci.plugins</groupId>

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,8 @@
7676
import org.apache.commons.lang.StringUtils;
7777
import org.jenkinsci.plugins.gitclient.GitClient;
7878
import org.jenkinsci.plugins.github.config.GitHubServerConfig;
79-
import org.kohsuke.accmod.Restricted;
80-
import org.kohsuke.accmod.restrictions.NoExternalUse;
8179
import org.kohsuke.github.GitHub;
8280
import org.kohsuke.github.GitHubBuilder;
83-
import org.kohsuke.github.HttpConnector;
8481
import org.kohsuke.github.RateLimitHandler;
8582
import org.kohsuke.github.extras.OkHttpConnector;
8683

@@ -198,13 +195,23 @@ public static FormValidation checkScanCredentials(@CheckForNull Item context, St
198195
GitHub connector = Connector.connect(apiUri, credentials);
199196
try {
200197
try {
198+
boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials;
199+
if (githubAppAuthentication) {
200+
int remaining = connector.getRateLimit().getRemaining();
201+
return FormValidation.ok("GHApp verified, remaining rate limit: %d", remaining);
202+
}
203+
201204
return FormValidation.ok("User %s", connector.getMyself().getLogin());
202-
} catch (IOException e){
203-
return FormValidation.error("Invalid credentials");
205+
} catch (Exception e) {
206+
return FormValidation.error("Invalid credentials: %s", e.getMessage());
204207
}
205208
} finally {
206209
Connector.release(connector);
207210
}
211+
} catch (IllegalArgumentException | InvalidPrivateKeyException e) {
212+
String msg = "Exception validating credentials " + CredentialsNameProvider.name(credentials);
213+
LOGGER.log(Level.WARNING, msg, e);
214+
return FormValidation.error(e, msg);
208215
} catch (IOException e) {
209216
// ignore, never thrown
210217
LOGGER.log(Level.WARNING, "Exception validating credentials {0} on {1}", new Object[]{
@@ -512,7 +519,7 @@ static boolean isCredentialValid(GitHub gitHub) {
512519
return true;
513520
} else {
514521
try {
515-
gitHub.getMyself();
522+
gitHub.getRateLimit();
516523
return true;
517524
} catch (IOException e) {
518525
if (LOGGER.isLoggable(FINE)) {

0 commit comments

Comments
 (0)