Skip to content

Commit

Permalink
Merge pull request #7230 from freedomofpress/release/2.10.0
Browse files Browse the repository at this point in the history
Backport 2.10.0 changes
  • Loading branch information
legoktm authored Sep 19, 2024
2 parents dbdccc5 + 5be503e commit 139591a
Show file tree
Hide file tree
Showing 64 changed files with 1,087 additions and 314 deletions.
14 changes: 1 addition & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -239,19 +239,7 @@ rust-audit:

securedrop/config.py: ## Generate the test SecureDrop application config.
@echo "███ Generating securedrop/config.py..."
@cd securedrop && source_secret_key=$(shell head -c 32 /dev/urandom | base64) \
journalist_secret_key=$(shell head -c 32 /dev/urandom | base64) \
scrypt_id_pepper=$(shell head -c 32 /dev/urandom | base64) \
scrypt_gpg_pepper=$(shell head -c 32 /dev/urandom | base64) \
python -c 'import os; from jinja2 import Environment, FileSystemLoader; \
env = Environment(loader=FileSystemLoader(".")); \
ctx = {"securedrop_app_gpg_fingerprint": "65A1B5FF195B56353CC63DFFCC40EF1228271441"}; \
ctx.update(dict((k, {"stdout":v}) for k,v in os.environ.items())); \
ctx = open("config.py", "w").write(env.get_template("config.py.example").render(ctx))'
@echo >> securedrop/config.py
@echo "SUPPORTED_LOCALES = $$(make --quiet supported-locales)" >> securedrop/config.py
@echo "SUPPORTED_LOCALES.append('en_US')" >> securedrop/config.py
@echo
@./securedrop/bin/dev-config

HOOKS_DIR=.githooks

Expand Down
31 changes: 30 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
# Changelog

## 2.10.0~rc1
## 2.10.0

This release contains fixes for issues described in the most recent security audit by 7A Security, see
our [blog post](https://securedrop.org/news/securedrop-2_10_0-released/) for more details. It also contains other maintenance fixes.

### Security

* Don't allow admins to look up arbitrary users' TOTP secrets via the web (SEC-01-001 WP4)
* Validate user provided same password back to server (SEC-01-002 WP4)
* Require POST requests for `/logout` for CSRF protection (SEC-01-003 WP4)
* Set password for redis access (SEC-01-008 WP3)
* Set `SameSite=Strict` on all cookies for more CSRF protection

### Web applications
* Dependency updates:
* sequoia-openpgp (Rust crate) from 1.20.0 to 1.21.1 (#7197)
* setuptools from 56.0.0 to 70.3.0 for CVE-2024-6345 (#7205, #7214)
* openssl (Rust crate) from 0.10.60 to 0.10.66 for RUSTSEC-2024-0357 (#7206)

### Journalist Workstation
* Dependency updates:
* setuptools from 56.0.0 to 70.3.0 for CVE-2024-6345 (#7205, #7214)
* Remove d2to1 and pbr (#7205)

### Development
* Don't point people to the decommissioned SecureDrop forum (#7204)
* Migrate all CI jobs to GitHub Actions (#7216, #7217, #7218, #7219, #7220, #7222, #7223)
* Improve staging job by using upstream gcloud-sdk image and enforcing GCE VM lifespan (#7215, #7224)
* Dependency updates:
* certifi from 2023.7.22 to 2024.7.4 for CVE-2024-39689 (#7199)
* Remove pytest-catchlog (#7199)

## 2.9.0

Expand Down
2 changes: 1 addition & 1 deletion install_files/ansible-base/group_vars/all/securedrop
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Variables that apply to both the app and monitor server go in this file
# If the monitor or app server need different values define the variable in
# hosts_vars/app.yml or host_vars/mon.yml
securedrop_version: "2.10.0~rc1"
securedrop_version: "2.10.0"
securedrop_app_code_sdist_name: "securedrop-app-code-{{ securedrop_version | replace('~', '-') }}.tar.gz"

grsecurity: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,48 @@
tags:
- securedrop_config

- name: Generate 32-byte value for "redis password".
shell: "head -c 32 /dev/urandom | base64"
register: redis_password
when: not config.stat.exists
tags:
- securedrop_config

- name: Add 32-byte value for "redis password" to config.py.
lineinfile:
dest: "{{ securedrop_code }}/config.py"
regexp: "redis_password"
line: "REDIS_PASSWORD = '{{ redis_password.stdout }}'"
when: not config.stat.exists
tags:
- securedrop_config

- name: Create rq_config.py with the redis password
copy:
content: "REDIS_PASSWORD = \"{{ redis_password.stdout }}\""
dest: "{{ securedrop_code }}/rq_config.py"
owner: "root"
group: "www-data"
mode: "0640"
when: not config.stat.exists
tags:
- securedrop_config

- name: Add 32-byte value for "redis password" to /etc/redis/redis.conf.
lineinfile:
dest: "/etc/redis/redis.conf"
regexp: "^requirepass"
line: "requirepass {{ redis_password.stdout }}"
insertafter: EOF
when: not config.stat.exists
register: redis_conf

- name: Restart redis
service:
name: redis-server
state: restarted
when: redis_conf.changed

- name: Declare Application GPG fingerprint in config.py.
lineinfile:
dest: "{{ securedrop_code }}/config.py"
Expand Down
2 changes: 1 addition & 1 deletion molecule/shared/stable.ver
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.9.0
2.10.0
2 changes: 1 addition & 1 deletion molecule/testinfra/app-code/test_securedrop_rqworker.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_securedrop_rqworker_service(host):
securedrop_test_vars.securedrop_code,
securedrop_test_vars.securedrop_venv_site_packages,
),
f"ExecStart={securedrop_test_vars.securedrop_venv_bin}/rqworker",
f"ExecStart={securedrop_test_vars.securedrop_venv_bin}/rqworker -c rq_config",
"PrivateDevices=yes",
"PrivateTmp=yes",
"ProtectSystem=full",
Expand Down
38 changes: 38 additions & 0 deletions molecule/testinfra/app/test_redis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Test redis is configured as desired
"""

import re

import testutils

sdvars = testutils.securedrop_test_vars
testinfra_hosts = [sdvars.app_hostname]


def test_auth_required(host):
"""
Verify the redis server requires authentication
"""
response = host.run("bash -c 'echo \"PING\" | redis-cli'").stdout.strip()
assert response == "NOAUTH Authentication required."


def test_password_works(host):
"""
Verify the redis password works
"""
f = host.file("/var/www/securedrop/rq_config.py")
with host.sudo():
# First let's check file permissions
assert f.is_file
assert f.user == "root"
assert f.group == "www-data"
assert f.mode == 0o640
contents = f.content_string
password = re.search('"(.*?)"', contents).group(1)
# Now run an authenticated PING
response = host.run(
f'bash -c \'echo "PING" | REDISCLI_AUTH="{password}" redis-cli\''
).stdout.strip()
assert response == "PONG"
1 change: 1 addition & 0 deletions securedrop/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Don't version control config.py because it contains secret values
config.py
rq_config.py

# compiled file
securedrop-adminc
Expand Down
34 changes: 34 additions & 0 deletions securedrop/bin/dev-config
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os
import base64
import subprocess

from jinja2 import Environment, FileSystemLoader


def generate_random(length):
# Emulate the {"stdout": "..."} dictionaries Ansible produces when configuring non-development environments.
return {'stdout': base64.b64encode(os.urandom(length)).decode()}


env = Environment(loader=FileSystemLoader("."))

ctx = {
"securedrop_app_gpg_fingerprint": "65A1B5FF195B56353CC63DFFCC40EF1228271441",
'source_secret_key': generate_random(32),
'journalist_secret_key': generate_random(32),
'scrypt_id_pepper': generate_random(32),
'scrypt_gpg_pepper': generate_random(32),
'redis_password': generate_random(32),
}

with open('securedrop/config.py', 'w') as f:
text = env.get_template("securedrop/config.py.example").render(ctx)
text += '\n'
supported_locales = subprocess.check_output(['make', '--quiet', 'supported-locales']).decode().strip()
text += f'SUPPORTED_LOCALES = {supported_locales}\n'
text += 'SUPPORTED_LOCALES.append("en_US")\n'
f.write(text)

with open('securedrop/rq_config.py', 'w') as f:
f.write('REDIS_PASSWORD = "{}"\n'.format(ctx['redis_password']['stdout']))
7 changes: 6 additions & 1 deletion securedrop/bin/dev-deps
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ function run_xvfb() {

function run_redis() {
rm -f "${REPOROOT}/securedrop/dump.rdb"
setsid redis-server >& /tmp/redis.out || cat /tmp/redis.out
redis_password=$(cd "${REPOROOT}/securedrop" && python -c "import rq_config; print(rq_config.REDIS_PASSWORD)")
echo "$redis_password" > /tmp/redispasswd
echo "requirepass ${redis_password}" | sudo -u redis tee -a /etc/redis/redis.conf
echo "Starting redis..."
sudo service redis-server start
}

function setup_vncauth {
Expand All @@ -47,6 +51,7 @@ function append_to_exit() {
function maybe_create_config_py() {
if ! test -f "${REPOROOT}/securedrop/config.py" ; then
append_to_exit "rm ${REPOROOT}/securedrop/config.py"
append_to_exit "rm ${REPOROOT}/securedrop/rq_config.py"
(cd "$REPOROOT" && make test-config)
fi
}
Expand Down
2 changes: 1 addition & 1 deletion securedrop/bin/generate-docs-screenshots
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ source "${BASH_SOURCE%/*}/dev-deps"

run_xvfb &
run_tor &
run_redis &
run_x11vnc &
urandom
build_redwood
maybe_create_config_py
run_redis

pybabel compile --directory translations/
pytest -v --page-layout "${@:-tests/functional/pageslayout}"
4 changes: 2 additions & 2 deletions securedrop/bin/run
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ cd "${REPOROOT}/securedrop"
source /opt/venvs/securedrop-app-code/bin/activate
source "${BASH_SOURCE%/*}/dev-deps"

run_redis &
urandom
build_redwood
maybe_create_config_py
run_redis
reset_demo
maybe_use_tor

# run the batch processing services normally managed by systemd
/opt/venvs/securedrop-app-code/bin/rqworker &
PYTHONPATH="${REPOROOT}/securedrop" /opt/venvs/securedrop-app-code/bin/rqworker -c rq_config &
PYTHONPATH="${REPOROOT}/securedrop" /opt/venvs/securedrop-app-code/bin/python "${REPOROOT}/securedrop/scripts/rqrequeue" --interval 60 &
PYTHONPATH="${REPOROOT}/securedrop" /opt/venvs/securedrop-app-code/bin/python "${REPOROOT}/securedrop/scripts/shredder" --interval 60 &
PYTHONPATH="${REPOROOT}/securedrop" /opt/venvs/securedrop-app-code/bin/python "${REPOROOT}/securedrop/scripts/source_deleter" --interval 10 &
Expand Down
2 changes: 1 addition & 1 deletion securedrop/bin/run-test
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ source "${BASH_SOURCE%/*}/dev-deps"

run_xvfb
run_tor &
run_redis &
setup_vncauth
run_x11vnc &
urandom
build_redwood
maybe_create_config_py
run_redis

if [ -n "${CIRCLE_BRANCH:-}" ] ; then
touch tests/log/firefox.log
Expand Down
2 changes: 1 addition & 1 deletion securedrop/bin/translation-test
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ source "${BASH_SOURCE%/*}/dev-deps"

run_xvfb
run_tor &
run_redis &
setup_vncauth
run_x11vnc &
urandom
build_redwood
maybe_create_config_py
run_redis
pybabel compile --directory translations/

mkdir -p "/tmp/test-results/logs"
Expand Down
2 changes: 2 additions & 0 deletions securedrop/config.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,5 @@ DEFAULT_LOCALE = 'en_US'

# How long a session is valid before it expires and logs a user out
SESSION_EXPIRATION_MINUTES = 120

REDIS_PASSWORD = '{{ redis_password.stdout }}'
7 changes: 5 additions & 2 deletions securedrop/debian/app-code/etc/apparmor.d/usr.sbin.apache2
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,13 @@
/var/www/securedrop/journalist_templates/_sources_confirmation_final_modal.html r,
/var/www/securedrop/journalist_templates/_sources_confirmation_modal.html r,
/var/www/securedrop/journalist_templates/account_edit_hotp_secret.html r,
/var/www/securedrop/journalist_templates/account_new_two_factor.html r,
/var/www/securedrop/journalist_templates/account_new_two_factor_totp.html r,
/var/www/securedrop/journalist_templates/account_new_two_factor_hotp.html r,
/var/www/securedrop/journalist_templates/admin.html r,
/var/www/securedrop/journalist_templates/admin_add_user.html r,
/var/www/securedrop/journalist_templates/admin_edit_hotp_secret.html r,
/var/www/securedrop/journalist_templates/admin_new_user_two_factor.html r,
/var/www/securedrop/journalist_templates/admin_new_user_two_factor_totp.html r,
/var/www/securedrop/journalist_templates/admin_new_user_two_factor_hotp.html r,
/var/www/securedrop/journalist_templates/base.html r,
/var/www/securedrop/journalist_templates/col.html r,
/var/www/securedrop/journalist_templates/config.html r,
Expand All @@ -213,6 +215,7 @@
/var/www/securedrop/pretty_bad_protocol/_meta.py r,
/var/www/securedrop/request_that_secures_file_uploads.py r,
/var/www/securedrop/rm.py r,
/var/www/securedrop/rq_config.py r,
/var/www/securedrop/sdconfig.py r,
/var/www/securedrop/secure_tempfile.py r,
/var/www/securedrop/source.py r,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Wants=redis-server.service

[Service]
Environment=PYTHONPATH="/var/www/securedrop:/opt/venvs/securedrop-app-code/lib/python3.8/site-packages"
ExecStart=/opt/venvs/securedrop-app-code/bin/rqworker
ExecStart=/opt/venvs/securedrop-app-code/bin/rqworker -c rq_config
PrivateDevices=yes
PrivateTmp=yes
ProtectSystem=full
Expand Down
8 changes: 7 additions & 1 deletion securedrop/debian/changelog
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
securedrop (2.10.0+focal) focal; urgency=medium

* see changelog.md

-- SecureDrop Team <[email protected]> Tue, 17 Sep 2024 16:05:58 -0400

securedrop (2.10.0~rc1+focal) focal; urgency=medium

* see changelog.md

-- SecureDrop Team <[email protected]> Fri, 28 Jun 2024 11:37:37 -0400
-- SecureDrop Team <[email protected]> Thu, 29 Aug 2024 14:42:38 -0700

securedrop (2.9.0+focal) focal; urgency=medium

Expand Down
25 changes: 24 additions & 1 deletion securedrop/debian/securedrop-app-code.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ export_journalist_public_key() {

}

# Password-protect access to Redis
set_redis_password() {
# Only run when upgrading, which means config.py exists and rq_config.py does not.
if ! test -f "/var/www/securedrop/rq_config.py" && test -f "/var/www/securedrop/config.py"; then
password=$(head -c 32 /dev/urandom | base64)
echo "requirepass $password" | sudo -u redis tee -a /etc/redis/redis.conf
# Set in config.py for web apps
echo "REDIS_PASSWORD = \"$password\"" >> /var/www/securedrop/config.py
# Create separate rq_config for rqworker
touch /var/www/securedrop/rq_config.py
chown root:www-data /var/www/securedrop/rq_config.py
chmod 640 /var/www/securedrop/rq_config.py
echo "REDIS_PASSWORD = \"$password\"" > /var/www/securedrop/rq_config.py
service redis-server restart
fi
}

case "$1" in
configure)
Expand Down Expand Up @@ -235,12 +251,16 @@ case "$1" in
chown -R root:root /var/www/securedrop
chmod 755 /var/www/securedrop

# Make sure config.py is owned by root and readable by www-data,
# Make sure config.py and rq_config.py are owned by root and readable by www-data,
# but not world-readable
if [ -f "/var/www/securedrop/config.py" ]; then
chown root:www-data /var/www/securedrop/config.py
chmod 640 /var/www/securedrop/config.py
fi
if [ -f "/var/www/securedrop/rq_config.py" ]; then
chown root:www-data /var/www/securedrop/rq_config.py
chmod 640 /var/www/securedrop/rq_config.py
fi
# And logo needs to be writable by webserver user
# If there's no custom logo yet, copy the default in its place
if [ ! -f "/var/www/securedrop/static/i/custom_logo.png" ]; then
Expand Down Expand Up @@ -291,6 +311,9 @@ case "$1" in
# GPG -> Sequoia migration
export_journalist_public_key

# Set redis password
set_redis_password

# Version migrations
database_migration

Expand Down
Loading

0 comments on commit 139591a

Please sign in to comment.