Action Required - Atlassian Connect installation lifecycle security improvements

Thanks @HanjooSong.
Can I assume that the name will be updated to authenticateAsymmetric in future versions ?

Hi @HanjooSong ,

I have 2 question regarding this,

  1. I have put atlassian: connect: fallback-install-hook-enabled: false
    in my application.yml file still I am able to make request successful.
    Do I need to keep this configuration somewhere else?

  2. After the app is successfully installed I see below error in the log -

java.lang.IllegalArgumentException: Collection is empty
        at java.util.EnumSet.copyOf(EnumSet.java:174)
        at org.springframework.boot.web.error.ErrorAttributeOptions.excluding(ErrorAttributeOptions.java:79)
        at com.atlassian.connect.spring.internal.AtlassianConnectErrorController.error(AtlassianConnectErrorController.java:44)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:84)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712)
        at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:461)
        at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384)
        at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312)
        at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:394)
        at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:253)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:175)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

2021-09-09 18:31:03.950 ERROR 31204 --- [nio-8080-exec-4] o.a.c.c.C.[Tomcat].[localhost]           : Exception Processing ErrorPage[errorCode=0, location=/error]

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Collection is empty
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:84)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712)
        at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:461)
        at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384)
        at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312)
        at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:394)
        at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:253)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:175)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalArgumentException: Collection is empty
        at java.util.EnumSet.copyOf(EnumSet.java:174)
        at org.springframework.boot.web.error.ErrorAttributeOptions.excluding(ErrorAttributeOptions.java:79)
        at com.atlassian.connect.spring.internal.AtlassianConnectErrorController.error(AtlassianConnectErrorController.java:44)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
        ... 71 common frames omitted

In order to educate myself on the new installation security mechanism, I’ve just read through the entirety of this thread.

I’m going to have a little lie-down now.

6 Likes

Hi @HanjooSong ,

We are using ACSB 2.2.3 version and we updated our apps as suggested. We added both “signed-install”: true and “fallback-install-hook-enabled: false”. However, I can still send requests to /installed end-point as in the below curl command which results in a new record in atlassian_host table. Does it mean that security patch does not work? Because an attacker can simulate this and create millions of records in my database.

curl --location --request POST 'https://{my-app-domain}/installed' \
--header 'User-Agent: Atlassian HttpClient unknown / JIRA-1001.0.0-SNAPSHOT (100177) / Atlassian-Connect/1001.0.0-SNAPSHOT' \
--header 'Atlassian-Connect-Version: 1001.0.0-SNAPSHOT' \
--header 'Accept-Encoding: gzip' \
--header 'Content-Type: application/json; charset=UTF-8' \
--data-raw '{
    "key": "app-key",
    "clientKey": "1",
    "publicKey": "1",
    "sharedSecret": "1",
    "serverVersion": "100177",
    "pluginsVersion": "1001.0.0-SNAPSHOT",
    "baseUrl": "1",
    "productType": "jira",
    "description": "1",
    "eventType": "installed"
}'

I’m not sure that makes sense. Whenever an installation does a re-installation, they will pick up the new descriptor, won’t they?

1 Like

Since we can now verify the iss attribute of the JWT using asymmetric key verification, is the clientKey attribute of the installation request payload now redundant?

Hi, yes I will have it in the backlog for furture change. But we will not remove the previous one as to avoid a breaking change. We will add deprecated notes to the old one and simply add new authenticateAsymmetric fn.

1 Like

Hi @david.pinn , yes the clientKey and iss will be the same and clientKey will be redundant after this change because we will always have JWT token for install hooks now. But we do not have any plans to remove this item from the request body because we know that many apps may still rely on this field.

Hi @mehmetaydogdu, apologies for the confusion. After double checking the ACSB changes it looks like the configuration name has been renamed to below right before the announcement.

atlassian:
  connect:
    allow-symmetric-auth-install-callback: false

Thanks for the update @HanjooSong ,
After replacing fallback-install-hook-enabled with allow-symmetric-auth-install-callback, now the same curl command returns 401 http status code. This means that the security patch works.

Hi @HanjooSong

I am upgrading from ACE 6.xxx to ACS 7.4.x, in order to comply with the breaking changes. I have added following to the descriptor:
“apiMigrations”: {
“gdpr”: true,
“context-qsh”: true,
“signed-install”:true
},
“authentication”: {
“kid”: “kid_for_public_key”,
“typ”: “JWT”,
“alg”: “RS256”
},

but I am getting an error while updating the app to ACE:
“Installation failed. The app includes a web-fragment condition (entity_property_equal_to) with invalid parameters (This entity type is not valid, you provided ‘project’)”

The decsriptor file contains modules->webItems and modules->webPanels which has the condition:
{
“condition”: “entity_property_equal_to”,
“params”: {
“entity”: “project”,
“propertyKey”: “xxxxxxxx”,
“value”: “true”
}
}

Is it related to the changes done for ACE upgrade ?

Hi @ArchitaJindal
authentication in your descriptor file should not be changed. I believe this is how it used to look like for your app before making the change?

“authentication”: {
“type”: “JWT”
}

Hi @HanjooSong

I did try it by keeping authentication as jwt without any other params, but still, the error was the same. It did not make any difference to the error.

Hi @ArchitaJindal
The error message that you are getting is related to generalPages module.
I found a related conversation in this post.

In short,

The condition entity_property_equal_to and other entity_property conditions only support addon entity for the generalPages module.

There is also a ticket in one of the team’s backlog to update the developer guide with this information.

Hi @HanjooSong ,

We are going through some heavy changes in one of our apps and that is blocking the fixing of this vulnerability.
Can you please tell me if it is possible to opt-in like “signed-install”: false in order to show you that we are aware of the problem but that we are going to address it in a few months (so after the deadline) ?
Thanks

Hi @dusan.spaic
Yes, setting “signed-install”: false in your app descriptor will help us to make sure to exclude your app during the enforcement stage. We will then make sure to communicate for the future plan.

Dear @HanjooSong ,

I am a little puzzled about the fallback mechanism. We are using a custom implementation, so implementing the fallback and the new method at the same time adds some complexity here.

To clarify, I understand there are three risks that are mitigated by the fallback:

  • New installs happening right after the release of our new app version might still use the old symmetric approach as the app descriptor has not propagated to all instances yet, making the installation of a new app fail.
  • Users might uninstall the app in the hours after the upgrade, but because the app is not upgraded yet we would not accept the request and get some ghost tenant data.
  • If Atlassian for any reason takes back the whole change, installations would still work.

I think those risks are quite small compared to maintaining both paths in a custom implementation, or am I mistaken?

Especially, for risk #1 (installation fails): doesn’t the MPAC read the descriptor when starting a new installation?

Hi @SteffenMueller
Yes you are right and those risks are quite small. To give more idea around the risks that you have listed;

  • We have not seen any issues related to case #1, new installs will always use the most recent app version(descriptor) as you have pointed out.
  • For case #2, we’ve noticed it happen only one time when a vendor requested for a secret key rotation followed by their app version upgrade, this is because secret key rotation is essentially a re-install with an existing version. But, we did not notice any other issues related to uninstall requests directly from the user.
    FYI, ACE and ACSB frameworks also does not delete any tenant data by default. I suppose it can be problematic only if you are relying on uninstallation callbacks to run some other complicated tasks.
  • If we were to rollback, we will communicate in advance and wait until all the required actions are taken from all vendors again.
1 Like