Skip to content

Commit

Permalink
feat(api): Update documentation to reflect security header support
Browse files Browse the repository at this point in the history
Switch to the new security header reporting endpoint for CSP and expose Expect-CT as a first-class citizen.
  • Loading branch information
dcramer committed Apr 25, 2018
1 parent 5870b8a commit 0ee6c66
Show file tree
Hide file tree
Showing 19 changed files with 837 additions and 92 deletions.
1 change: 1 addition & 0 deletions src/sentry/api/serializers/models/project_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def serialize(self, obj, attrs, user):
'secret': obj.dsn_private,
'public': obj.dsn_public,
'csp': obj.csp_endpoint,
'security': obj.security_endpoint,
'minidump': obj.minidump_endpoint,
},
'dateCreated': obj.date_added,
Expand Down
12 changes: 12 additions & 0 deletions src/sentry/models/projectkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ def csp_endpoint(self):
self.public_key,
)

@property
def security_endpoint(self):
endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT
if not endpoint:
endpoint = options.get('system.url-prefix')

return '%s%s?sentry_key=%s' % (
endpoint,
reverse('sentry-api-security-report', args=[self.project_id]),
self.public_key,
)

@property
def minidump_endpoint(self):
endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT
Expand Down
14 changes: 14 additions & 0 deletions src/sentry/static/sentry/app/components/previewFeature.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, {Component} from 'react';
import {t} from '../locale';

export default class PreviewFeature extends Component {
render() {
return (
<div className="alert alert-block alert-warn">
{t(
'This feature is a preview and may change in the future. Thanks for being an early adopter!'
)}
</div>
);
}
}
2 changes: 1 addition & 1 deletion src/sentry/static/sentry/app/data/forms/cspReports.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const route = '/settings/:orgId/:projectId/csp/';
const formGroups = [
{
// Form "section"/"panel"
title: 'Settings',
title: 'CSP Settings',
fields: [
{
name: 'sentry:csp_ignored_sources_defaults',
Expand Down
32 changes: 24 additions & 8 deletions src/sentry/static/sentry/app/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,30 @@ const projectSettingsRoutes = (
import(/*webpackChunkName: "ProjectUserFeedbackSettings"*/ './views/settings/project/projectUserFeedback')}
component={errorHandler(LazyLoad)}
/>
<Route
key="csp/"
path="csp/"
name="CSP Reports"
componentPromise={() =>
import(/*webpackChunkName: "ProjectCspReports"*/ './views/settings/project/projectCspReports')}
component={errorHandler(LazyLoad)}
/>
<Redirect from="csp/" to="security-headers/" />
<Route key="security-headers/" path="security-headers/" name="Security Headers">
<IndexRoute
componentPromise={() =>
import(/*webpackChunkName: "ProjectSecurityHeaders"*/ './views/settings/projectSecurityHeaders')}
component={errorHandler(LazyLoad)}
/>
<Route
path="csp/"
key="csp/"
name="Content Security Policy"
componentPromise={() =>
import(/*webpackChunkName: "ProjectCspReports"*/ './views/settings/projectSecurityHeaders/csp')}
component={errorHandler(LazyLoad)}
/>
<Route
path="expect-ct/"
key="expect-ct/"
name="Certificate Transparency"
componentPromise={() =>
import(/*webpackChunkName: "ProjectExpectCtReports"*/ './views/settings/projectSecurityHeaders/expectCt')}
component={errorHandler(LazyLoad)}
/>
</Route>
<Route path="plugins/" name="Integrations" component={errorHandler(ProjectPlugins)} />
<Route
path="plugins/:pluginId/"
Expand Down
4 changes: 3 additions & 1 deletion src/sentry/static/sentry/app/views/projectSettings/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ const ProjectSettings = createReactClass({
>
{t('Error Tracking')}
</ListLink>
<ListLink to={`${pathPrefix}/csp/`}>{t('CSP Reports')}</ListLink>
<ListLink to={`${pathPrefix}/security-headers/`}>
{t('Security Headers')}
</ListLink>
<ListLink to={`${pathPrefix}/user-feedback/`}>{t('User Feedback')}</ListLink>
<ListLink to={`${pathPrefix}/filters/`}>{t('Inbound Filters')}</ListLink>
<ListLink to={`${pathPrefix}/keys/`}>{t('Client Keys')} (DSN)</ListLink>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,16 @@ class Field extends React.Component {
hasControlState={!flexibleControlStateSize}
style={style}
>
<FieldDescription inline={inline} htmlFor={id}>
{label && (
<FieldLabel>
{label} {required && <FieldRequiredBadge />}
</FieldLabel>
)}
{help && <FieldHelp>{help}</FieldHelp>}
</FieldDescription>
{(label || help) && (
<FieldDescription inline={inline} htmlFor={id}>
{label && (
<FieldLabel>
{label} {required && <FieldRequiredBadge />}
</FieldLabel>
)}
{help && <FieldHelp>{help}</FieldHelp>}
</FieldDescription>
)}

{Control}
</FieldWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export default function getConfiguration({project}) {
title: t('Error Tracking'),
},
{
path: `${pathPrefix}/csp/`,
title: t('CSP Reports'),
path: `${pathPrefix}/security-headers/`,
title: t('Security Headers'),
},
{
path: `${pathPrefix}/user-feedback/`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ProjectKeyCredentials extends React.Component {

showDsn: PropTypes.bool,
showDsnPublic: PropTypes.bool,
showCspEndpoint: PropTypes.bool,
showSecurityEndpoint: PropTypes.bool,
showMinidump: PropTypes.bool,
showPublicKey: PropTypes.bool,
showSecretKey: PropTypes.bool,
Expand All @@ -25,7 +25,7 @@ class ProjectKeyCredentials extends React.Component {
static defaultProps = {
showDsn: true,
showDsnPublic: true,
showCspEndpoint: true,
showSecurityEndpoint: true,
showMinidump: true,
showPublicKey: false,
showSecretKey: false,
Expand All @@ -38,7 +38,7 @@ class ProjectKeyCredentials extends React.Component {
data,
showDsn,
showDsnPublic,
showCspEndpoint,
showSecurityEndpoint,
showMinidump,
showPublicKey,
showSecretKey,
Expand All @@ -61,23 +61,19 @@ class ProjectKeyCredentials extends React.Component {
</Field>
)}

{showCspEndpoint && (
{showSecurityEndpoint && (
<Field
label={t('CSP Endpoint')}
help={tct(
'Use your CSP endpoint in the [directive] directive in your [header] header.',
{
directive: <code>report-uri</code>,
header: <code>Content-Security-Policy</code>,
}
label={t('Security Header Endpoint')}
help={t(
'Use your security header endpoint for features like CSP and Expect-CT reports.'
)}
inline={false}
flexibleControlStateSize
>
<TextCopyInput>
{getDynamicText({
value: data.dsn.csp,
fixed: data.dsn.csp.replace(
value: data.dsn.security,
fixed: data.dsn.security.replace(
new RegExp(`\/${projectId}$`),
'/<<projectId>>'
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'react-emotion';

import {t, tct} from '../../../locale';
Expand All @@ -8,6 +8,8 @@ import ExternalLink from '../../../components/externalLink';
import Form from '../components/forms/form';
import JsonForm from '../components/forms/jsonForm';
import {Panel, PanelBody, PanelHeader} from '../../../components/panels';
import ReportUri, {getSecurityDsn} from './reportUri';
import PreviewFeature from '../../../components/previewFeature';
import SettingsPageHeader from '../components/settingsPageHeader';
import TextBlock from '../components/text/textBlock';
import formGroups from '../../../data/forms/cspReports';
Expand All @@ -16,11 +18,8 @@ const CodeBlock = styled.pre`
word-break: break-all;
white-space: pre-wrap;
`;
const TextBlockNoMargin = styled(TextBlock)`
margin-bottom: 0;
`;

class ProjectCspReports extends AsyncView {
export default class ProjectCspReports extends AsyncView {
static propTypes = {
setProjectNavSection: PropTypes.func,
};
Expand All @@ -39,10 +38,6 @@ class ProjectCspReports extends AsyncView {
}

getInstructions() {
let endpoint = this.state.keyList.length
? this.state.keyList[0].dsn.csp
: 'https://sentry.example.com/api/csp-report/';

return (
'def middleware(request, response):\n' +
" response['Content-Security-Policy'] = \\\n" +
Expand All @@ -51,23 +46,19 @@ class ProjectCspReports extends AsyncView {
" \"style-src 'self' 'unsafe-inline' cdn.example.com; \" \\\n" +
' "img-src * data:; " \\\n' +
' "report-uri ' +
endpoint +
getSecurityDsn(this.state.keyList) +
'"\n' +
' return response\n'
);
}

getReportOnlyInstructions() {
let endpoint = this.state.keyList.length
? this.state.keyList[0].dsn.csp
: 'https://sentry.example.com/api/csp-report/';

return (
'def middleware(request, response):\n' +
" response['Content-Security-Policy-Report-Only'] = \\\n" +
' "default-src \'self\'; " \\\n' +
' "report-uri ' +
endpoint +
getSecurityDsn(this.state.keyList) +
'"\n' +
' return response\n'
);
Expand All @@ -78,28 +69,11 @@ class ProjectCspReports extends AsyncView {

return (
<div>
<SettingsPageHeader title={t('CSP Reports')} />

<div className="alert alert-block alert-info">
{t(`Psst! This feature is still a work-in-progress. Thanks for being an early
adopter!`)}
</div>

<TextBlock>
{tct(
`[link:Content Security Policy]
(CSP) is a security standard which helps prevent cross-site scripting (XSS),
clickjacking and other code injection attacks resulting from execution of
malicious content in the trusted web page context. It's enforced by browser
vendors, and Sentry supports capturing CSP violations using the standard
reporting hooks.`,
{
link: (
<ExternalLink href="https://en.wikipedia.org/wiki/Content_Security_Policy" />
),
}
)}
</TextBlock>
<SettingsPageHeader title={t('Content Security Policy')} />

<PreviewFeature />

<ReportUri keyList={this.state.keyList} params={this.props.params} />

<Form
saveOnBlur
Expand All @@ -111,9 +85,25 @@ class ProjectCspReports extends AsyncView {
</Form>

<Panel>
<PanelHeader>{t('Integration')}</PanelHeader>
<PanelHeader>{t('About')}</PanelHeader>

<PanelBody disablePadding={false}>
<TextBlock>
{tct(
`[link:Content Security Policy]
(CSP) is a security standard which helps prevent cross-site scripting (XSS),
clickjacking and other code injection attacks resulting from execution of
malicious content in the trusted web page context. It's enforced by browser
vendors, and Sentry supports capturing CSP violations using the standard
reporting hooks.`,
{
link: (
<ExternalLink href="https://en.wikipedia.org/wiki/Content_Security_Policy" />
),
}
)}
</TextBlock>

<TextBlock>
{tct(
`To configure [csp:CSP] reports
Expand All @@ -125,20 +115,20 @@ class ProjectCspReports extends AsyncView {
)}
</TextBlock>

<TextBlockNoMargin>
<TextBlock noMargin>
{t(
'For example, in Python you might achieve this via a simple web middleware'
)}
</TextBlockNoMargin>
</TextBlock>
<CodeBlock>{this.getInstructions()}</CodeBlock>

<TextBlockNoMargin>
<TextBlock noMargin>
{t(`Alternatively you can setup CSP reports to simply send reports rather than
actually enforcing the policy`)}
</TextBlockNoMargin>
</TextBlock>
<CodeBlock>{this.getReportOnlyInstructions()}</CodeBlock>

<TextBlockNoMargin css={{marginTop: 30}}>
<TextBlock noMargin css={{marginTop: 30}}>
{tct(
`We recommend setting this up to only run on a percentage of requests, as
otherwise you may find that you've quickly exhausted your quota. For more
Expand All @@ -149,12 +139,10 @@ class ProjectCspReports extends AsyncView {
),
}
)}
</TextBlockNoMargin>
</TextBlock>
</PanelBody>
</Panel>
</div>
);
}
}

export default ProjectCspReports;
Loading

0 comments on commit 0ee6c66

Please sign in to comment.