From e413f0561a5cb3c8bc61c5403568305ce0a73e83 Mon Sep 17 00:00:00 2001 From: Court Ewing Date: Fri, 1 Feb 2019 13:17:37 -0500 Subject: [PATCH 1/7] csp: optional strict mode to block insecure browsers A content security policy is a great addition to the protections built into Kibana, but it's not effective in older browsers (like IE11) that do not enforce the policy. When CSP strict mode is enabled, right before the Kibana app is bootstrapped, a basic safety check is performed to see if "naked" inline scripts are rejected. If inline scripting is allowed by the browser, then an error message is presented to the user and Kibana never attempts to bootstrap. --- docs/setup/production.asciidoc | 20 ++++ docs/setup/settings.asciidoc | 2 + src/server/config/schema.js | 1 + src/ui/ui_render/bootstrap/template.js.hbs | 126 +++++++++++---------- src/ui/ui_render/ui_render_mixin.js | 1 + src/ui/ui_render/views/ui_app.pug | 17 ++- 6 files changed, 106 insertions(+), 61 deletions(-) diff --git a/docs/setup/production.asciidoc b/docs/setup/production.asciidoc index 94b1e1fb339fc4..2297e53a0bebe0 100644 --- a/docs/setup/production.asciidoc +++ b/docs/setup/production.asciidoc @@ -2,6 +2,7 @@ == Using Kibana in a production environment * <> +* <> * <> * <> @@ -36,6 +37,25 @@ which users can load which dashboards. For information about setting up Kibana users, see {kibana-ref}/using-kibana-with-security.html[Configuring security in Kibana]. +[float] +[[csp-strict-mode]] +=== Require Content Security Policy + +Kibana uses a Content Security Policy to help prevent the browser from allowing +unsafe scripting, but older browsers will silently ignore this policy. If your +organization does not need to support Internet Explorer 11 or much older +versions of our other supported browsers, we recommend that you enable Kibana's +`strict` mode for content security policy, which will block access to Kibana +for any browser that does not enforce even a rudimentary set of CSP +protections. + +To do this, set `csp.strict` to `true` in your `kibana.yml`: + +-------- +csp.strict: true +-------- + + [float] [[enabling-ssl]] === Enabling SSL diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 07a2cd07700888..3fbb98183ba7b7 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -21,6 +21,8 @@ you'll need to update your `kibana.yml` file. You can also enable SSL and set a `csp.rules:`:: A template https://w3c.github.io/webappsec-csp/[content-security-policy] that disables certain unnecessary and potentially insecure capabilities in the browser. All instances of `{nonce}` will be replaced with an automatically generated nonce at load time. We strongly recommend that you keep the default CSP rules that ship with Kibana. +`csp.strict:`:: *Default: `false`* Blocks access to Kibana to any browser that does not enforce even rudimentary CSP rules. In practice, this will disable support for older, less safe browsers like Internet Explorer. + `elasticsearch.customHeaders:`:: *Default: `{}`* Header names and values to send to Elasticsearch. Any custom headers cannot be overwritten by client-side headers, regardless of the `elasticsearch.requestHeadersWhitelist` configuration. diff --git a/src/server/config/schema.js b/src/server/config/schema.js index 7ffa052d47797e..c0c00acc536740 100644 --- a/src/server/config/schema.js +++ b/src/server/config/schema.js @@ -97,6 +97,7 @@ export default () => Joi.object({ csp: Joi.object({ rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_RULES), + strict: Joi.boolean().default(false), }).default(), cpu: Joi.object({ diff --git a/src/ui/ui_render/bootstrap/template.js.hbs b/src/ui/ui_render/bootstrap/template.js.hbs index f25e4db3ec35f4..7c41450c648994 100644 --- a/src/ui/ui_render/bootstrap/template.js.hbs +++ b/src/ui/ui_render/bootstrap/template.js.hbs @@ -1,59 +1,67 @@ -window.onload = function () { - var files = [ - '{{dllBundlePath}}/vendors.bundle.dll.js', - '{{regularBundlePath}}/commons.bundle.js', - '{{regularBundlePath}}/{{appId}}.bundle.js' - ]; - - var failure = function () { - // make subsequent calls to failure() noop - failure = function () {}; - - var err = document.createElement('h1'); - err.style['color'] = 'white'; - err.style['font-family'] = 'monospace'; - err.style['text-align'] = 'center'; - err.style['background'] = '#F44336'; - err.style['padding'] = '25px'; - err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage; - - document.body.innerHTML = err.outerHTML; - } - - function loadStyleSheet(path) { - var dom = document.createElement('link'); - - dom.addEventListener('error', failure); - dom.setAttribute('rel', 'stylesheet'); - dom.setAttribute('href', path); - document.head.appendChild(dom); - } - - function createJavascriptElement(path) { - var dom = document.createElement('script'); - - dom.setAttribute('async', ''); - dom.addEventListener('error', failure); - dom.setAttribute('src', file); - dom.addEventListener('load', next); - document.head.appendChild(dom); - } - - {{#each styleSheetPaths}} - loadStyleSheet('{{this}}'); - {{/each}} - - (function next() { - var file = files.shift(); - if (!file) return; - - var dom = document.createElement('script'); - - dom.setAttribute('async', ''); - dom.setAttribute('nonce', window.__webpack_nonce__); - dom.addEventListener('error', failure); - dom.setAttribute('src', file); - dom.addEventListener('load', next); - document.head.appendChild(dom); - }()); -}; +if (window.__kbnStrictCsp && window.__kbnCspNotEnforced__) { + var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); + legacyBrowserError.style = 'display: flex;' +} else { + var loadingMessage = document.getElementById('kbn_loading_message'); + loadingMessage.style = 'display: flex;' + + window.onload = function () { + var files = [ + '{{dllBundlePath}}/vendors.bundle.dll.js', + '{{regularBundlePath}}/commons.bundle.js', + '{{regularBundlePath}}/{{appId}}.bundle.js' + ]; + + var failure = function () { + // make subsequent calls to failure() noop + failure = function () {}; + + var err = document.createElement('h1'); + err.style['color'] = 'white'; + err.style['font-family'] = 'monospace'; + err.style['text-align'] = 'center'; + err.style['background'] = '#F44336'; + err.style['padding'] = '25px'; + err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage; + + document.body.innerHTML = err.outerHTML; + } + + function loadStyleSheet(path) { + var dom = document.createElement('link'); + + dom.addEventListener('error', failure); + dom.setAttribute('rel', 'stylesheet'); + dom.setAttribute('href', path); + document.head.appendChild(dom); + } + + function createJavascriptElement(path) { + var dom = document.createElement('script'); + + dom.setAttribute('async', ''); + dom.addEventListener('error', failure); + dom.setAttribute('src', file); + dom.addEventListener('load', next); + document.head.appendChild(dom); + } + + {{#each styleSheetPaths}} + loadStyleSheet('{{this}}'); + {{/each}} + + (function next() { + var file = files.shift(); + if (!file) return; + + var dom = document.createElement('script'); + + dom.setAttribute('async', ''); + dom.setAttribute('nonce', window.__webpack_nonce__); + dom.addEventListener('error', failure); + dom.setAttribute('src', file); + dom.addEventListener('load', next); + document.head.appendChild(dom); + }()); + }; +} diff --git a/src/ui/ui_render/ui_render_mixin.js b/src/ui/ui_render/ui_render_mixin.js index 410aa3a6743f94..70fd67f2925064 100644 --- a/src/ui/ui_render/ui_render_mixin.js +++ b/src/ui/ui_render/ui_render_mixin.js @@ -217,6 +217,7 @@ export function uiRenderMixin(kbnServer, server, config) { const response = h.view('ui_app', { nonce, + strictCsp: config.get('csp.strict'), uiPublicUrl: `${basePath}/ui`, bootstrapScriptUrl: `${basePath}/bundles/app/${app.getId()}/bootstrap.js`, i18n: (id, options) => i18n.translate(id, options), diff --git a/src/ui/ui_render/views/ui_app.pug b/src/ui/ui_render/views/ui_app.pug index e7f30318072c95..10ae572da6f992 100644 --- a/src/ui/ui_render/views/ui_app.pug +++ b/src/ui/ui_render/views/ui_app.pug @@ -101,8 +101,7 @@ block content } } - - .kibanaWelcomeView + .kibanaWelcomeView(id="kbn_loading_message", style="display: none;") .kibanaLoaderWrap .kibanaLoader .kibanaWelcomeLogoCircle @@ -110,6 +109,20 @@ block content .kibanaWelcomeText(data-error-message=i18n('common.ui.welcomeErrorMessage', { defaultMessage: 'Kibana did not load properly. Check the server output for more information.' })) | #{i18n('common.ui.welcomeMessage', { defaultMessage: 'Loading Kibana' })} + .kibanaWelcomeView(id="kbn_legacy_browser_error", style="display: none;") + .kibanaLoaderWrap + .kibanaWelcomeLogoCircle + .kibanaWelcomeLogo + .kibanaWelcomeText + | #{i18n('common.ui.legacyBrowserMessage', { defaultMessage: 'Your browser version does not meet the required security capabilities of this application.' })} + + script. + // Since this script tag does not contain a nonce, this code will not run + // in browsers that support content security policy(CSP). This is + // intentional as we check for the existence of __kbnCspNotEnforced__ in + // bootstrap. + window.__kbnCspNotEnforced__ = true; script(nonce=nonce). + window.__kbnStrictCsp = !{strictCsp}; window.__webpack_nonce__ = '!{nonce}'; script(src=bootstrapScriptUrl, nonce=nonce) From e4c83f36221166202158179dbfe7649d337e3d4b Mon Sep 17 00:00:00 2001 From: Court Ewing Date: Fri, 1 Feb 2019 20:44:54 -0500 Subject: [PATCH 2/7] fix blank page in IE --- src/ui/ui_render/bootstrap/template.js.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/ui_render/bootstrap/template.js.hbs b/src/ui/ui_render/bootstrap/template.js.hbs index 7c41450c648994..726dbb43055020 100644 --- a/src/ui/ui_render/bootstrap/template.js.hbs +++ b/src/ui/ui_render/bootstrap/template.js.hbs @@ -1,9 +1,9 @@ if (window.__kbnStrictCsp && window.__kbnCspNotEnforced__) { var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); - legacyBrowserError.style = 'display: flex;' + legacyBrowserError.style.display = 'flex'; } else { var loadingMessage = document.getElementById('kbn_loading_message'); - loadingMessage.style = 'display: flex;' + loadingMessage.style.display = 'flex'; window.onload = function () { var files = [ From c8571c2c3b69518922e3f5e725f35cea6f3967c5 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 1 Feb 2019 18:58:21 -0800 Subject: [PATCH 3/7] some minor design cleanup and copy --- src/ui/ui_render/views/ui_app.pug | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ui/ui_render/views/ui_app.pug b/src/ui/ui_render/views/ui_app.pug index 10ae572da6f992..98a2c79b99d3c1 100644 --- a/src/ui/ui_render/views/ui_app.pug +++ b/src/ui/ui_render/views/ui_app.pug @@ -16,12 +16,23 @@ block content background-color: #{darkMode ? '#242424' : '#F5F7FA'}; } + .kibanaWelcomeTitle { + color: #000; + font-size: 20px; + font-family: Sans-serif; + margin-top: 20px; + color: #000; + animation: fadeIn 1s ease-in-out; + animation-fill-mode: forwards; + opacity: 0; + animation-delay: 1.0s; + } + .kibanaWelcomeText { - color: #222; + color: #; font-size: 14px; font-family: Sans-serif; - margin-top: 20px; - color: #666; + color: #343741; animation: fadeIn 1s ease-in-out; animation-fill-mode: forwards; opacity: 0; @@ -35,6 +46,10 @@ block content margin-top: 40px; } + .kibanaLoaderWrap + * { + margin-top: 24px; + } + .kibanaLoader { height: 128px; width: 128px; @@ -113,8 +128,10 @@ block content .kibanaLoaderWrap .kibanaWelcomeLogoCircle .kibanaWelcomeLogo + h2.kibanaWelcomeTitle + | #{i18n('common.ui.legacyBrowserTitle', { defaultMessage: 'Please upgrade your browser' })} .kibanaWelcomeText - | #{i18n('common.ui.legacyBrowserMessage', { defaultMessage: 'Your browser version does not meet the required security capabilities of this application.' })} + | #{i18n('common.ui.legacyBrowserMessage', { defaultMessage: 'This Kibana installation has strict security requirements enabled that your current browser does not meet.' })} script. // Since this script tag does not contain a nonce, this code will not run From f5c2a779166d176a71262137c341fcba3f419ce2 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 1 Feb 2019 19:02:11 -0800 Subject: [PATCH 4/7] replace dupe --- src/ui/ui_render/views/ui_app.pug | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/ui_render/views/ui_app.pug b/src/ui/ui_render/views/ui_app.pug index 98a2c79b99d3c1..4fba50ce9f7c45 100644 --- a/src/ui/ui_render/views/ui_app.pug +++ b/src/ui/ui_render/views/ui_app.pug @@ -21,7 +21,6 @@ block content font-size: 20px; font-family: Sans-serif; margin-top: 20px; - color: #000; animation: fadeIn 1s ease-in-out; animation-fill-mode: forwards; opacity: 0; From 8d06908f5f3d92c97b5f74e82fe45a8e37eaa16b Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 1 Feb 2019 19:03:43 -0800 Subject: [PATCH 5/7] fix other dupe color prop --- src/ui/ui_render/views/ui_app.pug | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/ui_render/views/ui_app.pug b/src/ui/ui_render/views/ui_app.pug index 4fba50ce9f7c45..7ea341272dcf26 100644 --- a/src/ui/ui_render/views/ui_app.pug +++ b/src/ui/ui_render/views/ui_app.pug @@ -28,7 +28,6 @@ block content } .kibanaWelcomeText { - color: #; font-size: 14px; font-family: Sans-serif; color: #343741; From 8a7dfce5efb59a572bace37625d698851a200f3d Mon Sep 17 00:00:00 2001 From: Court Ewing Date: Sat, 2 Feb 2019 20:05:15 -0500 Subject: [PATCH 6/7] underscores on both sides for consistency --- src/ui/ui_render/bootstrap/template.js.hbs | 2 +- src/ui/ui_render/views/ui_app.pug | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/ui_render/bootstrap/template.js.hbs b/src/ui/ui_render/bootstrap/template.js.hbs index 726dbb43055020..cac8dd9be5ff50 100644 --- a/src/ui/ui_render/bootstrap/template.js.hbs +++ b/src/ui/ui_render/bootstrap/template.js.hbs @@ -1,4 +1,4 @@ -if (window.__kbnStrictCsp && window.__kbnCspNotEnforced__) { +if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); legacyBrowserError.style.display = 'flex'; } else { diff --git a/src/ui/ui_render/views/ui_app.pug b/src/ui/ui_render/views/ui_app.pug index 7ea341272dcf26..11466e10bc95f5 100644 --- a/src/ui/ui_render/views/ui_app.pug +++ b/src/ui/ui_render/views/ui_app.pug @@ -138,6 +138,6 @@ block content // bootstrap. window.__kbnCspNotEnforced__ = true; script(nonce=nonce). - window.__kbnStrictCsp = !{strictCsp}; + window.__kbnStrictCsp__ = !{strictCsp}; window.__webpack_nonce__ = '!{nonce}'; script(src=bootstrapScriptUrl, nonce=nonce) From 4ad7fae650c6f3eeaa250c59a0e37d8fa17f2c97 Mon Sep 17 00:00:00 2001 From: Court Ewing Date: Sat, 2 Feb 2019 20:32:28 -0500 Subject: [PATCH 7/7] log message to prevent worldwide panic --- src/ui/ui_render/bootstrap/template.js.hbs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ui/ui_render/bootstrap/template.js.hbs b/src/ui/ui_render/bootstrap/template.js.hbs index cac8dd9be5ff50..c695bad775e856 100644 --- a/src/ui/ui_render/bootstrap/template.js.hbs +++ b/src/ui/ui_render/bootstrap/template.js.hbs @@ -2,6 +2,9 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); legacyBrowserError.style.display = 'flex'; } else { + if (!window.__kbnCspNotEnforced__ && window.console) { + window.console.log("^ A single error about an inline script not firing due to content security policy is expected!"); + } var loadingMessage = document.getElementById('kbn_loading_message'); loadingMessage.style.display = 'flex';