Uploaded image for project: 'camunda BPM'
  1. camunda BPM
  2. CAM-11840

Broken CORS support: OPTIONS preflight must not require authentication. Include Access-Control-Allow-Credentials, Access-Control-Allow-Headers

    • Icon: Bug Report Bug Report
    • Resolution: Fixed
    • Icon: L3 - Default L3 - Default
    • 7.13.0, 7.13.0-alpha5
    • 7.13.0
    • engine, rest-distro, run
    • None
    • All modern browsers

      When both camunda.bpm.run.auth.enabled (e.g. recommended by production.yml CAM-11838) and camunda.bpm.run.cors.enabled , Camunda BPM Run CORS configuration is broken, which breaks browser clients: (this is even after the origin has been configured/whitelisted using camunda.bpm.run.cors.allowed-origins)

      Access to XMLHttpRequest at 'https://camunda.example.com/engine-rest/process-definition/key/marketing.user.CloseAccount/start' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

      Specifically, the server returned 401 on (properly unauthenticated) preflight requests.
       

      1. OPTIONS preflight requests require authentication

      The W3 spec for CORS preflight requests clearly states that user credentials should be excluded. (see also: https://stackoverflow.com/a/15734032 )

      Please exclude OPTIONS in the authentication filter.

       

      2. OPTIONS response missing Access-Control-Allow-Credentials header

      See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

       

      Should be:

      Access-Control-Allow-Credentials: true

      Important: Access-Control-Allow-Credentials is mutually exclusive with:

      Access-Control-Allow-Origin: *
      

      (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials), and this is enforced by org.apache.catalina.filters.CorsFilter#parseAndStore meaning that for proper CORS support, it is mandatory for the administrator to set allowed origins other than *.
       

      3. OPTIONS response missing "Authorization" in Access-Control-Allow-Headers. And also (sent by Chrome) X-XSRF-Token, and just to be safe: X-CSRF-Token.

      See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers

      Currently:

      access-control-allow-headers: origin,x-requested-with,access-control-request-headers,content-type,access-control-request-method,accept

       

      Should be:

      access-control-allow-headers: origin,x-requested-with,access-control-request-headers,content-type,access-control-request-method,accept,authorization,x-xsrf-token,x-csrf-token

       

      Current Workaround

      • Not possible. Turning off authentication is not secure.
      • The only workaround is to use a server-side proxy, e.g. Pipedream

      Relevant source locations

      Related to: CAM-11290, CAM-11838

      Our experience with Camunda BPM Run: https://about.lovia.life/docs/infrastructure/camunda/

      cc tobias.metzke

        This is the controller panel for Smart Panels app

            [CAM-11840] Broken CORS support: OPTIONS preflight must not require authentication. Include Access-Control-Allow-Credentials, Access-Control-Allow-Headers

            Hendy Irawan added a comment - - edited

            tobias.metzke this is the patch to fix this bug. I should make a PR for this in GitHub.

            Update: PR here: https://github.com/camunda/camunda-bpm-platform/pull/816

            diff --git a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java
            index fc19641..bbda083 100644
            --- a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java
            +++ b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java
            @@ -76,6 +76,11 @@ public class CamundaBpmRunRestConfiguration {
                 registration.addUrlPatterns(restApiPathPattern);
             
                 registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_ORIGINS, camundaBpmRunProperties.getCors().getAllowedOrigins());
            +    if (!"*".equals(camundaBpmRunProperties.getCors().getAllowedOrigins())) {
            +      registration.addInitParameter(CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS, "true");
            +      registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_HEADERS,
            +        "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-XSRF-Token,X-CSRF-Token");
            +    }
                 return registration;
               }
             
            diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java
            index 3aab49a..f33d278 100644
            --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java
            +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java
            @@ -120,7 +120,7 @@ public class ProcessEngineAuthenticationFilter implements Filter {
                 }
                 String requestUrl = req.getRequestURI().substring(req.getContextPath().length() + servletPath.length());
             
            -    boolean requiresEngineAuthentication = requiresEngineAuthentication(requestUrl);
            +    boolean requiresEngineAuthentication = requiresEngineAuthentication(req.getMethod(), requestUrl);
             
                 if (!requiresEngineAuthentication) {
                   chain.doFilter(request, response);
            @@ -209,7 +209,11 @@ public class ProcessEngineAuthenticationFilter implements Filter {
                 engine.getIdentityService().clearAuthentication();
               }
             
            -  protected boolean requiresEngineAuthentication(String requestUrl) {
            +  protected boolean requiresEngineAuthentication(String requestMethod, String requestUrl) {
            +
            +    if ("OPTIONS".equals(requestMethod)) {
            +      return false;
            +    }
                 for (Pattern whiteListedUrlPattern : WHITE_LISTED_URL_PATTERNS) {
                   Matcher matcher = whiteListedUrlPattern.matcher(requestUrl);
                   if (matcher.matches()) {
            

            Hendy Irawan added a comment - - edited tobias.metzke this is the patch to fix this bug. I should make a PR for this in GitHub. Update : PR here: https://github.com/camunda/camunda-bpm-platform/pull/816 diff --git a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java index fc19641..bbda083 100644 --- a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java +++ b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java @@ -76,6 +76,11 @@ public class CamundaBpmRunRestConfiguration { registration.addUrlPatterns(restApiPathPattern); registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_ORIGINS, camundaBpmRunProperties.getCors().getAllowedOrigins()); + if (!"*".equals(camundaBpmRunProperties.getCors().getAllowedOrigins())) { + registration.addInitParameter(CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS, "true"); + registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_HEADERS, + "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-XSRF-Token,X-CSRF-Token"); + } return registration; } diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java index 3aab49a..f33d278 100644 --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/security/auth/ProcessEngineAuthenticationFilter.java @@ -120,7 +120,7 @@ public class ProcessEngineAuthenticationFilter implements Filter { } String requestUrl = req.getRequestURI().substring(req.getContextPath().length() + servletPath.length()); - boolean requiresEngineAuthentication = requiresEngineAuthentication(requestUrl); + boolean requiresEngineAuthentication = requiresEngineAuthentication(req.getMethod(), requestUrl); if (!requiresEngineAuthentication) { chain.doFilter(request, response); @@ -209,7 +209,11 @@ public class ProcessEngineAuthenticationFilter implements Filter { engine.getIdentityService().clearAuthentication(); } - protected boolean requiresEngineAuthentication(String requestUrl) { + protected boolean requiresEngineAuthentication(String requestMethod, String requestUrl) { + + if ("OPTIONS".equals(requestMethod)) { + return false; + } for (Pattern whiteListedUrlPattern : WHITE_LISTED_URL_PATTERNS) { Matcher matcher = whiteListedUrlPattern.matcher(requestUrl); if (matcher.matches()) {

            Hendy Irawan added a comment - - edited

            To do Docker in-container patch, this file need to be replaced:

            • /camunda/internal/camunda-bpm-run-core.jar
            kubectl create configmap camunda-internal --from-file=camunda-bpm-run-core.jar
            

            Oops: that didn't work as the file is over 45 MiB

            Patched my own Docker image here: https://hub.docker.com/repository/docker/hendy/camunda-bpm-platform

            Hendy Irawan added a comment - - edited To do Docker in-container patch, this file need to be replaced: /camunda/internal/camunda-bpm-run-core.jar kubectl create configmap camunda-internal --from-file=camunda-bpm-run-core.jar Oops: that didn't work as the file is over 45 MiB Patched my own Docker image here: https://hub.docker.com/repository/docker/hendy/camunda-bpm-platform

            Hendy Irawan added a comment -

            PR tested with Chrome Version 81.0.4044.129 (Official Build) (64-bit) on Ubuntu 18.04.4, on patched camunda-bpm-platform:run deployed on Kubernetes 1.16, nginx-ingress, LetsEncrypt SSL managed by cert-manager, fronted by CloudFront.

            Hendy Irawan added a comment - PR tested with Chrome Version 81.0.4044.129 (Official Build) (64-bit) on Ubuntu 18.04.4, on patched camunda-bpm-platform:run deployed on Kubernetes 1.16, nginx-ingress, LetsEncrypt SSL managed by cert-manager, fronted by CloudFront.

            hendy, thanks for your commitment to Camunda BPM Run, and thanks for spotting this issue! Unfortunately, we can not exclude all OPTIONS requests from authentication as the REST API offers some OPTIONS endpoints as well.

            The problem can be solved by making sure that the CORS filter is executed before the authentication filter.

            I will fix this now together with CAM-11885.

            Miklas Boskamp added a comment - hendy , thanks for your commitment to Camunda BPM Run, and thanks for spotting this issue! Unfortunately, we can not exclude all OPTIONS requests from authentication as the REST API offers some OPTIONS endpoints as well. The problem can be solved by making sure that the CORS filter is executed before the authentication filter. I will fix this now together with CAM-11885 .

            Hendy Irawan added a comment -

            Thanks miklas.boskamp, alright I understand. I think the fix in CorsFilter init parameters are still relevant, specifically:

            diff --git a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java
            index fc19641..bbda083 100644
            --- a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java
            +++ b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java
            @@ -76,6 +76,11 @@ public class CamundaBpmRunRestConfiguration {
                 registration.addUrlPatterns(restApiPathPattern);
             
                 registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_ORIGINS, camundaBpmRunProperties.getCors().getAllowedOrigins());
            +    if (!"*".equals(camundaBpmRunProperties.getCors().getAllowedOrigins())) {
            +      registration.addInitParameter(CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS, "true");
            +      registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_HEADERS,
            +        "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-XSRF-Token,X-CSRF-Token");
            +    }
                 return registration;
               }
            

            because without that, Chrome browser will fail preflight checks.

            Hendy Irawan added a comment - Thanks miklas.boskamp , alright I understand. I think the fix in CorsFilter init parameters are still relevant, specifically: diff --git a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java index fc19641..bbda083 100644 --- a/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java +++ b/distro/run/core/src/main/java/org/camunda/bpm/run/CamundaBpmRunRestConfiguration.java @@ -76,6 +76,11 @@ public class CamundaBpmRunRestConfiguration { registration.addUrlPatterns(restApiPathPattern); registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_ORIGINS, camundaBpmRunProperties.getCors().getAllowedOrigins()); + if (!"*".equals(camundaBpmRunProperties.getCors().getAllowedOrigins())) { + registration.addInitParameter(CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS, "true"); + registration.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_HEADERS, + "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-XSRF-Token,X-CSRF-Token"); + } return registration; } because without that, Chrome browser will fail preflight checks.

            hendy,

            please see my comment on CAM-11885. I don't think setting these parameters will be necessary because authentication will not be an issue for preflight requests anymore. If you feel otherwise, could you elaborate on why you think it is necessary to set Authorization, X-XSRF-Token, X-CSRF-Token" allowed headers and PARAM_CORS_SUPPORT_CREDENTIALS?

            Miklas Boskamp added a comment - hendy , please see my comment on CAM-11885 . I don't think setting these parameters will be necessary because authentication will not be an issue for preflight requests anymore. If you feel otherwise, could you elaborate on why you think it is necessary to set Authorization, X-XSRF-Token, X-CSRF-Token" allowed headers and PARAM_CORS_SUPPORT_CREDENTIALS ?

            Hendy Irawan added a comment -

            miklas.boskamp making preflight unauthenticated is only one part, this will make unauthenticated CORS work in the browser but authenticated CORS will still fail. If you don't configure these allow headers: (I've tested this, that's why I added these code in addition to making preflight unauthenticated)

            1. During preflight, CorsFilter does not set these allow headers
            2. Chrome gets a 200 OK for the preflight then checks the required allow headers
            3. Since the required headers do not fulfill requirements, Chrome fails the preflight and blocks sending the actual request

            Hendy Irawan added a comment - miklas.boskamp making preflight unauthenticated is only one part, this will make unauthenticated CORS work in the browser but authenticated CORS will still fail. If you don't configure these allow headers: (I've tested this, that's why I added these code in addition to making preflight unauthenticated) 1. During preflight, CorsFilter does not set these allow headers 2. Chrome gets a 200 OK for the preflight then checks the required allow headers 3. Since the required headers do not fulfill requirements, Chrome fails the preflight and blocks sending the actual request

            hendy, thanks again for the input. A first implementation fixes the authorization issue in preflight requests. We will not be able to prioritize adding the headers before the release of 7.13.
            I created CAM-11900 for this and we will take it into the 7.14 planning soon.

            Miklas Boskamp added a comment - hendy , thanks again for the input. A first implementation fixes the authorization issue in preflight requests. We will not be able to prioritize adding the headers before the release of 7.13. I created CAM-11900 for this and we will take it into the 7.14 planning soon.

              nikola.koevski Nikola Koevski
              hendy Hendy Irawan
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated:
                Resolved: