Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion docs/github-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ Fill out the form:
- 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
- Advanced: (optional) If you've installed your same GitHub app on multiple organisations you need the next step
* Owner: the name of the organisation or user, i.e. jenkinsci for https://github.com/jenkinsci
Copy link
Member

Choose a reason for hiding this comment

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

So under this approach you cannot reuse the Jenkins credentials object across orgs, and you need to duplicate the private key. Not as smooth as having Jenkins figure out which installation to work with, but better than nothing, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah I couldn't see a way to do that, as we don't have the context from the build job at all

Copy link
Member

Choose a reason for hiding this comment

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

Right, it would be a more intrusive patch since you would need to do something along the lines of defining a thread-local org and setting this from a new overload of Connector.connect which passes a repoOwner and then switching as many callers as possible to the new overload. Not sure it is worth the effort since I presume the common case is for the Jenkins installation to be used on only a single org.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure it is worth the effort since I presume the common case is for the Jenkins installation to be used on only a single org.

What about for GitHub Enterprise? It doesn't seem to be possible to install an app at the Enterprise level. My Jenkins instance works on repos across many organizations in a GitHub Enterprise instance.

Copy link
Member

Choose a reason for hiding this comment

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

I do not have GHE to test against but in that case with the current patch you would need to first install the app in each such org, then create a separate Jenkins credentials item for each installation.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, those were all public repos.

is there any way that we can get this owner based on the job config as it would already have the github repo information ?

Copy link
Member Author

Choose a reason for hiding this comment

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

The credential isn’t tied to a job it’s independent, not safely, there may be some possible hacks to do it though

Copy link
Contributor

Choose a reason for hiding this comment

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

My company uses Jenkins in a similar fashion as the company described by @nrayapati.

We deployed a single jenkins GitHub App that may be installed by any organization on our GHE instance. There are several hundred such organizations.

We allow our engineers to create jobs as needed within their own Jenkins folders and leverage the single GitHub App Credential.

Since any engineer can install our GitHub App on their organization, and any engineer can create an organization, we would be in the situation of needing to create a Credential for every organization as it's created.

I, too, would like to see support for a single global Credential to work for jobs across multiple organizations.

@nrayapati can you please file a Jira ticket (As this PR has already been merged and released) so that we can centralize the discussion about this use case on that ticket?

Copy link
Member

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Can we just use the org name in repo instead of the org name in credential config?

- Click OK

=== link:https://github.com/jenkinsci/configuration-as-code-plugin[Configuration as Code Plugin]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
public class GitHubAppCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials {

private static final String ERROR_AUTHENTICATING_GITHUB_APP = "Couldn't authenticate with GitHub app ID %s";
private static final String NOT_INSTALLED = ", has it been installed to your GitHub organisation / user?";

private static final String ERROR_NOT_INSTALLED = ERROR_AUTHENTICATING_GITHUB_APP + NOT_INSTALLED;

@NonNull
private final String appID;
Expand All @@ -39,6 +42,8 @@ public class GitHubAppCredentials extends BaseStandardCredentials implements Sta

private String apiUri;

private String owner;

@DataBoundConstructor
@SuppressWarnings("unused") // by stapler
public GitHubAppCredentials(
Expand Down Expand Up @@ -72,27 +77,55 @@ public Secret getPrivateKey() {
return privateKey;
}

/**
* Owner of this installation, i.e. a user or organisation,
* used to differeniate app installations when the app is installed to multiple organisations / users.
*
* If this is null then call listInstallations and if there's only one in the list then use that installation.
*
* @return the owner of the organisation or null.
*/
@CheckForNull
public String getOwner() {
return owner;
}

@DataBoundSetter
public void setOwner(String owner) {
this.owner = owner;
}

@SuppressWarnings("deprecation") // preview features are required for GitHub app integration, GitHub api adds deprecated to all preview methods
static String generateAppInstallationToken(String appId, String appPrivateKey, String apiUrl) {
static String generateAppInstallationToken(String appId, String appPrivateKey, String apiUrl, String owner) {
try {
String jwtToken = createJWT(appId, appPrivateKey);
GitHub gitHubApp = new GitHubBuilder().withEndpoint(apiUrl).withJwtToken(jwtToken).build();

GHApp app = gitHubApp.getApp();

List<GHAppInstallation> appInstallations = app.listInstallations().asList();
if (!appInstallations.isEmpty()) {
GHAppInstallation appInstallation = appInstallations.get(0);
GHAppInstallationToken appInstallationToken = appInstallation
.createToken(appInstallation.getPermissions())
.create();

return appInstallationToken.getToken();
if (appInstallations.isEmpty()) {
throw new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId));
}
GHAppInstallation appInstallation;
if (appInstallations.size() == 1) {
appInstallation = appInstallations.get(0);
Copy link
Member

Choose a reason for hiding this comment

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

Not verifying the owner?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've made an assumption that most installations only work with one org, so I've made it an advanced field and by default if and only if there's one installation the plugin will just pick that.

Copy link
Member

Choose a reason for hiding this comment

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

I get that, I am just saying that in case you did specify an owner, and there is exactly one installation, we are not verifying that the installation is in fact from that owner. Which feels wrong.

} else {
appInstallation = appInstallations.stream()
.filter(installation -> installation.getAccount().getLogin().equals(owner))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId)));
}

GHAppInstallationToken appInstallationToken = appInstallation
.createToken(appInstallation.getPermissions())
.create();

return appInstallationToken.getToken();
} catch (IOException e) {
throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId), e);
}
throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId));

}

/**
Expand All @@ -105,7 +138,7 @@ public Secret getPassword() {
apiUri = "https://api.github.com";
}

String appInstallationToken = generateAppInstallationToken(appID, privateKey.getPlainText(), apiUri);
String appInstallationToken = generateAppInstallationToken(appID, privateKey.getPlainText(), apiUri, owner);

return Secret.fromString(appInstallationToken);
}
Expand Down Expand Up @@ -163,18 +196,19 @@ public ListBoxModel doFillApiUriItems() {
public FormValidation doTestConnection(
@QueryParameter("appID") final String appID,
@QueryParameter("privateKey") final String privateKey,
@QueryParameter("apiUri") final String apiUri
@QueryParameter("apiUri") final String apiUri,
@QueryParameter("owner") final String owner

) {
GitHubAppCredentials gitHubAppCredential = new GitHubAppCredentials(
CredentialsScope.GLOBAL, "test-id-not-being-saved", null,
appID, Secret.fromString(privateKey)
);
gitHubAppCredential.setApiUri(apiUri);
gitHubAppCredential.setOwner(owner);
Copy link

@robbiemcmichael robbiemcmichael Mar 25, 2020

Choose a reason for hiding this comment

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

Commenting here since the CredentialsScope.GLOBAL part is just above this:

Do you know if it's possible to use system scope credentials with the branch source plugin? One issue we ran into with our credentials plugin was making the credentials for an org only available to the builds within that org.

Copy link
Member

Choose a reason for hiding this comment

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

It is not possible to use system scope credentials, but you can define credentials on an org folder so they are not accessible outside that folder.


try {
GitHub connect = Connector.connect(apiUri, gitHubAppCredential);

return FormValidation.ok("Success, Remaining rate limit: " + connect.getRateLimit().getRemaining());
} catch (Exception e) {
return FormValidation.error(e, String.format(ERROR_AUTHENTICATING_GITHUB_APP, appID));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
<secretTextarea xmlns="/io/jenkins/temp/jelly"/>
</f:entry>

<f:advanced>
<f:entry title="${%Owner}" field="owner">
<f:textbox/>
</f:entry>
</f:advanced>

<f:validateButton
title="${%Test Connection}" progress="${%Testing...}"
method="testConnection" with="appID,apiUri,privateKey" />
method="testConnection" with="appID,apiUri,privateKey,owner" />
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p>
The organisation or user that this app is to be used for, only required if this app is installed to multiple organisations.
</p>