Skip to content

Commit

Permalink
Feat: Implement strict Content Security Policy (#1358)
Browse files Browse the repository at this point in the history
  • Loading branch information
thekaveman committed Apr 20, 2023
2 parents 639b786 + 02b5ef9 commit a997078
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 14 deletions.
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

0 comments on commit a997078

Please sign in to comment.