From 07983da55e904c32cd041c463d9e0b7531b8ab7a Mon Sep 17 00:00:00 2001 From: Sida Say Date: Wed, 29 Jun 2022 11:30:55 +0700 Subject: [PATCH] Project improvement - revamp project structure for easy manage - add SSL support - add DB_PORT for support non standard port - update README to address new changes --- Dockerfile | 58 ++++------- Makefile | 2 +- README.md | 16 +-- configs/app/uwsgi.ini | 3 - prebuildfs/usr/sbin/install_packages | 24 +++++ .../etc/supervisor/supervisord.conf | 1 - {configs => rootfs/etc/uwsgi}/uwsgi.ini | 4 +- .../opt/templates/nginx-pi-ssl.conf.template | 22 +++++ .../opt/templates/nginx-pi.conf.template | 6 +- .../opt/templates}/nginx.conf.template | 2 +- .../opt/templates}/pi-config.template | 0 .../usr/local/bin}/_privacyidea_common.sh | 0 rootfs/usr/local/bin/configure_nginx.sh | 97 +++++++++++++++++++ .../usr/local/bin}/privacyidea_entrypoint.sh | 0 .../usr/local/bin}/start_privacyidea.sh | 16 ++- scripts/configure_nginx.sh | 15 --- 16 files changed, 193 insertions(+), 73 deletions(-) delete mode 100644 configs/app/uwsgi.ini create mode 100755 prebuildfs/usr/sbin/install_packages rename configs/supervisord-debian.conf => rootfs/etc/supervisor/supervisord.conf (97%) rename {configs => rootfs/etc/uwsgi}/uwsgi.ini (79%) create mode 100644 rootfs/opt/templates/nginx-pi-ssl.conf.template rename configs/nginx/pi.conf.template => rootfs/opt/templates/nginx-pi.conf.template (61%) rename {configs/nginx => rootfs/opt/templates}/nginx.conf.template (97%) rename {configs => rootfs/opt/templates}/pi-config.template (100%) rename {scripts => rootfs/usr/local/bin}/_privacyidea_common.sh (100%) create mode 100755 rootfs/usr/local/bin/configure_nginx.sh rename {scripts => rootfs/usr/local/bin}/privacyidea_entrypoint.sh (100%) mode change 100644 => 100755 rename {scripts => rootfs/usr/local/bin}/start_privacyidea.sh (88%) mode change 100644 => 100755 delete mode 100644 scripts/configure_nginx.sh diff --git a/Dockerfile b/Dockerfile index 807ed38..7d5335e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,48 +2,22 @@ FROM python:3.8.13-bullseye LABEL maintainer="Sida Say " -ARG REQUIRED_PACKAGE="ca-certificates git supervisor gettext-base nginx" +COPY prebuildfs / -RUN set -xe; \ - apt-get -y update && \ - apt-get install -y ${REQUIRED_PACKAGE} +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN install_packages ca-certificates git supervisor gettext-base nginx RUN mkdir -p mkdir /etc/privacyidea/data/keys \ - /opt/privacyidea \ /var/log/privacyidea && \ adduser --gecos "PrivacyIdea User" --disabled-password --home /home/privacyidea privacyidea --uid 1001 && \ addgroup privacyidea privacyidea && \ usermod -g 1001 privacyidea && \ - chown -R privacyidea:privacyidea /opt/privacyidea /etc/privacyidea /var/log/privacyidea + chown -R privacyidea:privacyidea /etc/privacyidea /var/log/privacyidea -# apt-get remove --purge --auto-remove -y ca-certificates && rm -rf /var/lib/apt/lists/* - -# COPY PI configuration -COPY --chown=privacyidea:privacyidea ./configs/pi-config.template /etc/privacyidea/pi-config.template - -# Remove default configuration from Nginx -# RUN rm /etc/nginx/conf.d/default.conf - -COPY ./configs/nginx /etc/nginx/templates - -# Copy the base uWSGI ini file to enable default dynamic uwsgi process number -COPY --chown=privacyidea:privacyidea ./configs/uwsgi.ini /etc/uwsgi/ - -# Custom Supervisord config -COPY --chown=privacyidea:privacyidea ./configs/supervisord-debian.conf /etc/supervisor/supervisord.conf - -COPY --chown=privacyidea:privacyidea ./configs/app /app - -COPY scripts/* /usr/local/bin/ - -RUN chmod +x /usr/local/bin/*.sh \ - && apt-get clean autoclean \ - && apt-get autoremove --yes \ - && rm -rf /var/lib/{apt,dpkg,cache,log}/ \ - && rm -rf /tmp/* +COPY rootfs / # Which uWSGI .ini file should be used, to make it customizable -ENV UWSGI_INI /app/uwsgi.ini +ENV UWSGI_INI /etc/uwsgi/uwsgi.ini # By default, run 2 processes ENV UWSGI_CHEAPER 2 @@ -68,28 +42,32 @@ ENV NGINX_WORKER_CONNECTIONS 1024 # By default, Nginx listens on port 80. # To modify this, change LISTEN_PORT environment variable. # (in a Dockerfile or with an option for `docker run`) -ENV LISTEN_PORT 80 +ENV NGINX_LISTEN_PORT 80 +ENV NGINX_LISTEN_SSL_PORT 443 + +ENV NGINX_SSL_ENABLED true ENV PI_SKIP_BOOTSTRAP=false \ DB_VENDOR=sqlite \ - PI_HOME=/opt/privacyidea - -ARG PI_VERSION=3.7.1 + PI_HOME=/opt/privacyidea \ + VIRTUAL_ENV=/opt/privacyidea -ENV VIRTUAL_ENV=/opt/privacyidea RUN python3 -m venv $VIRTUAL_ENV + ENV PATH="$VIRTUAL_ENV/bin:$PATH" +ARG PI_VERSION=3.7.1 + RUN pip3 install wheel && \ - pip3 install supervisor uwsgi pymysql-sa PyMySQL psycopg2-binary && \ + pip3 install uwsgi pymysql-sa PyMySQL psycopg2-binary && \ pip3 install -r https://raw.githubusercontent.com/privacyidea/privacyidea/v${PI_VERSION}/requirements.txt && \ pip3 install git+https://github.com/privacyidea/privacyidea.git@v${PI_VERSION} EXPOSE 80/tcp EXPOSE 443/tcp - ENTRYPOINT ["/usr/local/bin/privacyidea_entrypoint.sh"] -WORKDIR /app +WORKDIR /opt/privacyidea + VOLUME [ "/data/privacyidea" ] diff --git a/Makefile b/Makefile index 29f6aa0..b90e250 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ push: docker push michimau/privacyidea run: cleanup create_volume secretkey pipepper - docker run -v $(LOCAL_DATA_VOLUME):/data/privacyidea -p 80:80 -ti --name=privacyidea-dev --env-file=secretkey --env-file=pipepper khalibre/privacyidea:dev + docker run -p 80:80 -p 443:443 -ti --name=privacyidea-dev --env-file=secretkey --env-file=pipepper khalibre/privacyidea:dev create_volume: mkdir $(LOCAL_DATA_VOLUME) diff --git a/README.md b/README.md index 48ee0f1..626e87c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # PrivacyIdea Docker Image -This is a build environment to build a docker image for privacyIDEA base on [official NGINX image](https://hub.docker.com/_/nginx) and [PrivacyIDEA](https://github.com/privacyidea/privacyidea) +This is a build environment to build a docker image for privacyIDEA base on [official Python image](https://hub.docker.com/_/python) and [PrivacyIDEA](https://github.com/privacyidea/privacyidea) **Disclaimer**: The respective trademarks mentioned in the offering are owned by the respective companies. We do not provide commercial license of any of these products. This listing has an open source license. privacyIDEA is run and maintained by NetKnights, that is a completely and separate project from Khalibre. ## The image -The docker image is a self contained Debain with privacyIDEA installed, which will run on every distribution. +The docker image is a self contained Debain with privacyIDEA and NGINX installed, which will run on every distribution. ## Building @@ -40,7 +40,7 @@ The Khalibre privacyIDEA container can create a default admin user by setting th The Khalibre privacyIDEA requires a database to work. This is configured with the following environment variables: - - `DB_VENDOR`: Database vendor (support mysql or posgresql) No defaults. + - `DB_VENDOR`: Database vendor (support mysql, mariadb or posgresql) No defaults. - `DB_USER`: Database user. No defaults. - `DB_PASSWORD`: Database. No defaults. - `DB_NAME`: Database name. No defaults. @@ -48,11 +48,15 @@ The Khalibre privacyIDEA requires a database to work. This is configured with th ### NGINX configuration + - `NGINX_LISTEN_PORT`: Get the listen port for Nginx, default to 80 + - `NGINX_LISTEN_SSL_PORT`: Get the secured listen port for Nginx, default to 443 - `NGINX_MAX_UPLOAD`: Get the maximum upload file size for Nginx, default to 100Mb - - `NGINX_WORKER_PROCESSES`: Get the number of workers for Nginx, default to 1 - - `NGINX_WORKER_CONNECTIONS`: Set the max number of connections per worker for Nginx, if requested. - `NGINX_SERVER_TOKENS`: Hide Nginx server version on error pages and in the “Server HTTP” response header field - - `USE_LISTEN_PORT`: Get the listen port for Nginx, default to 80 + - `NGINX_SSL_CERT`: Path to SSL certificate, default to **/etc/nginx/certs/pi-server-cert.pem** + - `NGINX_SSL_ENABLED`: Set to true to enable SSL, default **false** + - `NGINX_SSL_KEY`: Path to SSL key, default **/etc/nginx/certs/pi-server-key.pem** + - `NGINX_WORKER_CONNECTIONS`: Set the max number of connections per worker for Nginx, if requested. + - `NGINX_WORKER_PROCESSES`: Get the number of workers for Nginx, default to 1 ### privacyIDEA configuration diff --git a/configs/app/uwsgi.ini b/configs/app/uwsgi.ini deleted file mode 100644 index ebe61b4..0000000 --- a/configs/app/uwsgi.ini +++ /dev/null @@ -1,3 +0,0 @@ -[uwsgi] -wsgi-file=/opt/privacyidea/etc/privacyidea/privacyideaapp.wsgi -buffer-size=8192 diff --git a/prebuildfs/usr/sbin/install_packages b/prebuildfs/usr/sbin/install_packages new file mode 100755 index 0000000..c957764 --- /dev/null +++ b/prebuildfs/usr/sbin/install_packages @@ -0,0 +1,24 @@ +#!/bin/sh +set -e +set -u +export DEBIAN_FRONTEND=noninteractive +n=0 +max=2 +until [ $n -gt $max ]; do + set +e + ( + apt-get update -qq && + apt-get install -y --no-install-recommends "$@" + ) + CODE=$? + set -e + if [ $CODE -eq 0 ]; then + break + fi + if [ $n -eq $max ]; then + exit $CODE + fi + echo "apt failed, retrying" + n=$(($n + 1)) +done +rm -r /var/lib/apt/lists /var/cache/apt/archives diff --git a/configs/supervisord-debian.conf b/rootfs/etc/supervisor/supervisord.conf similarity index 97% rename from configs/supervisord-debian.conf rename to rootfs/etc/supervisor/supervisord.conf index 5042084..2c08023 100644 --- a/configs/supervisord-debian.conf +++ b/rootfs/etc/supervisor/supervisord.conf @@ -1,6 +1,5 @@ [supervisord] nodaemon=true -user=root [program:uwsgi] command=/opt/privacyidea/bin/uwsgi --ini /etc/uwsgi/uwsgi.ini diff --git a/configs/uwsgi.ini b/rootfs/etc/uwsgi/uwsgi.ini similarity index 79% rename from configs/uwsgi.ini rename to rootfs/etc/uwsgi/uwsgi.ini index 55e15d2..127b00c 100644 --- a/configs/uwsgi.ini +++ b/rootfs/etc/uwsgi/uwsgi.ini @@ -1,6 +1,8 @@ [uwsgi] +wsgi-file=/opt/privacyidea/etc/privacyidea/privacyideaapp.wsgi +buffer-size=8192 socket = /tmp/uwsgi.sock -chown-socket = nginx:nginx +chown-socket = www-data:www-data chmod-socket = 664 # Graceful shutdown on SIGTERM, see https://github.com/unbit/uwsgi/issues/849#issuecomment-118869386 hook-master-start = unix_signal:15 gracefully_kill_them_all diff --git a/rootfs/opt/templates/nginx-pi-ssl.conf.template b/rootfs/opt/templates/nginx-pi-ssl.conf.template new file mode 100644 index 0000000..a74934e --- /dev/null +++ b/rootfs/opt/templates/nginx-pi-ssl.conf.template @@ -0,0 +1,22 @@ +server { + listen $NGINX_LISTEN_SSL_PORT ssl; + listen [::]:$NGINX_LISTEN_SSL_PORT ssl; + + ssl_certificate $NGINX_SSL_CERT; + ssl_certificate_key $NGINX_SSL_KEY; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + proxy_http_version 1.1; + + client_max_body_size $NGINX_MAX_UPLOAD; + location / { + + include uwsgi_params; + uwsgi_pass unix:///tmp/uwsgi.sock; + } +} diff --git a/configs/nginx/pi.conf.template b/rootfs/opt/templates/nginx-pi.conf.template similarity index 61% rename from configs/nginx/pi.conf.template rename to rootfs/opt/templates/nginx-pi.conf.template index 969a405..d4e7d08 100644 --- a/configs/nginx/pi.conf.template +++ b/rootfs/opt/templates/nginx-pi.conf.template @@ -1,7 +1,9 @@ server { - listen $LISTEN_PORT; - listen [::]:$LISTEN_PORT; + listen $NGINX_LISTEN_PORT; + listen [::]:$NGINX_LISTEN_PORT; + client_max_body_size $NGINX_MAX_UPLOAD; + proxy_http_version 1.1; location / { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; diff --git a/configs/nginx/nginx.conf.template b/rootfs/opt/templates/nginx.conf.template similarity index 97% rename from configs/nginx/nginx.conf.template rename to rootfs/opt/templates/nginx.conf.template index 05b2519..d0978bb 100644 --- a/configs/nginx/nginx.conf.template +++ b/rootfs/opt/templates/nginx.conf.template @@ -1,4 +1,4 @@ -user nginx; +user www-data; worker_processes $NGINX_WORKER_PROCESSES; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; diff --git a/configs/pi-config.template b/rootfs/opt/templates/pi-config.template similarity index 100% rename from configs/pi-config.template rename to rootfs/opt/templates/pi-config.template diff --git a/scripts/_privacyidea_common.sh b/rootfs/usr/local/bin/_privacyidea_common.sh similarity index 100% rename from scripts/_privacyidea_common.sh rename to rootfs/usr/local/bin/_privacyidea_common.sh diff --git a/rootfs/usr/local/bin/configure_nginx.sh b/rootfs/usr/local/bin/configure_nginx.sh new file mode 100755 index 0000000..e783b97 --- /dev/null +++ b/rootfs/usr/local/bin/configure_nginx.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +set -e + +function main { + + envsubst < /opt/templates/nginx.conf.template > /etc/nginx/nginx.conf + envsubst < /opt/templates/nginx-pi.conf.template > /etc/nginx/conf.d/pi.conf + if [ "$NGINX_SSL_ENABLED" = true ]; then + if [ -z "$NGINX_SSL_CERT" ] && [ -z "$NGINX_SSL_CERT" ]; + then + echo "SSL enabled but NGINX_SSL_CERT and NGINX_SSL_KEY are not defined, using generated certifiacate" + echo "Generate self signed certificate" + generate_cert + echo "" + echo "Finished generate certificates" + + export NGINX_SSL_CERT=/etc/nginx/certs/pi-server-cert.pem + export NGINX_SSL_KEY=/etc/nginx/certs/pi-server-key.pem + fi + envsubst < /opt/templates/nginx-pi-ssl.conf.template > /etc/nginx/conf.d/pi-ssl.conf + fi + +} + +function generate_cert { + + # Create certificate directory + mkdir -p /etc/nginx/certs + + # [ global parameters ] + # certificate configuration + readonly CERT_DAYS=36500 + readonly RSA_STR_LEN=4096 + readonly PREFIX=pi- + readonly CERT_DIR=/etc/nginx/certs + readonly KEY_DIR=/etc/nginx/certs + # certificate content definition + readonly ADDRESS_COUNTRY_CODE=KH + readonly ADDRESS_PREFECTURE=PI + readonly ADDRESS_CITY='Phnom Penh' + readonly COMPANY_NAME=Khalibre + readonly COMPANY_SECTION=DevOps + readonly CERT_PASSWORD= # no password + # - ca + readonly CA_DOMAIN='Khalibre DevOps' + readonly CA_EMAIL=ca@email.address + # - server + readonly SERVER_DOMAIN=localhost + readonly SERVER_EMAIL=server@email.address + + # [ functions ] + echo_cert_params() { + local company_domain="$1" + local company_email="$2" + + echo $ADDRESS_COUNTRY_CODE + echo $ADDRESS_PREFECTURE + echo $ADDRESS_CITY + echo $COMPANY_NAME + echo $COMPANY_SECTION + echo $company_domain + echo $company_email + echo $CERT_PASSWORD # password + echo $CERT_PASSWORD # password (again) + } + echo_ca_cert_params() { + echo_cert_params "$CA_DOMAIN" "$CA_EMAIL" + } + echo_server_cert_params() { + echo_cert_params "$SERVER_DOMAIN" "$SERVER_EMAIL" + } + + # [ main ] + # generate certificates + # - ca + openssl genrsa $RSA_STR_LEN > $KEY_DIR/${PREFIX}ca-key.pem + echo_ca_cert_params | \ + openssl req -new -x509 -nodes -days $CERT_DAYS -key $KEY_DIR/${PREFIX}ca-key.pem -out $CERT_DIR/${PREFIX}ca-cert.pem + # - server + echo_server_cert_params | \ + openssl req -newkey rsa:$RSA_STR_LEN -days $CERT_DAYS -nodes -keyout $KEY_DIR/${PREFIX}server-key.pem -out $CERT_DIR/${PREFIX}server-req.pem + openssl rsa -in $KEY_DIR/${PREFIX}server-key.pem -out $KEY_DIR/${PREFIX}server-key.pem + openssl x509 -req -in $CERT_DIR/${PREFIX}server-req.pem -days $CERT_DAYS -CA $CERT_DIR/${PREFIX}ca-cert.pem -CAkey $KEY_DIR/${PREFIX}ca-key.pem -set_serial 01 -out $CERT_DIR/${PREFIX}server-cert.pem + + # clean up (before permission changed) + rm $KEY_DIR/${PREFIX}ca-key.pem + rm $CERT_DIR/${PREFIX}server-req.pem + + # validate permission + chmod 400 $KEY_DIR/${PREFIX}server-key.pem + + # verify relationship among certificates + openssl verify -CAfile $CERT_DIR/${PREFIX}ca-cert.pem $CERT_DIR/${PREFIX}server-cert.pem + } + +main diff --git a/scripts/privacyidea_entrypoint.sh b/rootfs/usr/local/bin/privacyidea_entrypoint.sh old mode 100644 new mode 100755 similarity index 100% rename from scripts/privacyidea_entrypoint.sh rename to rootfs/usr/local/bin/privacyidea_entrypoint.sh diff --git a/scripts/start_privacyidea.sh b/rootfs/usr/local/bin/start_privacyidea.sh old mode 100644 new mode 100755 similarity index 88% rename from scripts/start_privacyidea.sh rename to rootfs/usr/local/bin/start_privacyidea.sh index 9410eb8..8d0624c --- a/scripts/start_privacyidea.sh +++ b/rootfs/usr/local/bin/start_privacyidea.sh @@ -21,13 +21,21 @@ function generate_pi_config { [ -z "$DB_USER" ] && echo "DB_USER should be defined" && return 1 [ -z "$DB_PASSWORD" ] && echo "DB_PASSWORD should be defined" && return 1 [ -z "$DB_NAME" ] && echo "DB_NAME should be defined" && return 1 - export SQLALCHEMY_DATABASE_URI=pymysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME} + if [ -z "$DB_PORT" ]; then + echo DB_PORT is not defined using default port + export DB_PORT=3306 + fi + export SQLALCHEMY_DATABASE_URI=pymysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} elif { [ "${DB_VENDOR}" = "postgresql" ]; } then [ -z "$DB_HOST" ] && echo "DB_HOST should be defined" && return 1 [ -z "$DB_USER" ] && echo "DB_USER should be defined" && return 1 [ -z "$DB_PASSWORD" ] && echo "DB_PASSWORD should be defined" && return 1 [ -z "$DB_NAME" ] && echo "DB_NAME should be defined" && return 1 - export SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://${DB_USER}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME} + if [ -z "$DB_PORT" ]; then + echo DB_PORT is not defined using default port + export DB_PORT=5432 + fi + export SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} else echo "DB_VENDOR enviroment varaible is not set. Using default SQLite..." echo "" @@ -40,7 +48,7 @@ function generate_pi_config { then echo "SQLALCHEMY_DATABASE_URI is undefieded" else - envsubst < /etc/privacyidea/pi-config.template > /etc/privacyidea/pi.cfg + envsubst < /opt/templates/pi-config.template > /etc/privacyidea/pi.cfg fi fi } @@ -77,6 +85,8 @@ function prestart_privacyidea { fi if [ "${PI_SKIP_BOOTSTRAP}" = false ]; then + ls -l /data + ls -l /data/privacyidea if [ ! -f /etc/privacyidea/encfile ]; then pi-manage create_enckey fi diff --git a/scripts/configure_nginx.sh b/scripts/configure_nginx.sh deleted file mode 100644 index 72323d8..0000000 --- a/scripts/configure_nginx.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -e - -function main { - - if [ -f /app/nginx.conf ]; then - cp /app/nginx.conf /etc/nginx/nginx.conf - else - envsubst < /etc/nginx/templates/nginx.conf.template > /etc/nginx/nginx.conf - envsubst < /etc/nginx/templates/pi.conf.template > /etc/nginx/conf.d/pi.conf - fi -} - -main