diff --git a/CHANGELOG.md b/CHANGELOG.md index 49caa20..aa14615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,20 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased](https://github.com/passbolt/passbolt_docker/compare/v2.1.0...HEAD) +## [Unreleased](https://github.com/passbolt/passbolt_docker/compare/v2.2.0...HEAD) + +## [2.2.0](https://github.com/passbolt/passbolt_docker/compare/v2.1.0...v2.2.0) - 2018-08-13 + +### Added + +- Added [wait-for-it](https://github.com/vishnubob/wait-for-it) instead of wait for to eliminate netcat dependency + +### Changed + +- Merged: hide nginx and php version [#107](https://github.com/passbolt/passbolt_docker/pull/107) +- Merged: restrict MySQL port access [#109](https://github.com/passbolt/passbolt_docker/pull/109) +- Supervisor config files split into conf.d/{php.conf,nginx.conf,cron.conf} +- Default stdout logging is more verbose now allowing users to see more details on the requests ## [2.1.0](https://github.com/passbolt/passbolt_docker/compare/v2.0.7...v2.1.0) - 2018-06-14 diff --git a/Dockerfile b/Dockerfile index 76f9df4..5d0e4a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM php:7-fpm LABEL maintainer="diego@passbolt.com" -ARG PASSBOLT_VERSION="2.1.0" +ARG PASSBOLT_VERSION="2.2.0" ARG PASSBOLT_URL="https://github.com/passbolt/passbolt_api/archive/v${PASSBOLT_VERSION}.tar.gz" ARG PHP_EXTENSIONS="gd \ @@ -41,7 +41,6 @@ RUN apt-get update \ libmcrypt4 \ mysql-client \ supervisor \ - netcat \ cron \ && mkdir /home/www-data \ && chown -R www-data:www-data /home/www-data \ @@ -75,10 +74,12 @@ RUN apt-get update \ && rm /etc/nginx/sites-enabled/default \ && apt-get purge -y --auto-remove $PASSBOLT_DEV_PACKAGES \ && rm -rf /var/lib/apt/lists/* \ - && rm /usr/local/bin/composer + && rm /usr/local/bin/composer \ + && echo 'php_flag[expose_php] = off' > /usr/local/etc/php-fpm.d/expose.conf \ + && sed -i 's/# server_tokens/server_tokens/' /etc/nginx/nginx.conf COPY conf/passbolt.conf /etc/nginx/conf.d/default.conf -COPY conf/supervisord.conf /etc/supervisor/supervisord.conf +COPY conf/supervisor/*.conf /etc/supervisor/conf.d/ COPY bin/docker-entrypoint.sh /docker-entrypoint.sh EXPOSE 80 443 diff --git a/conf/passbolt.conf b/conf/passbolt.conf index e20c7a6..7b3f453 100644 --- a/conf/passbolt.conf +++ b/conf/passbolt.conf @@ -12,6 +12,8 @@ server { root /var/www/passbolt/webroot; index index.php; + error_log /dev/stdout info; + access_log /dev/stdout; location / { try_files $uri $uri/ /index.php?$args; @@ -53,6 +55,8 @@ server { root /var/www/passbolt/webroot; index index.php; + error_log /dev/stdout info; + access_log /dev/stdout; location / { try_files $uri $uri/ /index.php?$args; diff --git a/conf/supervisor/cron.conf b/conf/supervisor/cron.conf new file mode 100644 index 0000000..5b6291f --- /dev/null +++ b/conf/supervisor/cron.conf @@ -0,0 +1,8 @@ +[program:cron] +command=cron -f -l +autostart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/conf/supervisor/nginx.conf b/conf/supervisor/nginx.conf new file mode 100644 index 0000000..ea91d89 --- /dev/null +++ b/conf/supervisor/nginx.conf @@ -0,0 +1,8 @@ +[program:nginx] +command=/usr/sbin/nginx -g 'daemon off;' +autostart=true +priority=10 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/conf/supervisor/php.conf b/conf/supervisor/php.conf new file mode 100644 index 0000000..c008c6b --- /dev/null +++ b/conf/supervisor/php.conf @@ -0,0 +1,8 @@ +[program:php-fpm] +command=php-fpm -F +autostart=true +priority=5 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/conf/supervisord.conf b/conf/supervisord.conf deleted file mode 100644 index cb90377..0000000 --- a/conf/supervisord.conf +++ /dev/null @@ -1,52 +0,0 @@ -; supervisor config file - -[unix_http_server] -file=/var/run/supervisor.sock ; (the path to the socket file) -chmod=0700 ; sockef file mode (default 0700) - -[supervisord] -logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) -pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) -childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) - -; the below section must remain in the config file for RPC -; (supervisorctl/web interface) to work, additional interfaces may be -; added by defining them in separate rpcinterface: sections -[rpcinterface:supervisor] -supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - -[supervisorctl] -serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket - -; The [include] section can just contain the "files" setting. This -; setting can list multiple files (separated by whitespace or -; newlines). It can also contain wildcards. The filenames are -; interpreted as relative to this file. Included files *cannot* -; include files themselves. - -[program:php-fpm] -command=php-fpm -autostart=true -priority=5 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:nginx] -command=nginx -g "daemon off;" -autostart=true -priority=10 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:cron] -command=cron -f -l -autostart=true -priority=20 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 5dbb088..0ff4ac0 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -7,7 +7,7 @@ services: volumes: - database_volume:/var/lib/mysql ports: - - 3306 + - "127.0.0.1:3306:3306" passbolt: build: diff --git a/docker-compose-pro.yml b/docker-compose-pro.yml index 2666bdf..1d943e7 100644 --- a/docker-compose-pro.yml +++ b/docker-compose-pro.yml @@ -7,10 +7,10 @@ services: volumes: - database_volume:/var/lib/mysql ports: - - 3306 + - "127.0.0.1:3306:3306" passbolt: - image: passbolt/passbolt:2.1.1-pro-debian + image: passbolt/passbolt:2.2.0-pro-debian tty: true depends_on: - db diff --git a/docker-compose.yml b/docker-compose.yml index 2b06a8e..10c12a2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,10 @@ services: volumes: - database_volume:/var/lib/mysql ports: - - 3306 + - "127.0.0.1:3306:3306" passbolt: - image: passbolt/passbolt:2.1.0-debian + image: passbolt/passbolt:2.2.0-debian tty: true depends_on: - db diff --git a/scripts/wait-for.sh b/scripts/wait-for.sh index 2c07ab3..bbe4043 100755 --- a/scripts/wait-for.sh +++ b/scripts/wait-for.sh @@ -1,79 +1,177 @@ -#!/bin/sh +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available -TIMEOUT=15 -QUIET=0 +cmdname=$(basename $0) -echoerr() { - if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi -} +echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } -usage() { - exitcode="$1" - cat << USAGE >&2 +usage() +{ + cat << USAGE >&2 Usage: - $0 host:port [-t timeout] [-- command args] - -q | --quiet Do not output any status messages - -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout - -- COMMAND ARGS Execute command with args after the test finishes + $cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes USAGE - exit "$exitcode" + exit 1 } -wait_for() { - for _ in $(seq $TIMEOUT) ; do - nc -z "$HOST" "$PORT" > /dev/null 2>&1 - - result=$? - if [ $result -eq 0 ] ; then - if [ $# -gt 0 ] ; then - exec "$@" - fi - exit 0 +wait_for() +{ + if [[ $TIMEOUT -gt 0 ]]; then + echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" + else + echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" fi - sleep 1 - done - echo "Operation timed out" >&2 - exit 1 + start_ts=$(date +%s) + while : + do + if [[ $ISBUSY -eq 1 ]]; then + nc -z $HOST $PORT + result=$? + else + (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 + result=$? + fi + if [[ $result -eq 0 ]]; then + end_ts=$(date +%s) + echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" + break + fi + sleep 1 + done + return $result } -while [ $# -gt 0 ] +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $QUIET -eq 1 ]]; then + timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & + else + timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & + fi + PID=$! + trap "kill -INT -$PID" INT + wait $PID + RESULT=$? + if [[ $RESULT -ne 0 ]]; then + echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" + fi + return $RESULT +} + +# process arguments +while [[ $# -gt 0 ]] do - case "$1" in - *:* ) - HOST=$(printf "%s\n" "$1"| cut -d : -f 1) - PORT=$(printf "%s\n" "$1"| cut -d : -f 2) - shift 1 - ;; - -q | --quiet) - QUIET=1 - shift 1 - ;; - -t) - TIMEOUT="$2" - if [ "$TIMEOUT" = "" ]; then break; fi - shift 2 - ;; - --timeout=*) - TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - break - ;; - --help) - usage 0 - ;; - *) - echoerr "Unknown argument: $1" - usage 1 - ;; - esac + case "$1" in + *:* ) + hostport=(${1//:/ }) + HOST=${hostport[0]} + PORT=${hostport[1]} + shift 1 + ;; + --child) + CHILD=1 + shift 1 + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -s | --strict) + STRICT=1 + shift 1 + ;; + -h) + HOST="$2" + if [[ $HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + HOST="${1#*=}" + shift 1 + ;; + -p) + PORT="$2" + if [[ $PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + PORT="${1#*=}" + shift 1 + ;; + -t) + TIMEOUT="$2" + if [[ $TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac done -if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then - echoerr "Error: you need to provide a host and port to test." - usage 2 +if [[ "$HOST" == "" || "$PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage fi -wait_for "$@" +TIMEOUT=${TIMEOUT:-15} +STRICT=${STRICT:-0} +CHILD=${CHILD:-0} +QUIET=${QUIET:-0} + +# check to see if timeout is from busybox? +# check to see if timeout is from busybox? +TIMEOUT_PATH=$(realpath $(which timeout)) +if [[ $TIMEOUT_PATH =~ "busybox" ]]; then + ISBUSY=1 + BUSYTIMEFLAG="-t" +else + ISBUSY=0 + BUSYTIMEFLAG="" +fi + +if [[ $CHILD -gt 0 ]]; then + wait_for + RESULT=$? + exit $RESULT +else + if [[ $TIMEOUT -gt 0 ]]; then + wait_for_wrapper + RESULT=$? + else + wait_for + RESULT=$? + fi +fi + +if [[ $CLI != "" ]]; then + if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then + echoerr "$cmdname: strict mode, refusing to execute subprocess" + exit $RESULT + fi + exec "${CLI[@]}" +else + exit $RESULT +fi diff --git a/spec/docker_image/image_spec.rb b/spec/docker_image/image_spec.rb index 175a2f1..2152e53 100644 --- a/spec/docker_image/image_spec.rb +++ b/spec/docker_image/image_spec.rb @@ -17,7 +17,13 @@ describe 'Dockerfile' do end let(:nginx_conf) { '/etc/nginx/nginx.conf' } + let(:php_conf) { '/usr/local/etc/php-fpm.d/expose.conf' } let(:site_conf) { '/etc/nginx/conf.d/default.conf' } + let(:supervisor_conf) do + [ '/etc/supervisor/conf.d/nginx.conf', + '/etc/supervisor/conf.d/php.conf', + '/etc/supervisor/conf.d/cron.conf' ] + end let(:passbolt_home) { '/var/www/passbolt' } let(:passbolt_tmp) { '/var/www/passbolt/tmp' } let(:passbolt_image) { '/var/www/passbolt/webroot/img/public' } @@ -47,6 +53,12 @@ describe 'Dockerfile' do it 'is installed' do expect(package('supervisor')).to be_installed end + + it 'has config files' do + supervisor_conf.each do |config| + expect(file(config)).to exist + end + end end describe 'passbolt directory structure' do @@ -71,6 +83,16 @@ describe 'Dockerfile' do end end + describe 'php config' do + it 'exists' do + expect(file(php_conf)).to exist + end + + it 'does not expose php version' do + expect(file(php_conf).content).to match(/^php_flag\[expose_php\]\s+=\s+off$/) + end + end + describe 'nginx configuration' do it 'is installed correctly' do expect(file(nginx_conf)).to exist @@ -93,6 +115,10 @@ describe 'Dockerfile' do it 'points to the correct root folder' do expect(file(site_conf).content).to match 'root /var/www/passbolt/webroot' end + + it 'has server tokens off' do + expect(file(nginx_conf).content).to match(/^\s+server_tokens off;/) + end end describe 'ports exposed' do diff --git a/spec/docker_runtime/runtime_spec.rb b/spec/docker_runtime/runtime_spec.rb index 2b6d10a..6578e38 100644 --- a/spec/docker_runtime/runtime_spec.rb +++ b/spec/docker_runtime/runtime_spec.rb @@ -49,7 +49,6 @@ describe 'passbolt_api service' do let(:passbolt_host) { @container.json['NetworkSettings']['IPAddress'] } let(:uri) { "/healthcheck/status.json" } let(:curl) { "curl -sk -o /dev/null -w '%{http_code}' -H 'Host: passbolt.local' https://#{passbolt_host}/#{uri}" } - let(:conf_app) { "curl -sk -o /dev/null -w '%{http_code}' -H 'Host: passbolt.local' https://#{passbolt_host}/conf/app.php" } describe 'php service' do it 'is running supervised' do @@ -114,4 +113,15 @@ describe 'passbolt_api service' do end end + describe 'hide information' do + let(:curl) { "curl -Isk -H 'Host: passbolt.local' https://#{passbolt_host}/" } + it 'hides php version' do + expect(command("#{curl} | grep 'X-Powered-By: PHP'").stdout).to be_empty + end + + it 'hides nginx version' do + expect(command("#{curl} | grep 'Server:'").stdout.strip).to match(/^Server:\s+nginx$/) + end + end + end