Merge pull request #8754 from ryanpetrello/strict-csp

Introduce a strict Content-Security-Policy

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-12-10 15:32:53 +00:00
committed by GitHub
13 changed files with 295 additions and 150 deletions

View File

@@ -248,6 +248,7 @@ TEMPLATES = [
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.tz', 'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'awx.ui.context_processors.csp',
'social_django.context_processors.backends', 'social_django.context_processors.backends',
'social_django.context_processors.login_redirect', 'social_django.context_processors.login_redirect',
], ],

View File

@@ -0,0 +1,8 @@
import base64
import os
def csp(request):
return {
'csp_nonce': base64.encodebytes(os.urandom(32)).decode().rstrip(),
}

View File

@@ -53,7 +53,7 @@
}, },
"scripts": { "scripts": {
"start": "PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start", "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": "TZ='UTC' react-scripts test --coverage --watchAll=false",
"test-watch": "TZ='UTC' react-scripts test", "test-watch": "TZ='UTC' react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",

View File

@@ -1,6 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <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 charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
@@ -12,6 +21,10 @@
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <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> </body>
</html> </html>

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './setupCSP';
import '@patternfly/react-core/dist/styles/base.css'; import '@patternfly/react-core/dist/styles/base.css';
import App from './App'; import App from './App';
import { BrandName } from './variables'; import { BrandName } from './variables';

View File

@@ -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 React from 'react';
import { import {
JobEventLine, JobEventLine,
@@ -9,84 +6,18 @@ import {
JobEventLineText, JobEventLineText,
} from './shared'; } 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({ function JobEvent({
counter, counter,
created,
event,
isClickable,
onJobEventClick,
stdout, stdout,
start_line,
style, style,
type, type,
lineTextHtml,
isClickable,
onJobEventClick,
}) { }) {
return !stdout ? null : ( return !stdout ? null : (
<div style={style} type={type}> <div style={style} type={type}>
{getLineTextHtml({ created, event, start_line, stdout }).map( {lineTextHtml.map(
({ lineNumber, html }) => ({ lineNumber, html }) =>
lineNumber >= 0 && ( lineNumber >= 0 && (
<JobEventLine <JobEventLine

View File

@@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import JobEvent from './JobEvent'; import JobEvent from './JobEvent';
const mockOnPlayStartEvent = { const mockOnPlayStartEvent = {
@@ -24,23 +23,64 @@ const selectors = {
lineText: 'JobEventLineText', 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 />', () => { describe('<JobEvent />', () => {
test('initially renders successfully', () => { test('initially renders successfully', () => {
mountWithContexts(<JobEvent {...mockOnPlayStartEvent} />); mountWithContexts(
<JobEvent
lineTextHtml={mockOnPlayStartLineTextHtml}
{...mockOnPlayStartEvent}
/>
);
}); });
test('playbook event timestamps are rendered', () => { test('playbook event timestamps are rendered', () => {
let wrapper = mountWithContexts(<JobEvent {...mockOnPlayStartEvent} />); let wrapper = mountWithContexts(
<JobEvent
lineTextHtml={mockOnPlayStartLineTextHtml}
{...mockOnPlayStartEvent}
/>
);
let lineText = wrapper.find(selectors.lineText); let lineText = wrapper.find(selectors.lineText);
expect( expect(
lineText.filterWhere(e => e.text().includes('18:11:22')) lineText.filterWhere(e => e.text().includes('18:11:22'))
).toHaveLength(1); ).toHaveLength(1);
const singleDigitTimestampEvent = { wrapper = mountWithContexts(
...mockOnPlayStartEvent, <JobEvent
created: '2019-07-11T08:01:02.906001Z', lineTextHtml={mockSingleDigitTimestampEventLineTextHtml}
}; {...singleDigitTimestampEvent}
wrapper = mountWithContexts(<JobEvent {...singleDigitTimestampEvent} />); />
);
lineText = wrapper.find(selectors.lineText); lineText = wrapper.find(selectors.lineText);
expect( expect(
lineText.filterWhere(e => e.text().includes('08:01:02')) lineText.filterWhere(e => e.text().includes('08:01:02'))
@@ -48,12 +88,14 @@ describe('<JobEvent />', () => {
}); });
test('ansi stdout colors are rendered as html', () => { 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); const lineText = wrapper.find(selectors.lineText);
expect( expect(
lineText lineText
.html() .html()
.includes('<span style="color:#486B00">ok: [localhost]</span>') .includes('<span class="output--1977390340">ok: [localhost]</span>')
).toBe(true); ).toBe(true);
}); });

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react'; import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -10,6 +10,9 @@ import {
InfiniteLoader, InfiniteLoader,
List, List,
} from 'react-virtualized'; } 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 AlertModal from '../../../components/AlertModal';
import { CardBody } from '../../../components/Card'; import { CardBody } from '../../../components/Card';
@@ -32,6 +35,106 @@ import {
AdHocCommandsAPI, AdHocCommandsAPI,
} from '../../../api'; } 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` const HeaderTitle = styled.div`
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -54,6 +157,8 @@ const OutputWrapper = styled.div`
font-size: 15px; font-size: 15px;
height: calc(100vh - 350px); height: calc(100vh - 350px);
outline: 1px solid #d7d7d7; outline: 1px solid #d7d7d7;
${({ cssMap }) =>
Object.keys(cssMap).map(className => `.${className}{${cssMap[className]}}`)}
`; `;
const OutputFooter = styled.div` const OutputFooter = styled.div`
@@ -122,6 +227,7 @@ class JobOutput extends Component {
remoteRowCount: 0, remoteRowCount: 0,
isHostModalOpen: false, isHostModalOpen: false,
hostEvent: {}, hostEvent: {},
cssMap: {},
}; };
this.cache = new CellMeasurerCache({ this.cache = new CellMeasurerCache({
@@ -164,7 +270,7 @@ class JobOutput extends Component {
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
// recompute row heights for any job events that have transitioned // recompute row heights for any job events that have transitioned
// from loading to loaded // from loading to loaded
const { currentlyLoading } = this.state; const { currentlyLoading, cssMap } = this.state;
let shouldRecomputeRowHeights = false; let shouldRecomputeRowHeights = false;
prevState.currentlyLoading prevState.currentlyLoading
.filter(n => !currentlyLoading.includes(n)) .filter(n => !currentlyLoading.includes(n))
@@ -172,6 +278,9 @@ class JobOutput extends Component {
shouldRecomputeRowHeights = true; shouldRecomputeRowHeights = true;
this.cache.clear(n); this.cache.clear(n);
}); });
if (Object.keys(cssMap).length !== Object.keys(prevState.cssMap).length) {
shouldRecomputeRowHeights = true;
}
if (shouldRecomputeRowHeights) { if (shouldRecomputeRowHeights) {
if (this.listRef.recomputeRowHeights) { if (this.listRef.recomputeRowHeights) {
this.listRef.recomputeRowHeights(); this.listRef.recomputeRowHeights();
@@ -300,6 +409,13 @@ class JobOutput extends Component {
return isHost; return isHost;
}; };
let actualLineTextHtml = [];
if (results[index]) {
const { lineTextHtml, lineCssMap } = getLineTextHtml(results[index]);
this.setState(({ cssMap }) => ({ cssMap: { ...cssMap, ...lineCssMap } }));
actualLineTextHtml = lineTextHtml;
}
return ( return (
<CellMeasurer <CellMeasurer
key={key} key={key}
@@ -314,6 +430,7 @@ class JobOutput extends Component {
onJobEventClick={() => this.handleHostEventClick(results[index])} onJobEventClick={() => this.handleHostEventClick(results[index])}
className="row" className="row"
style={style} style={style}
lineTextHtml={actualLineTextHtml}
{...results[index]} {...results[index]}
/> />
) : ( ) : (
@@ -389,7 +506,9 @@ class JobOutput extends Component {
handleResize({ width }) { handleResize({ width }) {
if (width !== this._previousWidth) { if (width !== this._previousWidth) {
this.cache.clearAll(); this.cache.clearAll();
this.listRef.recomputeRowHeights(); if (this.listRef?.recomputeRowHeights) {
this.listRef.recomputeRowHeights();
}
} }
this._previousWidth = width; this._previousWidth = width;
} }
@@ -404,6 +523,7 @@ class JobOutput extends Component {
hostEvent, hostEvent,
isHostModalOpen, isHostModalOpen,
remoteRowCount, remoteRowCount,
cssMap,
} = this.state; } = this.state;
if (hasContentLoading) { if (hasContentLoading) {
@@ -415,60 +535,62 @@ class JobOutput extends Component {
} }
return ( return (
<CardBody> <Fragment>
{isHostModalOpen && ( <CardBody>
<HostEventModal {isHostModalOpen && (
onClose={this.handleHostModalClose} <HostEventModal
isOpen={isHostModalOpen} onClose={this.handleHostModalClose}
hostEvent={hostEvent} 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}
/> />
)} <OutputWrapper cssMap={cssMap}>
<OutputHeader> <InfiniteLoader
<HeaderTitle> isRowLoaded={this.isRowLoaded}
<StatusIcon status={job.status} /> loadMoreRows={this.loadMoreRows}
<h1>{job.name}</h1> rowCount={remoteRowCount}
</HeaderTitle> >
<OutputToolbar job={job} onDelete={this.handleDeleteJob} /> {({ onRowsRendered, registerChild }) => (
</OutputHeader> <AutoSizer nonce={window.NONCE_ID} onResize={this.handleResize}>
<HostStatusBar counts={job.host_status_counts} /> {({ width, height }) => {
<PageControls return (
onScrollFirst={this.handleScrollFirst} <List
onScrollLast={this.handleScrollLast} ref={ref => {
onScrollNext={this.handleScrollNext} this.listRef = ref;
onScrollPrevious={this.handleScrollPrevious} registerChild(ref);
/> }}
<OutputWrapper> deferredMeasurementCache={this.cache}
<InfiniteLoader height={height || 1}
isRowLoaded={this.isRowLoaded} onRowsRendered={onRowsRendered}
loadMoreRows={this.loadMoreRows} rowCount={remoteRowCount}
rowCount={remoteRowCount} rowHeight={this.cache.rowHeight}
> rowRenderer={this.rowRenderer}
{({ onRowsRendered, registerChild }) => ( scrollToAlignment="start"
<AutoSizer onResize={this.handleResize}> width={width || 1}
{({ width, height }) => { overscanRowCount={20}
return ( />
<List );
ref={ref => { }}
this.listRef = ref; </AutoSizer>
registerChild(ref); )}
}} </InfiniteLoader>
deferredMeasurementCache={this.cache} <OutputFooter />
height={height || 1} </OutputWrapper>
onRowsRendered={onRowsRendered} </CardBody>
rowCount={remoteRowCount}
rowHeight={this.cache.rowHeight}
rowRenderer={this.rowRenderer}
scrollToAlignment="start"
width={width || 1}
overscanRowCount={20}
/>
);
}}
</AutoSizer>
)}
</InfiniteLoader>
<OutputFooter />
</OutputWrapper>
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
@@ -480,7 +602,7 @@ class JobOutput extends Component {
<ErrorDetail error={deletionError} /> <ErrorDetail error={deletionError} />
</AlertModal> </AlertModal>
)} )}
</CardBody> </Fragment>
); );
} }
} }

View File

@@ -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));
});

View File

@@ -19,3 +19,8 @@ global.console = {
...console, ...console,
debug: jest.fn(), 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;

View File

@@ -69,8 +69,6 @@ data:
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000; 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) # Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009)
add_header X-Frame-Options "DENY"; add_header X-Frame-Options "DENY";

View File

@@ -67,8 +67,6 @@ http {
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000; 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) # Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009)
add_header X-Frame-Options "DENY"; add_header X-Frame-Options "DENY";

View File

@@ -22,8 +22,6 @@ server {
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000; 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/ { location /static/ {
root /awx_devel; root /awx_devel;
@@ -84,8 +82,6 @@ server {
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000; 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/ { location /static/ {
root /awx_devel; root /awx_devel;