From 0be7b25c647eb4a16ef51c93a37b983c5afef184 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Tue, 29 Jan 2019 18:52:02 +0200 Subject: [PATCH] Make (most) containers run with a read-only filesystem --- CHANGELOG.md | 13 +++++++++---- group_vars/matrix-servers | 2 +- .../templates/systemd/matrix-corporal.service.j2 | 1 + .../templates/systemd/matrix-coturn.service.j2 | 2 ++ .../templates/systemd/matrix-mailer.service.j2 | 2 ++ .../templates/systemd/matrix-mxisd.service.j2 | 8 +++++++- roles/matrix-nginx-proxy/defaults/main.yml | 7 +++++-- .../templates/nginx/conf.d/matrix-synapse.conf.j2 | 2 +- .../templates/systemd/matrix-nginx-proxy.service.j2 | 4 ++++ roles/matrix-postgres/tasks/import_postgres.yml | 2 +- .../templates/systemd/matrix-postgres.service.j2 | 5 ++++- .../templates/systemd/matrix-riot-web.service.j2 | 2 ++ roles/matrix-synapse/defaults/main.yml | 5 ++++- .../synapse/systemd/matrix-synapse.service.j2 | 8 ++++++-- 14 files changed, 49 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be257ddd..29bded25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,18 @@ -# 2019-01-xx +# 2019-01-29 -## Running container processes as non-root +## Running container processes as non-root, without capabilities and read-only To improve security, this playbook no longer starts container processes as the `root` user. - Most containers were dropping privileges anyway, but we were trusting them with `root` privileges until they would do that. Not anymore -- container processes now start as a non-root user (usually `matrix`) from the get-go. -For additional security, various [capabilities are also dropped](https://github.com/projectatomic/atomic-site/issues/203) for all containers. +For additional security, various capabilities are also dropped (see [why it's important](https://github.com/projectatomic/atomic-site/issues/203)) for all containers. + +Additionally, most containers now use a read-only filesystem (see [why it's important](https://www.projectatomic.io/blog/2015/12/making-docker-images-write-only-in-production/)). +Containers are given write access only to the directories they need to write to. + +A minor breaking change is the `matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size` variable having being renamed to `matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size_mb` (note the `_mb` suffix). The new variable expects a number value (e.g. `25M` -> `25`). +If you weren't customizing this variable, this wouldn't affect you. ## matrix-mailer is now based on Exim, not Postfix diff --git a/group_vars/matrix-servers b/group_vars/matrix-servers index 70d63256..7d4becce 100644 --- a/group_vars/matrix-servers +++ b/group_vars/matrix-servers @@ -158,7 +158,7 @@ matrix_nginx_proxy_enabled: true matrix_nginx_proxy_proxy_matrix_client_api_addr_with_container: "{{ 'matrix-corporal:41080' if matrix_corporal_enabled else 'matrix-synapse:8008' }}" matrix_nginx_proxy_proxy_matrix_client_api_addr_sans_container: "{{ 'localhost:41080' if matrix_corporal_enabled else 'localhost:8008' }}" -matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size: "{{ matrix_synapse_max_upload_size_mb }}M" +matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size_mb: "{{ matrix_synapse_max_upload_size_mb }}" matrix_nginx_proxy_proxy_matrix_enabled: true matrix_nginx_proxy_proxy_riot_enabled: "{{ matrix_riot_web_enabled }}" diff --git a/roles/matrix-corporal/templates/systemd/matrix-corporal.service.j2 b/roles/matrix-corporal/templates/systemd/matrix-corporal.service.j2 index 3d8c083f..4035aa65 100644 --- a/roles/matrix-corporal/templates/systemd/matrix-corporal.service.j2 +++ b/roles/matrix-corporal/templates/systemd/matrix-corporal.service.j2 @@ -13,6 +13,7 @@ ExecStart=/usr/bin/docker run --rm --name matrix-corporal \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ + --read-only \ --network={{ matrix_docker_network }} \ {% if matrix_corporal_container_expose_ports %} -p 127.0.0.1:41080:41080 \ diff --git a/roles/matrix-coturn/templates/systemd/matrix-coturn.service.j2 b/roles/matrix-coturn/templates/systemd/matrix-coturn.service.j2 index cf2c096e..980ca984 100644 --- a/roles/matrix-coturn/templates/systemd/matrix-coturn.service.j2 +++ b/roles/matrix-coturn/templates/systemd/matrix-coturn.service.j2 @@ -13,6 +13,8 @@ ExecStart=/usr/bin/docker run --rm --name matrix-coturn \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ + --read-only \ + --tmpfs=/var/tmp:rw,noexec,nosuid,size=100m \ -p 3478:3478 \ -p 3478:3478/udp \ -p {{ matrix_coturn_turn_udp_min_port }}-{{ matrix_coturn_turn_udp_max_port }}:{{ matrix_coturn_turn_udp_min_port }}-{{ matrix_coturn_turn_udp_max_port }}/udp \ diff --git a/roles/matrix-mailer/templates/systemd/matrix-mailer.service.j2 b/roles/matrix-mailer/templates/systemd/matrix-mailer.service.j2 index d588ac0e..54beb215 100644 --- a/roles/matrix-mailer/templates/systemd/matrix-mailer.service.j2 +++ b/roles/matrix-mailer/templates/systemd/matrix-mailer.service.j2 @@ -11,6 +11,8 @@ ExecStart=/usr/bin/docker run --rm --name matrix-mailer \ --log-driver=none \ --user={{ matrix_mailer_container_user_uid }}:{{ matrix_mailer_container_user_gid }} \ --cap-drop=ALL \ + --read-only \ + --tmpfs=/var/spool/exim:rw,noexec,nosuid,size=100m \ --network={{ matrix_docker_network }} \ --env-file={{ matrix_mailer_base_path }}/env-mailer \ --hostname={{ hostname_matrix }} \ diff --git a/roles/matrix-mxisd/templates/systemd/matrix-mxisd.service.j2 b/roles/matrix-mxisd/templates/systemd/matrix-mxisd.service.j2 index 1835301a..de0c0681 100644 --- a/roles/matrix-mxisd/templates/systemd/matrix-mxisd.service.j2 +++ b/roles/matrix-mxisd/templates/systemd/matrix-mxisd.service.j2 @@ -12,17 +12,23 @@ Wants={{ service }} Type=simple ExecStartPre=-/usr/bin/docker kill matrix-mxisd ExecStartPre=-/usr/bin/docker rm matrix-mxisd + +# mxisd writes an SQLite shared library (libsqlitejdbc.so) to /tmp and executes it from there, +# so /tmp needs to be mounted with an exec option. ExecStart=/usr/bin/docker run --rm --name matrix-mxisd \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ + --read-only \ + --tmpfs=/tmp:rw,exec,nosuid,size=10m \ --network={{ matrix_docker_network }} \ {% if matrix_mxisd_container_expose_port %} -p 127.0.0.1:8090:8090 \ {% endif %} -v {{ matrix_mxisd_config_path }}:/etc/mxisd:ro \ - -v {{ matrix_mxisd_data_path }}:/var/mxisd \ + -v {{ matrix_mxisd_data_path }}:/var/mxisd:rw \ {{ matrix_mxisd_docker_image }} + ExecStop=-/usr/bin/docker kill matrix-mxisd ExecStop=-/usr/bin/docker rm matrix-mxisd Restart=always diff --git a/roles/matrix-nginx-proxy/defaults/main.yml b/roles/matrix-nginx-proxy/defaults/main.yml index 78b2da12..94a86adc 100644 --- a/roles/matrix-nginx-proxy/defaults/main.yml +++ b/roles/matrix-nginx-proxy/defaults/main.yml @@ -44,7 +44,10 @@ matrix_nginx_proxy_proxy_matrix_identity_api_addr_sans_container: "localhost:809 matrix_nginx_proxy_proxy_matrix_client_api_addr_with_container: "matrix-synapse:8008" matrix_nginx_proxy_proxy_matrix_client_api_addr_sans_container: "localhost:8008" # This needs to be equal or higher than the maximum upload size accepted by Synapse. -matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size: "25M" +matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size_mb: 25 + +# The tmpfs at /tmp needs to be large enough to handle multiple concurrent file uploads. +matrix_nginx_proxy_tmp_directory_size_mb: "{{ matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size_mb * 50 }}" # A list of strings containing additional configuration blocks to add to the matrix domain's server configuration. matrix_nginx_proxy_proxy_matrix_additional_server_configuration_blocks: [] @@ -85,4 +88,4 @@ matrix_ssl_lets_encrypt_support_email: "{{ host_specific_matrix_ssl_lets_encrypt matrix_ssl_base_path: "{{ matrix_base_data_path }}/ssl" matrix_ssl_config_dir_path: "{{ matrix_ssl_base_path }}/config" -matrix_ssl_log_dir_path: "{{ matrix_ssl_base_path }}/log" \ No newline at end of file +matrix_ssl_log_dir_path: "{{ matrix_ssl_base_path }}/log" diff --git a/roles/matrix-nginx-proxy/templates/nginx/conf.d/matrix-synapse.conf.j2 b/roles/matrix-nginx-proxy/templates/nginx/conf.d/matrix-synapse.conf.j2 index 3638e357..016b9414 100644 --- a/roles/matrix-nginx-proxy/templates/nginx/conf.d/matrix-synapse.conf.j2 +++ b/roles/matrix-nginx-proxy/templates/nginx/conf.d/matrix-synapse.conf.j2 @@ -116,7 +116,7 @@ server { proxy_set_header X-Forwarded-For $remote_addr; client_body_buffer_size 25M; - client_max_body_size {{ matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size }}; + client_max_body_size {{ matrix_nginx_proxy_proxy_matrix_client_api_client_max_body_size_mb }}M; proxy_max_temp_file_size 0; } diff --git a/roles/matrix-nginx-proxy/templates/systemd/matrix-nginx-proxy.service.j2 b/roles/matrix-nginx-proxy/templates/systemd/matrix-nginx-proxy.service.j2 index 810797d7..410d6a01 100644 --- a/roles/matrix-nginx-proxy/templates/systemd/matrix-nginx-proxy.service.j2 +++ b/roles/matrix-nginx-proxy/templates/systemd/matrix-nginx-proxy.service.j2 @@ -12,10 +12,13 @@ Wants={{ service }} Type=simple ExecStartPre=-/usr/bin/docker kill matrix-nginx-proxy ExecStartPre=-/usr/bin/docker rm matrix-nginx-proxy + ExecStart=/usr/bin/docker run --rm --name matrix-nginx-proxy \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ + --read-only \ + --tmpfs=/tmp:rw,noexec,nosuid,size={{ matrix_nginx_proxy_tmp_directory_size_mb }}m \ --network={{ matrix_docker_network }} \ -p 80:8080 \ -p 443:8443 \ @@ -24,6 +27,7 @@ ExecStart=/usr/bin/docker run --rm --name matrix-nginx-proxy \ -v {{ matrix_ssl_config_dir_path }}:{{ matrix_ssl_config_dir_path }}:ro \ -v {{ matrix_static_files_base_path }}:{{ matrix_static_files_base_path }}:ro \ {{ matrix_nginx_proxy_docker_image }} + ExecStop=-/usr/bin/docker kill matrix-nginx-proxy ExecStop=-/usr/bin/docker rm matrix-nginx-proxy ExecReload=/usr/bin/docker exec matrix-nginx-proxy /usr/sbin/nginx -s reload diff --git a/roles/matrix-postgres/tasks/import_postgres.yml b/roles/matrix-postgres/tasks/import_postgres.yml index 9f1c15c0..67d7f86c 100644 --- a/roles/matrix-postgres/tasks/import_postgres.yml +++ b/roles/matrix-postgres/tasks/import_postgres.yml @@ -73,7 +73,7 @@ - name: Note about Postgres importing alternative debug: - msg: > + msg: >- Importing Postgres database using the following command: `{{ matrix_postgres_import_command }}`. If this crashes, you can stop Postgres (`systemctl stop matrix-postgres`), delete its existing data (`rm -rf {{ matrix_postgres_data_path }}/*`), start it again (`systemctl start matrix-postgres`) diff --git a/roles/matrix-postgres/templates/systemd/matrix-postgres.service.j2 b/roles/matrix-postgres/templates/systemd/matrix-postgres.service.j2 index 89b5abb8..0d2b9fcf 100644 --- a/roles/matrix-postgres/templates/systemd/matrix-postgres.service.j2 +++ b/roles/matrix-postgres/templates/systemd/matrix-postgres.service.j2 @@ -11,9 +11,12 @@ ExecStart=/usr/bin/docker run --rm --name matrix-postgres \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ + --read-only \ + --tmpfs=/tmp:rw,noexec,nosuid,size=100m \ + --tmpfs=/run/postgresql:rw,noexec,nosuid,size=100m \ --network={{ matrix_docker_network }} \ --env-file={{ matrix_postgres_base_path }}/env-postgres-server \ - -v {{ matrix_postgres_data_path }}:/var/lib/postgresql/data \ + -v {{ matrix_postgres_data_path }}:/var/lib/postgresql/data:rw \ -v /etc/passwd:/etc/passwd:ro \ {{ matrix_postgres_docker_image_to_use }} ExecStop=-/usr/bin/docker stop matrix-postgres diff --git a/roles/matrix-riot-web/templates/systemd/matrix-riot-web.service.j2 b/roles/matrix-riot-web/templates/systemd/matrix-riot-web.service.j2 index e7929489..5cd69774 100644 --- a/roles/matrix-riot-web/templates/systemd/matrix-riot-web.service.j2 +++ b/roles/matrix-riot-web/templates/systemd/matrix-riot-web.service.j2 @@ -13,6 +13,8 @@ ExecStart=/usr/bin/docker run --rm --name matrix-riot-web \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ + --read-only \ + --tmpfs=/tmp:rw,noexec,nosuid,size=10m \ -v {{ matrix_riot_web_data_path }}/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /dev/null:/etc/nginx/conf.d/default.conf:ro \ -v {{ matrix_riot_web_data_path }}/config.json:/etc/riot-web/config.json:ro \ diff --git a/roles/matrix-synapse/defaults/main.yml b/roles/matrix-synapse/defaults/main.yml index a0c42b03..cc3eed17 100644 --- a/roles/matrix-synapse/defaults/main.yml +++ b/roles/matrix-synapse/defaults/main.yml @@ -39,6 +39,9 @@ matrix_synapse_max_upload_size_mb: 10 matrix_synapse_max_log_file_size_mb: 100 matrix_synapse_max_log_files_count: 10 +# The tmpfs at /tmp needs to be large enough to handle multiple concurrent file uploads. +matrix_synapse_tmp_directory_size_mb: "{{ matrix_synapse_max_upload_size_mb * 50 }}" + # Log levels # Possible options are defined here https://docs.python.org/3/library/logging.html#logging-levels # warning: setting log level to DEBUG will make synapse log sensitive information such @@ -187,4 +190,4 @@ matrix_mautrix_whatsapp_enabled: false matrix_mautrix_whatsapp_docker_image: "tulir/mautrix-whatsapp:latest" -matrix_mautrix_whatsapp_base_path: "{{ matrix_base_data_path }}/mautrix-whatsapp" \ No newline at end of file +matrix_mautrix_whatsapp_base_path: "{{ matrix_base_data_path }}/mautrix-whatsapp" diff --git a/roles/matrix-synapse/templates/synapse/systemd/matrix-synapse.service.j2 b/roles/matrix-synapse/templates/synapse/systemd/matrix-synapse.service.j2 index 79b3478c..a369190d 100644 --- a/roles/matrix-synapse/templates/synapse/systemd/matrix-synapse.service.j2 +++ b/roles/matrix-synapse/templates/synapse/systemd/matrix-synapse.service.j2 @@ -18,11 +18,14 @@ ExecStartPre=-/usr/bin/docker rm matrix-synapse # we'd write files to the local filesystem and fusermount will complain. ExecStartPre=/bin/sleep 5 {% endif %} + ExecStart=/usr/bin/docker run --rm --name matrix-synapse \ --log-driver=none \ --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ --cap-drop=ALL \ --entrypoint=python \ + --read-only \ + --tmpfs=/tmp:rw,noexec,nosuid,size={{ matrix_synapse_tmp_directory_size_mb }}m \ --network={{ matrix_docker_network }} \ -e SYNAPSE_CACHE_FACTOR={{ matrix_synapse_cache_factor }} \ {% if matrix_synapse_federation_enabled %} @@ -31,14 +34,15 @@ ExecStart=/usr/bin/docker run --rm --name matrix-synapse \ {% if matrix_synapse_container_expose_client_server_api_port %} -p 127.0.0.1:8008:8008 \ {% endif %} - -v {{ matrix_synapse_config_dir_path }}:/data \ - -v {{ matrix_synapse_run_path }}:/matrix-run \ + -v {{ matrix_synapse_config_dir_path }}:/data:ro \ + -v {{ matrix_synapse_run_path }}:/matrix-run:rw \ -v {{ matrix_synapse_base_path }}/storage:/matrix-media-store-parent:slave \ {% for volume in matrix_synapse_container_additional_volumes %} -v {{ volume.src }}:{{ volume.dst }}:{{ volume.options }} \ {% endfor %} {{ matrix_synapse_docker_image }} \ -m synapse.app.homeserver -c /data/homeserver.yaml + ExecStop=-/usr/bin/docker kill matrix-synapse ExecStop=-/usr/bin/docker rm matrix-synapse Restart=always