structurizr / ui

UI code for Structurizr Lite, on-premises, and cloud service.
https://docs.structurizr.com/ui
MIT License
43 stars 28 forks source link

Some firewalls block GET requests with a contentType header. #80

Closed hjkiefer closed 2 months ago

hjkiefer commented 2 months ago

Expected

It should be possible to run structurizr/lite as a stand-alone server on kubernetes for displaying single diagrams.

Actual

Running the server works, and I can actually display diagrams on our internal development environment. However, on some servers, it is not possible, since our firewall blocks GET requests with a contentType header.

Steps to reproduce

This is quite hard, since you need to deploy the server in front of a strict firewall.

Version/build information

Any version I have tried.

Severity

Minor

Priority

I'm willing to fix this myself and raise a PR (please confirm approach first)

More information

No response

hjkiefer commented 2 months ago

I have worked around the issue locally by removing the contentType header from the ApiController and UI code. This requires changes both in this repo and in the structurizr/ui repo.

I can raise PRs for both lite/onpremises and ui repos in order to mitigate the issue.

lite

--- a/src/main/java/com/structurizr/lite/web/ApiController.java
+++ b/src/main/java/com/structurizr/lite/web/ApiController.java
@@ -85,13 +85,7 @@ public class ApiController extends AbstractController {
                 throw new HttpUnauthorizedException("Incorrect API key");
             }

-            String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
-            if (StringUtils.isNullOrEmpty(contentType) || contentType.startsWith(";")) {
-                contentType = "";
-            } else if (!contentType.contains(" ")) {
-                String[] parts = contentType.split(";");
-                contentType = parts[0] + "; " + parts[1];
-            }
+            String contentType = "";

             String nonce = request.getHeader(HttpHeaders.NONCE);
             if (nonce == null || nonce.length() == 0) {

ui:

--- a/src/js/structurizr-client.js
+++ b/src/js/structurizr-client.js
@@ -28,10 +28,9 @@ structurizr.io.StructurizrApiClient = class StructurizrApiClient {
     getWorkspace(version, callback) {
         const self = this;
         const contentMd5 = CryptoJS.MD5("");
-        const contentType = '';
         const nonce = new Date().getTime();

-        const content = "GET" + "\n" + this.#getPath() + "/workspace/" + this.#workspaceId + "\n" + contentMd5 + "\n" + contentType + "\n" + nonce + "\n";
+        const content = "GET" + "\n" + this.#getPath() + "/workspace/" + this.#workspaceId + "\n" + contentMd5 + "\n" + "\n" + nonce + "\n";
         const hmac = CryptoJS.HmacSHA256(content, this.#apiSecret).toString(CryptoJS.enc.Hex);

         var url = this.#apiUrl + "/workspace/" + this.#workspaceId;
@@ -44,7 +43,6 @@ structurizr.io.StructurizrApiClient = class StructurizrApiClient {
             type: "GET",
             cache: false,
             headers: {
-                'Content-Type': contentType,
                 'Content-MD5': btoa(contentMd5),
                 'Nonce': nonce,
simonbrowndotje commented 2 months ago

The structurizr-ui repo is shared between Lite, the on-premises installation, and the cloud service, so any changes to the API code will in turn require changes to the Java client library (which is used by the Structurizr CLI), which will potentially affect backwards compatibility, so it isn't a change I really want to make. Which firewall are you using, and why can't you modify the firewall rules?

hjkiefer commented 2 months ago

It is a corporate firewall, which I am not in control of, unfortunately.

What are the things that needs to be tested for such a change to be merged?

I will attempt to get the traffic through, and maintain a branch locally with the above changes in the meantime.

hjkiefer commented 2 months ago

@simonbrowndotje I have looked further into this, and basically it comes down the OWASP CRS 3.2 ruleset, which blocks a request with a Content-Type header, which does not conform to a predefined pattern:

https://github.com/coreruleset/coreruleset/blob/154b89b694e1900083cb954b4c47d884ac7c59d0/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf#L956

The requests sent from the structurizr api client contains an empty content-type header, and if the above rule is applied, then the request is blocked.

I have attached a screenshot which shows the behavior: image

I don't quite understand the reply when I set the header to Content-Type: text/plain. If I don't pass on a header, then I get the expected reply with the structurizr json format.

simonbrowndotje commented 2 months ago

Thanks for the additional information - I've removed the empty header from GET requests, which should resolve the issue without the need to modify any of the server-side code.