Skip to content

Add Jira Connect public key storage

What does this MR do and why?

This is part of #372967 (closed). See !96818 (closed) for a full context MR.

When a user installs the GitLab for Jira app we receive an installed hook. It includes a JWT token that we have to verify using a public key. The public key is fetched from connect-install-keys.atlassian.com (see lib/atlassian/jira_connect/jwt/asymmetric.rb:15).

To make the app available for self-managed users, GitLab.com will serve as a proxy. It forwards the installed hook to the self-managed instance, but generates a new JWT token. To make this work, we need to:

  1. Build the JWT infrastructure (This MR)
    • Add a service to generate JWT tokens.
    • Store the public keys with an expiry date.
    • Provide an endpoint to fetch public keys.
  2. Allow the public key CDN URL to be configured. (!98437 (merged))
    • Add an application setting that defaults to https://connect-install-keys.atlassian.com and can be pointed to https://gitlab.com/-/jira_connect/-/jira_connect/public_keys.
  3. Forward the installed event to self-managed
    • Add a service that sends an installed hook to the self-managed instance when instance_url is updated.

I explained the problem in more detail in #372967 (closed)

How to set up and validate locally

  1. Go to http://localhost:3000/admin/application_settings/network
  2. Expand the Outbound requests section
  3. Enable Allow requests to the local network from web hooks and services
  4. Open a rails console rails c
  5. Enable the jira_connect_oauth_self_managed feature: Feature.enable(:jira_connect_oauth_self_managed)
  6. Execute the following lines:
# Create a JiraConnect installation
installation = JiraConnectInstallation.create(client_key: '123', shared_secret: '123', base_url: 'https://sample.atlassian.net')

# Generate a new JWT token for the installation
jwt = JiraConnect::CreateAsymmetricJwtService.new(installation).execute

# Fetch the public key ID from the JWT header. The 3rd parameter defines if the decoding should be verified with a public key. In this case, it is not.
key_id = Atlassian::Jwt.decode(jwt, nil, false, algorithm: 'RS256').last['kid']

# Retrieve the public key from storage
public_key_string = Gitlab::HTTP.get('http://127.0.0.1:3000/-/jira_connect/public_keys/' + key_id).body

# Read the public key
public_key = OpenSSL::PKey.read(public_key_string)

# Do a verified decoding of the JWT using the public key
Atlassian::Jwt.decode(jwt, public_key, true, algorithm: 'RS256').first.present?
  1. Verify that the last return value is true

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Andy Schoenen

Merge request reports