Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GitHub app authentication #269

Merged
merged 19 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ The GitHub Branch Source plugin allows you to create a new project based on the
GitHub users or organizations. Complete documentation is
[hosted by CloudBees](https://docs.cloudbees.com/docs/admin-resources/latest/plugins/github-branch-source).

### Guides

* [GitHub App authentication](docs/github-app.adoc)
* [Extension points provided by this plugin](docs/implementation.adoc)

## Extension plugins
Copy link
Contributor

Choose a reason for hiding this comment

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

Good call here. 💯


* [github-scm-trait-notification-context](https://github.com/jenkinsci/github-scm-trait-notification-context-plugin) -
allows overriding the `continuous-integration/jenkins/<context>` commit status name.

## Version History

See [the changelog](CHANGELOG.md).
140 changes: 140 additions & 0 deletions docs/github-app.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
= GitHub app authentication guide

This guide is targeted to users who want to use a link:https://developer.github.com/v3/apps/[GitHub app]
to authenticate to Jenkins.

== Why?

- the link:https://developer.github.com/apps/building-github-apps/understanding-rate-limits-for-github-apps/[rate limit]
for a GitHub app scales with your organization size, whereas a user based token has a limit of 5000 regardless of
how many repositories you have,
- for organization's that have 2fa enforced - no need to manage 2fa tokens for a 'bot' user
timja marked this conversation as resolved.
Show resolved Hide resolved
- 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

== Getting started

Before you get started make sure you have the required permissions:

=== GitHub

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,
otherwise:

- organization owner

or

- permission to manage GitHub apps has been
link:https://help.github.com/en/github/setting-up-and-managing-organizations-and-teams/adding-github-app-managers-in-your-organization[delegated to you].

=== Jenkins

You'll need the permission to create a new credential and update job configuration, the specific permissions are:

- Credentials/Create
- Job/Configure

== Creating the GitHub app

link:https://developer.github.com/apps/building-github-apps/creating-a-github-app/[Follow the GitHub guide for creating an app]

The only fields you need to fill out (currently) are:

- Github App name - i.e. `Jenkins - <team name>`
- Homepage URL - your company domain or a github repository
- Webhook URL - your jenkins instance, i.e. `https://<jenkins-host>/github-webhook/`
timja marked this conversation as resolved.
Show resolved Hide resolved

Permissions this plugin uses:

- Commit statuses - Read and Write
- 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
- Metadata: Read-only
- Pull requests: Read-only
- Webhooks (optional) - If you want the plugin to manage webhooks for you, Read and Write


Click 'Create GitHub app'

You now need to generate a private key authenticating to the GitHub app

Click the 'generate a private key' option.

After a couple of seconds the key will be downloaded to your downloads folder.

Now you need to convert the key into a different format that Jenkins can use:

[source,shell]
----
openssl pkcs8 -topk8 -inform PEM -outform PEM -in key-in-your-downloads-folder.pem -out converted-github-app.pem -nocrypt
----

== Install the GitHub app

- From the install app section of newly created app, install the app to your organization.

== Adding the Jenkins credential

=== UI

- From the Jenkins main page click 'Credentials'
- Pick your credential store, normally `(global)`
- Click 'Add credentials'

Fill out the form:

- Kind: GitHub app
- ID: i.e. github-app-<team-name>
- App ID: the github app ID, it can be found in the 'About' section of your GitHub app in the general tab.
- API endpoint (optional, only required for GitHub enterprise this will only show up if a GitHub enterprise server is configured).
- Key: click add, paste the contents of the converted private key
- Passphrase: do not fill this field, it will be ignored
- Click OK

=== link:https://github.com/jenkinsci/configuration-as-code-plugin[Configuration as Code Plugin]

[source,yaml]
----
credentials:
system:
domainCredentials:
- credentials:
- gitHubApp:
appID: "1111"
description: "GitHub app"
id: "github-app"
# apiUri: https://my-custom-github-enterprise.com/api/v3 # optional only required for GitHub enterprise
privateKey: "${GITHUB_APP_KEY}"
----

== Configuring the github organization folder

See the link:https://docs.cloudbees.com/docs/admin-resources/latest/plugins/github-branch-source[main documentation]
for how to create a GitHub folder.

- Load the folders configuration page
- Select the GitHub app credentials in the 'Credentials field drop down
- If you are using GitHub enterprise make sure the API url is set to your server,
(note you currently need to set the API url on both the credential and the job).

After selecting the credential you should see:

[quote]
----
GHApp verified, remaining rate limit: 5000
----

- Click save
- Click 'Scan organization now'
- Click 'Scan organisation log'

Verify at the bottom of the scan log it says:

[quote]
----
Finished: SUCCESS
----

=== Help?

Raise an issue on link:https://issues.jenkins-ci.org/[Jenkins jira]
setting the 'component' to be `github-brance-source-plugin`
2 changes: 1 addition & 1 deletion docs/implementation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ explicitly apply a `DefaultGitHubNotificationStrategy` to the source context in
Duplicate (by equality) strategies are ignored when applied to the source context.

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


28 changes: 27 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<hamcrest.version>2.2</hamcrest.version>
<useBeta>true</useBeta>
<jcasc.version>1.35</jcasc.version>
<jjwt.version>0.10.5</jjwt.version>
</properties>

<scm>
Expand Down Expand Up @@ -67,7 +68,13 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>2.1.18</version>
<version>2.2.0</version>
</dependency>
<!-- TODO: after upgrading jenkins.version >= 2.171, migrate dependency -->
<dependency>
<groupId>io.jenkins.temp.jelly</groupId>
<artifactId>multiline-secrets-ui</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.coravy.hudson.plugins.github</groupId>
Expand All @@ -79,6 +86,25 @@
<artifactId>display-url-api</artifactId>
<version>2.0</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>

<!-- Currently just here for interactive testing via hpi:run: -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,8 @@
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.github.config.GitHubServerConfig;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;
import org.kohsuke.github.HttpConnector;
import org.kohsuke.github.RateLimitHandler;
import org.kohsuke.github.extras.OkHttpConnector;

Expand Down Expand Up @@ -198,13 +195,23 @@ public static FormValidation checkScanCredentials(@CheckForNull Item context, St
GitHub connector = Connector.connect(apiUri, credentials);
try {
try {
boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials;
if (githubAppAuthentication) {
int remaining = connector.getRateLimit().getRemaining();
return FormValidation.ok("GHApp verified, remaining rate limit: %d", remaining);
}

return FormValidation.ok("User %s", connector.getMyself().getLogin());
} catch (IOException e){
return FormValidation.error("Invalid credentials");
} catch (Exception e) {
return FormValidation.error("Invalid credentials: %s", e.getMessage());
}
} finally {
Connector.release(connector);
}
} catch (IllegalArgumentException | InvalidPrivateKeyException e) {
String msg = "Exception validating credentials " + CredentialsNameProvider.name(credentials);
LOGGER.log(Level.WARNING, msg, e);
return FormValidation.error(e, msg);
} catch (IOException e) {
// ignore, never thrown
LOGGER.log(Level.WARNING, "Exception validating credentials {0} on {1}", new Object[]{
Expand Down Expand Up @@ -512,7 +519,7 @@ static boolean isCredentialValid(GitHub gitHub) {
return true;
} else {
try {
gitHub.getMyself();
gitHub.getRateLimit();
Copy link
Contributor

Choose a reason for hiding this comment

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

This text need to change: https://github.com/jenkinsci/github-branch-source-plugin/pull/269/files#diff-61948c4cd4e480f18c6e572cf88a055bR511

This should be fixed in github-api as well. Not your problem, I'll fill an issue over there.

Copy link
Contributor

Choose a reason for hiding this comment

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

This works because invalid credentials return 401 not 404.

Copy link
Member

Choose a reason for hiding this comment

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

return true;
} catch (IOException e) {
if (LOGGER.isLoggable(FINE)) {
Expand Down
Loading