The sonar web UI fails to load in Chrome if running behind istio with default settings

Sonarqube version

9.7.2 community.

Steps to reproduce

  • Open the web UI in a browser window with content security policy enabled
  • When prompted, enter the credentials

Expected behavior

The Sonarqube UI loads.

Actual behavior

The “Loading …” page with the rotating circle stays on forever. Errors appear in the browser’s debugger console.

A more technical description

The initial error message appearing in the browser console is:

Refused to execute inline script because it violates the following Content Security Policy directive: “script-src ‘self’”. Either the ‘unsafe-inline’ keyword, a hash (‘sha256-ba+QeiwMoR6CFSj9qTUv59/UFi4ZvVlinT0aq7bQvP0=’), or a nonce (‘nonce-…’) is required to enable inline execution.

This leaves most likely something un-initialized, since a more explicit error follows:

index.ts:105 Application failed to start! Error: sonar-ui-common init: web context needs to be initialized by Initializer.setUrlContext before being used
at Object.n.getUrlContext (init.js:62)
at t.submit (request.js:85)
at o (index.ts:75)
at index.ts:51
at new Promise ()
at Module.287 (index.ts:50)
at r (bootstrap:101)
at Object.285 (main.m.4ca8e4df.js:1)
at r (bootstrap:101)
at a (bootstrap:45)
i @ index.ts:105
(anonymous) @ index.ts:61
Promise.then (async)
287 @ index.ts:56
r @ bootstrap:101
285 @ main.m.4ca8e4df.js:1
r @ bootstrap:101
a @ bootstrap:45
(anonymous) @ bootstrap:238
(anonymous) @ main.m.4ca8e4df.js:1

What follows is a few more 404 errors caused by incorrectly generated script URLs (host.name being the host on which the web UI runs, and /sonarqube being the root path):

bootstrap:166 GET https://host.name/sonarqube/undefined/js/app.m.2c1363e2.chunk.js net::ERR_ABORTED 404
r.e @ bootstrap:166
j @ index.ts:81
287 @ index.ts:56
r @ bootstrap:101
285 @ main.m.4ca8e4df.js:1
r @ bootstrap:101
a @ bootstrap:45
(anonymous) @ bootstrap:238
(anonymous) @ main.m.4ca8e4df.js:1
bootstrap:166 GET https://host.name/sonarqube/undefined/js/11.m.33d9b55d.chunk.js net::ERR_ABORTED 404
r.e @ bootstrap:166
j @ index.ts:81
287 @ index.ts:56
r @ bootstrap:101
285 @ main.m.4ca8e4df.js:1
r @ bootstrap:101
a @ bootstrap:45
(anonymous) @ bootstrap:238
(anonymous) @ main.m.4ca8e4df.js:1
bootstrap:166 GET https://host.name/sonarqube/undefined/js/vendors-app.m.c1fec354.chunk.js net::ERR_ABORTED 404

When the application loads correctly (when I bypass the envoy proxy), the generated URLs don’t contain the /undefined path part. As far as I could research this, that’s because window.baseUrl is undefined, in the error case, and evaluates to https://host.name/sonarqube/ in the success case. The string undefined is interpreted as a relative URL, and as such gets appended to the page’s URL on requests to paths of the form undefined/js/some.script.js, resulting in https://host.name/sonarqube/undefined/js/some.script.js.

The reason for this is that envoy as configured by istio sets a content-security-policy header like this:

content-security-policy: frame-ancestors ‘none’; script-src ‘self’; object-src ‘none’;

This is a sane setting, added by istio by default. Inline scripts are considered inherently unsafe.

Error message 'Refused to execute inline script because… ’ means you have an inline <script>...</script> in the HTML code. Here you have opts:

  • add ‘sha256-ba+QeiwMoR6CFSj9qTUv59/UFi4ZvVlinT0aq7bQvP0=’ token into script-src directive. You have to modify istio setting of content-security-policy header. In case of upgrade Sonarqube this hash will change and you’ll have to adjust CSP respectively.
  • move code from <script>...</script> to external file and link it via <script src='file_name'>. In this case it will falls under script-src 'self' rule.

Also you can use 'nonce-value' instead of ‘sha256-ba+QeiwMoR6CFSj9qTUv59/UFi4ZvVlinT0aq7bQvP0=’, see how to get rid of error Refused to execute inline script.

I do not have an inline script - the sonarqube web UI has.

I have implemented a workaround in the meantime. Using a lua script envoyfilter istio resource (not trying to stack up buzzwords, that’s what it is) you can rewrite the envoy-generated CSP header.

The filter is not trivial, however, and slows down everything that passes through istio/envoy. And it’s a workaround that doesn’t attack the root cause.

The only way to fix the problem at its root is to change the sonarqube web UI so that it doesn’t use inline scripts anymore.

Since I don’t have a stake in this anymore, having found a workaround, I will un-watch this issue. I still think that fixing it would be useful. It’s such tiny issues that accumulate over time that eventually push people towards alternatives, which is why such tiny issues, IMO, should not be left behind.