Skip to content

Commit 6201081

Browse files
committed
Add section on configuring Content Security Policy (CSP)
Initial pass to add CSP. Also 2 typo fixes.
1 parent 9a68584 commit 6201081

File tree

1 file changed

+169
-2
lines changed

1 file changed

+169
-2
lines changed

doc/admin_guide.txt

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,173 @@ get the gzip version and not a brotli compressed version. This
286286
mechanism allows the admin to allow use of brotli and zstd for
287287
dynamic content, but not for static content.
288288

289+
Adding a Web Content Security Policy (CSP)
290+
==========================================
291+
292+
A Content Security Policy (`CSP`_) adds a layer of security to
293+
Roundup's web interface. It makes it more difficult for an
294+
attacker to compromise Roundup. By default Roundup does not add
295+
a CSP. If you need to implement a CSP, this section will help you
296+
understand how to add one and document the current level of
297+
support for CSP in Roundup.
298+
299+
Roundup's web interface has remained mostly unchanged since it
300+
was created over a decade ago. Current releases have been slowly
301+
modernizing the HTML to improve security. There are still some
302+
improvements that need to happen before the tightest CSP
303+
configurations can be used.
304+
305+
Writing a CSP is complex. This section just touches on how to
306+
create and install a CSP to improve security. Some of it might
307+
break functionality.
308+
309+
There are two ways to add a CSP:
310+
311+
1. a fixed CSP added by a server
312+
2. a dynamic CSP added by Roundup
313+
314+
Fixed CSP
315+
---------
316+
317+
If you are using a web server (Apache, Nginx) to run Roundup, you can
318+
add a ``Content-Security-Policy`` header using that server. WSGI
319+
servers like uWSGI can also be configured to add headers. An example
320+
header would look like::
321+
322+
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'strict-dynamic';
323+
324+
One thing that may need to be included is the ``unsafe-inline``.
325+
The default templates use ``onload``, ``onchange``, ``onsubmit``,
326+
and ``onclick`` JavaScript handlers. Without ``unsafe-inline``
327+
these won't work and popup helpers will not work. Sadly the use
328+
of ``unsafe-inline`` is a pretty big hole in this CSP. You can
329+
set the hashes for all the JavaScript handlers in the CSP. Then
330+
replace ``unsafe-inline`` with ``unsafe-hashes`` to help close
331+
this hole, but has its own issues. See `remediating
332+
unsafe-inline`_ for another way to mitigate this.
333+
334+
The inclusion of ``strict-dynamic`` allows trusted JavaScript
335+
files that are downloaded from Roundup to make changes to the web
336+
interface. These changes are also trusted code that will be run
337+
when invoked.
338+
339+
More secure CSPs can also be created. However because of the ability
340+
to customize the web interface, it is difficult to provide guidance.
341+
342+
Dynamic CSP
343+
-----------
344+
345+
Roundup creates a cryptographic nonce for every client request. The
346+
nonce is the value of the ``client.client_nonce`` property.
347+
348+
By changing the templates to use the nonce, we can better secure the
349+
Roundup instance. However the nonce has to be set in the CSP returned
350+
by Roundup.
351+
352+
One way to do this is to add a templating utility to the extensions
353+
directory that generates the CSP on the fly. For example::
354+
355+
default_security_headers = {
356+
'Content-Security-Policy': (
357+
"default-src 'self'; "
358+
"base-uri 'self'; "
359+
"script-src https: 'nonce-{nonce}' 'strict-dynamic'; "
360+
"style-src 'self' 'nonce-{nonce}'; "
361+
"img-src 'self' data:; "
362+
"frame-ancestors 'self'; "
363+
"object-src 'self' 'nonce-{nonce}'; "
364+
),
365+
}
366+
367+
368+
def AddHtmlHeaders(client, header_dict=None):
369+
''' Generate https headers from dict use default security headers
370+
371+
Setting the header with a value of None will not inject the
372+
header and can override the default set.
373+
374+
Header values will be formatted with a dictionary including a
375+
nonce. Use to set a nonce for inline scripts.
376+
'''
377+
try:
378+
if client.client_nonce is None:
379+
# logger.warning("client_nonce is None")
380+
client.client_nonce = client.session_api._gen_sid()
381+
except AttributeError:
382+
# client.client_nonce doesn't exist, create it
383+
# logger.warning("client_nonce does not exist, creating")
384+
client.client_nonce = client.session_api._gen_sid()
385+
386+
headers = default_security_headers.copy()
387+
if isinstance(header_dict, dict):
388+
headers.update(header_dict)
389+
390+
client_headers = client.additional_headers
391+
392+
for header, value in list(headers.items()):
393+
if value is None:
394+
continue
395+
client_headers[header] = value.format(
396+
nonce=client.client_nonce)
397+
398+
def init(instance):
399+
instance.registerUtil('AddHtmlHeaders', AddHtmlHeaders)
400+
401+
402+
Adding the following to ``page.html`` right after the opening
403+
``<html....`>`` tag::
404+
405+
<tal:code tal:content="python:utils.AddHtmlHeaders(request.client)" />
406+
407+
will invoke ``AddHtmlHeaders()`` to add the CSP header with the nonce.
408+
409+
With this set of CSP headers, all style, script and object tags will
410+
need a ``nonce`` attribute. This can be added by changing::
411+
412+
<script src="javascript.js"></script>
413+
414+
to::
415+
416+
<script
417+
tal:attributes="nonce request/client/client_nonce"
418+
src="javascript.js"></script>
419+
420+
for each script, object or style tag.
421+
422+
Remediating ``unsafe-inline``
423+
-----------------------------
424+
.. _remediating unsafe-inline:
425+
426+
Using a trusted script to set event handlers to replace the ``onX``
427+
handlers allows removal of the ``unsafe-inline`` handlers. If you
428+
remove ``unsafe-inline`` the ``onX`` handlers will not run. However
429+
you can use the label provided by the ``onX`` attribute to securely
430+
enable a callback function.
431+
432+
This method is a work in progress. As an example proof of concept,
433+
adding this "decorator" script at the end of page.html::
434+
435+
<script tal:attributes="nonce request/client/client_nonce">
436+
/* set submit event listener on forms that have an
437+
onsubmit (case insensitive) attribute */
438+
forms = document.querySelectorAll(form[onsubmit])
439+
for (let form of f) {
440+
form.addEventListener('submit',
441+
() => submit_once());
442+
};
443+
</script>
444+
445+
will set callback for the submit even on any form that has an onsubmit
446+
attribute to ``submit_once()``. ``submit_once`` is defined in Roundup's
447+
base_javascript and is generated with a proper nonce.
448+
449+
By including the nonce in the dynamic CSP, we can use our trusted
450+
"decorator" script to add event listeners. These listeners will call
451+
the trusted function in base_javascript to replace the ignored ``onX``
452+
handlers.
453+
454+
.. _CSP: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
455+
289456
Configuring native-fts Full Text Search
290457
=======================================
291458

@@ -527,7 +694,7 @@ port 7200 using the password ``mypassword`` to open database
527694
10. The ``redis_url`` setting can load a file to better
528695
secure the url. If you are using redis 6.0 or newer, you can
529696
specify a username/password and access control lists to
530-
improv the security of your data. Another good alternative
697+
improve the security of your data. Another good alternative
531698
is to talk to redis using a Unix domain socket.
532699

533700
If you are connecting to redis across the network rather
@@ -586,7 +753,7 @@ may be found in the `customisation documentation`_.
586753
Configuring Authentication Header/Variable
587754
------------------------------------------
588755

589-
The front end server running roundup can perform the user
756+
The front end server running Roundup can perform the user
590757
authentication. It pass the authenticated username to the backend in a
591758
variable. By default roundup looks for the ``REMOTE_USER`` variable
592759
This can be changed by setting the parameter ``http_auth_header`` in the

0 commit comments

Comments
 (0)