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)