diff --git a/docs/setup/production.asciidoc b/docs/setup/production.asciidoc index 132ad53369294e..1d76fea8c294ac 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 374d6fef590bda..7d9ee6b98a463d 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 ce2d52502abab8..7bef8d84bfae86 100644 --- a/src/server/config/schema.js +++ b/src/server/config/schema.js @@ -95,6 +95,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..c695bad775e856 100644 --- a/src/ui/ui_render/bootstrap/template.js.hbs +++ b/src/ui/ui_render/bootstrap/template.js.hbs @@ -1,59 +1,70 @@ -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; +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'; - function loadStyleSheet(path) { - var dom = document.createElement('link'); + window.onload = function () { + var files = [ + '{{dllBundlePath}}/vendors.bundle.dll.js', + '{{regularBundlePath}}/commons.bundle.js', + '{{regularBundlePath}}/{{appId}}.bundle.js' + ]; - dom.addEventListener('error', failure); - dom.setAttribute('rel', 'stylesheet'); - dom.setAttribute('href', path); - document.head.appendChild(dom); - } + var failure = function () { + // make subsequent calls to failure() noop + failure = function () {}; - function createJavascriptElement(path) { - var dom = document.createElement('script'); + 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; - dom.setAttribute('async', ''); - dom.addEventListener('error', failure); - dom.setAttribute('src', file); - dom.addEventListener('load', next); - document.head.appendChild(dom); - } + 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}} + {{#each styleSheetPaths}} + loadStyleSheet('{{this}}'); + {{/each}} - (function next() { - var file = files.shift(); - if (!file) return; + (function next() { + var file = files.shift(); + if (!file) return; - var dom = document.createElement('script'); + 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); - }()); -}; + 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 9b9403115a7c73..5d86404042c623 100644 --- a/src/ui/ui_render/ui_render_mixin.js +++ b/src/ui/ui_render/ui_render_mixin.js @@ -181,6 +181,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 4252f8f0af31cc..34b8f5cf6acf7a 100644 --- a/src/ui/ui_render/views/ui_app.pug +++ b/src/ui/ui_render/views/ui_app.pug @@ -16,12 +16,21 @@ block content background-color: #F5F7FA; } + .kibanaWelcomeTitle { + color: #000; + font-size: 20px; + font-family: Sans-serif; + margin-top: 20px; + animation: fadeIn 1s ease-in-out; + animation-fill-mode: forwards; + opacity: 0; + animation-delay: 1.0s; + } + .kibanaWelcomeText { - color: #222; 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 +44,10 @@ block content margin-top: 40px; } + .kibanaLoaderWrap + * { + margin-top: 24px; + } + .kibanaLoader { height: 128px; width: 128px; @@ -101,8 +114,7 @@ block content } } - - .kibanaWelcomeView + .kibanaWelcomeView(id="kbn_loading_message", style="display: none;") .kibanaLoaderWrap .kibanaLoader .kibanaWelcomeLogoCircle @@ -110,6 +122,22 @@ 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 + h2.kibanaWelcomeTitle + | #{i18n('common.ui.legacyBrowserTitle', { defaultMessage: 'Please upgrade your browser' })} + .kibanaWelcomeText + | #{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 + // 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)