-
Notifications
You must be signed in to change notification settings - Fork 695
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
Audit, clean up, and annotate Apache configs #5797
Conversation
I'm not so sure about removing ErrorDocument - a) are we sure we won't get stack traces on server errors on the SI and b) do we want to display the generic Apache 500 in the second-worst case? |
@zenmonkeykstop Good question! Let me provide some screenshots. Current "develop" implementation, on 404:That's a pretty nice experience, IMO, for a 404. Hypothetical Apache-404 via WSGIErrorOverride:I don't see a compelling reason to add this behavior. The Hypothetical Apache-500 via WSGIErrorOverride:The 500 use case there's a better argument for, but we're already seeing Apache handle those, because Flask can't. Try setting an To be clear, I've only tested the 404 and 500 use cases so far, but it seems like it'd be a significant UX loss to use the apache 404, and I can't identify any compelling security reasons to do it. Very open to discussion on it, though, so please keep the comments coming! |
@@ -2,7 +2,7 @@ | |||
- name: Install apache packages. | |||
apt: | |||
pkg: "{{ apache_packages }}" | |||
state: latest | |||
state: present |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed this to state=present to avoid yet another apt-get update
. Earlier in the playbook run, all packages are updated to via apt safe-upgrade (dist-upgrade, as of #5793), so there's no need to update the lists again.
2768229
to
fe49b2b
Compare
|
||
# Evaluate support for TLSv1.3 in Tor Browser for Onions, conservatively | ||
# we'll continue to support TLSv1.2 for now. | ||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TLSv1.3 support deserves a spike, but I don't see it as a release-blocker. Probably worth its own issue, though. cc @emkll
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, a good topic for follow-up. But right now, we only support TLS 1.2 which remains safe. TLS1.3 only would vastly improve cipher suite configuration however, see below.
# Deny all non-HTTP traffic, as a precaution | ||
RewriteEngine On | ||
RewriteCond %{THE_REQUEST} !HTTP/1\.1$ | ||
RewriteRule .* - [F] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't actually test these lines, but I added a comment describing their intent. It would be nice to test these types of mitigations functionally, but fell back to providing comments to clarify.
# research required. | ||
XSendFile On | ||
XSendFilePath /var/lib/securedrop/store/ | ||
XSendFilePath /var/lib/securedrop/tmp/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment saying that XSendFile may not actually be required these days. Would prefer to punt on functionality changes now, pending further testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More recently than this comment, I feel pretty strongly we should keep XSendFile, at least as long as we're using Apache. So, will clarify in the comment here.
Marking as ready for review, although #5799 remains a blocker due to CI woes. Please review in-line comments and discussion above, and weigh in. |
fe49b2b
to
3544167
Compare
2b9f790
to
1ff6163
Compare
The cautious approach to make the changes Focal-only is causing the tests to fail. Will update the tests to be distro-specific, as well, and request review. Marking as draft for now. |
5584e49
to
1bd79fd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking it as approved. But, I will not merge before @conorsch have a look at the change I pushed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes to allow DELETE
on the JI (as it will break some API/SDK/Client functionality), other comments are mostly either seeking clarification or further improvements, all of which should be considered non-blocking
install_files/ansible-base/roles/app/templates/sites-available/focal/journalist.conf
Outdated
Show resolved
Hide resolved
install_files/ansible-base/roles/app/templates/sites-available/focal/journalist.conf
Show resolved
Hide resolved
XSendFilePath /var/lib/securedrop/store/ | ||
XSendFilePath /var/lib/securedrop/tmp/ | ||
|
||
Header edit Set-Cookie ^(.*)$ $1;HttpOnly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flask will handle this in it's default configuration[1], and this setting can be set/overrided in the Flask config as well. It might be worth removing here to simplify, but I also see no harm in applying this here.
[1] https://flask.palletsprojects.com/en/1.1.x/api/?#flask.Flask.default_config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I'd prefer to leverage flask as much as possible (static config issues aside), this strikes me as useful defense. Besides, if we ever want to change it, it's simple enough to snip out in postinst when updating the Flask config.
XSendFilePath /var/lib/securedrop/tmp/ | ||
|
||
Header edit Set-Cookie ^(.*)$ $1;HttpOnly | ||
Header always append X-Frame-Options: DENY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we "append" (always) the header instead of "set", this means that we might have 2 competing headers, if the application or another layer provides their own. Do we know the precedence order? Curious why we aren't "setting" here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set should override any existing header
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you say below, we might want to prefer "always set" in these cases. See docs here https://httpd.apache.org/docs/current/mod/mod_headers.html#page-header :
This difference between onsuccess and always is a feature that resulted as a consequence of how httpd internally stores headers for a HTTP response, since it does not offer any "normalized" single list of headers. The main problem that can arise if the following concept is not kept in mind while writing the configuration is that some HTTP responses might end up with the same header duplicated (confusing users or sometimes even HTTP clients). For example, suppose that you have a simple PHP proxy setup with mod_proxy_fcgi and your backend PHP scripts adds the X-Foo: bar header to each HTTP response. As described above, mod_proxy_fcgi uses the always table to store headers, so a configuration like the following ends up in the wrong result, namely having the header duplicated with both values
We'll need to be careful not to set headers that Flask is already setting—to be defensive, those docs recommend:
# 'onsuccess' can be omitted since it is the default
Header onsuccess unset X-Foo
Header always set X-Foo "baz"
Will do a bit more reading there before changing.
Header set X-XSS-Protection: "1; mode=block" | ||
Header set X-Content-Type-Options: nosniff | ||
Header set X-Download-Options: noopen | ||
Header set Content-Security-Policy: "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason these headers are not "always set" ? I believe always set will add the headers on non-error pages. Since they are static pages, not necessarily required here, I think
# Evaluate support for TLSv1.3 in Tor Browser for Onions, conservatively | ||
# we'll continue to support TLSv1.2 for now. | ||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 | ||
SSLCipherSuite {{ securedrop_app_https_ssl_ciphers|join(':') }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should note here that Tor Onion Services provide transport layer encryption and server authentication, similar to TLS. HTTPS is used as defense-in-depth here. However:
A modern Cipher suite is very simply to allow TLS 1.3 only. In the absence of TLS 1.3 support (which will be tracked per the above), Mozilla recommends[1]:
In other words, we should consider removing the following cipher suites:
- ECDHE-ECDSA-AES256-SHA384
- ECDHE-RSA-AES256-SHA384
- ECDHE-ECDSA-AES128-SHA256
- ECDHE-RSA-AES128-SHA256
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make the ciphers distro-specific, will add
# we'll continue to support TLSv1.2 for now. | ||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 | ||
SSLCipherSuite {{ securedrop_app_https_ssl_ciphers|join(':') }} | ||
SSLHonorCipherOrder on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All our cipher suites should be strong, so this directive should not be required, but also fine to keep as-is
XSendFile Off | ||
|
||
# Prevent cookies from being accessed from Javascript. XSS mitigation. | ||
Header edit Set-Cookie ^(.*)$ $1;HttpOnly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as JI, this is flask controlled/default
install_files/ansible-base/roles/app/templates/sites-available/focal/source.conf
Outdated
Show resolved
Hide resolved
install_files/ansible-base/roles/app/templates/sites-available/focal/source.conf
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for comprehensive review, @emkll, I'll make a few adjustments to match your comments!
# research required. | ||
XSendFile On | ||
XSendFilePath /var/lib/securedrop/store/ | ||
XSendFilePath /var/lib/securedrop/tmp/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More recently than this comment, I feel pretty strongly we should keep XSendFile, at least as long as we're using Apache. So, will clarify in the comment here.
XSendFilePath /var/lib/securedrop/store/ | ||
XSendFilePath /var/lib/securedrop/tmp/ | ||
|
||
Header edit Set-Cookie ^(.*)$ $1;HttpOnly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I'd prefer to leverage flask as much as possible (static config issues aside), this strikes me as useful defense. Besides, if we ever want to change it, it's simple enough to snip out in postinst when updating the Flask config.
XSendFilePath /var/lib/securedrop/tmp/ | ||
|
||
Header edit Set-Cookie ^(.*)$ $1;HttpOnly | ||
Header always append X-Frame-Options: DENY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you say below, we might want to prefer "always set" in these cases. See docs here https://httpd.apache.org/docs/current/mod/mod_headers.html#page-header :
This difference between onsuccess and always is a feature that resulted as a consequence of how httpd internally stores headers for a HTTP response, since it does not offer any "normalized" single list of headers. The main problem that can arise if the following concept is not kept in mind while writing the configuration is that some HTTP responses might end up with the same header duplicated (confusing users or sometimes even HTTP clients). For example, suppose that you have a simple PHP proxy setup with mod_proxy_fcgi and your backend PHP scripts adds the X-Foo: bar header to each HTTP response. As described above, mod_proxy_fcgi uses the always table to store headers, so a configuration like the following ends up in the wrong result, namely having the header duplicated with both values
We'll need to be careful not to set headers that Flask is already setting—to be defensive, those docs recommend:
# 'onsuccess' can be omitted since it is the default
Header onsuccess unset X-Foo
Header always set X-Foo "baz"
Will do a bit more reading there before changing.
install_files/ansible-base/roles/app/templates/sites-available/focal/journalist.conf
Outdated
Show resolved
Hide resolved
# Evaluate support for TLSv1.3 in Tor Browser for Onions, conservatively | ||
# we'll continue to support TLSv1.2 for now. | ||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 | ||
SSLCipherSuite {{ securedrop_app_https_ssl_ciphers|join(':') }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make the ciphers distro-specific, will add
install_files/ansible-base/roles/app/templates/sites-available/focal/source.conf
Outdated
Show resolved
Hide resolved
Options None | ||
AllowOverride None | ||
<Limit GET POST HEAD> | ||
Require ip {{ securedrop_app_apache_allow_from }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant docs here:
- https://httpd.apache.org/docs/2.4/howto/access.html
- https://httpd.apache.org/docs/2.4/mod/mod_authz_core.html#require
Worth being explicit about the default states, will add.
f6e958d
to
b7c0ef1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Headers on existing develop and those on this branch are the same, the only difference is the presence of X-CONTENT-SECURITY-POLICY
replaced by CONTENT-SECURITY-POLICY
(see [1]):
Updated headers on this branch:
### Source Interface
HTTP/1.1 200 OK
Date: Tue, 23 Feb 2021 16:19:07 GMT
Server: Apache
X-Frame-Options: DENY
Referrer-Policy: same-origin
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
X-Download-Options: noopen
X-Content-Type-Options: nosniff
Content-Length: 5079
Vary: Cookie,Accept-Encoding
Cache-Control: no-store
Set-Cookie: ss=something; HttpOnly; Path=/;HttpOnly
Content-Type: text/html; charset=utf-8
### Journalist Interface
HTTP/1.1 200 OK
Date: Tue, 23 Feb 2021 16:26:54 GMT
Server: Apache
X-Frame-Options: DENY
Referrer-Policy: no-referrer
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
Content-Length: 3375
Vary: Cookie,Accept-Encoding
Cache-Control: no-store
Set-Cookie: js=something; HttpOnly; Path=/;HttpOnly
Content-Type: text/html; charset=utf-8
Headers on current main
### Source Interface
HTTP/1.1 200 OK
Date: Tue, 23 Feb 2021 16:19:42 GMT
Server: Apache
X-Frame-Options: DENY
Content-Length: 3995
Vary: Cookie,Accept-Encoding
Cache-Control: no-store
Set-Cookie: ss=something; HttpOnly; Path=/;HttpOnly
Referrer-Policy: same-origin
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
X-Download-Options: noopen
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
Content-Type: text/html; charset=utf-8
### Journalist Interface
HTTP/1.1 200 OK
Date: Tue, 23 Feb 2021 16:40:31 GMT
Server: Apache
X-Frame-Options: DENY
Content-Length: 3351
Vary: Cookie,Accept-Encoding
Cache-Control: no-store
Set-Cookie: js=something; HttpOnly; Path=/;HttpOnly
Referrer-Policy: no-referrer
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
Content-Type: text/html; charset=utf-8
Etags are being served:
$ torify curl -I http://$JI.onion/api/v1/sources/cdf989d3-e1b0-4bb9-90b3-91fd9780f1e5/submissions/a56210ac-9c21-487f-a6c5-b135d2bd1b14/download -H 'Authorization: Token something' -H 'Content-Type: application/json'
HTTP/1.1 200 OK
Date: Tue, 23 Feb 2021 16:57:47 GMT
Server: Apache
X-Frame-Options: DENY
Referrer-Policy: no-referrer
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';
Content-Disposition: attachment; filename=1-innocent_odometer-msg.gpg
Cache-Control: no-store
Expires: Wed, 24 Feb 2021 04:57:47 GMT
Etag: sha256:ab73d64ef24e709bbf0136cc3e4f11dbd408a9bd3a9a994c5197b832849d792c
Last-Modified: Tue, 23 Feb 2021 16:52:19 GMT
Content-Length: 595
Content-Type: application/pgp-encrypted
Deletion now works:
torify curl -X DELETE http://$JI.onion/api/v1/sources/cdf989d3-e1b0-4bb9-90b3-91fd9780f1e5 -H 'Authorization: Token something' -H 'Content-Type: application/json' |jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 45 100 45 0 0 48 0 --:--:-- --:--:-- --:--:-- 48
{
"message": "Source and submissions deleted"
}
Good to merge when CI passes
[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
# Set a strict CSP; "default-src 'self'" prevents 3rd party subresources from | ||
# loading and prevents inline script from executing. | ||
Header onsuccess unset Content-Security-Policy | ||
Header always set Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can also set frame-ancestors
to none
here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
Reviews the Apache virtual host configurations, makes a few changes for clarity's sake, but mostly adds comments throughout. A few takeaways: Removes custom ErrorDocument These config lines were inactive. In order to activate them, we'd need to set "WSGIErrorOverride On", which would cause non-200 HTTP status codes to be handled by Apache directly. That can work, especially in the case of 404, but it isn't really what we want: we already have a fairly nice 404 handler inside the app. For a 500, that's almost certainly a failure in the Flask app somewhere, so Apache will handle it anyway. Removes redundant server blocks It's important to restrict access to the root volume, which is required because [0]: Note that the default access for <Directory "/"> is to permit all access. This means that Apache httpd will serve any file mapped from an URL. It is recommended that you change this with a block such as We were still using the old-style access syntax, so updated that according to the latest 2.4.x "require" statements, following guidance in [1]. Make Apache configs distro-specific Out of an abundance of caution, I preserved the Xenial configs as written, since we're approaching EOL for Xenial in a month or two. The new changes will be Focal-specific. [0] http://httpd.apache.org/docs/current/mod/core.html#directory [1] https://httpd.apache.org/docs/2.4/howto/access.html
Makes them distro-specific, same as for the Ansible logic that writes out the templates. Made a few adjustments, particularly where the old tests had options hardcoded, since we've more recently moved to a vars-based testing approach.
Without the access to the /var/www, our apps can not be accessed.
Required for curation actions in the Journalist Interface.
For reference, see [0]. Basically it's that list minus the chacha poly ciphers. We'll keep the cipher list for Xenial as-is, since Xenial is about to go away. [0] https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.1.1d&guideline=5.6
Based on the comments in [0], ensures that headers are always sent to the client, regardless of HTTP status code. We do this by unsetting the header if the request was successful, then forcing the same header with desired value using the "always" conditional. [0] https://httpd.apache.org/docs/current/mod/mod_headers.html
8e06819
to
c3d4626
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @conorsch looks good to merge now that CI is passing, we should test comprehensively as part of 1.8.0 prerelease testing, including the use of HTTPS certs
Status
Ready for review.
Description of Changes
Fixes #1775, fixes #1607.
Changes proposed in this pull request:
Reviews the Apache virtual host configurations, makes a few changes for
clarity's sake, but mostly adds comments throughout. A few takeaways:
Removes custom ErrorDocument
These config lines were inactive. In order to activate them, we'd need
to set "WSGIErrorOverride On", which would cause non-200 HTTP status
codes to be handled by Apache directly. That can work, especially in the
case of 404, but it isn't really what we want: we already have a fairly
nice 404 handler inside the app. For a 500, that's almost certainly a
failure in the Flask app somewhere, so Apache will handle it anyway.
Removes redundant server blocks
It's important to restrict access to the root volume, which is required
because [0]:
We were still using the old-style access syntax, so updated that according
to the latest 2.4.x "require" statements, following guidance in [1].
Make Apache configs distro-specific
Out of an abundance of caution, I preserved the Xenial configs as
written, since we're approaching EOL for Xenial in a month or two.
The new changes will be Focal-specific.
[0] http://httpd.apache.org/docs/current/mod/core.html#directory
[1] https://httpd.apache.org/docs/2.4/howto/access.html
Testing
Given that the scope of these changes is intended to be as minimal as possible, I'm comfortable trusting the upcoming QA test plan. In particular, we should test HTTPS behavior under Focal (which I myself haven't done yet).
Deployment
Was careful to keep the changes Focal-specific, to simplify the testing story. Overall, these changes are dev-oriented, adding comments and pointing out areas for future improvement.
Checklist
If you made changes to the server application code:
make lint
) and tests (make test
) pass in the development containerIf you made changes to
securedrop-admin
:make -C admin test
) pass in the admin development containerIf you made changes to the system configuration:
If you added or removed a file deployed with the application:
If you made non-trivial code changes:
Choose one of the following:
If you added or updated a code dependency:
Choose one of the following: