diff --git a/.gitignore b/.gitignore index 34d6ef3..6c08575 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ fabric.properties # Generated docker files conf/*.key + +# src directory used for local development +src diff --git a/Dockerfile b/Dockerfile index 162c763..0e25b8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,15 @@ -FROM alpine:3.6 +FROM php:7-fpm-alpine3.7 -LABEL maintainer="diego@passbolt.com" +LABEL MAINTAINER diego@passbolt.com -ENV PASSBOLT_VERSION 1.6.9 -ENV PASSBOLT_URL https://github.com/passbolt/passbolt_api/archive/v${PASSBOLT_VERSION}.tar.gz +ENV PASSBOLT_URL https://github.com/passbolt/passbolt_api/archive/develop.tar.gz -ARG BASE_PHP_DEPS="php5-curl \ - php5-common \ - php5-gd \ - php5-intl \ - php5-json \ - php5-mcrypt \ - php5-mysql \ - php5-xsl \ - php5-fpm \ - php5-phar \ - php5-posix \ - php5-xml \ - php5-openssl \ - php5-zlib \ - php5-ctype \ - php5-pdo \ - php5-pdo_mysql \ - php5-pear" +ARG PHP_EXTENSIONS="gd \ + intl \ + pdo_mysql \ + xsl" -ARG PHP_GNUPG_DEPS="php5-dev \ +ARG PHP_GNUPG_BUILD_DEPS="php7-dev \ make \ gcc \ g++ \ @@ -36,40 +21,35 @@ ARG PHP_GNUPG_DEPS="php5-dev \ zlib-dev \ file" -RUN apk add --no-cache $BASE_PHP_DEPS \ - sed \ - coreutils \ - tar \ - bash \ - curl \ +RUN apk add --no-cache $PHP_GNUPG_BUILD_DEPS \ nginx \ gpgme \ gnupg1 \ - recode \ - libxml2 \ - openssl \ - libpcre32 \ mysql-client \ - ca-certificates - -RUN apk add --no-cache $PHP_GNUPG_DEPS \ - && ln -s /usr/bin/php5 /usr/bin/php \ - && ln -s /usr/bin/phpize5 /usr/bin/phpize \ - && sed -i "s/ -n / /" $(which pecl) \ - && pecl install gnupg \ - && pecl install redis \ - && echo "extension=gnupg.so" > /etc/php5/conf.d/gnupg.ini \ - && echo "extension=redis.so" > /etc/php5/conf.d/redis.ini \ - && apk del $PHP_GNUPG_DEPS \ + libpng-dev \ + icu-dev \ + libxslt-dev \ + libmcrypt-dev \ + supervisor \ + && pecl install gnupg redis mcrypt-snapshot \ + && docker-php-ext-install -j4 $PHP_EXTENSIONS \ + && docker-php-ext-enable $PHP_EXTENSIONS gnupg redis mcrypt \ + && apk del $PHP_GNUPG_BUILD_DEPS \ && curl -sS https://getcomposer.org/installer | php \ - && mv composer.phar /usr/local/bin/composer \ - && mkdir /var/www/passbolt \ + && mv composer.phar /usr/local/bin/composer + +RUN mkdir -p /var/www/passbolt \ && curl -sSL $PASSBOLT_URL | tar zxf - -C /var/www/passbolt --strip-components 1 \ - && chown -R nginx:nginx /var/www/passbolt \ - && chmod -R +w /var/www/passbolt/app/tmp \ - && chmod +w /var/www/passbolt/app/webroot/img/public + && cd /var/www/passbolt \ + && composer install \ + && chown -R www-data:www-data /var/www/passbolt \ + && chmod 775 $(find /var/www/passbolt/tmp -type d) \ + && chmod 664 $(find /var/www/passbolt/tmp -type f) \ + && chmod 775 $(find /var/www/passbolt/webroot/img/public -type d) \ + && chmod 664 $(find /var/www/passbolt/webroot/img/public -type f) COPY conf/passbolt.conf /etc/nginx/conf.d/default.conf +COPY conf/supervisord.conf /etc/supervisord.conf COPY bin/docker-entrypoint.sh /docker-entrypoint.sh EXPOSE 80 443 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..cc5bf5e --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +group :test do + gem 'docker-api' + gem 'rake' + gem 'serverspec' + gem 'pry' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..49219f5 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,58 @@ +GEM + remote: https://rubygems.org/ + specs: + coderay (1.1.2) + diff-lcs (1.3) + docker-api (1.34.0) + excon (>= 0.47.0) + multi_json + excon (0.60.0) + method_source (0.9.0) + multi_json (1.12.2) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-ssh (4.2.0) + net-telnet (0.1.1) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rake (12.3.0) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.1) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-its (1.2.0) + rspec-core (>= 3.0.0) + rspec-expectations (>= 3.0.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) + serverspec (2.41.3) + multi_json + rspec (~> 3.0) + rspec-its + specinfra (~> 2.72) + sfl (2.3) + specinfra (2.73.0) + net-scp + net-ssh (>= 2.7, < 5.0) + net-telnet + sfl + +PLATFORMS + ruby + +DEPENDENCIES + docker-api + pry + rake + serverspec + +BUNDLED WITH + 1.16.1 diff --git a/README.md b/README.md index 3449470..f5d2bb5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Passbolt docker official image +# Warning + +This is a work in progress branch use at your own risk. + # What is passbolt? Passbolt is a free and open source password manager that allows team members to @@ -14,8 +18,6 @@ track issues and pull requests. Users that do not require any special modifications are encouraged to `docker pull` the [official docker image from the docker hub](https://hub.docker.com/r/passbolt/passbolt/). -There is also a yet unofficial project to use [passbolt along with docker-compose](https://github.com/dlen/passbolt-compose) for easier the setup process. - # Build the image Inside the repo directory: @@ -30,16 +32,22 @@ Passbolt requires mysql to be running. The following example use mysql official with the default passbolt credentials. ```bash -$ docker run -e MYSQL_ROOT_PASSWORD= \ - -e MYSQL_DATABASE=passbolt \ - -e MYSQL_USER=passbolt \ - -e MYSQL_PASSWORD=P4ssb0lt \ +$ docker run -e MYSQL_ROOT_PASSWORD= \ + -e MYSQL_DATABASE= \ + -e MYSQL_USER= \ + -e MYSQL_PASSWORD= \ mysql ``` Then you can start passbolt just by providing the database container ip in the `db_host` environment variable. -`$ docker run -e DB_HOST= passbolt:local` +```bash +$ docker run -e DATASOURCES_DEFAULT_HOST= \ + -e DATASOURCES_DEFAULT_PASSWORD= \ + -e DATASOURCES_DEFAULT_USERNAME= \ + -e DATASOURCES_DEFAULT_DATABASE= \ + passbolt:local +``` Once the process is done, just navigate to the following url in your browser: https://passbolt_container_ip @@ -54,50 +62,39 @@ And access it using https://localhost:host_port # Configure passbolt -## Environment variables +## Environment variables reference Passbolt docker image provides several environment variables to configure different aspects: -### GnuPG key creation related variables - -* KEY_LENGTH: gpg desired key length -* SUBKEY_LENGTH: gpg desired subkey length -* KEY_NAME: key owner name -* KEY_EMAIL: key owner email address -* KEY_EXPIRATION: key expiration date - -### App file variables - -* FINGERPRINT: GnuPG fingerprint -* REGISTRATION: Defines if users can register (defaults to false) -* SSL: Forces passbolt to redirect to SSL any non-SSL request - -### Core file variables - -* SALT: a random string used by cakephp in security hashing methods -* CIPHERSEED: a random string used by cakephp to encrypt/decrypt strings -* URL: URL of the passbolt installation (defaults to http://passbolt.local) - -### Database variables - -* DB_HOST: database hostname This param has to be specified either using env var or in database.php (defaults to passbolt.local) -* DB_PORT: database port (defaults to 3306) -* DB_USER: database username (defaults to passbolt) -* DB_PASS: database password (defaults to P4ssb0lt) -* DB_NAME: database name (defaults to passbolt) - -### Email variables - -* EMAIL_TRANSPORT: transport protocol ( defaults to Smtp) -* EMAIL_FROM: from email address ( defaults to contact@mydomain.local) -* EMAIL_HOST: server hostname ( defaults to localhost) -* EMAIL_PORT: server port ( defaults to 587) -* EMAIL_TIMEOUT: timeout ( defaults to 30s) -* EMAIL_AUTH: disable smtp auth ( defaults to true) -* EMAIL_USERNAME: username for email server auth ( defaults to email_user) -* EMAIL_PASSWORD: password for email server auth ( defaults to email_password) -* EMAIL_CLIENT: hostname to send as smtp helo ( defaults to null) -* EMAIL_TLS: set tls, boolean ( defaults to false) +* APP_FULL_BASE_URL: Defines Passbolt base url (Example https://yourdomain.com) +* DATASOURCES_DEFAULT_HOST: database hostname (defaults to localhost) +* DATASOURCES_DEFAULT_PORT: database port (defaults to 3306) +* DATASOURCES_DEFAULT_USERNAME: database username (defaults to my_app) +* DATASOURCES_DEFAULT_PASSWORD: database password (defaults to secret) +* DATASOURCES_DEFAULT_DATABASE: database name (defaults to my_app) +* EMAIL_DEFAULT_FROM: from email address (defaults to contact@mydomain.local) +* EMAIL_DEFAULT_TRANSPORT: sets transport method (defaults to default) +* EMAIL_TRANSPORT_DEFAULT_HOST: server hostname (defaults to localhost) +* EMAIL_TRANSPORT_DEFAULT_PORT: server port (defaults to 25) +* EMAIL_TRANSPORT_DEFAULT_TIMEOUT: timeout (defaults to 30) +* EMAIL_TRANSPORT_DEFAULT_USERNAME: username for email server auth (defaults to null) +* EMAIL_TRANSPORT_DEFAULT_PASSWORD: password for email server auth (defaults to null) +* EMAIL_TRANSPORT_DEFAULT_CLIENT: client (defaults to null) +* EMAIL_TRANSPORT_DEFAULT_TLS: set tls (defaults to null) +* EMAIL_TRANSPORT_DEFAULT_URL: set url (defaults to null) +* GNUPGHOME: Path to gnupghome directory (defaults to web_user_home_directory/.gnupg ) +* PASSBOLT_KEY_LENGTH: gpg desired key length +* PASSBOLT_SUBKEY_LENGTH: gpg desired subkey length +* PASSBOLT_KEY_NAME: key owner name +* PASSBOLT_KEY_EMAIL: key owner email address +* PASSBOLT_KEY_EXPIRATION: key expiration date +* PASSBOLT_GPG_SERVER_KEY_FINGERPRINT: GnuPG fingerprint +* PASSBOLT_GPG_SERVER_KEY_PUBLIC: Path to GnuPG public server key +* PASSBOLT_GPG_SERVER_KEY_PRIVATE: Path to GnuPG private server key +* PASSBOLT_REGISTRATION_PUBLIC: Defines if users can register (defaults to false) +* PASSBOLT_SSL_FORCE: Forces passbolt to redirect to SSL any non-SSL request +* PASSBOLT_SECURITY_SET_HEADERS: Forces passbolt to send CSP Headers (defaults to true) +* SECURITY_SALT: A random number user in security hashing methods. ## Advanced configuration @@ -106,12 +103,9 @@ It it possible to mount the desired configuration files as volumes. ### Configuration files subject to be persisted: -* /var/www/passbolt/app/Config/app.php -* /var/www/passbolt/app/Config/core.php -* /var/www/passbolt/app/Config/database.php -* /var/www/passbolt/app/Config/email.php -* /var/www/passbolt/app/Config/gpg/serverkey.asc -* /var/www/passbolt/app/Config/gpg/serverkey.private.asc +* /var/www/passbolt/config/app.php +* /var/www/passbolt/config/gpg/serverkey.asc +* /var/www/passbolt/config/gpg/serverkey_private.asc * /var/www/passbolt/app/webroot/img/public/images ### SSL certificate files @@ -121,57 +115,6 @@ It is also possible to mount a ssl certificate on the following paths: * /etc/ssl/certs/certificate.crt * /etc/ssl/certs/certificate.key -# Examples - -For the following examples it is assumed that passbolt container image has been built from this repo following the instructions -described on the [Build](#build-the-image) section. - -In the following example passbolt is launched with the defaults enabled usind mysql official docker container to store passbolt data: - -```bash -$ docker run -e MYSQL_ROOT_PASSWORD=c0mplexp4ss \ - -e MYSQL_DATABASE=passbolt \ - -e MYSQL_USER=passbolt \ - -e MYSQL_PASSWORD=P4ssb0lt \ - mysql -``` - -Once mysql container is running we should extract its ip address. Let's assume 172.17.0.2 for this example - -`$ docker run -e DB_HOST=172.17.0.2 passbolt:local` - -Point your browser to the passbolt container ip or localhost:exposed_port. - -## Advanced configuration - -In the following example passbolt is launched with a customized setup mounting and persisting configuration files. We also make use of -mysql official docker container to store passbolt data. - -```bash -$ docker run -e MYSQL_ROOT_PASSWORD=c0mplexp4ss \ - -e MYSQL_DATABASE=passbolt \ - -e MYSQL_USER=passbolt \ - -e MYSQL_PASSWORD=P4ssb0lt \ - mysql -``` - -Using docker inspect or any other method you can get the ip address of the mysql container. This example uses 172.17.0.2. - -Once this container is running and you have the mysql ip address we run passbolt container mounting all configuration files stored -under a example conf directory in $PWD - -```bash -$ docker run -v $PWD/conf/app.php:/var/www/passbolt/app/Config/app.php \ - -v $PWD/conf/core.php:/var/www/passbolt/app/Config/core.php \ - -v $PWD/conf/database.php:/var/www/passbolt/app/Config/database.php \ - -v $PWD/conf/email.php:/var/www/passbolt/app/Config/email.php \ - -v $PWD/conf/private.asc:/var/www/passbolt/app/Config/gpg/serverkey.private.asc \ - -v $PWD/conf/public.asc:/var/www/passbolt/app/Config/gpg/serverkey.asc \ - passbolt:local -``` - -Navigate with the browser to the passbolt container ip or localhost:exposed_port - # Requirements: * rng-tools are required on host machine to speed up entropy generation on containers. This way gpg key creation on passbolt container will be faster. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..11ba867 --- /dev/null +++ b/Rakefile @@ -0,0 +1,27 @@ +require 'rake' +require 'rspec/core/rake_task' + +task :spec => 'spec:all' +task :default => :spec + +namespace :spec do + targets = [] + Dir.glob('./spec/*').each do |dir| + next unless File.directory?(dir) + target = File.basename(dir) + target = "_#{target}" if target == "default" + targets << target + end + + task :all => targets + task :default => :all + + targets.each do |target| + original_target = target == "_default" ? target[1..-1] : target + desc "Run serverspec tests to #{original_target}" + RSpec::Core::RakeTask.new(target.to_sym) do |t| + ENV['TARGET_HOST'] = original_target + t.pattern = "spec/#{original_target}/*_spec.rb" + end + end +end diff --git a/bin/docker-entrypoint.sh b/bin/docker-entrypoint.sh index b7a52b6..a13ec1b 100755 --- a/bin/docker-entrypoint.sh +++ b/bin/docker-entrypoint.sh @@ -1,232 +1,99 @@ -#!/bin/bash +#!/usr/bin/env sh set -eo pipefail -gpg_private_key=/var/www/passbolt/app/Config/gpg/serverkey.private.asc -gpg_public_key=/var/www/passbolt/app/Config/gpg/serverkey.asc -gpg=$(which gpg) +gpg_private_key="${PASSBOLT_GPG_SERVER_KEY_PRIVATE:-/var/www/passbolt/config/gpg/serverkey_private.asc}" +gpg_public_key="${PASSBOLT_GPG_SERVER_KEY_PUBLIC:-/var/www/passbolt/config/gpg/serverkey.asc}" -core_config='/var/www/passbolt/app/Config/core.php' -db_config='/var/www/passbolt/app/Config/database.php' -app_config='/var/www/passbolt/app/Config/app.php' -email_config='/var/www/passbolt/app/Config/email.php' ssl_key='/etc/ssl/certs/certificate.key' ssl_cert='/etc/ssl/certs/certificate.crt' gpg_gen_key() { - su -m -c "$gpg --batch --gen-key < $gpg_private_key" -ls /bin/bash nginx - su -m -c "$gpg --armor --export $KEY_EMAIL > $gpg_public_key" -ls /bin/bash nginx + su -m -c "gpg --batch --gen-key < $gpg_private_key" -ls /bin/sh www-data + su -c "gpg --armor --export $key_email > $gpg_public_key" -ls /bin/sh www-data } gpg_import_key() { - - local key_id=$(su -m -c "gpg --with-colons $gpg_private_key | grep sec |cut -f5 -d:" -ls /bin/bash nginx) - - su -m -c "$gpg --batch --import $gpg_public_key" -ls /bin/bash nginx - su -m -c "gpg -K $key_id" -ls /bin/bash nginx || su -m -c "$gpg --batch --import $gpg_private_key" -ls /bin/bash nginx -} - -core_setup() { - #Env vars: - # SALT - # CIPHERSEED - # URL - - local default_salt='DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi' - local default_seed='76859309657453542496749683645' - local default_url='http://passbolt.local' - - cp $core_config{.default,} - sed -i s:$default_salt:${SALT:-$default_salt}:g $core_config - sed -i s:$default_seed:${CIPHERSEED:-$default_seed}:g $core_config - sed -i "/example.com/ s:\/\/::" $core_config - sed -i "s|http://example.com|${URL:-$default_url}|g" $core_config -} - -db_setup() { - #Env vars: - # DB_HOST - # DB_USER - # DB_PASS - # DB_NAME - - local default_host='localhost' - local default_user='user' - local default_pass='password' - local default_db='database_name' - - cp $db_config{.default,} - sed -i "/$default_host/a\ \t\t'port' => '${DB_PORT:-3306}'," $db_config - sed -i s:$default_host:${DB_HOST:-db}:g $db_config - sed -i s:$default_user:${DB_USER:-passbolt}:g $db_config - sed -i s:$default_pass\',:${DB_PASS:-P4ssb0lt}\',:g $db_config - sed -i s:$default_db:${DB_NAME:-passbolt}:g $db_config -} - -app_setup() { - #Env vars: - # FINGERPRINT - # REGISTRATION - # SSL - - local default_home='/home/www-data/.gnupg' - local default_public_key='unsecure.key' - local default_private_key='unsecure_private.key' - local default_fingerprint='2FC8945833C51946E937F9FED47B0811573EE67E' - local gpg_home='/var/lib/nginx/.gnupg' - local auto_fingerprint=$(su -m -c "$gpg --fingerprint |grep fingerprint| awk '{for(i=4;i<=NF;++i)printf \$i}'" -ls /bin/bash nginx) - - cp $app_config{.default,} - sed -i s:$default_home:$gpg_home:g $app_config - sed -i s:$default_public_key:serverkey.asc:g $app_config - sed -i s:$default_private_key:serverkey.private.asc:g $app_config - sed -i s:$default_fingerprint:${FINGERPRINT:-$auto_fingerprint}:g $app_config - sed -i "/force/ s:true:${SSL:-true}:" $app_config - sed -i "/'registration'/{n; s:false:${REGISTRATION:-false}:}" $app_config -} - -email_setup() { - #Env vars: - # EMAIL_TRANSPORT - # EMAIL_FROM - # EMAIL_HOST - # EMAIL_PORT - # EMAIL_TIMEOUT - # EMAIL_AUTH - # EMAIL_USERNAME - # EMAIL_PASSWORD - # EMAIL_CLIENT - # EMAIL_TLS - - local default_transport='Smtp' - local default_from='contact@passbolt.com' - local default_host='smtp.mandrillapp.com' - local default_port='587' - local default_timeout='30' - local default_username="''" - local default_password="''" - local default_client=null - - cp $email_config{.default,} - sed -i s:$default_transport:${EMAIL_TRANSPORT:-Smtp}:g $email_config - sed -i s:$default_from:${EMAIL_FROM:-contact@mydomain.local}:g $email_config - sed -i s:$default_host:${EMAIL_HOST:-localhost}:g $email_config - sed -i s:$default_port:${EMAIL_PORT:-587}:g $email_config - sed -i s:$default_timeout:${EMAIL_TIMEOUT:-30}:g $email_config - if [ "$EMAIL_AUTH" = "false" ] ; then - sed -i "0,/"$default_username"/s:"$default_username":null:" $email_config - sed -i "0,/"$default_password"/s:"$default_password":null:" $email_config - else - sed -i "0,/"$default_username"/s:"$default_username":'${EMAIL_USERNAME:-email_user}':" $email_config - sed -i "0,/"$default_password"/s:"$default_password":'${EMAIL_PASSWORD:-email_password}':" $email_config - fi - if [ -n "$EMAIL_CLIENT" ] ; then - sed -i "0,/"$default_client"/s:"$default_client":'$EMAIL_CLIENT':" $email_config - fi - sed -i "0,/tls/s:false:${EMAIL_TLS:-false}:" $email_config - + key_id=$(su -m -c "gpg --with-colons $gpg_private_key | grep sec |cut -f5 -d:" -ls /bin/sh www-data) + su -c "gpg --batch --import $gpg_public_key" -ls /bin/sh www-data + su -c "gpg -K $key_id" -ls /bin/sh www-data || su -m -c "gpg --batch --import $gpg_private_key" -ls /bin/sh www-data } gen_ssl_cert() { openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \ - -subj "/C=FR/ST=Denial/L=Springfield/O=Dis/CN=www.passbolt.local" \ + -subj '/C=FR/ST=Denial/L=Springfield/O=Dis/CN=www.passbolt.local' \ -keyout $ssl_key -out $ssl_cert } install() { - local database_host=${DB_HOST:-$(cat $db_config | grep -m1 "'host'" | sed -r "s/\s*'host' => '(.*)',/\1/")} - local database_port=${DB_PORT:-$(cat $db_config | grep -m1 "'port' => '\d" | sed -r "s/\s*'port' => '(.*)',/\1/")} - local database_user=${DB_USER:-$(cat $db_config | grep -m1 "'login'" | sed -r "s/\s*'login' => '(.*)',/\1/")} - local database_pass=${DB_PASS:-$(cat $db_config | grep -m1 "'password'" | sed -r "s/\s*'password' => '(.*)',/\1/")} - local database_name=${DB_NAME:-$(cat $db_config | grep -m1 "'database'" | sed -r "s/\s*'database' => '(.*)',/\1/")} - tables=$(mysql -u ${database_user:-passbolt} -h $database_host -P ${database_port:-3306} -p -BN -e "SHOW TABLES FROM ${database_name:-passbolt}" -p${database_pass:-P4ssb0lt} |wc -l) + tables=$(mysql \ + -u "$DATASOURCES_DEFAULT_USERNAME" \ + -h "$DATASOURCES_DEFAULT_HOST" \ + -P "$DATASOURCES_DEFAULT_PORT" \ + -BN -e "SHOW TABLES FROM $DATASOURCES_DEFAULT_DATABASE" \ + -p"$DATASOURCES_DEFAULT_PASSWORD" |wc -l) + app_config="/var/www/passbolt/config/app.php" - if [ $tables -eq 0 ]; then - su -c "/var/www/passbolt/app/Console/cake install --send-anonymous-statistics true --no-admin" -ls /bin/bash nginx + if [ ! -f "$app_config" ]; then + su -c 'cp /var/www/passbolt/config/app.default.php /var/www/passbolt/config/app.php' -s /bin/sh www-data + fi + + if [ -z "$PASSBOLT_GPG_SERVER_KEY_FINGERPRINT" ]; then + gpg_auto_fingerprint="$(su -c "gpg --with-fingerprint $gpg_public_key | grep fingerprint | awk '{for(i=4;i<=NF;++i)printf \$i}'" -ls /bin/sh www-data)" + export PASSBOLT_GPG_SERVER_KEY_FINGERPRINT=$gpg_auto_fingerprint + fi + + if [ "$tables" -eq 0 ]; then + su -c '/var/www/passbolt/bin/cake passbolt install --no-admin --force' -s /bin/sh www-data else echo "Enjoy! ☮" fi } -php_fpm_setup() { - sed -i '/^user\s/ s:nobody:nginx:g' /etc/php5/php-fpm.conf - sed -i '/^group\s/ s:nobody:nginx:g' /etc/php5/php-fpm.conf - cp /etc/php5/php-fpm.conf /etc/php5/fpm.d/www.conf - sed -i '/^include\s/ s:^:#:' /etc/php5/fpm.d/www.conf -} - -check_permissions() { - chown -R nginx:nginx /var/www/passbolt - chmod -R +w /var/www/passbolt/app/tmp - chmod +w /var/www/passbolt/app/webroot/img/public -} - email_cron_job() { - local root_crontab='/etc/crontabs/root' - local cron_task_dir='/etc/periodic/1min' - local cron_task='/etc/periodic/1min/email_queue_processing' - local process_email="/var/www/passbolt/app/Console/cake EmailQueue.sender --quiet" + root_crontab='/etc/crontabs/root' + cron_task_dir='/etc/periodic/1min' + cron_task='/etc/periodic/1min/email_queue_processing' + process_email="/var/www/passbolt/bin/cake EmailQueue.sender --quiet" mkdir -p $cron_task_dir - - if ! grep $cron_task_dir $root_crontab > /dev/null; then - echo "* * * * * run-parts $cron_task_dir" >> $root_crontab - fi + echo "* * * * * run-parts $cron_task_dir" >> $root_crontab echo "#!/bin/sh" > $cron_task chmod +x $cron_task - echo "su -c \"$process_email\" -ls /bin/bash nginx" >> $cron_task - - crond -f -c /etc/crontabs & + echo "su -c \"$process_email\" -s /bin/sh www-data" >> $cron_task } -if [ ! -f $gpg_private_key ] && [ ! -L $gpg_private_key ] || \ - [ ! -f $gpg_public_key ] && [ ! -L $gpg_public_key ]; then +if [ ! -f "$gpg_private_key" ] && [ ! -L "$gpg_private_key" ] || \ + [ ! -f "$gpg_public_key" ] && [ ! -L "$gpg_public_key" ]; then gpg_gen_key + gpg_import_key else gpg_import_key fi -if [ ! -f $core_config ] && [ ! -L $core_config ]; then - core_setup -fi - -if [ ! -f $db_config ] && [ ! -L $db_config ]; then - db_setup -fi - -if [ ! -f $app_config ] && [ ! -L $app_config ]; then - app_setup -fi - -if [ ! -f $email_config ] && [ ! -L $email_config ]; then - email_setup -fi - -if [ ! -f $ssl_key ] && [ ! -L $ssl_key ] && \ - [ ! -f $ssl_cert ] && [ ! -L $ssl_cert ]; then +if [ ! -f "$ssl_key" ] && [ ! -L "$ssl_key" ] && \ + [ ! -f "$ssl_cert" ] && [ ! -L "$ssl_cert" ]; then gen_ssl_cert fi -check_permissions - -php_fpm_setup - install - -php-fpm5 - email_cron_job -nginx -g "pid /tmp/nginx.pid; daemon off;" - +/usr/bin/supervisord -n -c /etc/supervisord.conf diff --git a/conf/passbolt.conf b/conf/passbolt.conf index fb8b961..d40fcfd 100644 --- a/conf/passbolt.conf +++ b/conf/passbolt.conf @@ -12,18 +12,6 @@ server { root /var/www/passbolt; - # X-Frame-Options is to prevent from clickJacking attack - add_header X-Frame-Options SAMEORIGIN; - - # disable content-type sniffing on some browsers. - add_header X-Content-Type-Options nosniff; - - # This header enables the Cross-site scripting (XSS) filter - add_header X-XSS-Protection "1; mode=block"; - - # This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack - add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;"; - location / { try_files $uri $uri/ /index.php?$args; index index.php; @@ -38,10 +26,11 @@ server { fastcgi_param SERVER_NAME $http_host; } - location ~* \.(jpe?g|woff|woff2|ttf|gif|png|bmp|ico|css|js|json|pdf|zip|htm|html|docx?|xlsx?|pptx?|txt|wav|swf|svg|avi|mp\d)$ { + location ~* \.(jpe?g|woff|woff2|ttf|gif|png|bmp|ico|css|js|ejs|json|pdf|zip|htm|html|docx?|xlsx?|pptx?|txt|wav|swf|svg|avi|mp\d)$ { access_log off; log_not_found off; - try_files $uri /app/webroot/$uri /index.php?$args; + try_files $uri /webroot/$uri /index.php?$args; + #try_files $uri /app/webroot/$uri /index.php?$args; } } @@ -56,21 +45,9 @@ server { ssl_prefer_server_ciphers on; ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; ssl_session_tickets off; - add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; root /var/www/passbolt; - # X-Frame-Options is to prevent from clickJacking attack - add_header X-Frame-Options SAMEORIGIN; - - # disable content-type sniffing on some browsers. - add_header X-Content-Type-Options nosniff; - - # This header enables the Cross-site scripting (XSS) filter - add_header X-XSS-Protection "1; mode=block"; - location / { try_files $uri $uri/ /index.php?$args; index index.php; @@ -88,6 +65,6 @@ server { location ~* \.(jpe?g|woff|woff2|ttf|gif|png|bmp|ico|css|js|json|pdf|zip|htm|html|docx?|xlsx?|pptx?|txt|wav|swf|svg|avi|mp\d)$ { access_log off; log_not_found off; - try_files $uri /app/webroot/$uri /index.php?$args; + try_files $uri /webroot/$uri /index.php?$args; } } diff --git a/conf/supervisord.conf b/conf/supervisord.conf new file mode 100644 index 0000000..9cd1f5a --- /dev/null +++ b/conf/supervisord.conf @@ -0,0 +1,36 @@ +[unix_http_server] +file=/tmp/supervisor.sock ; (the path to the socket file) + +[supervisord] +logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) +logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) +logfile_backups=10 ; (num of main logfile rotation backups;default 10) +loglevel=info ; (log level;default info; others: debug,warn,trace) +pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +nodaemon=false ; (start in foreground if true;default false) +minfds=1024 ; (min. avail startup file descriptors;default 1024) +minprocs=200 ; (min. avail process descriptors;default 200) + +; 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:///tmp/supervisor.sock ; use a unix:// URL for a unix socket + +[program:php-fpm] +command=php-fpm +autostart=true +priority=5 + +[program:nginx] +command=nginx -g "pid /tmp/nginx.pid; daemon off;" +autostart=true +priority=10 + +[program:crond] +command=crond -f -c /etc/crontabs +autostart=true +priority=20 diff --git a/spec/docker_image/image_spec.rb b/spec/docker_image/image_spec.rb new file mode 100644 index 0000000..ef89ec5 --- /dev/null +++ b/spec/docker_image/image_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe 'Dockerfile' do + + before(:all) do + set :env, { + 'DATASOURCES_DEFAULT_HOST' => '172.17.0.2', + 'DATASOURCES_DEFAULT_PASSWORD' => 'P4ssb0lt', + 'DATASOURCES_DEFAULT_USERNAME' => 'passbolt', + 'DATASOURCES_DEFAULT_DATABASE' => 'passbolt', + 'PASSBOLT_GPG_KEYRING' => '/var/lib/nginx/.gnupg' + } + + @image = Docker::Image.build_from_dir(ROOT_DOCKERFILES) + set :docker_image, @image.id + set :docker_container_create_options, { 'Cmd' => '/bin/sh' } + end + + let(:nginx_conf) { '/etc/nginx/nginx.conf' } + let(:site_conf) { '/etc/nginx/conf.d/default.conf' } + let(:passbolt_home) { '/var/www/passbolt' } + let(:passbolt_tmp) { '/var/www/passbolt/tmp' } + let(:passbolt_image) { '/var/www/passbolt/webroot/img/public' } + let(:passbolt_owner) { 'www-data' } + let(:exposed_ports) { [ '80', '443' ] } + let(:composer) { '/usr/local/bin/composer'} + let(:php_extensions) { [ + 'curl', 'gd', 'intl', 'json', 'mcrypt', 'mysqlnd', 'xsl', 'phar', + 'posix', 'xml', 'xsl', 'zlib', 'ctype', 'pdo', 'gnupg', 'pdo_mysql' + ] } + + describe 'passbolt required php extensions' do + it 'has php extensions installed' do + php_extensions.each do |ext| + expect(command("php --ri #{ext}").exit_status).to eq 0 + end + end + end + + describe 'php composer' do + it 'is installed' do + expect(file(composer)).to be_executable + end + end + + describe 'supervisor' do + it 'is installed' do + expect(package('supervisor')).to be_installed + end + end + + describe 'passbolt directory structure' do + it 'must exist and be directories' do + expect(file(passbolt_home)).to be_a_directory + expect(file(passbolt_tmp)).to be_a_directory + expect(file(passbolt_image)).to be_a_directory + end + + it 'must be owned by correct user' do + expect(file(passbolt_home)).to be_owned_by(passbolt_owner) + expect(file(passbolt_tmp)).to be_owned_by(passbolt_owner) + expect(file(passbolt_image)).to be_owned_by(passbolt_owner) + end + + it 'must have the correct permissions on tmp' do + expect(file(passbolt_tmp)).to be_mode('775') + end + + it 'must have the correct permissions on img' do + expect(file(passbolt_image)).to be_mode('775') + end + end + + describe 'nginx configuration' do + it 'is installed correctly' do + expect(file(nginx_conf)).to exist + end + + it 'has the correct permissions' do + expect(file(nginx_conf)).to be_owned_by 'root' + end + end + + describe 'nginx site configuration' do + it 'is installed correctly' do + expect(file(site_conf)).to exist + end + + it 'has the correct permissions' do + expect(file(site_conf)).to be_owned_by 'root' + end + end + + describe 'ports exposed' do + it 'exposes port' do + exposed_ports.each do |port| + expect(@image.json['ContainerConfig']['ExposedPorts']).to include("#{port}/tcp") + end + end + end +end diff --git a/spec/docker_runtime/runtime_spec.rb b/spec/docker_runtime/runtime_spec.rb new file mode 100644 index 0000000..752f6a4 --- /dev/null +++ b/spec/docker_runtime/runtime_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe 'passbolt_api service' do + + before(:all) do + @mysql = Docker::Container.create( + 'Env' => [ + 'MYSQL_ROOT_PASSWORD=test', + 'MYSQL_DATABASE=passbolt', + 'MYSQL_USER=passbolt', + 'MYSQL_PASSWORD=P4ssb0lt' + ], + "Healthcheck" => { + "Test": [ + "CMD-SHELL", + "mysqladmin ping --silent" + ] + }, + 'Image' => 'mysql') + @mysql.start + + while @mysql.json['State']['Health']['Status'] != 'healthy' + sleep 1 + end + + @image = Docker::Image.build_from_dir(ROOT_DOCKERFILES) + @container = Docker::Container.create( + 'Env' => [ + "DATASOURCES_DEFAULT_HOST=#{@mysql.json['NetworkSettings']['IPAddress']}", + 'DATASOURCES_DEFAULT_PASSWORD=P4ssb0lt', + 'DATASOURCES_DEFAULT_USERNAME=passbolt', + 'DATASOURCES_DEFAULT_DATABASE=passbolt', + ], + 'Image' => @image.id) + @container.start + @container.logs(stdout: true) + + set :docker_container, @container.id + sleep 17 + end + + after(:all) do + @mysql.kill + @container.kill + end + + let(:http_path) { "/healthcheck/status.json" } + let(:healthcheck) { 'curl -s -o /dev/null -w "%{http_code}" http://localhost/healthcheck/status.json' } + + describe 'php service' do + it 'is running supervised' do + expect(process('php-fpm')).to be_running.under('supervisor') + end + + it 'has its port open' do + expect(port(9000)).to be_listening.with('tcp') + end + end + + describe 'email cron' do + it 'is running supervised' do + expect(service('crond')).to be_running.under('supervisor') + end + end + + describe 'web service' do + it 'is running supervised' do + expect(service('nginx')).to be_running.under('supervisor') + end + + it 'is listening on port 80' do + expect(port(80)).to be_listening.with('tcp') + end + + it 'is listening on port 443' do + expect(port(443)).to be_listening.with('tcp') + end + end + + describe 'passbolt status' do + it 'returns 200' do + expect(command(healthcheck).stdout).to eq '200' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..9620baa --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,9 @@ +require 'serverspec' +require 'docker' + +ROOT_DOCKERFILES = File.expand_path('../../', __FILE__) + +set :backend, :docker +Docker.options[:read_timeout] = 3600 +Docker.options[:write_timeout] = 3600 +