From 38cd8b4d1cb8f39c3052d588db31e7d768abc9e7 Mon Sep 17 00:00:00 2001 From: Chris Hubing Date: Mon, 19 Aug 2019 20:22:40 +0000 Subject: [PATCH] inital commit of COmanage match for Dockerhub --- DatabaseConnectivityTestCommand.php | 82 ++++ DatabaseSetupAlreadyCommand.php | 64 +++ Dockerfile | 112 +++++ Jenkinsfile | 125 ++++++ README.md | 63 ++- apache-include-directory-match | 6 + apache-include-virtual-host-port443-base | 14 + apache-include-virtual-host-port80-redirect | 6 + comanage_match_console_logging.patch | 36 ++ comanage_shibboleth_sp_utils.sh | 317 +++++++++++++ comanage_utils.sh | 469 ++++++++++++++++++++ common.bash | 2 + docker-comanage-match-entrypoint | 24 + 13 files changed, 1319 insertions(+), 1 deletion(-) create mode 100644 DatabaseConnectivityTestCommand.php create mode 100644 DatabaseSetupAlreadyCommand.php create mode 100644 Dockerfile create mode 100644 Jenkinsfile create mode 100644 apache-include-directory-match create mode 100644 apache-include-virtual-host-port443-base create mode 100644 apache-include-virtual-host-port80-redirect create mode 100644 comanage_match_console_logging.patch create mode 100755 comanage_shibboleth_sp_utils.sh create mode 100644 comanage_utils.sh create mode 100644 common.bash create mode 100755 docker-comanage-match-entrypoint diff --git a/DatabaseConnectivityTestCommand.php b/DatabaseConnectivityTestCommand.php new file mode 100644 index 0000000..8d05c3b --- /dev/null +++ b/DatabaseConnectivityTestCommand.php @@ -0,0 +1,82 @@ +config(); + + $config = new \Doctrine\DBAL\Configuration(); + + $cfargs = [ + 'dbname' => $cfg['database'], + 'user' => $cfg['username'], + 'password' => $cfg['password'], + 'host' => $cfg['host'], + 'driver' => ($cfg['driver'] == 'Cake\Database\Driver\Postgres' ? "pdo_pgsql" : "pdo_mysql") + ]; + + $conn = DriverManager::getConnection($cfargs, $config); + + try { + $conn->query('SELECT NOW()'); + $io->out("Connected to database"); + return 0; + } + catch(\Exception $e) { + $io->out("Unable to connect to database"); + return 1; + } + } +} diff --git a/DatabaseSetupAlreadyCommand.php b/DatabaseSetupAlreadyCommand.php new file mode 100644 index 0000000..f6be8e8 --- /dev/null +++ b/DatabaseSetupAlreadyCommand.php @@ -0,0 +1,64 @@ +findById('1')->firstOrFail(); + $io->out("Match database is setup"); + return 0; + } + catch(\Exception $e) { + $io->out("Match database is not setup"); + return 1; + } + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8e31efc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,112 @@ +# COmanage Match Dockerfile +# +# Portions licensed to the University Corporation for Advanced Internet +# Development, Inc. ("UCAID") under one or more contributor license agreements. +# See the NOTICE file distributed with this work for additional information +# regarding copyright ownership. +# +# UCAID licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +FROM php:7.3.5-apache-stretch + +# Official PHP image with Apache HTTPD includes +# --with-openssl +# --with-mbstring +# but intl, pdo, pdo_pgsql, pgsql, +# extensions must be built. +RUN apt-get update && apt-get install -y \ + libicu-dev \ + libpq-dev \ + patch \ + ssl-cert \ + wget \ + zlib1g \ + libpcre3-dev \ + && docker-php-ext-configure intl \ + && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ + && docker-php-ext-install intl pdo pdo_pgsql pgsql \ + && docker-php-source delete \ + && apt-get purge -y \ + libicu-dev \ + libpq-dev \ + && apt-get clean + +ARG COMANAGE_MATCH_VERSION +ENV COMANAGE_MATCH_VERSION ${COMANAGE_MATCH_VERSION:-develop} +LABEL comanage_match_version=${COMANAGE_MATCH_VERSION} + +ENV COMANAGE_MATCH_SRC_URL=https://github.internet2.edu/COmanage/match/archive/${COMANAGE_MATCH_VERSION}.tar.gz + +ARG COMANAGE_MATCH_DIR +ENV COMANAGE_MATCH_DIR ${COMANAGE_MATCH_DIR:-/srv/comanage-match} +LABEL comanage_match_dir=${COMANAGE_MATCH_DIR} + +WORKDIR $COMANAGE_MATCH_DIR + +RUN mkdir -p ${COMANAGE_MATCH_DIR} \ + && wget -O comanage.tar.gz ${COMANAGE_MATCH_SRC_URL} \ + && tar -zxf comanage.tar.gz -C ${COMANAGE_MATCH_DIR} --strip-components=1 \ + && rm -f comanage.tar.gz \ + && rm -f ${COMANAGE_MATCH_DIR}/app/tmp \ + && rm -f ${COMANAGE_MATCH_DIR}/app/logs \ + && mkdir ${COMANAGE_MATCH_DIR}/app/tmp \ + && mkdir ${COMANAGE_MATCH_DIR}/app/logs \ + && chown -R www-data:www-data ${COMANAGE_MATCH_DIR}/app/tmp \ + && chown -R www-data:www-data ${COMANAGE_MATCH_DIR}/app/logs \ + && cd /var/www/html \ + && ln -s ${COMANAGE_MATCH_DIR}/app/webroot match + +RUN a2enmod headers \ + && a2enmod ssl \ + && a2enmod rewrite \ + && a2dissite 000-default.conf \ + && a2disconf other-vhosts-access-log \ + && cd /etc/apache2 \ + && ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem cert.pem \ + && ln -s /etc/ssl/private/ssl-cert-snakeoil.key privkey.pem + +COPY apache-include-directory-match /etc/apache2/ +COPY apache-include-virtual-host-port443-base /etc/apache2/ +COPY apache-include-virtual-host-port80-redirect /etc/apache2/ + +COPY comanage_utils.sh /usr/local/lib/ +COPY comanage_shibboleth_sp_utils.sh /usr/local/lib/ +COPY docker-comanage-match-entrypoint /usr/local/bin/ + +# Patch to configure console logging. The patch is +# applied by the entry point script when appropriate. +# +# The patch is the output of the command +# +# diff -Naur app.php.original app.php > comanage_match_console_logging.patch +COPY comanage_match_console_logging.patch /usr/local/src/ + +# Add commands for testing database connectivity and setup status until +# they are part of Match source. +COPY DatabaseConnectivityTestCommand.php ${COMANAGE_MATCH_DIR}/app/src/Command/ +COPY DatabaseSetupAlreadyCommand.php ${COMANAGE_MATCH_DIR}/app/src/Command/ + +VOLUME ${COMANAGE_MATCH_DIR}/local + +EXPOSE 80 443 + +# Allow values for first administrator bootstrapped into the +# platform to be specified at image build time, in addition to +# being injected at run time through the entrypoint script. +ARG COMANAGE_MATCH_ADMIN_USERNAME + +# Set simple defaults for first administrator bootstrapped into the +ENV COMANAGE_MATCH_ADMIN_USERNAME ${COMANAGE_MATCH_ADMIN_USERNAME:-match.admin} + +ENTRYPOINT ["docker-comanage-match-entrypoint"] + +CMD ["apache2-foreground"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..8c48398 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,125 @@ +pipeline { + agent any + environment { + maintainer = "t" + imagename = 'g' + tag = 'l' + } + stages { + stage('Setting build context') { + steps { + script { + maintainer = maintain() + imagename = imagename() + if(env.BRANCH_NAME == "master") { + tag = "latest" + } else { + tag = env.BRANCH_NAME + } + if(!imagename){ + echo "You must define an imagename in common.bash" + currentBuild.result = 'FAILURE' + } + sh 'mkdir -p tmp && mkdir -p bin' + dir('tmp'){ + git([ url: "https://github.internet2.edu/docker/util.git", credentialsId: "jenkins-github-access-token" ]) + sh 'rm -rf ../bin/*' + sh 'mv ./bin/* ../bin/.' + } + // Build and test scripts expect that 'tag' is present in common.bash. This is necessary for both Jenkins and standalone testing. + // We don't care if there are more 'tag' assignments there. The latest one wins. + sh "echo >> common.bash ; echo \"tag=\\\"${tag}\\\"\" >> common.bash ; echo common.bash ; cat common.bash" + } + } + } + stage('Clean') { + steps { + script { + try{ + sh 'bin/destroy.sh >> debug' + } catch(error) { + def error_details = readFile('./debug'); + def message = "BUILD ERROR: There was a problem building the Base Image. \n\n ${error_details}" + sh "rm -f ./debug" + handleError(message) + } + } + } + } + stage('Build') { + steps { + script { + try{ + docker.withRegistry('https://registry.hub.docker.com/', "dockerhub-$maintainer") { + baseImg = docker.build("$maintainer/$imagename", "--build-arg GROUPER_CONTAINER_VERSION=$tag --no-cache .") + } + } catch(error) { + def error_details = readFile('./debug'); + def message = "BUILD ERROR: There was a problem building ${imagename}:${tag}. \n\n ${error_details}" + sh "rm -f ./debug" + handleError(message) + } + } + } + } + stage('Test') { + steps { + script { + try { + // sh 'bin/test.sh 2>&1 | tee debug ; test ${PIPESTATUS[0]} -eq 0' + echo no test yet + } catch (error) { + def error_details = readFile('./debug') + def message = "BUILD ERROR: There was a problem testing ${imagename}:${tag}. \n\n ${error_details}" + sh "rm -f ./debug" + handleError(message) + } + } + } + } + + stage('Push') { + steps { + script { + docker.withRegistry('https://registry.hub.docker.com/', "dockerhub-$maintainer") { + baseImg.push("$tag") + } + } + } + } + stage('Notify') { + steps{ + echo "$maintainer" + slackSend color: 'good', message: "$maintainer/$imagename:$tag pushed to DockerHub" + } + } + } + post { + always { + echo 'Done Building.' + } + failure { + // slackSend color: 'good', message: "Build failed" + handleError("BUILD ERROR: There was a problem building ${maintainer}/${imagename}:${tag}.") + } + } +} + + +def maintain() { + def matcher = readFile('common.bash') =~ 'maintainer="(.+)"' + matcher ? matcher[0][1] : 'tier' +} + +def imagename() { + def matcher = readFile('common.bash') =~ 'imagename="(.+)"' + matcher ? matcher[0][1] : null +} + +def handleError(String message){ + echo "${message}" + currentBuild.setResult("FAILED") + slackSend color: 'danger', message: "${message}" + //step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'chubing@internet2.edu', sendToIndividuals: true]) + sh 'exit 1' +} diff --git a/README.md b/README.md index 8a506ce..7b57cf7 100644 --- a/README.md +++ b/README.md @@ -1 +1,62 @@ -# comanage-match-base \ No newline at end of file + + +# COmanage Match Base Image + +Intended to build a COmanage Match base image +using the official PHP with Apache image as the foundation. + +By itself the image built from this Dockerfile does **not** provide any +method for authentication and is not suitable for deployment. + +The image built from this Dockerfile is used as the base +for images that include an authentication mechanism. +See other documentation in this +repository for examples on how to build images on this +one that include authentication methods like Basic Auth, +Shibboleth SP, and mod\_auth\_openidc. + +## Build Arguments + +Building the image requires the following build argument: + +``` +--build-arg COMANAGE_MATCH_VERSION= +``` + +## Building + +``` +docker build \ + --build-arg COMANAGE_MATCH_VERSION= \ + -t comanage-match-base: . +``` + +## Building Example + +``` +export COMANAGE_MATCH_VERSION=1.0.0 +export COMANAGE_MATCH_BASE_IMAGE_VERSION=1 +TAG="${COMANAGE_MATCH_VERSION}-${COMANAGE_MATCH_BASE_IMAGE_VERSION}" +docker build \ + --build-arg COMANAGE_MATCH_VERSION=${COMANAGE_MATCH_VERSION} \ + -t comanage-match-base:${TAG} . +``` diff --git a/apache-include-directory-match b/apache-include-directory-match new file mode 100644 index 0000000..3e7a6aa --- /dev/null +++ b/apache-include-directory-match @@ -0,0 +1,6 @@ + +Options Indexes FollowSymLinks +DirectoryIndex index.php +AllowOverride All +Require all granted + diff --git a/apache-include-virtual-host-port443-base b/apache-include-virtual-host-port443-base new file mode 100644 index 0000000..044b231 --- /dev/null +++ b/apache-include-virtual-host-port443-base @@ -0,0 +1,14 @@ +ServerName https://${COMANAGE_MATCH_VIRTUAL_HOST_FQDN}:443 + +DocumentRoot /var/www/html + +RedirectMatch ^/$ /match/ + +LogLevel warn + +Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains" + +SSLEngine on +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH +SSLHonorCipherOrder on diff --git a/apache-include-virtual-host-port80-redirect b/apache-include-virtual-host-port80-redirect new file mode 100644 index 0000000..f4b5383 --- /dev/null +++ b/apache-include-virtual-host-port80-redirect @@ -0,0 +1,6 @@ + +ServerName http://${COMANAGE_MATCH_VIRTUAL_HOST_FQDN}:80 +RewriteEngine On +RewriteCond %{HTTPS} off +RewriteRule ^ https://%{HTTP_HOST}:443%{REQUEST_URI} [R=302,L,QSA] + diff --git a/comanage_match_console_logging.patch b/comanage_match_console_logging.patch new file mode 100644 index 0000000..c8e59e1 --- /dev/null +++ b/comanage_match_console_logging.patch @@ -0,0 +1,36 @@ +--- app.php.original 2019-05-18 06:27:49.677219272 -0500 ++++ app.php 2019-05-18 06:36:53.873233082 -0500 +@@ -318,27 +318,21 @@ + */ + 'Log' => [ + 'debug' => [ +- 'className' => 'Cake\Log\Engine\FileLog', +- 'path' => LOGS, +- 'file' => 'debug', +- 'url' => env('LOG_DEBUG_URL', null), ++ 'className' => 'Cake\Log\Engine\ConsoleLog', ++ 'stream' => '/dev/stdout', + 'scopes' => false, + 'levels' => ['notice', 'info', 'debug'], + ], + 'error' => [ +- 'className' => 'Cake\Log\Engine\FileLog', +- 'path' => LOGS, +- 'file' => 'error', +- 'url' => env('LOG_ERROR_URL', null), ++ 'className' => 'Cake\Log\Engine\ConsoleLog', ++ 'stream' => '/dev/stdout', + 'scopes' => false, + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + ], + // To enable this dedicated query log, you need set your datasource's log flag to true + 'queries' => [ +- 'className' => 'Cake\Log\Engine\FileLog', +- 'path' => LOGS, +- 'file' => 'queries', +- 'url' => env('LOG_QUERIES_URL', null), ++ 'className' => 'Cake\Log\Engine\ConsoleLog', ++ 'stream' => '/dev/stdout', + 'scopes' => ['queriesLog'], + ], + ], diff --git a/comanage_shibboleth_sp_utils.sh b/comanage_shibboleth_sp_utils.sh new file mode 100755 index 0000000..dd9cacf --- /dev/null +++ b/comanage_shibboleth_sp_utils.sh @@ -0,0 +1,317 @@ +#!/bin/bash + +# COmanage Registry Shibboleth SP Dockerfile entrypoint +# +# Portions licensed to the University Corporation for Advanced Internet +# Development, Inc. ("UCAID") under one or more contributor license agreements. +# See the NOTICE file distributed with this work for additional information +# regarding copyright ownership. +# +# UCAID licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if [ -n "$COMANAGE_DEBUG" ] +then + OUTPUT=/dev/stdout +else + OUTPUT=/dev/null +fi + +########################################## +# Consume injected environment variables +# Globals: +# See function +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_shibboleth_sp_utils::consume_injected_environment() { + + echo "Examining environment variables for Shibboleth SP..." > "$OUTPUT" + + # Configuration details that may be injected through environment + # variables or the contents of files. + # + # SHIBBOLETH_SP_METADATA_PROVIDER_XML may also be injected in the + # same way but because of the presence of special characters in the + # XML it is handled differently. + + injectable_config_vars=( + SHIBBOLETH_SP_ENTITY_ID + SHIBBOLETH_SP_CERT + SHIBBOLETH_SP_PRIVKEY + SHIBBOLETH_SP_SIGNING_CERT + SHIBBOLETH_SP_SIGNING_PRIVKEY + SHIBBOLETH_SP_ENCRYPT_CERT + SHIBBOLETH_SP_ENCRYPT_PRIVKEY + SHIBBOLETH_SP_SAMLDS_URL + ) + + # If the file associated with a configuration variable is present then + # read the value from it into the appropriate variable. So for example + # if the variable COMANAGE_REGISTRY_DATASOURCE_FILE exists and its + # value points to a file on the file system then read the contents + # of that file into the variable COMANAGE_REGISTRY_DATASOURCE. + + for config_var in "${injectable_config_vars[@]}" + do + eval file_name=\$"${config_var}_FILE"; + + if [ -e "$file_name" ]; then + payload=`cat $file_name` + declare "${config_var}"="${payload}" + fi + done + + echo "Done examining environment variables" > "$OUTPUT" +} + +########################################## +# Prepare shibboleth2.xml configuration file +# Globals: +# OUTPUT +# SHIBBOLETH_SP_ENTITY_ID +# SHIBBOLETH_SP_SAMLDS_URL +# SHIBBOLETH_SP_METADATA_PROVIDER_XML_FILE +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_shibboleth_sp_utils::prepare_shibboleth2xml() { + + local shib_file + local xml_content_file + local sed_script_file + + # If no shibboleth2.xml file is present then create one using + # injected information or defaults that are not particularly + # useful in a federated context but will allow shibd to start. + shib_file='/etc/shibboleth/shibboleth2.xml' + + if [[ ! -e "${shib_file}" ]]; then + cp "${shib_file}.template" "${shib_file}" > "${OUTPUT}" 2>&1 + sed -i -e s@%%SHIBBOLETH_SP_ENTITY_ID%%@"${SHIBBOLETH_SP_ENTITY_ID:-https://comanage.registry/shibboleth}"@ "${shib_file}" > "${OUTPUT}" 2>&1 + sed -i -e s@%%SHIBBOLETH_SP_SAMLDS_URL%%@"${SHIBBOLETH_SP_SAMLDS_URL:-https://localhost/registry/pages/eds/index}"@ "${shib_file}" > "${OUTPUT}" 2>&1 + + # The metadata provider injected input most likely contains special characters + # so use a sed script instead of simple substitution on the command line. + + if [[ -n "${SHIBBOLETH_SP_METADATA_PROVIDER_XML_FILE}" ]]; then + xml_content_file="${SHIBBOLETH_SP_METADATA_PROVIDER_XML_FILE}" + else + xml_content_file=`/bin/mktemp` + echo "${SHIBBOLETH_SP_METADATA_PROVIDER_XML:-}" > "${xml_content_file}" + fi + + sed_script_file=`/bin/mktemp` + cat > ${sed_script_file}< "${OUTPUT}" 2>&1 + + chmod 0644 "${shib_file}" > "${OUTPUT}" 2>&1 + + rm -f "${xml_content_file}" > "${OUTPUT}" 2>&1 + rm -f "${sed_script_file}" > "${OUTPUT}" 2>&1 + + fi +} + +########################################## +# Prepare SAML certs and keys +# Globals: +# SHIBBOLETH_SP_CERT +# SHIBBOLETH_SP_PRIVKEY +# SHIBBOLETH_SP_SIGNING_CERT +# SHIBBOLETH_SP_SIGNING_PRIVKEY +# SHIBBOLETH_SP_ENCRYPT_CERT +# SHIBBOLETH_SP_ENCRYPT_PRIVKEY +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_shibboleth_sp_utils::prepare_saml_cert_key() { + + local saml_file + local owner + + if [[ -e '/etc/debian_version' ]]; then + owner='_shibd' + elif [[ -e '/etc/centos-release' ]]; then + owner='shibd' + fi + + # If defined use configured location of Shibboleth SP SAML certificate and key. + saml_file='/etc/shibboleth/sp-cert.pem' + if [[ -n "${SHIBBOLETH_SP_CERT}" ]]; then + cp "${SHIBBOLETH_SP_CERT}" "${saml_file}" + chown "${owner}" "${saml_file}" + chmod 0644 "${saml_file}" + fi + + saml_file='/etc/shibboleth/sp-key.pem' + if [[ -n "${SHIBBOLETH_SP_PRIVKEY}" ]]; then + cp "${SHIBBOLETH_SP_PRIVKEY}" "${saml_file}" + chown "${owner}" "${saml_file}" + chmod 0600 "${saml_file}" + fi + + saml_file='/etc/shibboleth/sp-signing-cert.pem' + if [[ -n "${SHIBBOLETH_SP_SIGNING_CERT}" ]]; then + cp "${SHIBBOLETH_SP_SIGNING_CERT}" "${saml_file}" + chown "${owner}" "${saml_file}" + chmod 0644 "${saml_file}" + fi + + saml_file='/etc/shibboleth/sp-signing-key.pem' + if [[ -n "${SHIBBOLETH_SP_SIGNING_PRIVKEY}" ]]; then + cp "${SHIBBOLETH_SP_SIGNING_PRIVKEY}" "${saml_file}" + chown "${owner}" "${saml_file}" + chmod 0600 "${saml_file}" + fi + + saml_file='/etc/shibboleth/sp-encrypt-cert.pem' + if [[ -n "${SHIBBOLETH_SP_ENCRYPT_CERT}" ]]; then + cp "${SHIBBOLETH_SP_ENCRYPT_CERT}" "${saml_file}" + chown "${owner}" "${saml_file}" + chmod 0644 "${saml_file}" + fi + + saml_file='/etc/shibboleth/sp-encrypt-key.pem' + if [[ -n "${SHIBBOLETH_SP_ENCRYPT_PRIVKEY}" ]]; then + cp "${SHIBBOLETH_SP_ENCRYPT_PRIVKEY}" "${saml_file}" + chown "${owner}" "${saml_file}" + chmod 0600 "${saml_file}" + fi +} + +########################################## +# Manage UID and GID on files +# Globals: +# None +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_shibboleth_sp_utils::manage_uid_gid() { + + local owner + local ownership + local not_readable + + # A deployer may make their own mapping between the shibd username + # and the UID, and between the shibd group and GID, so before starting + # make sure files have the correct ownership and group. + + not_readable='/tmp/shibd-not-readable' + + if [[ -e '/etc/debian_version' ]]; then + owner='_shibd' + ownership="${owner}:${owner}" + + chown "${ownership}" /etc/shibboleth/sp-cert.pem > /dev/null 2>&1 + chown "${ownership}" /etc/shibboleth/sp-key.pem > /dev/null 2>&1 + + chown "${ownership}" /etc/shibboleth/sp-signing-cert.pem > /dev/null 2>&1 + chown "${ownership}" /etc/shibboleth/sp-signing-key.pem > /dev/null 2>&1 + + chown "${ownership}" /etc/shibboleth/sp-encrypt-cert.pem > /dev/null 2>&1 + chown "${ownership}" /etc/shibboleth/sp-encrypt-key.pem > /dev/null 2>&1 + + chown "${ownership}" /opt/shibboleth-sp/var > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/run > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/run/shibboleth > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/run/shibboleth/shibd.sock > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log/shibboleth > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log/shibboleth/transaction.log > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log/shibboleth/signature.log > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log/shibboleth/shibd_warn.log > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log/shibboleth/shibd.log > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/log/shibboleth-www > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/cache > /dev/null 2>&1 + chown "${ownership}" /opt/shibboleth-sp/var/cache/shibboleth > /dev/null 2>&1 + elif [[ -e '/etc/centos-release' ]]; then + owner='shibd' + ownership="${owner}:${owner}" + + chown "${ownership}" /etc/shibboleth/sp-cert.pem > /dev/null 2>&1 + chown "${ownership}" /etc/shibboleth/sp-key.pem > /dev/null 2>&1 + + chown "${ownership}" /etc/shibboleth/sp-signing-cert.pem > /dev/null 2>&1 + chown "${ownership}" /etc/shibboleth/sp-signing-key.pem > /dev/null 2>&1 + + chown "${ownership}" /etc/shibboleth/sp-encrypt-cert.pem > /dev/null 2>&1 + chown "${ownership}" /etc/shibboleth/sp-encrypt-key.pem > /dev/null 2>&1 + fi + + # Warn about any files the shibd user cannot read. + sudo -u "${owner}" find /etc/shibboleth ! -readable > "${not_readable}" 2>/dev/null + if [[ -s "${not_readable}" ]]; then + echo "WARNING: the following files are not readable by ${owner}" + cat "${not_readable}" + echo "" + fi + + rm -f "${not_readable}" > /dev/null 2>&1 +} + +########################################## +# Exec to start and become Shibboleth SP shibd +# Globals: +# None +# Arguments: +# Command and arguments to exec +# Returns: +# Does not return +########################################## +function comanage_shibboleth_sp_utils::exec_shibboleth_sp_daemon() { + + local user + local group + local shibd_daemon + local config + local pidfile + + comanage_shibboleth_sp_utils::consume_injected_environment + + comanage_shibboleth_sp_utils::prepare_shibboleth2xml + + comanage_shibboleth_sp_utils::prepare_saml_cert_key + + comanage_shibboleth_sp_utils::manage_uid_gid + + config='/etc/shibboleth/shibboleth2.xml' + pidfile='/var/run/shibboleth/shibd.pid' + + if [[ -e '/etc/debian_version' ]]; then + user='_shibd' + group='_shibd' + shibd_daemon='/opt/shibboleth-sp/sbin/shibd' + elif [[ -e '/etc/centos-release' ]]; then + user='shibd' + group='shibd' + shibd_daemon='/usr/sbin/shibd' + export LD_LIBRARY_PATH=/opt/shibboleth/lib64 + fi + + exec "${shibd_daemon}" -f -u "${user}" -g "${group}" -c "${config}" -p "${pidfile}" -F +} diff --git a/comanage_utils.sh b/comanage_utils.sh new file mode 100644 index 0000000..6aa193c --- /dev/null +++ b/comanage_utils.sh @@ -0,0 +1,469 @@ +#!/bin/bash + +# COmanage Match bash shell utilities +# +# Portions licensed to the University Corporation for Advanced Internet +# Development, Inc. ("UCAID") under one or more contributor license agreements. +# See the NOTICE file distributed with this work for additional information +# regarding copyright ownership. +# +# UCAID licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if [ -n "$COMANAGE_DEBUG" ] +then + OUTPUT=/dev/stdout +else + OUTPUT=/dev/null +fi + +########################################## +# Configure console (stdout) logging +# Globals: +# COMANAGE_MATCH_DIR +# OUTPUT +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::configure_console_logging() { + pushd "$COMANAGE_MATCH_DIR/app/config" > "$OUTPUT" 2>&1 + patch < /usr/local/src/comanage_match_console_logging.patch > "$OUTPUT" 2>&1 + popd > "$OUTPUT" 2>&1 +} + +########################################## +# Configure TIER logging +# Globals: +# ENV +# USERTOKEN +# OUTPUT +# Arguments: +# NONE +# Returns: +# None +########################################## +function comanage_utils::configure_tier_logging() { + + comanage_utils::manage_tier_environment + + # Create pipes to use for COmanage Match instead of standard log files. + rm -rf "$COMANAGE_MATCH_DIR/app/logs" > "$OUTPUT" 2>&1 + mkfifo -m 666 "$COMANAGE_MATCH_DIR/app/logs/error.log" > "$OUTPUT" 2>&1 + mkfifo -m 666 "$COMANAGE_MATCH_DIR/app/logs/debug.log" > "$OUTPUT" 2>&1 + + # Format any output from COmanange Registry into standard TIER form. + (cat <> "$COMANAGE_MATCH_DIR/app/logs/error.log" | awk -v ENV="$ENV" -v UT="$USERTOKEN" '{printf "comanage_match;error.log;%s;%s;%s\n", ENV, UT, $0; fflush()}' 1>/tmp/logpipe)& + (cat <> "$COMANAGE_MATCH_DIR/app/logs/debug.log" | awk -v ENV="$ENV" -v UT="$USERTOKEN" '{printf "comanage_match;debug.log;%s;%s;%s\n", ENV, UT, $0; fflush()}' 1>/tmp/logpipe)& +} + +########################################## +# Consume injected environment variables +# Globals: +# See function +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::consume_injected_environment() { + + echo "Examining environment variables..." > "$OUTPUT" + + # Configuration details that may be injected through environment + # variables or the contents of files. + local injectable_config_vars + + injectable_config_vars=( + COMANAGE_MATCH_ADMIN_USERNAME + COMANAGE_MATCH_DATASOURCE + COMANAGE_MATCH_DATABASE + COMANAGE_MATCH_DATABASE_HOST + COMANAGE_MATCH_DATABASE_USER + COMANAGE_MATCH_DATABASE_USER_PASSWORD + COMANAGE_MATCH_SECURITY_SALT + COMANAGE_MATCH_SECURITY_SEED + COMANAGE_MATCH_VIRTUAL_HOST_FQDN + HTTPS_CERT_FILE + HTTPS_PRIVKEY_FILE + ) + + # If the file associated with a configuration variable is present then + # read the value from it into the appropriate variable. So for example + # if the variable COMANAGE_MATCH_DATASOURCE_FILE exists and its + # value points to a file on the file system then read the contents + # of that file into the variable COMANAGE_MATCH_DATASOURCE. + + local config_var + for config_var in "${injectable_config_vars[@]}" + do + local file_name + eval file_name=\$"${config_var}_FILE"; + + if [[ -e "$file_name" ]]; then + declare -g "${config_var}"=`cat $file_name` + echo "Set ${config_var} to be contents of ${file_name}" > "$OUTPUT" + fi + done + + echo "Done examining environment variables" > "$OUTPUT" +} + +########################################## +# Exec to start and become Apache HTTP Server +# Globals: +# None +# Arguments: +# Command and arguments to exec +# Returns: +# Does not return +########################################## +function comanage_utils::exec_apache_http_server() { + + comanage_utils::consume_injected_environment + + comanage_utils::configure_console_logging + + comanage_utils::prepare_local_directory + + comanage_utils::prepare_database_config + + comanage_utils::prepare_https_cert_key + + comanage_utils::prepare_server_name + + comanage_utils::wait_database_connectivity + + comanage_utils::match_setup + + comanage_utils::tmp_ownership + + # first arg is `-f` or `--some-option` + if [ "${1#-}" != "$1" ]; then + set -- apache2-foreground "$@" + fi + + exec "$@" +} + +########################################## +# Manage TIER environment variables +# Globals: +# None +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::manage_tier_environment() { + + # If ENV or USERTOKEN as injected by the deployer contain a semi-colon remove it. + if [[ ${ENV} =~ .*";".* ]]; then + ENV=`echo ${ENV} | tr -d ';'` + export ENV + fi + + if [[ ${USERTOKEN} =~ .*";".* ]]; then + USERTOKEN=`echo ${USERTOKEN} | tr -d ';'` + export USERTOKEN + fi + + # If ENV or USERTOKEN as injected by the deployer contain a space remove it. + if [[ ${ENV} =~ [[:space:]] ]]; then + ENV=`echo ${ENV} | tr -d [:space:]` + export ENV + fi + + if [[ ${USERTOKEN} =~ [[:space:]] ]]; then + USERTOKEN=`echo ${USERTOKEN} | tr -d [:space:]` + export USERTOKEN + fi +} + +########################################## +# Prepare database configuration +# Globals: +# COMANAGE_MATCH_DATABASE +# COMANAGE_MATCH_DATABASE_HOST +# COMANAGE_MATCH_DATABASE_USER +# COMANAGE_MATCH_DATABASE_USER_PASSWORD +# COMANAGE_MATCH_DATASOURCE +# COMANAGE_MATCH_DIR +# OUTPUT +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::prepare_database_config() { + + # If the COmanage Match database configuration file does not exist + # then try to create it from injected information with reasonable defaults + # that aid simple evaluation deployments. + local database_config + database_config="$COMANAGE_MATCH_DIR/local/Config/database.php" + + if [[ ! -e "$database_config" ]]; then + cat > "$database_config" < [ + 'default' => [ + 'className' => 'Cake\Database\Connection', + // Postgres is currently the only supported backend for COmanage Match + 'driver' => 'Cake\Database\Driver\Postgres', + 'host' => '${COMANAGE_MATCH_DATABASE_HOST:-comanage-match-database}', + 'username' => '${COMANAGE_MATCH_DATABASE_USER:-match_user}', + 'password' => '${COMANAGE_MATCH_DATABASE_USER_PASSWORD:-password}', + 'database' => '${COMANAGE_MATCH_DATABASE:-match}', + ] + ] + ]; +EOF + echo "Wrote new database configuration file ${database_config}" > "$OUTPUT" + fi +} + +########################################## +# Prepare cert and key for HTTPS +# Globals: +# HTTPS_CERT_FILE +# HTTPS_PRIVKEY_FILE +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::prepare_https_cert_key() { + + local cert_path + local privkey_path + local web_user + + if [[ -e '/etc/debian_version' ]]; then + cert_path='/etc/apache2/cert.pem' + privkey_path='/etc/apache2/privkey.pem' + web_user='www-data' + elif [[ -e '/etc/centos-release' ]]; then + cert_path='/etc/httpd/cert.pem' + privkey_path='/etc/httpd/privkey.pem' + web_user='apache' + fi + + # If defined use configured location of Apache HTTP Server + # HTTPS certificate and key files. The certificate file may also + # include intermediate CA certificates, sorted from leaf to root. + if [[ -n "${HTTPS_CERT_FILE}" ]]; then + rm -f "${cert_path}" + cp "${HTTPS_CERT_FILE}" "${cert_path}" + chown "${web_user}" "${cert_path}" + chmod 0644 "${cert_path}" + echo "Copied HTTPS certificate file ${HTTPS_CERT_FILE} to ${cert_path}" > "$OUTPUT" + echo "Set ownership of ${cert_path} to ${web_user}" > "$OUTPUT" + fi + + if [[ -n "${HTTPS_PRIVKEY_FILE}" ]]; then + rm -f "${privkey_path}" + cp "${HTTPS_PRIVKEY_FILE}" "${privkey_path}" + chown "${web_user}" "${privkey_path}" + chmod 0600 "${privkey_path}" + echo "Copied HTTPS private key file ${HTTPS_PRIVKEY_FILE} to ${privkey_path}" > "$OUTPUT" + echo "Set ownership of ${privkey_path} to ${web_user}" > "$OUTPUT" + fi +} + +########################################## +# Prepare local directory structure +# Globals: +# COMANAGE_MATCH_DIR +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::prepare_local_directory() { + + # Make sure the directory structure we need is available + # in the data volume for $COMANAGE_MATCH_DIR/local + local directories + + declare -a directories=("Config" + ) + + local dir + local full_path + for dir in "${directories[@]}" + do + full_path="${COMANAGE_MATCH_DIR}/local/${dir}" + if [[ ! -d "${full_path}" ]]; then + mkdir -p "${full_path}" > "$OUTPUT" 2>&1 + echo "Created directory ${full_path}" + fi + done +} + +########################################## +# Prepare web server name +# Globals: +# COMANAGE_MATCH_VIRTUAL_HOST_FQDN +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::prepare_server_name() { + + # If COMANAGE_MATCH_VIRTUAL_HOST_FQDN has not been injected try to determine + # it from the HTTPS_CERT_FILE. + if [[ -z "$COMANAGE_MATCH_VIRTUAL_HOST_FQDN" ]]; then + COMANAGE_MATCH_VIRTUAL_HOST_FQDN=$(openssl x509 -in /etc/apache2/cert.pem -text -noout | + sed -n '/X509v3 Subject Alternative Name:/ {n;p}' | + sed -E 's/.*DNS:(.*)\s*$/\1/') + if [[ -n "$COMANAGE_MATCH_VIRTUAL_HOST_FQDN" ]]; then + echo "Set COMANAGE_MATCH_VIRTUAL_HOST_FQDN=${COMANAGE_MATCH_VIRTUAL_HOST_FQDN} using Subject Alternative Name from x509 certificate" > "$OUTPUT" + else + COMANAGE_MATCH_VIRTUAL_HOST_FQDN=$(openssl x509 -in /etc/apache2/cert.pem -subject -noout | + sed -E 's/subject=.*CN=(.*)\s*/\1/') + if [[ -n "$COMANAGE_MATCH_VIRTUAL_HOST_FQDN" ]]; then + echo "Set COMANAGE_MATCH_VIRTUAL_HOST_FQDN=${COMANAGE_MATCH_VIRTUAL_HOST_FQDN} using CN from x509 certificate" > "$OUTPUT" + fi + fi + fi + + # Configure Apache HTTP Server with the server name. + # This configures the server name for the default Debian + # Apache HTTP Server configuration but not the server name used + # by any virtual hosts. + if [[ -e '/etc/debian_version' ]]; then + cat > /etc/apache2/conf-available/server-name.conf < "$OUTPUT" 2>&1 + fi + + # Export the server name so that it may be used by + # Apache HTTP Server virtual host configurations. + export COMANAGE_MATCH_VIRTUAL_HOST_FQDN +} + +########################################## +# Clear CakePHP cache files +# Globals: +# None +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::match_clear_cache() { + + pushd "$COMANAGE_MATCH_DIR/app" > "$OUTPUT" 2>&1 + ./bin/cake schema_cache clear > "$OUTPUT" 2>&1 + popd > "$OUTPUT" 2>&1 +} + +########################################## +# Run COmanage Match setup shell command +# Globals: +# COMANAGE_MATCH_ADMIN_USERNAME +# COMANAGE_MATCH_DIR +# COMANAGE_MATCH_SECURITY_SALT +# COMANAGE_MATCH_SECURITY_SEED +# OUTPUT +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::match_setup() { + + # We only want to run the setup script once since it creates + # state in the database. + local auto_generated_security + pushd "$COMANAGE_MATCH_DIR/app" > "$OUTPUT" 2>&1 + echo "Testing if COmanage Match setup has been done previously..." > "$OUTPUT" + if ! ./bin/cake databaseSetupAlready > "$OUTPUT" 2>&1; then + echo "COmanage Match database is not setup yet..." > "$OUTPUT" + rm -f "$COMANAGE_MATCH_DIR/local/Config/security.salt" > "$OUTPUT" 2>&1 + echo "Running ./bin/cake setup..." > "$OUTPUT" + ./bin/cake setup --admin-username "${COMANAGE_MATCH_ADMIN_USERNAME}" > "$OUTPUT" 2>&1 + echo "Set admin username ${COMANAGE_MATCH_ADMIN_USERNAME}" > "$OUTPUT" + auto_generated_security=1 + fi + + popd > "$OUTPUT" 2>&1 + + comanage_utils::match_clear_cache + + # If COmanage Match CakePHP security salt has been + # injected and the files do not otherwise exist create them. + if [[ -n "$COMANAGE_MATCH_SECURITY_SALT" && + ( -n "$auto_generated_security" || ! -e "$COMANAGE_MATCH_DIR/local/Config/security.salt" ) ]]; then + echo "$COMANAGE_MATCH_SECURITY_SALT" > "$COMANAGE_MATCH_DIR/local/Config/security.salt" + fi +} + +########################################## +# Set tmp directory file ownership +# Globals: +# COMANAGE_MATCH_DIR +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::tmp_ownership() { + + # Ensure that the web server user owns the tmp directory + # and all children. + local tmp_dir + local ownership + + if [[ -e '/etc/debian_version' ]]; then + ownership='www-data:www-data' + elif [[ -e '/etc/centos-release' ]]; then + ownership='apache:apache' + fi + + tmp_dir="${COMANAGE_MATCH_DIR}/app/tmp" + + chown -R "${ownership}" "${tmp_dir}" + + echo "Recursively set ownership of ${tmp_dir} to ${ownership}" > "$OUTPUT" + +} + +########################################## +# Wait until able to connect to database +# Globals: +# COMANAGE_MATCH_DIR +# OUTPUT +# Arguments: +# None +# Returns: +# None +########################################## +function comanage_utils::wait_database_connectivity() { + pushd "$COMANAGE_MATCH_DIR/app" > "$OUTPUT" 2>&1 + + # Loop until we are able to open a connection to the database. + echo "Testing database connectivity..." > "$OUTPUT" + until ./bin/cake databaseConnectivityTest > "$OUTPUT" 2>&1; do + >&2 echo "Database is unavailable - sleeping" + sleep 1 + done + + echo "Database is available" > "$OUTPUT" + + popd > "$OUTPUT" 2>&1 +} diff --git a/common.bash b/common.bash new file mode 100644 index 0000000..d7a5637 --- /dev/null +++ b/common.bash @@ -0,0 +1,2 @@ +maintainer="tier" +imagename="comanage-match-base" diff --git a/docker-comanage-match-entrypoint b/docker-comanage-match-entrypoint new file mode 100755 index 0000000..2200e7d --- /dev/null +++ b/docker-comanage-match-entrypoint @@ -0,0 +1,24 @@ +#!/bin/bash + +# COmanage Match Dockerfile entrypoint +# +# Portions licensed to the University Corporation for Advanced Internet +# Development, Inc. ("UCAID") under one or more contributor license agreements. +# See the NOTICE file distributed with this work for additional information +# regarding copyright ownership. +# +# UCAID licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /usr/local/lib/comanage_utils.sh + +comanage_utils::exec_apache_http_server "$@"