Proxy Jira installed events to self-managed
What does this MR do and why?
This is currently blocked by !98437 (merged)
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 (!98431 (merged))
- 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 (This MR)
- 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)
🔒 Security
When a user installs the GitLab.com for Jira app, Jira sends an installed hook to GitLab.com. This is a handshake to install the app and exchange the secret token. The request is authenticated using an asymmetric JWT.
Currently, the app can only be installed on GitLab.com and we try to make it work for self-managed. The idea is that GitLab.com will serve as a proxy and forward the installation hook to a self-managed instance. Unfortunately, we can't re-use the JWT token that came with the original installation hook because it verifies the URL of the receiver. Therefor, we are building a new JWT that can be verified in the same way as the original JWT from Jira.
The token requires a public key to be decoded. This is usually fetched from https://connect-install-keys.atlassian.com/KEY_ID
but self-managed instances need to fetch it from https://gitlab.com/-/jira_connect/public_keys/KEY_ID
. For each hook, we generate a new key and store it in REDIS for 5 minutes.
Here is the flow:
- User installs the app on GitLab.com
- GitLab.com receives an installed with a JWT token
- GitLab.com reads the public KEY_ID from the
kid
header of the JWT token - GitLab.com fetches the public key from
https://connect-install-keys.atlassian.com/KEY_ID
- GitLab.com decodes the token using the public key
- GitLab.com verifies the token
- GitLab.com stores the secret_key from the decoded token
- User stets the
jira_connect_proxy_url
application setting to point tohttps://gitlab.com
- User enters their instance_url
- GitLab.com stores the instance_url
- GitLab.com creates a JWT token with the instance_url as the receiver and stores the public key in REDIS
- GitLab.com forwards the secret_key encoded in the JWT token to the instance using an installed hook
- Self-managed reads the public KEY_ID from the
kid
header of the JWT token - Self-managed fetches the public key from
https://gitlab.com/-/jira_connect/public_keys/KEY_ID
- Self-managed decodes and verifies the token and stores the secret_key
- The app is now installed on the self-managed instance
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
How to set up and validate locally
To QA the feature in an environment that is close to production, the app needs to be installed on a Jira namespace. This requires to have:
- 2x publicly available GitLab instances.
- 1x Jira cloud namespace.
This can be archived using Gitpod. I listed the necessary steps in the Advanced setup section. There is a simpler way to test the feature locally described in the Simple setup section.
Simple setup
To test the feature using your local GDK:
- Clone the jira-connect-test-tool
git clone git@gitlab.com:gitlab-org/manage/integrations/jira-connect-test-tool.git
- Start the app
bundle exec rackup
. (The app requires your GDK GitLab to be available onhttp://127.0.0.1:3000
.) - On GDK switch to the
andysoiron/proxy-jira-connect-installed-events
branch. - Open
config/gitlab.yml
and uncomment the jira_connect config - Restart GDK to pick up new settings
gdk restart
. - Enable the feature flags:
Feature.enable(:jira_connect_oauth) Feature.enable(:jira_connect_oauth_self_managed) Feature.enable(:jira_connect_oauth_self_managed_setting)
- Go to
http://127.0.0.1:3000/-/profile/personal_access_tokens
. - Create a new token with the
api
scope and copy the token - Go to
http://localhost:9292
- Paste the token and select Install GitLab.com Jira Cloud app.
- You should now see the GitLab for Jira Cloud app rendered in an iframe.
- Select GitLab (self-managed)
- Enter
http://127.0.0.1:3000
. Usually this should point to a different GitLab instance. For the simple test, we can use the same instance. - Follow the development log
tail -f log/development.log
- Select Save
- The log should now show 3 requests:
-
PUT "/-/jira_connect/installations"
This is the request sent by the app frontend to update the installation on the proxy instance. -
POST "/-/jira_connect/events/installed"
This is the request sent by the proxy instance to the self-managed instance to create the installation record -
GET "/-/jira_connect/public_keys/{{some public key id}}"
. This is the request sent from the self-managed instance to fetch the public key from the proxy instance.
-
Advanced setup
You need:
- 2x GitPod instances running on the
proxy-jira-connect-installed-events
- 1x Jira cloud namespace
- Start two publicly available GitLab instances
- Go to https://gitlab.com/gitlab-org/gitlab/-/tree/andysoiron/proxy-jira-connect-installed-events
- Click GitPod to start a GitPod instance
- Do this twice to get two instances running
- Wait until the instances are up and running
- On each instance go to the
gitlab
foldercd gitlab
- Start a rails console
rails c
- Enable the feature flags:
Feature.enable(:jira_connect_oauth) Feature.enable(:jira_connect_oauth_self_managed) Feature.enable(:jira_connect_oauth_self_managed_setting)
- Choose one GitPod instance to act as GitLab.com and the other as self-managed
- On the GitLab.com instance edit
app/controllers/jira_connect/public_keys_controller.rb
and remove|| !Gitlab.com?
from line 13 - On the self-managed instance, go to the Ports tab next to Terminal, click the
🌐 button in the port3000
row to access the instance. - Log in as root or an admin account
- Go to
https://3000-YOUR_SELF_MANAGED_INSTANCE.gitpod.io/admin/applications
- Click New Application
- Fill out the fields
-
Name:
GitLab for Jira app
-
Redirect URI:
https://3000-YOUR_DOTCOM_INSTANCE.ws-eu75.gitpod.io/-/jira_connect/oauth_callbacks
(This should point to the other GitPod instance) -
Scopes:
api
- Make sure Trusted and Confidential are not selected
-
Name:
- Click Save Application
- Copy Application ID
- Go to
https://3000-YOUR_SELF_MANAGED_INSTANCE.gitpod.io/admin/application_settings/general
- Expand GitLab for Jira App
- Paste the Application ID
- Set Jira Connect Proxy URL to
https://3000-YOUR_DOTCOM_INSTANCE.ws-eu75.gitpod.io
- Click Save changes
- On both GitPod instances, go to the Ports tab
- Click the
🔒 button next to the3000
port - On Jira cloud got to Apps -> Manage your apps
- Enable developer mode
- Click Upload app
- Paste
https://3000-YOUR_DOTCOM_INSTANCE.ws-eu75.gitpod.io/-/jira_connect/app_descriptor.json
- Click Upload
- Click Get Started
- Choose GitLab (self-managed)
- Enter the URL of the self-managed instance:
https://3000-YOUR_SELF_MANAGED_INSTANCE.gitpod.io
- Click Save
- On the rails console of the self-managed instance, there should now be one Jira installation
JiraConnectInstallation.count
I've recorded a video while writing the steps above: https://youtu.be/qUfQkVrm6uk
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.