diff --git a/compose/production/mattermost/Dockerfile b/compose/production/mattermost/app/Dockerfile similarity index 100% rename from compose/production/mattermost/Dockerfile rename to compose/production/mattermost/app/Dockerfile diff --git a/compose/production/mattermost/entrypoint.sh b/compose/production/mattermost/app/entrypoint.sh similarity index 100% rename from compose/production/mattermost/entrypoint.sh rename to compose/production/mattermost/app/entrypoint.sh diff --git a/compose/production/mattermost/db/Dockerfile b/compose/production/mattermost/db/Dockerfile new file mode 100644 index 0000000..d261fd2 --- /dev/null +++ b/compose/production/mattermost/db/Dockerfile @@ -0,0 +1,33 @@ +FROM postgres:9.4-alpine + +ENV DEFAULT_TIMEZONE UTC + +# Install some packages to use WAL +RUN echo "azure<5.0.0" > pip-constraints.txt +RUN apk add --no-cache \ + build-base \ + curl \ + libc6-compat \ + libffi-dev \ + linux-headers \ + python-dev \ + py-pip \ + py-cryptography \ + pv \ + libressl-dev \ + && pip install --upgrade pip \ + && pip --no-cache-dir install -c pip-constraints.txt 'wal-e<1.0.0' envdir \ + && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* + +# Add wale script +COPY setup-wale.sh /docker-entrypoint-initdb.d/ + +#Healthcheck to make sure container is ready +HEALTHCHECK CMD pg_isready -U $POSTGRES_USER -d $POSTGRES_DB || exit 1 + +# Add and configure entrypoint and command +COPY entrypoint.sh / +ENTRYPOINT ["/entrypoint.sh"] +CMD ["postgres"] + +VOLUME ["/var/run/postgresql", "/usr/share/postgresql/", "/var/lib/postgresql/data", "/tmp", "/etc/wal-e.d/env"] diff --git a/compose/production/mattermost/db/entrypoint.sh b/compose/production/mattermost/db/entrypoint.sh new file mode 100755 index 0000000..ce0facc --- /dev/null +++ b/compose/production/mattermost/db/entrypoint.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# if wal-e backup is not enabled, use minimal wal-e logging to reduce disk space +export WAL_LEVEL=${WAL_LEVEL:-minimal} +export ARCHIVE_MODE=${ARCHIVE_MODE:-off} +export ARCHIVE_TIMEOUT=${ARCHIVE_TIMEOUT:-60} + +function update_conf () { + wal=$1 + # PGDATA is defined in upstream postgres dockerfile + config_file=$PGDATA/postgresql.conf + + # Check if configuration file exists. If not, it probably means that database is not initialized yet + if [ ! -f $config_file ]; then + return + fi + # Reinitialize config + sed -i "s/log_timezone =.*$//g" $PGDATA/postgresql.conf + sed -i "s/timezone =.*$//g" $PGDATA/postgresql.conf + sed -i "s/wal_level =.*$//g" $config_file + sed -i "s/archive_mode =.*$//g" $config_file + sed -i "s/archive_timeout =.*$//g" $config_file + sed -i "s/archive_command =.*$//g" $config_file + + # Configure wal-e + if [ "$wal" = true ] ; then + /docker-entrypoint-initdb.d/setup-wale.sh + fi + echo "log_timezone = $DEFAULT_TIMEZONE" >> $config_file + echo "timezone = $DEFAULT_TIMEZONE" >> $config_file +} + +if [ "${1:0:1}" = '-' ]; then + set -- postgres "$@" +fi + +if [ "$1" = 'postgres' ]; then + # Check wal-e variables + wal_enable=true + VARS=(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY WALE_S3_PREFIX AWS_REGION) + for v in ${VARS[@]}; do + if [ "${!v}" = "" ]; then + echo "$v is required for Wal-E but not set. Skipping Wal-E setup." + wal_enable=false + fi + done + + # Setup wal-e env variables + if [ "$wal_enable" = true ] ; then + for v in ${VARS[@]}; do + export $v="${!v}" + done + WAL_LEVEL=archive + ARCHIVE_MODE=on + fi + + # Update postgresql configuration + update_conf $wal_enable + + # Run the postgresql entrypoint + docker-entrypoint.sh postgres +fi diff --git a/compose/production/mattermost/db/setup-wale.sh b/compose/production/mattermost/db/setup-wale.sh new file mode 100755 index 0000000..cf34ea5 --- /dev/null +++ b/compose/production/mattermost/db/setup-wale.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# wal-e specific configuration +echo "wal_level = $WAL_LEVEL" >> $PGDATA/postgresql.conf +echo "archive_mode = $ARCHIVE_MODE" >> $PGDATA/postgresql.conf +echo "archive_command = '/usr/bin/wal-e wal-push %p'" >> $PGDATA/postgresql.conf +echo "archive_timeout = $ARCHIVE_TIMEOUT" >> $PGDATA/postgresql.conf diff --git a/compose/production/mattermost/web/Dockerfile b/compose/production/mattermost/web/Dockerfile new file mode 100644 index 0000000..a138e0c --- /dev/null +++ b/compose/production/mattermost/web/Dockerfile @@ -0,0 +1,19 @@ +FROM nginx:mainline-alpine + +# Remove default configuration and add our custom Nginx configuration files +RUN rm /etc/nginx/conf.d/default.conf \ + && apk add --no-cache curl + +COPY ["./mattermost", "./mattermost-ssl", "/etc/nginx/sites-available/"] +COPY ./security.conf /etc/nginx/conf.d/ + +# Add and setup entrypoint +COPY entrypoint.sh / + +#Healthcheck to make sure container is ready +HEALTHCHECK CMD curl --fail http://localhost:80 || exit 1 + +ENTRYPOINT ["/entrypoint.sh"] + +VOLUME ["/var/run", "/etc/nginx/conf.d/", "/var/cache/nginx/"] + diff --git a/compose/production/mattermost/web/entrypoint.sh b/compose/production/mattermost/web/entrypoint.sh new file mode 100755 index 0000000..6a7d9bc --- /dev/null +++ b/compose/production/mattermost/web/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# Define default value for app container hostname and port +APP_HOST=${APP_HOST:-app} +APP_PORT_NUMBER=${APP_PORT_NUMBER:-8000} + +# Check if SSL should be enabled (if certificates exists) +if [ -f "/cert/cert.pem" -a -f "/cert/key-no-password.pem" ]; then + echo "found certificate and key, linking ssl config" + ssl="-ssl" +else + echo "linking plain config" +fi +# Ensure that the configuration file is not present before linking. +test -w /etc/nginx/conf.d/mattermost.conf && rm /etc/nginx/conf.d/mattermost.conf +# Linking Nginx configuration file +ln -s -f /etc/nginx/sites-available/mattermost$ssl /etc/nginx/conf.d/mattermost.conf + +# Setup app host and port on configuration file +sed -i "s/{%APP_HOST%}/${APP_HOST}/g" /etc/nginx/conf.d/mattermost.conf +sed -i "s/{%APP_PORT%}/${APP_PORT_NUMBER}/g" /etc/nginx/conf.d/mattermost.conf + +# Run Nginx +exec nginx -g 'daemon off;' diff --git a/compose/production/mattermost/web/mattermost b/compose/production/mattermost/web/mattermost new file mode 100644 index 0000000..ac301ae --- /dev/null +++ b/compose/production/mattermost/web/mattermost @@ -0,0 +1,39 @@ +map $http_x_forwarded_proto $proxy_x_forwarded_proto { + default $http_x_forwarded_proto; + '' $scheme; +} + +server { + listen 80; + + location ~ /api/v[0-9]+/(users/)?websocket$ { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 50M; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_pass http://{%APP_HOST%}:{%APP_PORT%}; + } + + location / { + gzip on; + + client_max_body_size 50M; + proxy_set_header Connection ""; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_pass http://{%APP_HOST%}:{%APP_PORT%}; + } +} diff --git a/compose/production/mattermost/web/mattermost-ssl b/compose/production/mattermost/web/mattermost-ssl new file mode 100644 index 0000000..42f1fdf --- /dev/null +++ b/compose/production/mattermost/web/mattermost-ssl @@ -0,0 +1,57 @@ +server { + listen 80 default_server; + server_name _; + return 301 https://$host$request_uri; +} + +map $http_x_forwarded_proto $proxy_x_forwarded_proto { + default $http_x_forwarded_proto; + '' $scheme; +} + +server { + listen 443 ssl http2; + + ssl_certificate /cert/cert.pem; + ssl_certificate_key /cert/key-no-password.pem; + ssl_session_timeout 5m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:MEDIUM:!SSLv2:!PSK:!SRP:!ADH:!AECDH; + ssl_prefer_server_ciphers on; + + location ~ /api/v[0-9]+/(users/)?websocket$ { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header Connection "upgrade"; + + client_max_body_size 50M; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_pass http://{%APP_HOST%}:{%APP_PORT%}; + } + + location / { + gzip on; + proxy_set_header X-Forwarded-Ssl on; + + client_max_body_size 50M; + proxy_set_header Connection ""; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_pass http://{%APP_HOST%}:{%APP_PORT%}; + } +} + +# See https://docs.mattermost.com/install/install-ubuntu-1604.html#configuring-nginx-with-ssl-and-http-2 for the SSL configuration diff --git a/compose/production/mattermost/web/security.conf b/compose/production/mattermost/web/security.conf new file mode 100644 index 0000000..136f59c --- /dev/null +++ b/compose/production/mattermost/web/security.conf @@ -0,0 +1,22 @@ +# don't send the nginx version number in error pages and Server header +server_tokens off; + +# config to don't allow the browser to render the page inside an frame or iframe +# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking +# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri +# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options +add_header X-Frame-Options SAMEORIGIN; + +# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header, +# to disable content-type sniffing on some browsers. +# https://www.owasp.org/index.php/List_of_useful_HTTP_headers +# currently supported in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx +# http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx +# 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020 +add_header X-Content-Type-Options nosniff; + +# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers. +# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for +# this particular website if it was disabled by the user. +# https://www.owasp.org/index.php/List_of_useful_HTTP_headers +add_header X-XSS-Protection "1; mode=block"; diff --git a/extra/mattermost.yml b/extra/mattermost.yml index 99fdd28..1c26fe1 100644 --- a/extra/mattermost.yml +++ b/extra/mattermost.yml @@ -3,7 +3,8 @@ version: "3" services: db: - build: db + build: + context: compose/production/mattermost/db read_only: true restart: unless-stopped volumes: @@ -23,7 +24,7 @@ services: app: build: - context: compose/production/mattermost #app + context: compose/production/mattermost/app # uncomment following lines for team edition or change UID/GID args: - edition=team @@ -42,22 +43,23 @@ services: # - /pki_chain.pem:/etc/ssl/certs/pki_chain.pem:ro env_file: - ../.envs/.production/.mattermost - environment: - # set same as db credentials and dbname - #- MM_USERNAME=mmuser - #- MM_PASSWORD=mmuser_password - #- MM_DBNAME=mattermost + #environment: + # # set same as db credentials and dbname + # #- MM_USERNAME=mmuser + # #- MM_PASSWORD=mmuser_password + # #- MM_DBNAME=mattermost - # use the credentials you've set above, in the format: - # MM_SQLSETTINGS_DATASOURCE=postgres://${MM_USERNAME}:${MM_PASSWORD}@db:5432/${MM_DBNAME}?sslmode=disable&connect_timeout=10 - #- MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mmuser_password@db:5432/mattermost?sslmode=disable&connect_timeout=10 - - MM_SQLSETTINGS_DATASOURCE=postgres://${MM_USERNAME}:${MM_PASSWORD}@db:5432/${MM_DBNAME}?sslmode=disable&connect_timeout=10 + # # use the credentials you've set above, in the format: + # # MM_SQLSETTINGS_DATASOURCE=postgres://${MM_USERNAME}:${MM_PASSWORD}@db:5432/${MM_DBNAME}?sslmode=disable&connect_timeout=10 + # #- MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mmuser_password@db:5432/mattermost?sslmode=disable&connect_timeout=10 + # - MM_SQLSETTINGS_DATASOURCE=postgres://${MM_USERNAME}:${MM_PASSWORD}@db:5432/${MM_DBNAME}?sslmode=disable&connect_timeout=10 - # in case your config is not in default location - #- MM_CONFIG=/mattermost/config/config.json + # # in case your config is not in default location + # #- MM_CONFIG=/mattermost/config/config.json web: - build: web + build: + context: compose/production/mattermost/web #ports: # - "80:80" # - "443:443" diff --git a/scripts/subinstallers/gen_prod_env.sh b/scripts/subinstallers/gen_prod_env.sh index a32c2cd..ead670d 100755 --- a/scripts/subinstallers/gen_prod_env.sh +++ b/scripts/subinstallers/gen_prod_env.sh @@ -84,4 +84,4 @@ MATTERMOST_PROD_FILE="./.envs/.production/.mattermost" echo "MM_USERNAME=mmuser" > $MATTERMOST_PROD_FILE echo "MM_PASSWORD=$1" >> $MATTERMOST_PROD_FILE echo "MM_DBNAME=mattermost" >> $MATTERMOST_PROD_FILE - +echo "MM_SQLSETTINGS_DATASOURCE=postgres://${MM_USERNAME}:${MM_PASSWORD}@db:5432/${MM_DBNAME}?sslmode=disable&connect_timeout=10" >> $MATTERMOST_PROD_FILE