diff options
author | softwarefactory-project-zuul[bot] <33884098+softwarefactory-project-zuul[bot]@users.noreply.github.com> | 2020-12-10 16:32:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-10 16:32:53 +0100 |
commit | 4c5757b3bd1c3dd2cd6ca6988cd99215dbb74c82 (patch) | |
tree | c8c25865a31f4171379198e9a6783a8de651d6c8 | |
parent | Merge pull request #8790 from rooftopcellist/quantity_not_exported (diff) | |
parent | FFox ESR 78 Compatibility (diff) | |
download | awx-4c5757b3bd1c3dd2cd6ca6988cd99215dbb74c82.tar.xz awx-4c5757b3bd1c3dd2cd6ca6988cd99215dbb74c82.zip |
Merge pull request #8754 from ryanpetrello/strict-csp
Introduce a strict Content-Security-Policy
Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
-rw-r--r-- | awx/settings/defaults.py | 1 | ||||
-rw-r--r-- | awx/ui/context_processors.py | 8 | ||||
-rw-r--r-- | awx/ui_next/package.json | 2 | ||||
-rw-r--r-- | awx/ui_next/public/index.html | 15 | ||||
-rw-r--r-- | awx/ui_next/src/index.jsx | 1 | ||||
-rw-r--r-- | awx/ui_next/src/screens/Job/JobOutput/JobEvent.jsx | 77 | ||||
-rw-r--r-- | awx/ui_next/src/screens/Job/JobOutput/JobEvent.test.jsx | 62 | ||||
-rw-r--r-- | awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx | 236 | ||||
-rw-r--r-- | awx/ui_next/src/setupCSP.js | 30 | ||||
-rw-r--r-- | awx/ui_next/src/setupTests.js | 5 | ||||
-rw-r--r-- | installer/roles/kubernetes/templates/configmap.yml.j2 | 2 | ||||
-rw-r--r-- | installer/roles/local_docker/templates/nginx.conf.j2 | 2 | ||||
-rw-r--r-- | tools/docker-compose/nginx.vh.default.conf | 4 |
13 files changed, 295 insertions, 150 deletions
diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 6204486456..d47277eaed 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -248,6 +248,7 @@ TEMPLATES = [ 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', + 'awx.ui.context_processors.csp', 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], diff --git a/awx/ui/context_processors.py b/awx/ui/context_processors.py new file mode 100644 index 0000000000..87c071c285 --- /dev/null +++ b/awx/ui/context_processors.py @@ -0,0 +1,8 @@ +import base64 +import os + + +def csp(request): + return { + 'csp_nonce': base64.encodebytes(os.urandom(32)).decode().rstrip(), + } diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index 252319a713..d223b47d29 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -53,7 +53,7 @@ }, "scripts": { "start": "PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start", - "build": "react-scripts build", + "build": "INLINE_RUNTIME_CHUNK=false react-scripts build", "test": "TZ='UTC' react-scripts test --coverage --watchAll=false", "test-watch": "TZ='UTC' react-scripts test", "eject": "react-scripts eject", diff --git a/awx/ui_next/public/index.html b/awx/ui_next/public/index.html index 2d7ff373b7..dc5174aa7c 100644 --- a/awx/ui_next/public/index.html +++ b/awx/ui_next/public/index.html @@ -1,6 +1,15 @@ <!DOCTYPE html> <html lang="en"> <head> + <% if (process.env.NODE_ENV === 'production') { %> + <script nonce="{{ csp_nonce }}" type="text/javascript"> + window.NONCE_ID = '{{ csp_nonce }}'; + </script> + <meta + http-equiv="Content-Security-Policy" + content="default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{{ csp_nonce }}' *.pendo.io; img-src 'self' *.pendo.io data:;" + /> + <% } %> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> @@ -12,6 +21,10 @@ </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> - <div id="app" style="height: 100%"></div> + <% if (process.env.NODE_ENV === 'production') { %> + <style nonce="{{ csp_nonce }}">.app{height: 100%;}</style><div id="app" class="app"></div> + <% } else { %> + <div id="app" style="height: 100%"></div> + <% } %> </body> </html> diff --git a/awx/ui_next/src/index.jsx b/awx/ui_next/src/index.jsx index ad616077ef..8bbeb9e866 100644 --- a/awx/ui_next/src/index.jsx +++ b/awx/ui_next/src/index.jsx @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import './setupCSP'; import '@patternfly/react-core/dist/styles/base.css'; import App from './App'; import { BrandName } from './variables'; diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobEvent.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobEvent.jsx index 59908f695c..29f02e8245 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/JobEvent.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/JobEvent.jsx @@ -1,6 +1,3 @@ -import Ansi from 'ansi-to-html'; -import hasAnsi from 'has-ansi'; -import { AllHtmlEntities } from 'html-entities'; import React from 'react'; import { JobEventLine, @@ -9,84 +6,18 @@ import { JobEventLineText, } from './shared'; -const EVENT_START_TASK = 'playbook_on_task_start'; -const EVENT_START_PLAY = 'playbook_on_play_start'; -const EVENT_STATS_PLAY = 'playbook_on_stats'; -const TIME_EVENTS = [EVENT_START_TASK, EVENT_START_PLAY, EVENT_STATS_PLAY]; - -const ansi = new Ansi({ - stream: true, - colors: { - 0: '#000', - 1: '#A30000', - 2: '#486B00', - 3: '#795600', - 4: '#00A', - 5: '#A0A', - 6: '#004368', - 7: '#AAA', - 8: '#555', - 9: '#F55', - 10: '#5F5', - 11: '#FF5', - 12: '#55F', - 13: '#F5F', - 14: '#5FF', - 15: '#FFF', - }, -}); -const entities = new AllHtmlEntities(); - -function getTimestamp({ created }) { - const date = new Date(created); - - const dateHours = date.getHours(); - const dateMinutes = date.getMinutes(); - const dateSeconds = date.getSeconds(); - - const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours; - const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes; - const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds; - - return `${stampHours}:${stampMinutes}:${stampSeconds}`; -} - -function getLineTextHtml({ created, event, start_line, stdout }) { - const sanitized = entities.encode(stdout); - return sanitized.split('\r\n').map((lineText, index) => { - let html; - if (hasAnsi(lineText)) { - html = ansi.toHtml(lineText); - } else { - html = lineText; - } - - if (index === 1 && TIME_EVENTS.includes(event)) { - const time = getTimestamp({ created }); - html += `<span class="time">${time}</span>`; - } - - return { - lineNumber: start_line + index, - html, - }; - }); -} - function JobEvent({ counter, - created, - event, - isClickable, - onJobEventClick, stdout, - start_line, style, type, + lineTextHtml, + isClickable, + onJobEventClick, }) { return !stdout ? null : ( <div style={style} type={type}> - {getLineTextHtml({ created, event, start_line, stdout }).map( + {lineTextHtml.map( ({ lineNumber, html }) => lineNumber >= 0 && ( <JobEventLine diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobEvent.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobEvent.test.jsx index baceb1c6f4..e76ae0a3ac 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/JobEvent.test.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/JobEvent.test.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; - import JobEvent from './JobEvent'; const mockOnPlayStartEvent = { @@ -24,23 +23,64 @@ const selectors = { lineText: 'JobEventLineText', }; +const singleDigitTimestampEvent = { + ...mockOnPlayStartEvent, + created: '2019-07-11T08:01:02.906001Z', +}; + +const mockSingleDigitTimestampEventLineTextHtml = [ + { lineNumber: 0, html: '' }, + { + lineNumber: 1, + html: + 'PLAY [add hosts to inventory] **************************************************<span class="time">08:01:02</span>', + }, +]; + +const mockAnsiLineTextHtml = [ + { + lineNumber: 4, + html: '<span class="output--1977390340">ok: [localhost]</span>', + }, +]; + +const mockOnPlayStartLineTextHtml = [ + { lineNumber: 0, html: '' }, + { + lineNumber: 1, + html: + 'PLAY [add hosts to inventory] **************************************************<span class="time">18:11:22</span>', + }, +]; + describe('<JobEvent />', () => { test('initially renders successfully', () => { - mountWithContexts(<JobEvent {...mockOnPlayStartEvent} />); + mountWithContexts( + <JobEvent + lineTextHtml={mockOnPlayStartLineTextHtml} + {...mockOnPlayStartEvent} + /> + ); }); test('playbook event timestamps are rendered', () => { - let wrapper = mountWithContexts(<JobEvent {...mockOnPlayStartEvent} />); + let wrapper = mountWithContexts( + <JobEvent + lineTextHtml={mockOnPlayStartLineTextHtml} + {...mockOnPlayStartEvent} + /> + ); let lineText = wrapper.find(selectors.lineText); expect( lineText.filterWhere(e => e.text().includes('18:11:22')) ).toHaveLength(1); - const singleDigitTimestampEvent = { - ...mockOnPlayStartEvent, - created: '2019-07-11T08:01:02.906001Z', - }; - wrapper = mountWithContexts(<JobEvent {...singleDigitTimestampEvent} />); + wrapper = mountWithContexts( + <JobEvent + lineTextHtml={mockSingleDigitTimestampEventLineTextHtml} + {...singleDigitTimestampEvent} + /> + ); lineText = wrapper.find(selectors.lineText); expect( lineText.filterWhere(e => e.text().includes('08:01:02')) @@ -48,12 +88,14 @@ describe('<JobEvent />', () => { }); test('ansi stdout colors are rendered as html', () => { - const wrapper = mountWithContexts(<JobEvent {...mockRunnerOnOkEvent} />); + const wrapper = mountWithContexts( + <JobEvent lineTextHtml={mockAnsiLineTextHtml} {...mockRunnerOnOkEvent} /> + ); const lineText = wrapper.find(selectors.lineText); expect( lineText .html() - .includes('<span style="color:#486B00">ok: [localhost]</span>') + .includes('<span class="output--1977390340">ok: [localhost]</span>') ).toBe(true); }); diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx index 9c6b324891..4d78d3b364 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { withRouter } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -10,6 +10,9 @@ import { InfiniteLoader, List, } from 'react-virtualized'; +import Ansi from 'ansi-to-html'; +import hasAnsi from 'has-ansi'; +import { AllHtmlEntities } from 'html-entities'; import AlertModal from '../../../components/AlertModal'; import { CardBody } from '../../../components/Card'; @@ -32,6 +35,106 @@ import { AdHocCommandsAPI, } from '../../../api'; +const EVENT_START_TASK = 'playbook_on_task_start'; +const EVENT_START_PLAY = 'playbook_on_play_start'; +const EVENT_STATS_PLAY = 'playbook_on_stats'; +const TIME_EVENTS = [EVENT_START_TASK, EVENT_START_PLAY, EVENT_STATS_PLAY]; + +const ansi = new Ansi({ + stream: true, + colors: { + 0: '#000', + 1: '#A30000', + 2: '#486B00', + 3: '#795600', + 4: '#00A', + 5: '#A0A', + 6: '#004368', + 7: '#AAA', + 8: '#555', + 9: '#F55', + 10: '#5F5', + 11: '#FF5', + 12: '#55F', + 13: '#F5F', + 14: '#5FF', + 15: '#FFF', + }, +}); +const entities = new AllHtmlEntities(); + +function getTimestamp({ created }) { + const date = new Date(created); + + const dateHours = date.getHours(); + const dateMinutes = date.getMinutes(); + const dateSeconds = date.getSeconds(); + + const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours; + const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes; + const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds; + + return `${stampHours}:${stampMinutes}:${stampSeconds}`; +} + +const styleAttrPattern = new RegExp('style="[^"]*"', 'g'); + +function createStyleAttrHash(styleAttr) { + let hash = 0; + for (let i = 0; i < styleAttr.length; i++) { + hash = (hash << 5) - hash; // eslint-disable-line no-bitwise + hash += styleAttr.charCodeAt(i); + hash &= hash; // eslint-disable-line no-bitwise + } + return `${hash}`; +} + +function replaceStyleAttrs(html) { + const allStyleAttrs = [...new Set(html.match(styleAttrPattern))]; + const cssMap = {}; + let result = html; + for (let i = 0; i < allStyleAttrs.length; i++) { + const styleAttr = allStyleAttrs[i]; + const cssClassName = `output-${createStyleAttrHash(styleAttr)}`; + + cssMap[cssClassName] = styleAttr.replace('style="', '').slice(0, -1); + result = result.split(styleAttr).join(`class="${cssClassName}"`); + } + return { cssMap, result }; +} + +function getLineTextHtml({ created, event, start_line, stdout }) { + const sanitized = entities.encode(stdout); + let lineCssMap = {}; + const lineTextHtml = []; + + sanitized.split('\r\n').forEach((lineText, index) => { + let html; + if (hasAnsi(lineText)) { + const { cssMap, result } = replaceStyleAttrs(ansi.toHtml(lineText)); + html = result; + lineCssMap = { ...lineCssMap, ...cssMap }; + } else { + html = lineText; + } + + if (index === 1 && TIME_EVENTS.includes(event)) { + const time = getTimestamp({ created }); + html += `<span class="time">${time}</span>`; + } + + lineTextHtml.push({ + lineNumber: start_line + index, + html, + }); + }); + + return { + lineCssMap, + lineTextHtml, + }; +} + const HeaderTitle = styled.div` display: inline-flex; align-items: center; @@ -54,6 +157,8 @@ const OutputWrapper = styled.div` font-size: 15px; height: calc(100vh - 350px); outline: 1px solid #d7d7d7; + ${({ cssMap }) => + Object.keys(cssMap).map(className => `.${className}{${cssMap[className]}}`)} `; const OutputFooter = styled.div` @@ -122,6 +227,7 @@ class JobOutput extends Component { remoteRowCount: 0, isHostModalOpen: false, hostEvent: {}, + cssMap: {}, }; this.cache = new CellMeasurerCache({ @@ -164,7 +270,7 @@ class JobOutput extends Component { componentDidUpdate(prevProps, prevState) { // recompute row heights for any job events that have transitioned // from loading to loaded - const { currentlyLoading } = this.state; + const { currentlyLoading, cssMap } = this.state; let shouldRecomputeRowHeights = false; prevState.currentlyLoading .filter(n => !currentlyLoading.includes(n)) @@ -172,6 +278,9 @@ class JobOutput extends Component { shouldRecomputeRowHeights = true; this.cache.clear(n); }); + if (Object.keys(cssMap).length !== Object.keys(prevState.cssMap).length) { + shouldRecomputeRowHeights = true; + } if (shouldRecomputeRowHeights) { if (this.listRef.recomputeRowHeights) { this.listRef.recomputeRowHeights(); @@ -300,6 +409,13 @@ class JobOutput extends Component { return isHost; }; + let actualLineTextHtml = []; + if (results[index]) { + const { lineTextHtml, lineCssMap } = getLineTextHtml(results[index]); + this.setState(({ cssMap }) => ({ cssMap: { ...cssMap, ...lineCssMap } })); + actualLineTextHtml = lineTextHtml; + } + return ( <CellMeasurer key={key} @@ -314,6 +430,7 @@ class JobOutput extends Component { onJobEventClick={() => this.handleHostEventClick(results[index])} className="row" style={style} + lineTextHtml={actualLineTextHtml} {...results[index]} /> ) : ( @@ -389,7 +506,9 @@ class JobOutput extends Component { handleResize({ width }) { if (width !== this._previousWidth) { this.cache.clearAll(); - this.listRef.recomputeRowHeights(); + if (this.listRef?.recomputeRowHeights) { + this.listRef.recomputeRowHeights(); + } } this._previousWidth = width; } @@ -404,6 +523,7 @@ class JobOutput extends Component { hostEvent, isHostModalOpen, remoteRowCount, + cssMap, } = this.state; if (hasContentLoading) { @@ -415,60 +535,62 @@ class JobOutput extends Component { } return ( - <CardBody> - {isHostModalOpen && ( - <HostEventModal - onClose={this.handleHostModalClose} - isOpen={isHostModalOpen} - hostEvent={hostEvent} + <Fragment> + <CardBody> + {isHostModalOpen && ( + <HostEventModal + onClose={this.handleHostModalClose} + isOpen={isHostModalOpen} + hostEvent={hostEvent} + /> + )} + <OutputHeader> + <HeaderTitle> + <StatusIcon status={job.status} /> + <h1>{job.name}</h1> + </HeaderTitle> + <OutputToolbar job={job} onDelete={this.handleDeleteJob} /> + </OutputHeader> + <HostStatusBar counts={job.host_status_counts} /> + <PageControls + onScrollFirst={this.handleScrollFirst} + onScrollLast={this.handleScrollLast} + onScrollNext={this.handleScrollNext} + onScrollPrevious={this.handleScrollPrevious} /> - )} - <OutputHeader> - <HeaderTitle> - <StatusIcon status={job.status} /> - <h1>{job.name}</h1> - </HeaderTitle> - <OutputToolbar job={job} onDelete={this.handleDeleteJob} /> - </OutputHeader> - <HostStatusBar counts={job.host_status_counts} /> - <PageControls - onScrollFirst={this.handleScrollFirst} - onScrollLast={this.handleScrollLast} - onScrollNext={this.handleScrollNext} - onScrollPrevious={this.handleScrollPrevious} - /> - <OutputWrapper> - <InfiniteLoader - isRowLoaded={this.isRowLoaded} - loadMoreRows={this.loadMoreRows} - rowCount={remoteRowCount} - > - {({ onRowsRendered, registerChild }) => ( - <AutoSizer onResize={this.handleResize}> - {({ width, height }) => { - return ( - <List - ref={ref => { - this.listRef = ref; - registerChild(ref); - }} - deferredMeasurementCache={this.cache} - height={height || 1} - onRowsRendered={onRowsRendered} - rowCount={remoteRowCount} - rowHeight={this.cache.rowHeight} - rowRenderer={this.rowRenderer} - scrollToAlignment="start" - width={width || 1} - overscanRowCount={20} - /> - ); - }} - </AutoSizer> - )} - </InfiniteLoader> - <OutputFooter /> - </OutputWrapper> + <OutputWrapper cssMap={cssMap}> + <InfiniteLoader + isRowLoaded={this.isRowLoaded} + loadMoreRows={this.loadMoreRows} + rowCount={remoteRowCount} + > + {({ onRowsRendered, registerChild }) => ( + <AutoSizer nonce={window.NONCE_ID} onResize={this.handleResize}> + {({ width, height }) => { + return ( + <List + ref={ref => { + this.listRef = ref; + registerChild(ref); + }} + deferredMeasurementCache={this.cache} + height={height || 1} + onRowsRendered={onRowsRendered} + rowCount={remoteRowCount} + rowHeight={this.cache.rowHeight} + rowRenderer={this.rowRenderer} + scrollToAlignment="start" + width={width || 1} + overscanRowCount={20} + /> + ); + }} + </AutoSizer> + )} + </InfiniteLoader> + <OutputFooter /> + </OutputWrapper> + </CardBody> {deletionError && ( <AlertModal isOpen={deletionError} @@ -480,7 +602,7 @@ class JobOutput extends Component { <ErrorDetail error={deletionError} /> </AlertModal> )} - </CardBody> + </Fragment> ); } } diff --git a/awx/ui_next/src/setupCSP.js b/awx/ui_next/src/setupCSP.js new file mode 100644 index 0000000000..77d40c5775 --- /dev/null +++ b/awx/ui_next/src/setupCSP.js @@ -0,0 +1,30 @@ +/* eslint-disable */ + +// Set a special variable to add `nonce` attributes to all styles/script tags +// See https://github.com/webpack/webpack/pull/3210 +__webpack_nonce__ = window.NONCE_ID; + +// Send report when a CSP violation occurs +// See: https://w3c.github.io/webappsec-csp/2/#violation-reports +// See: https://developer.mozilla.org/en-US/docs/Web/API/SecurityPolicyViolationEvent +document.addEventListener('securitypolicyviolation', e => { + const violation = { + 'csp-report': { + 'blocked-uri': e.blockedURI, + 'document-uri': e.documentURI, + 'effective-directive': e.effectiveDirective, + 'original-policy': e.originalPolicy, + referrer: e.referrer, + 'status-code': e.statusCode, + 'violated-directive': e.violatedDirective, + }, + }; + if (e.sourceFile) violation['csp-report']['source-file'] = e.sourceFile; + if (e.lineNumber) violation['csp-report']['line-number'] = e.lineNumber; + if (e.columnNumber) violation['csp-report']['column-number'] = e.columnNumber; + + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/csp-violation/', true); + xhr.setRequestHeader('content-type', 'application/csp-report'); + xhr.send(JSON.stringify(violation)); +}); diff --git a/awx/ui_next/src/setupTests.js b/awx/ui_next/src/setupTests.js index 7d59ff1a4c..5518d62c93 100644 --- a/awx/ui_next/src/setupTests.js +++ b/awx/ui_next/src/setupTests.js @@ -19,3 +19,8 @@ global.console = { ...console, debug: jest.fn(), }; + +// This global variable is part of our Content Security Policy framework +// and so this mock ensures that we don't encounter a reference error +// when running the tests +global.__webpack_nonce__ = null; diff --git a/installer/roles/kubernetes/templates/configmap.yml.j2 b/installer/roles/kubernetes/templates/configmap.yml.j2 index b7553811c1..b239b96783 100644 --- a/installer/roles/kubernetes/templates/configmap.yml.j2 +++ b/installer/roles/kubernetes/templates/configmap.yml.j2 @@ -69,8 +69,6 @@ data: # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) add_header Strict-Transport-Security max-age=15768000; - add_header Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; - add_header X-Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; # Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009) add_header X-Frame-Options "DENY"; diff --git a/installer/roles/local_docker/templates/nginx.conf.j2 b/installer/roles/local_docker/templates/nginx.conf.j2 index 0c93510bc9..327b59a2fe 100644 --- a/installer/roles/local_docker/templates/nginx.conf.j2 +++ b/installer/roles/local_docker/templates/nginx.conf.j2 @@ -67,8 +67,6 @@ http { # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) add_header Strict-Transport-Security max-age=15768000; - add_header Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; - add_header X-Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; # Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009) add_header X-Frame-Options "DENY"; diff --git a/tools/docker-compose/nginx.vh.default.conf b/tools/docker-compose/nginx.vh.default.conf index ff7f604b5e..73a4d1cd8d 100644 --- a/tools/docker-compose/nginx.vh.default.conf +++ b/tools/docker-compose/nginx.vh.default.conf @@ -22,8 +22,6 @@ server { # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) add_header Strict-Transport-Security max-age=15768000; - add_header Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; - add_header X-Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; location /static/ { root /awx_devel; @@ -84,8 +82,6 @@ server { # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) add_header Strict-Transport-Security max-age=15768000; - add_header Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; - add_header X-Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' *.pendo.io; img-src 'self' *.pendo.io data:; report-uri /csp-violation/"; location /static/ { root /awx_devel; |