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:
- 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.
- 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 tohttps://gitlab.com/-/jira_connect/-/jira_connect/public_keys
.
- Add an application setting that defaults to
- 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.
- Add a service that sends an installed hook to the self-managed instance when
I explained the problem in more detail in #372967 (closed)
How to set up and validate locally
- Go to http://localhost:3000/admin/application_settings/network
- Expand the Outbound requests section
- Enable Allow requests to the local network from web hooks and services
- Open a rails console
rails c
- Enable the
jira_connect_oauth_self_managed
feature:Feature.enable(:jira_connect_oauth_self_managed)
- 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?
- 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.
-
I have evaluated the MR acceptance checklist for this MR.