<img height="1" width="1" style="display: none" src="https://www.facebook.com/tr?id=976470819114134&amp;ev=PageView&amp;noscript=1">

How to integrate Jenkins with Atlassian products




Integrating Jenkins with Atlassian products

It’s more important than ever to reduce the dependency on service accounts and ensure that automated actions are kicked off by the actual people requesting them, as opposed to shared credentials. This reduces the ways in which secrets can be inappropriately stored, it makes auditing much easier, and further centralises authentication and authorisation to IAM instead of application-specific API tokens and schemes.

In order for an automated action to be kicked off in a remote system, the system must trust that the request it's getting was properly authenticated; that a remote system is trusted beyond only the administrators involved enabling the connection, and can impersonate other users. Atlassian applications do this with OAuth through a framework called Application Links, available to developers as a Java API. Atlassian makes the source code available at packages.atlassian.com, which may assist with troubleshooting.

Atlassian's new Bitbucket integration for Jenkins

The Bitbucket team quietly released support for Application Links in Jenkins. Previously, Jenkins administrators would have to roll their own plugin extending Jenkins' OAuth Credentials framework: a challenging task, given the nuances of scopes and requirements of the OAuth Framework. Bitbucket can now kick off a job in Jenkins, without needing to store credentials in Bitbucket.

Beyond Bitbucket: using Atlassian's Jenkins plugin with Jira, Confluence, Fisheye/Crucible and even Bamboo

You can follow these steps for Jira too, and add Jira to Jenkins as a "Bitbucket server consumer". This should work for all Atlassian applications, as they share the same Application Links framework. 

Process to set up two-way OAuth with Impersonation between Jenkins and an Atlassian application

Ensure that Jenkins is using the same source for users as your Atlassian applications - whether it's Crowd, an LDAP server or a more exotic source. A version of the Bitbucket integration plugin Jenkins released after June 2020 must also be installed.


1. Collect your Jenkins instance's public key

Inside Jenkins' Script Console, execute the following:

Base64.getEncoder().encodeToString(identity.InstanceIdentityProvider.RSA.getKeyPair().getPublic().getEncoded())


You will get a Base64-encoded public key string that you will need later on.


2. Follow the official guide - to a point

See the guide under "Create an Application".

Follow this guide to the point where it directs you to leave the Incoming Authentication box unchecked. As of writing, this was the last direction. Instead, check this box to go to the next page.


3. Complete the two-way authentication setup

Now insert again the Consumer Key and Consumer Name that you set up on the previous page of the Application Link Wizard and in the Bitbucket Server Consumer setup in Jenkins. Finally, put in the public key string you obtained earlier and finish the setup.


4. Establish trust with the OAuth Dance

Although you've created the connection, it will not work until you go to a special page in Jenkins to authorise Jira access to Jenkins and impersonate users.

  1. Obtain the Application Link ID. There are a couple of ways to do this. Either:
    1. Visit <BASE URL>/rest/applinks/3.0/applicationlink and pull your ID from the relevant connection, or
    2. Inspect the link on the Application Links page: the ID is in the TR element for that row of the Application Links table.
  1. Visit the dance initialisation page: <BASE URL>/plugins/servlet/applinks/oauth/login-dance/authorize?applicationLinkID=<APPLICATION LINK ID>

At this point, it will either work or you will encounter obstacles. If you are redirected to Jenkins, you will receive a prompt like below, then "allow" to complete the trust establishment.





Issues encountered

If there are any difficulties setting up this process, this guide may help.

CSRF issues: 403 Forbidden

There may be issues initially with Jenkins' CSRF protection: Jira may report "403 Forbidden" when attempting to request the OAuth Trust user authorisation URL from Jenkins, and Jenkins could report "hudson.security.csrf.CrumbFilter#doFilter: No valid crumb is included in request for /bitbucket/oauth/request-token by anonymous. Returning 403."

This can be worked around by temporarily disabling CSRF by adding the JVM system property "hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true"

The root cause of this issue could be running Jira on the same machine as Jenkins, so Jenkins thinks the anonymous connection coming from localhost is too suspicious. (You may not encounter this issue).


Reverse proxy issues: OAuth Signature Invalid

After passing that hurdle, Jira may still refuse to offer the OAuth Trust user authorisation URL, as the hosts configured in the Application Link do not match the hosts in the exchange. One could try and follow various guides to configure the Nginx reverse proxy to correctly identify the hosts but bypassing the reverse proxy entirely is the solution suggested by Atlassian as a workaround. Atlassian provides a decent guide on this.

Using the Jenkins Application Link with Scriptrunner

Adaptavist advises how application links can be used to access remote REST APIs.

Atlassian further explains here

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.JiraAuthenticationContext

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Request
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException

import com.atlassian.applinks.api.ApplicationLink
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.ApplicationLinkRequestFactory
import com.atlassian.applinks.api.ApplicationLinkResponseHandler
import com.atlassian.applinks.api.CredentialsRequiredException
import com.atlassian.applinks.api.application.generic.GenericApplicationType

import groovy.json.JsonSlurper
 
import static com.atlassian.sal.api.net.Request.MethodType.GET
import static com.atlassian.sal.api.net.Request.MethodType.POST

ApplicationLinkResponseHandler handler = new ApplicationLinkResponseHandler<Map>() {
    
    @Override
    Map credentialsRequired(Response response) throws ResponseException {
        null
    }
 
    @Override
    Map handle(Response response) throws ResponseException {
        // If you are getting CSRF issues, this assertion will fail with status code 403
        assert response.statusCode == 200
        new JsonSlurper().parseText(response.getResponseBodyAsString())
    }
    
}

ApplicationLinkService applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)

//If you only have a single generic application link, this works fine
//ApplicationLink appLink = appLinkService.getPrimaryApplicationLink(GenericApplicationType)
// Or search by name
ApplicationLink applicationLink = applicationLinkService.getApplicationLinks(GenericApplicationType).
    find { it.getName() == 'Jenkins Proxy Bypass' }

// As long as a user has authorised Jira to act on their behalf, you can impersonate that user
//   but only if you change your context in Jira to that user as well:
String impersonatingUsername = 'autoaudit'
JiraAuthenticationContext jiraAuthenticationContext = ComponentAccessor.getJiraAuthenticationContext()
jiraAuthenticationContext.setLoggedInUser(ComponentAccessor.getUserManager().getUserByName(impersonatingUsername))
// From this point, the rest of this script's actions are done as this user too so ensure you switch back

ApplicationLinkRequestFactory applicationLinkRequestFactory = applicationLink.createAuthenticatedRequestFactory() 

// If you haven't authorised Jira in Jenkins as the impersonated user yet, you will be asked to do so:
Request request
try {
    request = applicationLinkRequestFactory.createRequest(GET, "/me/api/json")
} catch (CredentialsRequiredException e) {
    return "${e.message} Visit ${applicationLinkRequestFactory.getAuthorisationURI()} as user '${jiraAuthenticationContext.getLoggedInUser().getUsername()}' to authorize."
}

//[_class:hudson.model.User ... id:<Your username> ... ]
Map jenkinsUser = request.execute(handler)

assert jiraAuthenticationContext.getLoggedInUser().getUsername() == jenkinsUser['id']
jenkinsUser







Need help configuring your Atlassian tools? Reach out to us for help.