Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Implement strict Content Security Policy #1358

Merged
merged 6 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 12 additions & 4 deletions benefits/core/templates/core/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
CA State Template v6.0.7 does not include jQuery
See https://github.com/Office-of-Digital-Services/California-State-Web-Template/releases/tag/v6.0.7
{% endcomment %}
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
<script nonce="{{ request.csp_nonce }}"
src="https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"
integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ="
crossorigin="anonymous"></script>

{% include "core/includes/analytics.html" with api_key=analytics.api_key uid=analytics.uid did=analytics.did %}
</head>
Expand Down Expand Up @@ -125,9 +128,12 @@ <h1>{{ page.headline }}</h1>

But we aren't using CA State Template Javascript, so include Bootstrap directly
{% endcomment %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script nonce="{{ request.csp_nonce }}"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>

<script>
<script nonce="{{ request.csp_nonce }}">
$(function() {
document.cookie = "testcookie"
if (document.cookie.indexOf("testcookie") < 0) {
Expand All @@ -144,6 +150,8 @@ <h1>{{ page.headline }}</h1>
});
</script>

{% if request.recaptcha %}<script src="{{ request.recaptcha.script_api }}"></script>{% endif %}
{% if request.recaptcha %}
<script nonce="{{ request.csp_nonce }}" src="{{ request.recaptcha.script_api }}"></script>
{% endif %}
</body>
</html>
3 changes: 2 additions & 1 deletion benefits/core/templates/core/includes/analytics.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script type="text/javascript">
<script nonce="{{ request.csp_nonce }}" type="text/javascript">
(function(e,t){var n=e.amplitude||{_q:[],_iq:{}};var r=t.createElement("script")
;r.type="text/javascript"
;r.integrity="sha384-u0hlTAJ1tNefeBKwiBNwB4CkHZ1ck4ajx/pKmwWtc+IufKJiCQZ+WjJIi+7C6Ntm"
;r.crossOrigin="anonymous";r.async=true
;r.src="https://cdn.amplitude.com/libs/amplitude-8.1.0-min.gz.js"
;r.nonce="{{ request.csp_nonce }}"
;r.onload=function(){if(!e.amplitude.runQueuedFunctions){
console.log("[Amplitude] Error: could not load SDK")}}
;var i=t.getElementsByTagName("script")[0];i.parentNode.insertBefore(r,i)
Expand Down
4 changes: 2 additions & 2 deletions benefits/core/templates/core/includes/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
</div>
{% endif %}

<script>
<script nonce="{{ request.csp_nonce }}">
$(function() {
// listen for a custom "submitting" event on the form, for button interactions
$("#{{ form.id }}").on("submitting", function(e) {
Expand Down Expand Up @@ -77,7 +77,7 @@
{% endcomment %}
<input type="hidden" name="{{ request.recaptcha.data_field }}" value="">

<script>
<script nonce="{{ request.csp_nonce }}">
function recaptchaSubmit($event) {
// checks the validity of the form. Return if invalid; HTML5 validation errors should display
if (!$event.currentTarget.form.checkValidity()) {
Expand Down
4 changes: 2 additions & 2 deletions benefits/enrollment/templates/enrollment/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ <h1 class="pb-lg-8 pb-4">{{ page.headline }}</h1>

{% comment %} This Javascript code must be before the forms. {% endcomment %}
{% if payment_processor %}
<script>
<script nonce="{{ request.csp_nonce }}">
var startedEvent = "started payment connection", closedEvent = "closed payment connection";

$.getScript("{{ payment_processor.card_tokenize_url }}")
$.ajax({ dataType: "script", attrs: { nonce: "{{ request.csp_nonce }}"}, url: "{{ payment_processor.card_tokenize_url }}" })
.done(function() {
$.get("{{ payment_processor.access_token_url }}", function(data) {
$(".loading").remove();
Expand Down
5 changes: 5 additions & 0 deletions benefits/sentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@


SENTRY_ENVIRONMENT = os.environ.get("SENTRY_ENVIRONMENT", "local")
SENTRY_CSP_REPORT_URI = None


def git_available():
Expand Down Expand Up @@ -82,5 +83,9 @@ def configure():
send_default_pii=False,
event_scrubber=EventScrubber(denylist=get_denylist()),
)

# override the module-level variable when configuration happens, if set
global SENTRY_CSP_REPORT_URI
SENTRY_CSP_REPORT_URI = os.environ.get("SENTRY_REPORT_URI", "")
else:
print("SENTRY_DSN not set, so won't send events")
17 changes: 13 additions & 4 deletions benefits/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ def _filter_empty(ls):
# In particular, note that the inner single-quotes are required!
# https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings

CSP_DEFAULT_SRC = ["'self'"]
CSP_BASE_URI = ["'none'"]

CSP_IMG_SRC = ["'self'", "data:"]
CSP_DEFAULT_SRC = ["'self'"]

CSP_CONNECT_SRC = ["'self'", "https://api.amplitude.com/"]
env_connect_src = _filter_empty(os.environ.get("DJANGO_CSP_CONNECT_SRC", "").split(","))
Expand All @@ -292,8 +292,18 @@ def _filter_empty(ls):
if len(env_frame_src) > 0:
CSP_FRAME_SRC = env_frame_src

CSP_IMG_SRC = ["'self'", "data:"]

# Configuring strict Content Security Policy
# https://django-csp.readthedocs.io/en/latest/nonce.html
CSP_INCLUDE_NONCE_IN = ["script-src"]

CSP_OBJECT_SRC = ["'none'"]

if sentry.SENTRY_CSP_REPORT_URI:
CSP_REPORT_URI = [sentry.SENTRY_CSP_REPORT_URI]

CSP_SCRIPT_SRC = [
"'unsafe-inline'",
"https://cdn.amplitude.com/libs/",
"https://cdn.jsdelivr.net/",
"*.littlepay.com",
Expand All @@ -305,7 +315,6 @@ def _filter_empty(ls):

CSP_STYLE_SRC = [
"'self'",
"'unsafe-inline'",
"https://california.azureedge.net/",
"https://fonts.googleapis.com/css",
]
Expand Down
6 changes: 5 additions & 1 deletion docs/configuration/content-security-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

> The HTTP `Content-Security-Policy` response header allows web site administrators to control resources the user agent is
> allowed to load for a given page.

>
> With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against
> cross-site scripting attacks

!!! warning "Strict CSP"

Benefits configures a Strict Content Security Policy. Read more about Strict CSP from Google: <https://csp.withgoogle.com/docs/strict-csp.html>.

## `django-csp`

!!! tldr "django-csp docs"
Expand Down
10 changes: 10 additions & 0 deletions docs/configuration/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,5 +177,15 @@ Enables [sending events to Sentry](../../deployment/troubleshooting/#error-monit

Segments errors by which deployment they occur in. This defaults to `local`, and can be set to match one of the [environment names](../../deployment/infrastructure/#environments).

### `SENTRY_REPORT_URI`

!!! tldr "Sentry docs"

[Security Policy Reporting](https://docs.sentry.io/product/security-policy-reporting/)

Collect information on Content-Security-Policy (CSP) violations. Read more about [CSP configuration in Benefits](./content-security-policy.md).

To enable report collection, set this env var to the authenticated Sentry endpoint.

[app-service-config]: https://docs.microsoft.com/en-us/azure/app-service/configure-common?tabs=portal
[getting-started_create-env]: ../getting-started/README.md#create-an-environment-file
1 change: 1 addition & 0 deletions terraform/app_service.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ resource "azurerm_linux_web_app" "main" {
# Sentry
"SENTRY_DSN" = "${local.secret_prefix}sentry-dsn)",
"SENTRY_ENVIRONMENT" = local.env_name,
"SENTRY_REPORT_URI" = "${local.secret_prefix}sentry-report-uri)",

# Environment variables for data migration
"MST_SENIOR_GROUP_ID" = "${local.secret_prefix}mst-senior-group-id)",
Expand Down