Action Required - Atlassian Connect installation lifecycle security improvements

Hi, I must have missed @OfirNir 's comment earlier, the fake organisation was still allowed to be authenticated as the behaviour is not forced yet. For ACSB you can set this property to not allow the legacy JWT tokens.
atlassian.connect.fallback-install-hook-enabled=false

2 Likes

@HanjooSong thanks for your efforts and your fast replies in here. I have an issue related to the lifecycle requests and was wondering if you could clarify. Let me know if we should take this discussion to a PM, but I thought others reading here might be interested as well.

It seems that the disabled and uninstalled lifecycle hooks are called in parallel, i.e. Connect doesn’t wait for the disabled request to finish before calling uninstalled on our servers. I have observed only 5 milliseconds apart from those 2 requests hitting our servers, and in some cases this leads to a race condition so that we persist the uninstalled status first and the disabled status last, which is incorrect.

I was wondering how the Atlassian Connect frameworks (ACE + ACSB) handle this race condition and how we’re supposed to do it using a custom implementation. As an alternative, could we ask that Connect waits for the disabled webhook to return within a reasonable amount of time (e.g. 5 seconds) before it continues to call uninstalled?

Thanks for any insights you can provide!

1 Like

Hey @HanjooSong, we’re currently migrating to the new mechanism, and we have a question:

After updating our app descriptor, do we still have to support the old-style webhooks for backwards compatibility or other reasons? Or we are guaranteed to receive only asymmetrically-signed installed/uninstalled webhooks?

I’d expect that Jira always fetches our app descriptor and decides which signature algorithm to use based on the “apiMigrations” section, but can we rely on that?

Also, what happens if a customer installs our app before the update, then we roll out the updated app descriptor, then the customer decides to uninstall our app? What kind of signature will we get for the “uninstalled” webhook?

2 Likes

Hi @pzvyagin
I would recommend to support the old-style install hook authentication just like ACE and ACSB does. As you have pointed out for uninstallations, some sites may take hours for your app to be upgraded automatically.
Installations are unlikely to have the same issue but you should keep in mind that there is always a small gap between calls to your app descriptor and the installation hook, which can happen during your app release.

1 Like

We have implemented the new Install Hook for Atlassian Connect for our Custom JWT Auth. solution. We are using Express.js but not ACE (Atlassian Connect Express) which does not export reusable middleware functions.

We have published our middleware as an open-source project. It is available as a npm module.

Cross posting of: Unofficial library that implements new Connect installation hook for Express and similar frameworks

4 Likes

@JulianWolf Thanks for publishing this!

1 Like

Hi @HanjooSong

How/where can we test the new install/uninstall hooks to check if they work correctly?

Thanks in advance.

1 Like

@HanjooSong Can you help elaborate the below statement :

What is the the legacy install callback/ shared secret and what is the patch here? And do the ACE users have to make any additional changes ?

Currently we were on ace version 3.4.1 and we have a custom implementation of /installed path like

this.mRouter.post('/installed', (req, res) => {
      AppController.handlePluginInstall(req, res, addon);
    });
  static handlePluginInstall(req, res, addon) {
    addon.settings.set('clientInfo', req.body, req.body.clientKey)
      .then(() => {
        res.sendStatus(200);
        // custom logic
      })
      .catch((error) => {
        res.sendStatus(400);
        // custom logic
      });
  }

Changes which we are making

  1. ace version update to 7.4.4
  2. adding “signed-install”: true under apiMigrations in descriptor
  3. add verify middleware in our custom /installed path
    this.mRouter.post('/installed', verify(addon), (req, res) => {
      AppController.handlePluginInstall(req, res, addon);
    });
    

Can you help verify do we need to make any further changes ?
With signed-install opt-in support, do we need to be backward compatible while the update is deployed to atlassian instances as per the guidelines mentioned here - https://developer.atlassian.com/platform/marketplace/upgrading-and-versioning-cloud-apps/

As per the above documentation we have done following changes to our plugin which uses ACSB.

1. Upgrade ACSB version to 2.2.3
2. Add “signed-install”: true to the existing

"apiMigrations": {
	"context-qsh": true,
	"signed-install": true
},

in atlassian-connect.json
3. Add fallback-install-hook-enabled: false to the existing

atlassian:
  connect:
    fallback-install-hook-enabled: false

in application.yml file.

Questions:

  1. Are these the only changes to be done?
  2. How can we test this after applying these changes?

Thank you

Hi, if you don’t have an existing cloud site for testing your app in develop mode, you can always spin up a new one for testing.
https://developer.atlassian.com/cloud/jira/platform/getting-started-with-connect/#step-1--get-a-cloud-development-site
Your app should receive a JWT token in the Authorization header which you can decode to check if the signed algorithm is RS256.

1 Like

Hi, can I ask if verify(addon) middleware that you are using is a custom authentication middleware or a same one as ACE provided authenticateInstall function?
If you are using ACE’s authenticateInstall function, you will automatically get the backward compatibility.
The additional patch will simply remove the support for this backwards compatibility feature in ACE, and there won’t be any additional actions required from your end apart from the ACE version update.

Hi @ShihaazBuhary , for apps using the ACSB framework, the changes that you mentioned would be all that’s needed. For testing your app, you can always spin up a new cloud site if you don’t already have one for testing.
Your app should receive a JWT token in the Authorization header, which you can decode(For example from https://jwt.io/) to check if the signed algorithm is RS256.

verify(addon) middleware we are using is the one provided by ace in verify-installation.js
This internally calls ACE provided authenticateInstall function.

Just to confirm what will be the effect if we do not make the changes by 29th October and release it at a later date.

Thanks for your response.

@HanjooSong Thank you for your response. How do I see the Authorization header that my app is receiving? Can I check that in browser developer tools? In browser developer tools, I can only see the cloud.session.token while installing the app url but not the Jwt token. Could you please help me on this? Thanks.

Hi @ShihaazBuhary, I was assuming you have full control of your app and could inspect any incoming requests.
But one of the easiest way to inspect requests during local development is to use a tunnel such as ngrok. Ngrok provides an inspection tool that you can monitor all requests if you access http://localhost:4040 once you have setup the app and tunnel properly.

Hi @HanjooSong, Thank you so much for your help. I followed your instructions and was able to successfully decode the Jwt token returned at the first installation. Thanks again. :slight_smile:

Hello @HanjooSong
We were using a custom /uninstalled lifecycle

this.mRouter.post('/uninstalled', addon.authenticate(), (req, res) => {
      AppController.handlePluginUninstall(req, res);
    });

After updating ace version and opting into signed-installed the /uninstalled post request is failing with below error

Authentication verification error (401): Invalid JWT: Algorithm from the header “RS256” does not match

On further analysis of authentication.js in ace we found the below code in getVerifiedClaims function

  let verifiedClaims;
  try {
    verifiedClaims = jwt.decodeSymmetric(
      token,
      secret,
      jwt.SymmetricAlgorithm.HS256,
      false
    );
  } catch (error) {
    return Promise.reject({
      code: 400,
      message: `Unable to decodeSymmetric JWT token: ${error}`,
      ctx: {}
    });
  }

We believe this causing the issue as it is trying to use HS256 algo.

Please suggest how can we authenticate the /uninstalled post request

1 Like

Hello @PiyushAnand,
You need to use addon.authenticateInstall() for /uninstalled and /installed webhooks now.

3 Likes

Thanks @akhaneev.
This seems to work.
@akhaneev and @HanjooSong my concern would be that as per the name addon.authenticateInstall() this function is to be used for authenticating the the /installed lifecycle. If any further changes are made to the installed lifecycle, this might again break my /uninstall lifecycle flow.

Hi @PiyushAnand I agree with your concern, it makes more sense to be renamed to like authenticateAsymmetric. But it can be used for both install/uninstall hooks and will not be tied to install hooks only.