diff --git a/.changelog/9847.txt b/.changelog/9847.txt new file mode 100644 index 0000000000..a3010258b3 --- /dev/null +++ b/.changelog/9847.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: support stricter content security policies +``` diff --git a/ui/packages/consul-ui/app/initializers/ivy-codemirror.js b/ui/packages/consul-ui/app/initializers/ivy-codemirror.js deleted file mode 100644 index c4f3436057..0000000000 --- a/ui/packages/consul-ui/app/initializers/ivy-codemirror.js +++ /dev/null @@ -1,11 +0,0 @@ -export function initialize(application) { - const IvyCodeMirrorComponent = application.resolveRegistration('component:ivy-codemirror'); - // Make sure ivy-codemirror respects/maintains a `name=""` attribute - IvyCodeMirrorComponent.reopen({ - attributeBindings: ['name'], - }); -} - -export default { - initialize, -}; diff --git a/ui/packages/consul-ui/app/instance-initializers/ivy-codemirror.js b/ui/packages/consul-ui/app/instance-initializers/ivy-codemirror.js new file mode 100644 index 0000000000..f347060efe --- /dev/null +++ b/ui/packages/consul-ui/app/instance-initializers/ivy-codemirror.js @@ -0,0 +1,30 @@ +/* globals CodeMirror */ +export function initialize(application) { + const appName = application.application.name; + const doc = application.lookup('service:-document'); + // pick codemirror syntax highlighting paths out of index.html + const fs = JSON.parse(doc.querySelector(`[data-${appName}-fs]`).textContent); + // configure syntax highlighting for CodeMirror + CodeMirror.modeURL = { + replace: function(n, mode) { + switch (mode) { + case 'javascript': + return fs['codemirror/mode/javascript/javascript.js']; + case 'ruby': + return fs['codemirror/mode/ruby/ruby.js']; + case 'yaml': + return fs['codemirror/mode/yaml/yaml.js']; + } + }, + }; + + const IvyCodeMirrorComponent = application.resolveRegistration('component:ivy-codemirror'); + // Make sure ivy-codemirror respects/maintains a `name=""` attribute + IvyCodeMirrorComponent.reopen({ + attributeBindings: ['name'], + }); +} + +export default { + initialize, +}; diff --git a/ui/packages/consul-ui/ember-cli-build.js b/ui/packages/consul-ui/ember-cli-build.js index c91ee00a2b..8312bdf778 100644 --- a/ui/packages/consul-ui/ember-cli-build.js +++ b/ui/packages/consul-ui/ember-cli-build.js @@ -37,6 +37,10 @@ module.exports = function(defaults) { plugins: ['@babel/plugin-proposal-object-rest-spread'], sourceMaps: sourcemaps ? 'inline' : false, }, + autoImport: { + // allows use of a CSP without 'unsafe-eval' directive + forbidEval: true, + }, codemirror: { keyMaps: ['sublime'], addonFiles: [ diff --git a/ui/packages/consul-ui/lib/startup/templates/body.html.js b/ui/packages/consul-ui/lib/startup/templates/body.html.js index cff576754d..10fa4cecd6 100644 --- a/ui/packages/consul-ui/lib/startup/templates/body.html.js +++ b/ui/packages/consul-ui/lib/startup/templates/body.html.js @@ -16,24 +16,20 @@ module.exports = ({ appName, environment, rootURL, config }) => ` } + ${environment === 'test' ? `` : ``} - ${ @@ -42,19 +38,5 @@ ${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.oper : `` } - ${environment === 'test' ? `` : ``} `; diff --git a/ui/packages/consul-ui/server/index.js b/ui/packages/consul-ui/server/index.js index 56f8bcebce..a2abdb55e8 100644 --- a/ui/packages/consul-ui/server/index.js +++ b/ui/packages/consul-ui/server/index.js @@ -25,6 +25,14 @@ module.exports = function(app, options) { } next(); }); + + // sets the base CSP policy for the UI + app.use(function(request, response, next) { + response.set({ + 'Content-Security-Policy': `default-src 'self' ws: localhost:${options.liveReloadPort} http: localhost:${options.liveReloadPort}; img-src 'self' data: ; style-src 'self' 'unsafe-inline'`, + }); + next(); + }); // Serve the coverage folder for easy viewing during development app.use('/coverage', express.static('coverage')); }; diff --git a/ui/packages/consul-ui/vendor/init.js b/ui/packages/consul-ui/vendor/init.js index 63d99761ff..7852b86490 100644 --- a/ui/packages/consul-ui/vendor/init.js +++ b/ui/packages/consul-ui/vendor/init.js @@ -1,4 +1,20 @@ (function(doc, appName) { + const fs = JSON.parse(doc.querySelector(`[data-${appName}-fs]`).textContent); + const appendScript = function(src) { + var $script = doc.createElement('script'); + $script.src = src; + doc.body.appendChild($script); + }; + + // polyfills + if (!('TextDecoder' in window)) { + appendScript(fs['text-encoding/encoding-indexes.js']); + appendScript(fs['text-encoding/encoding.js']); + } + if (!(window.CSS && window.CSS.escape)) { + appendScript(fs['css.escape/css.escape.js']); + } + try { const $appMeta = doc.querySelector(`[name="${appName}/config/environment"]`); // pick out the operatorConfig from our application/json script tag