diff --git a/.github/workflows/release-latest.yml b/.github/workflows/release-latest.yml index 0c7e94b2..4f7bf0f3 100644 --- a/.github/workflows/release-latest.yml +++ b/.github/workflows/release-latest.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - target: [misp-core, misp-modules, misp-core-slim, misp-modules-slim, misp-guard] + target: [misp-core, misp-modules, misp-core-slim, misp-modules-slim, misp-guard, misp-nginx] permissions: contents: read diff --git a/.github/workflows/test-build-latest.yml b/.github/workflows/test-build-latest.yml index e74c0c4c..d5f122f9 100644 --- a/.github/workflows/test-build-latest.yml +++ b/.github/workflows/test-build-latest.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - target: [misp-core, misp-modules, misp-core-slim, misp-modules-slim, misp-guard] + target: [misp-core, misp-modules, misp-core-slim, misp-modules-slim, misp-guard, misp-nginx] steps: - name: Checkout repository diff --git a/.gitignore b/.gitignore index c26e3d74..b719cd72 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ env.hcl rootca.crt cert.pem +key.pem docker-compose.override.yml .DS_Store .AppleDouble diff --git a/Makefile b/Makefile index e5eb499c..c274ef45 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := help -.PHONY: help build build-core build-modules build-guard build-slim bake up down logs shellcheck clean prune +.PHONY: help build build-core build-nginx build-modules build-guard build-slim bake up down logs shellcheck clean prune help: @echo "Usage: make " @@ -8,6 +8,7 @@ help: @echo "Build targets:" @echo " build Build all images via docker compose" @echo " build-core Build misp-core only" + @echo " build-nginx Build misp-nginx only" @echo " build-modules Build misp-modules only" @echo " build-guard Build misp-guard only" @echo " build-slim Build all slim variant images" @@ -29,6 +30,9 @@ build: build-core: docker compose build misp-core +build-nginx: + docker compose build misp-nginx + build-modules: docker compose build misp-modules @@ -51,7 +55,7 @@ logs: docker compose logs -f shellcheck: - shellcheck core/files/*.sh core/files/kubernetes/*.sh guard/files/*.sh + shellcheck core/files/*.sh core/files/kubernetes/*.sh nginx/files/*.sh guard/files/*.sh clean: docker compose down -v --remove-orphans diff --git a/core/Dockerfile b/core/Dockerfile index d5c8e1b4..5c5279ec 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -1,175 +1,189 @@ ARG DOCKER_HUB_PROXY="" - +# --- php-base FROM "${DOCKER_HUB_PROXY}python:3.14-slim-trixie" AS php-base -ENV DEBIAN_FRONTEND=noninteractive + ARG CORE_TAG ARG CORE_COMMIT +ARG CORE_FLAVOR ARG PHP_API_VERSION RUN : "${PHP_API_VERSION:?Build argument PHP_API_VERSION is required and cannot be empty.}" ARG PHP_PACKAGE_VERSION RUN : "${PHP_PACKAGE_VERSION:?Build argument PHP_PACKAGE_VERSION is required and cannot be empty.}" -ARG CORE_FLAVOR + +# environment +ENV DEBIAN_FRONTEND=noninteractive # Uncomment when building in corporate environments # COPY ./cert.pem /usr/local/share/ca-certificates/rootca.pem # COPY ./cert.pem /usr/lib/ssl/cert.pem RUN <<-EOF - apt-get update - if [ "${CORE_FLAVOR}" = "slim" ]; then - apt-get install -y --no-install-recommends \ - lsb-release \ - ca-certificates \ - tini \ - gettext \ - procps \ - sudo \ - nginx \ - openssl \ - gpg \ - gpg-agent \ - mariadb-client \ - rsync \ - rsyslog \ - php${PHP_PACKAGE_VERSION} \ - php${PHP_PACKAGE_VERSION}-curl \ - php${PHP_PACKAGE_VERSION}-xml \ - php${PHP_PACKAGE_VERSION}-mbstring \ - php${PHP_PACKAGE_VERSION}-mysql \ - php${PHP_PACKAGE_VERSION}-redis \ - php${PHP_PACKAGE_VERSION}-gd \ - php${PHP_PACKAGE_VERSION}-fpm \ - php${PHP_PACKAGE_VERSION}-zip \ - unzip \ - zip \ - curl \ - uuid-runtime \ - jq - else - apt-get install -y --no-install-recommends \ - lsb-release \ - ca-certificates \ - tini \ - gettext \ - procps \ - sudo \ - nginx \ - openssl \ - gpg \ - gpg-agent \ - mariadb-client \ - rsync \ - rsyslog \ - php${PHP_PACKAGE_VERSION} \ - php${PHP_PACKAGE_VERSION}-apcu \ - php${PHP_PACKAGE_VERSION}-curl \ - php${PHP_PACKAGE_VERSION}-xml \ - php${PHP_PACKAGE_VERSION}-intl \ - php${PHP_PACKAGE_VERSION}-bcmath \ - php${PHP_PACKAGE_VERSION}-mbstring \ - php${PHP_PACKAGE_VERSION}-mysql \ - php${PHP_PACKAGE_VERSION}-redis \ - php${PHP_PACKAGE_VERSION}-gd \ - php${PHP_PACKAGE_VERSION}-fpm \ - php${PHP_PACKAGE_VERSION}-zip \ - php${PHP_PACKAGE_VERSION}-ldap \ - libmagic1 \ - libldap-common \ - librdkafka1 \ - libbrotli1 \ - libsimdjson25 \ - libzstd1 \ - ssdeep \ - stunnel4 \ - libfuzzy2 \ - unzip \ - zip \ - curl \ - uuid-runtime \ - jq - fi + apt-get update + + if [ "${CORE_FLAVOR}" = "slim" ]; then + apt-get install -y --no-install-recommends \ + lsb-release \ + ca-certificates \ + tini \ + gettext \ + procps \ + sudo \ + openssl \ + gpg \ + gpg-agent \ + mariadb-client \ + rsync \ + rsyslog \ + php${PHP_PACKAGE_VERSION} \ + php${PHP_PACKAGE_VERSION}-curl \ + php${PHP_PACKAGE_VERSION}-xml \ + php${PHP_PACKAGE_VERSION}-mbstring \ + php${PHP_PACKAGE_VERSION}-mysql \ + php${PHP_PACKAGE_VERSION}-redis \ + php${PHP_PACKAGE_VERSION}-gd \ + php${PHP_PACKAGE_VERSION}-fpm \ + php${PHP_PACKAGE_VERSION}-zip \ + unzip \ + zip \ + curl \ + uuid-runtime \ + jq + else + apt-get install -y --no-install-recommends \ + lsb-release \ + ca-certificates \ + tini \ + gettext \ + procps \ + sudo \ + openssl \ + gpg \ + gpg-agent \ + mariadb-client \ + rsync \ + rsyslog \ + php${PHP_PACKAGE_VERSION} \ + php${PHP_PACKAGE_VERSION}-apcu \ + php${PHP_PACKAGE_VERSION}-curl \ + php${PHP_PACKAGE_VERSION}-xml \ + php${PHP_PACKAGE_VERSION}-intl \ + php${PHP_PACKAGE_VERSION}-bcmath \ + php${PHP_PACKAGE_VERSION}-mbstring \ + php${PHP_PACKAGE_VERSION}-mysql \ + php${PHP_PACKAGE_VERSION}-redis \ + php${PHP_PACKAGE_VERSION}-gd \ + php${PHP_PACKAGE_VERSION}-fpm \ + php${PHP_PACKAGE_VERSION}-zip \ + php${PHP_PACKAGE_VERSION}-ldap \ + libmagic1 \ + libldap-common \ + librdkafka1 \ + libbrotli1 \ + libsimdjson25 \ + libzstd1 \ + ssdeep \ + stunnel4 \ + libfuzzy2 \ + unzip \ + zip \ + curl \ + uuid-runtime \ + jq + fi EOF +# --- composer-build FROM php-base AS composer-build -ENV COMPOSER_ALLOW_SUPERUSER=1 +# environment WORKDIR /tmp -RUN curl -o /tmp/composer.json https://raw.githubusercontent.com/MISP/MISP/${CORE_COMMIT:-${CORE_TAG}}/app/composer.json -RUN sed -i '/cake-resque/d' /tmp/composer.json -RUN sed -i 's/authentication",/authentication"/' /tmp/composer.json +ENV COMPOSER_ALLOW_SUPERUSER=1 +# prepare composer.json +RUN curl -o /tmp/composer.json https://raw.githubusercontent.com/MISP/MISP/${CORE_COMMIT:-${CORE_TAG}}/app/composer.json && \ + sed -i '/cake-resque/d' /tmp/composer.json && \ + sed -i 's/authentication",/authentication"/' /tmp/composer.json + +# inject composer binary COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -RUN composer config --no-interaction allow-plugins.composer/installers true -RUN composer install + +# install dependencies RUN <<-EOF - if [ "${CORE_FLAVOR}" = "slim" ]; then - : - else - composer require --with-all-dependencies --no-interaction \ - elasticsearch/elasticsearch:^8.7.0 \ - jakub-onderka/openid-connect-php:^1.0.0 \ - certmichelin/openid-connect-php:1.3.0 \ - aws/aws-sdk-php - fi + composer config --no-interaction allow-plugins.composer/installers true + composer install + + if [ "${CORE_FLAVOR}" = "slim" ]; then + : + else + composer require --with-all-dependencies --no-interaction \ + elasticsearch/elasticsearch:^8.7.0 \ + jakub-onderka/openid-connect-php:^1.0.0 \ + certmichelin/openid-connect-php:1.3.0 \ + aws/aws-sdk-php + fi EOF - +# --- php-build FROM php-base AS php-build + +# environment ENV TZ=Etc/UTC RUN <<-EOF - if [ "${CORE_FLAVOR}" = "slim" ]; then - apt-get install -y --no-install-recommends \ - gcc \ - g++ \ - git \ - make \ - php${PHP_PACKAGE_VERSION}-dev \ - php-pear - else - apt-get install -y --no-install-recommends \ - gcc \ - g++ \ - git \ - make \ - php${PHP_PACKAGE_VERSION}-dev \ - php-pear \ - libbrotli-dev \ - libfuzzy-dev \ - librdkafka-dev \ - libsimdjson-dev \ - libzstd-dev - fi - apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* + if [ "${CORE_FLAVOR}" = "slim" ]; then + apt-get install -y --no-install-recommends \ + gcc \ + g++ \ + git \ + make \ + php${PHP_PACKAGE_VERSION}-dev \ + php-pear + else + apt-get install -y --no-install-recommends \ + gcc \ + g++ \ + git \ + make \ + php${PHP_PACKAGE_VERSION}-dev \ + php-pear \ + libbrotli-dev \ + libfuzzy-dev \ + librdkafka-dev \ + libsimdjson-dev \ + libzstd-dev + fi + apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* EOF -RUN update-alternatives --set php /usr/bin/php${PHP_PACKAGE_VERSION} -RUN update-alternatives --set php-config /usr/bin/php-config${PHP_PACKAGE_VERSION} -RUN update-alternatives --set phpize /usr/bin/phpize${PHP_PACKAGE_VERSION} +# configure alternatives for php +RUN update-alternatives --set php /usr/bin/php${PHP_PACKAGE_VERSION} && \ + update-alternatives --set php-config /usr/bin/php-config${PHP_PACKAGE_VERSION} && \ + update-alternatives --set phpize /usr/bin/phpize${PHP_PACKAGE_VERSION} RUN <<-EOF - pecl channel-update pecl.php.net - if [ "${CORE_FLAVOR}" = "slim" ]; then - tar -czf /pecl_libs.tar.gz -T /dev/null - else - cp "/usr/lib/$(gcc -dumpmachine)"/libfuzzy.* /usr/lib - pecl install rdkafka - pecl install simdjson - pecl install zstd - pecl install brotli - git clone --recursive --depth=1 https://github.com/JakubOnderka/pecl-text-ssdeep.git /tmp/pecl-text-ssdeep - cd /tmp/pecl-text-ssdeep && phpize && ./configure && make && make install - tar -czf /pecl_libs.tar.gz \ - /usr/lib/php/${PHP_API_VERSION}/ssdeep.so \ - /usr/lib/php/${PHP_API_VERSION}/rdkafka.so \ - /usr/lib/php/${PHP_API_VERSION}/brotli.so \ - /usr/lib/php/${PHP_API_VERSION}/simdjson.so \ - /usr/lib/php/${PHP_API_VERSION}/zstd.so - fi + pecl channel-update pecl.php.net + + if [ "${CORE_FLAVOR}" = "slim" ]; then + tar -czf /pecl_libs.tar.gz -T /dev/null + else + cp "/usr/lib/$(gcc -dumpmachine)"/libfuzzy.* /usr/lib + pecl install rdkafka + pecl install simdjson + pecl install zstd + pecl install brotli + git clone --recursive --depth=1 https://github.com/JakubOnderka/pecl-text-ssdeep.git /tmp/pecl-text-ssdeep + cd /tmp/pecl-text-ssdeep && phpize && ./configure && make && make install + tar -czf /pecl_libs.tar.gz \ + /usr/lib/php/${PHP_API_VERSION}/ssdeep.so \ + /usr/lib/php/${PHP_API_VERSION}/rdkafka.so \ + /usr/lib/php/${PHP_API_VERSION}/brotli.so \ + /usr/lib/php/${PHP_API_VERSION}/simdjson.so \ + /usr/lib/php/${PHP_API_VERSION}/zstd.so + fi EOF - +# --- python-build FROM php-base AS python-build RUN apt-get install -y --no-install-recommends \ @@ -182,12 +196,12 @@ RUN apt-get install -y --no-install-recommends \ # Download MISP using git in the /var/www/ directory. Remove unnecessary items. RUN <<-EOF - if [ ! -z "${CORE_COMMIT}" ]; then - git clone https://github.com/MISP/MISP.git /var/www/MISP && cd /var/www/MISP && git checkout "${CORE_COMMIT}" - else - git clone --branch "${CORE_TAG}" --depth 1 https://github.com/MISP/MISP.git /var/www/MISP - fi - cd /var/www/MISP || exit; git submodule update --init --recursive . + if [ ! -z "${CORE_COMMIT}" ]; then + git clone https://github.com/MISP/MISP.git /var/www/MISP && cd /var/www/MISP && git checkout "${CORE_COMMIT}" + else + git clone --branch "${CORE_TAG}" --depth 1 https://github.com/MISP/MISP.git /var/www/MISP + fi + cd /var/www/MISP || exit; git submodule update --init --recursive . EOF ARG PYPI_SETUPTOOLS_VERSION @@ -205,54 +219,54 @@ ARG PYPI_PYZMQ_VERSION ARG PYPI_REDIS_VERSION ARG PYPI_TAXII2_CLIENT_VERSION RUN <<-EOF - mkdir /wheels - - # Add additional dependencies (container specific) - # The "set" line contains the list of modules we want to ensure are present. - # PYPI_MODULE_NAME_VERSION env vars can be set to specify the version desired, - # e.g. PYPI_SURICATA_VERSION="==2.0" to specify exactly version 2.0 for the suricata package - # - # 1. Check for presence of each module in requirements.txt - # 2. If missing, add it (with optional version from env (defaults to empty string)) - # 3. If present, replace with our specified version if it exists, otherwise leave - # the upstream version alone. - set -- "jsonschema" "lief" "misp-stix" "plyara" "pydeep2" "pymisp" "python-magic" "pyzmq" "redis" "taxii2-client" "setuptools" "supervisor" - for mod in "$@"; do - mod_version_var=$(echo "PYPI_${mod}_VERSION" | tr '[:lower:]' '[:upper:]' | tr '-' '_') - mod_version=$(eval "echo \"\$$mod_version_var\"") - grep -q ${mod} /var/www/MISP/requirements.txt - exists=$? - if [ "${exists}" -eq "1" ]; then - echo "Adding missing module ${mod} with version '${mod_version}'" - echo ${mod}${mod_version} >> /var/www/MISP/requirements.txt + mkdir /wheels + + # Add additional dependencies (container specific) + # The "set" line contains the list of modules we want to ensure are present. + # PYPI_MODULE_NAME_VERSION env vars can be set to specify the version desired, + # e.g. PYPI_SURICATA_VERSION="==2.0" to specify exactly version 2.0 for the suricata package + # + # 1. Check for presence of each module in requirements.txt + # 2. If missing, add it (with optional version from env (defaults to empty string)) + # 3. If present, replace with our specified version if it exists, otherwise leave + # the upstream version alone. + set -- "jsonschema" "lief" "misp-stix" "plyara" "pydeep2" "pymisp" "python-magic" "pyzmq" "redis" "taxii2-client" "setuptools" "supervisor" + for mod in "$@"; do + mod_version_var=$(echo "PYPI_${mod}_VERSION" | tr '[:lower:]' '[:upper:]' | tr '-' '_') + mod_version=$(eval "echo \"\$$mod_version_var\"") + grep -q ${mod} /var/www/MISP/requirements.txt + exists=$? + if [ "${exists}" -eq "1" ]; then + echo "Adding missing module ${mod} with version '${mod_version}'" + echo ${mod}${mod_version} >> /var/www/MISP/requirements.txt + else + if [ "$(echo ${mod_version} | wc -m)" -gt 1 ]; then + echo "Overwriting existing module ${mod}, version '${mod_version}'" + sed -i "/${mod}/s/.*/${mod}${mod_version}/" /var/www/MISP/requirements.txt else - if [ "$(echo ${mod_version} | wc -m)" -gt 1 ]; then - echo "Overwriting existing module ${mod}, version '${mod_version}'" - sed -i "/${mod}/s/.*/${mod}${mod_version}/" /var/www/MISP/requirements.txt - else - echo "Skipping overwriting ${mod} due to missing version variable" - fi + echo "Skipping overwriting ${mod} due to missing version variable" fi - done; - - pip wheel --no-cache-dir -w /wheels/ -r /var/www/MISP/requirements.txt - - # Remove files we do not care for - find /var/www/MISP/INSTALL/* ! -name 'MYSQL.sql' -type f -exec rm {} + - find /var/www/MISP/INSTALL/* ! -name 'MYSQL.sql' -type l -exec rm {} + - # Remove most files in .git - we do not use git functionality in docker - find /var/www/MISP/.git/* ! -name HEAD -exec rm -rf {} + - # Remove libraries submodules - rm -r /var/www/MISP/PyMISP - rm -r /var/www/MISP/app/files/scripts/cti-python-stix2 - rm -r /var/www/MISP/app/files/scripts/misp-stix - rm -r /var/www/MISP/app/files/scripts/mixbox - rm -r /var/www/MISP/app/files/scripts/python-cybox - rm -r /var/www/MISP/app/files/scripts/python-maec - rm -r /var/www/MISP/app/files/scripts/python-stix + fi + done; + + pip wheel --no-cache-dir -w /wheels/ -r /var/www/MISP/requirements.txt + + # Remove files we do not care for + find /var/www/MISP/INSTALL/* ! -name 'MYSQL.sql' -type f -exec rm {} + + find /var/www/MISP/INSTALL/* ! -name 'MYSQL.sql' -type l -exec rm {} + + # Remove most files in .git - we do not use git functionality in docker + find /var/www/MISP/.git/* ! -name HEAD -exec rm -rf {} + + # Remove libraries submodules + rm -r /var/www/MISP/PyMISP + rm -r /var/www/MISP/app/files/scripts/cti-python-stix2 + rm -r /var/www/MISP/app/files/scripts/misp-stix + rm -r /var/www/MISP/app/files/scripts/mixbox + rm -r /var/www/MISP/app/files/scripts/python-cybox + rm -r /var/www/MISP/app/files/scripts/python-maec + rm -r /var/www/MISP/app/files/scripts/python-stix EOF - +# --- final stage FROM php-base ENV PATH="/var/www/MISP/app/Console:${PATH}" @@ -278,23 +292,20 @@ COPY --from=composer-build --chown=www-data:www-data --chmod=0550 /tmp/Plugin /v # Gather these in one layer, only act on actual directories under /etc/php/ RUN <<-EOF - if [ "${CORE_FLAVOR}" = "slim" ]; then - set -- - else - set -- "ssdeep" "rdkafka" "brotli" "simdjson" "zstd" - fi - for mod in "$@"; do - for dir in /etc/php/*/; do - echo "extension=${mod}.so" > "${dir}mods-available/${mod}.ini" - done; - phpenmod "${mod}" + if [ "${CORE_FLAVOR}" = "slim" ]; then + set -- + else + set -- "ssdeep" "rdkafka" "brotli" "simdjson" "zstd" + fi + for mod in "$@"; do + for dir in /etc/php/*/; do + echo "extension=${mod}.so" > "${dir}mods-available/${mod}.ini" done; - phpenmod redis + phpenmod "${mod}" + done; + phpenmod redis EOF -# nginx -RUN rm /etc/nginx/sites-enabled/*; mkdir -p /run/php /etc/nginx/certs - # Make a copy of the file and configuration stores, so we can sync from it # The spirit of the upstream dockerization is to make: @@ -303,20 +314,20 @@ RUN rm /etc/nginx/sites-enabled/*; mkdir -p /run/php /etc/nginx/certs # 3) Directories writable, because sometimes MISP add new files RUN <<-EOF - cp -R /var/www/MISP/app/files /var/www/MISP/app/files.dist - echo "${CORE_COMMIT:-${CORE_TAG}}" > /var/www/MISP/app/files/VERSION - cp -R /var/www/MISP/app/Config /var/www/MISP/app/Config.dist - find /var/www/MISP \( ! -user www-data -or ! -group www-data \) -exec chown www-data:www-data '{}' +; - find /var/www/MISP -not -perm 550 -type f -exec chmod 0550 '{}' +; - find /var/www/MISP -not -perm 770 -type d -exec chmod 0770 '{}' +; - # Diagnostics wants this file to be present and writable even if we do not use git in docker land - touch /var/www/MISP/.git/ORIG_HEAD && chmod 0600 /var/www/MISP/.git/ORIG_HEAD && chown www-data:www-data /var/www/MISP/.git/ORIG_HEAD + cp -R /var/www/MISP/app/files /var/www/MISP/app/files.dist + cp -R /var/www/MISP/app/Config /var/www/MISP/app/Config.dist + echo "${CORE_COMMIT:-${CORE_TAG}}" > /var/www/MISP/app/files/VERSION + find /var/www/MISP \( ! -user www-data -or ! -group www-data \) -exec chown www-data:www-data '{}' +; + find /var/www/MISP -not -perm 550 -type f -exec chmod 0550 '{}' +; + find /var/www/MISP -not -perm 770 -type d -exec chmod 0770 '{}' +; + # Diagnostics wants this file to be present and writable even if we do not use git in docker land + touch /var/www/MISP/.git/ORIG_HEAD && chmod 0600 /var/www/MISP/.git/ORIG_HEAD && chown www-data:www-data /var/www/MISP/.git/ORIG_HEAD EOF # Copy all our image specific files to appropriate locations COPY files/ / -# Template PHP version into nginx config -RUN sed -i "s/@PHP_PACKAGE_VERSION@/${PHP_PACKAGE_VERSION}/g" /etc/nginx/includes/misp + +# define entrypoint ENTRYPOINT [ "/entrypoint.sh" ] # Change Workdirectory diff --git a/core/files/configure_misp.sh b/core/files/configure_misp.sh index 627df9c2..1f64748e 100755 --- a/core/files/configure_misp.sh +++ b/core/files/configure_misp.sh @@ -11,7 +11,7 @@ export SETTING_CONTACT="${MISP_CONTACT}" export SETTING_EMAIL="${MISP_EMAIL}" init_minimum_config() { - # Temporarily disable DB to apply config file settings, reenable after if needed + # Temporarily disable DB to apply config file settings, reenable after if needed sudo -u www-data /var/www/MISP/app/Console/cake Admin setSetting -q "MISP.system_setting_db" false init_settings "minimum_config" } @@ -33,7 +33,7 @@ configure_gnupg() { fi export GPG_DIR=/var/www/MISP/.gnupg - GPG_ASC=/var/www/MISP/app/webroot/gpg.asc + GPG_ASC=$GPG_DIR/gpg.asc GPG_TMP=/tmp/gpg.tmp if [ ! -f "${GPG_DIR}/trustdb.gpg" ]; then @@ -64,6 +64,9 @@ GPGEOF if [ ! -f ${GPG_ASC} ]; then echo "... exporting GPG key" sudo -u www-data gpg --homedir ${GPG_DIR} --export --armor ${MISP_EMAIL} > ${GPG_ASC} + + # misp looks for hardcoded webroot path to show link in footer template + sudo -u www-data ln -s ${GPG_ASC} /var/www/MISP/app/webroot/gpg.asc else echo "... found exported key ${GPG_ASC}" fi @@ -235,7 +238,7 @@ set_up_ldap() { # LDAPAUTH_LDAPSEARCHFILTER may be empty check_env_vars LDAPAUTH_LDAPSERVER LDAPAUTH_LDAPDN LDAPAUTH_LDAPREADERUSER LDAPAUTH_LDAPREADERPASSWORD LDAPAUTH_LDAPSEARCHATTRIBUTE LDAPAUTH_LDAPDEFAULTROLEID LDAPAUTH_LDAPDEFAULTORGID LDAPAUTH_LDAPEMAILFIELD LDAPAUTH_LDAPNETWORKTIMEOUT LDAPAUTH_LDAPPROTOCOL LDAPAUTH_LDAPALLOWREFERRALS LDAPAUTH_STARTTLS LDAPAUTH_MIXEDAUTH LDAPAUTH_UPDATEUSER LDAPAUTH_DEBUG LDAPAUTH_LDAPTLSREQUIRECERT LDAPAUTH_LDAPTLSCUSTOMCACERT LDAPAUTH_LDAPTLSCRLCHECK LDAPAUTH_LDAPTLSPROTOCOLMIN - # This variable can be false or a string, but the value in the below json object handed to modify_config.php is unquoted, + # This variable can be false or a string, but the value in the below json object handed to modify_config.php is unquoted, # so we quote the value if it's not true or false we to end up with a valid json object. if [[ ! "$LDAPAUTH_LDAPTLSCUSTOMCACERT" =~ ^(0|false|1|true)$ ]]; then LDAPAUTH_LDAPTLSCUSTOMCACERT="\"$LDAPAUTH_LDAPTLSCUSTOMCACERT\"" @@ -287,7 +290,7 @@ set_up_aad() { # Check required variables check_env_vars AAD_CLIENT_ID AAD_TENANT_ID AAD_CLIENT_SECRET AAD_REDIRECT_URI AAD_PROVIDER AAD_PROVIDER_USER AAD_MISP_ORGADMIN AAD_MISP_SITEADMIN AAD_CHECK_GROUPS - # Note: Not necessary to edit bootstrap.php to load AadAuth Cake plugin because + # Note: Not necessary to edit bootstrap.php to load AadAuth Cake plugin because # existing loadAll() call in bootstrap.php already loads all available Cake plugins # Set auth mechanism to AAD in config.php file @@ -452,11 +455,11 @@ set_up_proxy() { apply_updates() { # Disable 'ZeroMQ_enable' to get better logs when applying updates -# sudo -u www-data /var/www/MISP/app/Console/cake Admin setSetting -q "Plugin.ZeroMQ_enable" false + # sudo -u www-data /var/www/MISP/app/Console/cake Admin setSetting -q "Plugin.ZeroMQ_enable" false # Run updates (strip colors since output might end up in a log) sudo -u www-data /var/www/MISP/app/Console/cake Admin runUpdates | stdbuf -oL sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" # Re-enable 'ZeroMQ_enable' -# sudo -u www-data /var/www/MISP/app/Console/cake Admin setSetting -q "Plugin.ZeroMQ_enable" true + # sudo -u www-data /var/www/MISP/app/Console/cake Admin setSetting -q "Plugin.ZeroMQ_enable" true } init_user() { @@ -768,4 +771,5 @@ echo "MISP | Create default Scheduled Tasks ..." && create_default_scheduled_tas echo "MISP | Configure misp-guard CA certificate ..." && configure_misp_guard_ca echo "MISP | Mark instance live" && print_version + sudo -u www-data /var/www/MISP/app/Console/cake Admin live 1 diff --git a/core/files/entrypoint.sh b/core/files/entrypoint.sh index 96071127..06b2c9d9 100755 --- a/core/files/entrypoint.sh +++ b/core/files/entrypoint.sh @@ -73,6 +73,7 @@ export FASTCGI_READ_TIMEOUT=${FASTCGI_READ_TIMEOUT:-300s} export FASTCGI_SEND_TIMEOUT=${FASTCGI_SEND_TIMEOUT:-300s} export FASTCGI_CONNECT_TIMEOUT=${FASTCGI_CONNECT_TIMEOUT:-300s} +export PHP_LISTEN_FPM=true export PHP_FCGI_CHILDREN=${PHP_FCGI_CHILDREN:-5} export PHP_FCGI_START_SERVERS=${PHP_FCGI_START_SERVERS:-2} export PHP_FCGI_SPARE_SERVERS=${PHP_FCGI_SPARE_SERVERS:-1} diff --git a/core/files/entrypoint_fpm.sh b/core/files/entrypoint_fpm.sh index e3806288..5abc4414 100755 --- a/core/files/entrypoint_fpm.sh +++ b/core/files/entrypoint_fpm.sh @@ -8,6 +8,40 @@ term_proc() { trap term_proc SIGTERM +init_mysql() { + # Test when MySQL is ready.... + # wait for Database come ready + isDBup () { + echo "SHOW STATUS" | $MYSQL_CMD 1>/dev/null + echo $? + } + + isDBinitDone () { + # Table attributes has existed since at least v2.1 + echo "DESCRIBE attributes" | $MYSQL_CMD 1>/dev/null + echo $? + } + + RETRY=100 + until [ $(isDBup) -eq 0 ] || [ $RETRY -le 0 ] ; do + echo "... waiting for database to come up" + sleep 5 + RETRY=$(( RETRY - 1)) + done + if [ $RETRY -le 0 ]; then + >&2 echo "... error: Could not connect to Database on $MYSQL_HOST:$MYSQL_PORT" + exit 1 + fi + + if [ $(isDBinitDone) -eq 0 ]; then + echo "... database has already been initialized" + export DB_ALREADY_INITIALISED=true + else + echo "... database has not been initialized, importing MySQL scheme..." + $MYSQL_CMD < /var/www/MISP/INSTALL/MYSQL.sql + fi +} + redirect_logs() { tail -F /var/www/MISP/app/tmp/logs/error.log > /dev/stdout 2>/dev/null & } @@ -49,7 +83,7 @@ change_php_vars() { echo "Configure PHP | Setting 'date.timezone = ${TZ}'" sed -i "s|^;date.timezone =.*|date.timezone = ${TZ}|" "$FILE" done - + for FILE in /etc/php/*/cli/php.ini do [[ -e $FILE ]] || break @@ -73,9 +107,9 @@ change_php_vars() { fi echo "Configure PHP | Setting 'pm.max_requests = ${PHP_FCGI_MAX_REQUESTS}'" sed -i -E "s/;?pm.max_requests = .*/pm.max_requests = ${PHP_FCGI_MAX_REQUESTS}/" "$FILE" - if [[ "$FASTCGI_STATUS_LISTEN" != "" ]]; then - echo "Configure PHP | Setting 'pm.status_path = /status'" - sed -i -E "s/;?pm.status_path = .*/pm.status_path = \/status/" "$FILE" + if [[ "$FASTCGI_LISTEN_STATUS" != "" ]]; then + echo "Configure PHP | Setting 'pm.status_path = /fpm-status'" + sed -i -E "s/;?pm.status_path = .*/pm.status_path = \/fpm-status/" "$FILE" if [[ -n "$PHP_LISTEN_FPM" ]]; then echo "Configure PHP | Setting 'pm.status_listen' to [::]:9003" sed -i -E "s/;?pm.status_listen = .*/pm.status_listen = [::]:9003/" "$FILE" @@ -107,9 +141,30 @@ if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then return fi -echo "Configure PHP | Change PHP values ..." && change_php_vars +echo "INIT | Loading environment and functions" + +# Initialize MySQL +echo "INIT | Initialize MySQL ..." +init_mysql + +echo "INIT | Change PHP values ..." +change_php_vars + +# Initialize MISP +echo "INIT | Initialize MISP installation ..." +/init_misp.sh + +# Run configure MISP script +echo "INIT | Configure MISP installation ..." +/configure_misp.sh + +# Run customization scripts +if [[ -x /custom/files/customize_misp.sh ]]; then + echo "INIT | Customize MISP installation ..." + /custom/files/customize_misp.sh +fi -echo "Configure PHP | Starting PHP FPM" +echo "MISP | Starting PHP FPM" /usr/local/sbin/php-fpm -R -F & master_pid=$! # Wait for it diff --git a/core/files/etc/nginx/includes/misp b/core/files/etc/nginx/includes/misp deleted file mode 100644 index 36672b2b..00000000 --- a/core/files/etc/nginx/includes/misp +++ /dev/null @@ -1,36 +0,0 @@ -# define the root dir -root /var/www/MISP/app/webroot; -index index.php; - -# incrase the maximum body size -client_max_body_size 50M; - -# added headers for hardening browser security -add_header X-Content-Type-Options "nosniff" always; -add_header X-Download-Options "noopen" always; -add_header X-Frame-Options "SAMEORIGIN" always; -add_header X-Permitted-Cross-Domain-Policies "none" always; -add_header X-Robots-Tag "none" always; - -# remove X-Powered-By and nginx version, which is an information leak -fastcgi_hide_header X-Powered-By; -server_tokens off; - -location ^~ /css/ { - try_files $uri =404; -} - -location / { - try_files $uri $uri/ /index.php$is_args$query_string; -} - -location ~ ^/[^/]+\.php(/|$) { - include snippets/fastcgi-php.conf; - fastcgi_pass unix:/var/run/php/php@PHP_PACKAGE_VERSION@-fpm.sock; - fastcgi_read_timeout 300s; - fastcgi_send_timeout 300s; - fastcgi_connect_timeout 300s; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - set $path_info $fastcgi_path_info; - fastcgi_param PATH_INFO $path_info; -} diff --git a/core/files/etc/nginx/sites-available/misp443 b/core/files/etc/nginx/sites-available/misp443 deleted file mode 100644 index 6cdf2a66..00000000 --- a/core/files/etc/nginx/sites-available/misp443 +++ /dev/null @@ -1,30 +0,0 @@ -server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - - # disable access logs - access_log off; - log_not_found off; - error_log /dev/stderr error; - - # ssl options - ssl_certificate /etc/nginx/certs/cert.pem; - ssl_certificate_key /etc/nginx/certs/key.pem; - ssl_session_timeout 1d; - ssl_session_cache shared:MozSSL:10m; # about 40000 sessions - ssl_session_tickets off; - - # ssl intermediate configuration - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ecdh_curve X25519:prime256v1:secp384r1; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; - ssl_prefer_server_ciphers off; - - # ssl enable HSTS - add_header Strict-Transport-Security "max-age=15768000; includeSubdomains"; - add_header X-Frame-Options SAMEORIGIN; - - # include misp - include includes/misp; -} diff --git a/core/files/etc/nginx/sites-available/misp80 b/core/files/etc/nginx/sites-available/misp80 deleted file mode 100644 index 14042525..00000000 --- a/core/files/etc/nginx/sites-available/misp80 +++ /dev/null @@ -1,12 +0,0 @@ -server { - listen 80 default_server; - listen [::]:80 default_server; - - # disable access logs - access_log off; - log_not_found off; - error_log /dev/stderr error; - - # include includes/misp; - # return 301 https://$host$request_uri; -} diff --git a/core/files/etc/nginx/sites-available/php-fpm-status b/core/files/etc/nginx/sites-available/php-fpm-status deleted file mode 100644 index a29e1d6f..00000000 --- a/core/files/etc/nginx/sites-available/php-fpm-status +++ /dev/null @@ -1,8 +0,0 @@ -server { - listen 8999; - location ~ ^/status$ { - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - include fastcgi_params; - fastcgi_pass unix:/run/php/php-fpm-status.sock; - } -} diff --git a/core/files/etc/supervisor/conf.d/10-supervisor.conf b/core/files/etc/supervisor/conf.d/10-supervisor.conf index 26d15fe0..56400489 100644 --- a/core/files/etc/supervisor/conf.d/10-supervisor.conf +++ b/core/files/etc/supervisor/conf.d/10-supervisor.conf @@ -11,15 +11,6 @@ port=127.0.0.1:9001 username=%(ENV_SUPERVISOR_USERNAME)s password=%(ENV_SUPERVISOR_PASSWORD)s -[program:nginx] -command=/entrypoint_nginx.sh -autorestart=true -redirect_stderr=true -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - [program:php-fpm] command=/entrypoint_fpm.sh autorestart=true diff --git a/core/files/entrypoint_nginx.sh b/core/files/init_misp.sh similarity index 53% rename from core/files/entrypoint_nginx.sh rename to core/files/init_misp.sh index 69e586d7..1a2ef40c 100755 --- a/core/files/entrypoint_nginx.sh +++ b/core/files/init_misp.sh @@ -1,13 +1,5 @@ #!/bin/bash -term_proc() { - echo "Entrypoint NGINX caught SIGTERM signal!" - echo "Killing process $master_pid" - kill -TERM "$master_pid" 2>/dev/null -} - -trap term_proc SIGTERM - update_database_tls_config() { local key="$1" local value="$2" @@ -64,40 +56,6 @@ safe_sed_i() { rm -f "$tmp" } -init_mysql(){ - # Test when MySQL is ready.... - # wait for Database come ready - isDBup () { - echo "SHOW STATUS" | $MYSQL_CMD 1>/dev/null - echo $? - } - - isDBinitDone () { - # Table attributes has existed since at least v2.1 - echo "DESCRIBE attributes" | $MYSQL_CMD 1>/dev/null - echo $? - } - - RETRY=100 - until [ $(isDBup) -eq 0 ] || [ $RETRY -le 0 ] ; do - echo "... waiting for database to come up" - sleep 5 - RETRY=$(( RETRY - 1)) - done - if [ $RETRY -le 0 ]; then - >&2 echo "... error: Could not connect to Database on $MYSQL_HOST:$MYSQL_PORT" - exit 1 - fi - - if [ $(isDBinitDone) -eq 0 ]; then - echo "... database has already been initialized" - export DB_ALREADY_INITIALISED=true - else - echo "... database has not been initialized, importing MySQL scheme..." - $MYSQL_CMD < /var/www/MISP/INSTALL/MYSQL.sql - fi -} - init_misp_data_files(){ # Init config (shared with host) echo "... initialize configuration files" @@ -269,7 +227,7 @@ enforce_misp_data_permissions(){ echo "find & change ... chown -R www-data:www-data /var/www/MISP/app/tmp" && find /var/www/MISP/app/tmp \( ! -user www-data -or ! -group www-data \) -exec chown www-data:www-data {} + # Enforce 0770 on all files and dirs - app/tmp contains a mix of cache, exports and temp files that all need to be writable by www-data echo "find & change... chmod -R 0770 /var/www/MISP/app/tmp" && find /var/www/MISP/app/tmp ! -perm 0770 -exec chmod 0770 {} + - + echo "find & change ... chown -R www-data:www-data /var/www/MISP/app/files" && find /var/www/MISP/app/files \( ! -user www-data -or ! -group www-data \) -exec chown www-data:www-data {} + # Enforce 0770 on all files and dirs - app/files contains a mix of scripts and user data echo "find & change ... chmod -R 0770 /var/www/MISP/app/files" && find /var/www/MISP/app/files ! -perm 0770 -exec chmod 0770 {} + @@ -284,196 +242,11 @@ enforce_misp_data_permissions(){ echo "... chmod 600 /var/www/MISP/app/Config/{config,database,email}.php" && chmod 600 /var/www/MISP/app/Config/{bootstrap,config,database,email}.php } -flip_nginx() { - local live="$1"; - local reload="$2"; - - if [[ "$live" = "true" ]]; then - NGINX_DOC_ROOT=/var/www/MISP/app/webroot - elif [[ -x /custom/files/var/www/html/index.php ]]; then - NGINX_DOC_ROOT=/custom/files/var/www/html/ - else - NGINX_DOC_ROOT=/var/www/html/ - fi - - # must be valid for all roots - echo "... nginx docroot set to ${NGINX_DOC_ROOT}" - sed -i "s|root.*var/www.*|root ${NGINX_DOC_ROOT};|" /etc/nginx/includes/misp - - if [[ "$reload" = "true" ]]; then - echo "... nginx reloaded" - nginx -s reload - fi -} - -init_nginx() { - # Adjust global settings - echo "... adjusting mime types" - sed -i "s/\bjs;$/js mjs;/" /etc/nginx/mime.types - - # Adjust timeouts - echo "... adjusting 'fastcgi_read_timeout' to ${FASTCGI_READ_TIMEOUT}" - sed -i "s/fastcgi_read_timeout .*;/fastcgi_read_timeout ${FASTCGI_READ_TIMEOUT};/" /etc/nginx/includes/misp - echo "... adjusting 'fastcgi_send_timeout' to ${FASTCGI_SEND_TIMEOUT}" - sed -i "s/fastcgi_send_timeout .*;/fastcgi_send_timeout ${FASTCGI_SEND_TIMEOUT};/" /etc/nginx/includes/misp - echo "... adjusting 'fastcgi_connect_timeout' to ${FASTCGI_CONNECT_TIMEOUT}" - sed -i "s/fastcgi_connect_timeout .*;/fastcgi_connect_timeout ${FASTCGI_CONNECT_TIMEOUT};/" /etc/nginx/includes/misp - - # Adjust maximum allowed size of the client request body - echo "... adjusting 'client_max_body_size' to ${NGINX_CLIENT_MAX_BODY_SIZE}" - sed -i "s/client_max_body_size .*;/client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE};/" /etc/nginx/includes/misp - - # Adjust forwarding header settings (clean up first) - sed -i '/real_ip_header/d' /etc/nginx/includes/misp - sed -i '/real_ip_recursive/d' /etc/nginx/includes/misp - sed -i '/set_real_ip_from/d' /etc/nginx/includes/misp - if [[ "$NGINX_X_FORWARDED_FOR" = "true" ]]; then - echo "... enabling X-Forwarded-For header" - echo "... setting 'real_ip_header X-Forwarded-For'" - echo "... setting 'real_ip_recursive on'" - sed -i "/index index.php/a real_ip_header X-Forwarded-For;\nreal_ip_recursive on;" /etc/nginx/includes/misp - if [[ ! -z "$NGINX_SET_REAL_IP_FROM" ]]; then - SET_REAL_IP_FROM_PRINT=$(echo $NGINX_SET_REAL_IP_FROM | tr ',' '\n') - for real_ip in ${SET_REAL_IP_FROM_PRINT[@]}; do - echo "... setting 'set_real_ip_from ${real_ip}'" - done - SET_REAL_IP_FROM=$(echo $NGINX_SET_REAL_IP_FROM | tr ',' '\n' | while read line; do echo -n "set_real_ip_from ${line};\n"; done) - SET_REAL_IP_FROM_ESCAPED=$(echo $SET_REAL_IP_FROM | sed '$!s/$/\\/' | sed 's/\\n$//') - sed -i "/real_ip_recursive on/a $SET_REAL_IP_FROM_ESCAPED" /etc/nginx/includes/misp - fi - fi - - # Adjust Content-Security-Policy - echo "... adjusting Content-Security-Policy" - # Remove any existing CSP header - sed -i '/add_header Content-Security-Policy/d' /etc/nginx/includes/misp - - if [[ -n "$CONTENT_SECURITY_POLICY" ]]; then - # If $CONTENT_SECURITY_POLICY is set, add CSP header - echo "... setting Content-Security-Policy to '$CONTENT_SECURITY_POLICY'" - sed -i "/add_header X-Download-Options/a add_header Content-Security-Policy \"$CONTENT_SECURITY_POLICY\";" /etc/nginx/includes/misp - else - # Otherwise, do not add any CSP headers - echo "... no Content-Security-Policy header will be set as CONTENT_SECURITY_POLICY is not defined" - fi - - # Adjust X-Frame-Options - echo "... adjusting X-Frame-Options" - # Remove any existing X-Frame-Options header - sed -i '/add_header X-Frame-Options/d' /etc/nginx/includes/misp - - if [[ -z "$X_FRAME_OPTIONS" ]]; then - echo "... setting 'X-Frame-Options SAMEORIGIN'" - sed -i "/add_header X-Download-Options/a add_header X-Frame-Options \"SAMEORIGIN\" always;" /etc/nginx/includes/misp - else - echo "... setting 'X-Frame-Options $X_FRAME_OPTIONS'" - sed -i "/add_header X-Download-Options/a add_header X-Frame-Options \"$X_FRAME_OPTIONS\";" /etc/nginx/includes/misp - fi - - # Adjust HTTP Strict Transport Security (HSTS) - echo "... adjusting HTTP Strict Transport Security (HSTS)" - # Remove any existing HSTS header - sed -i '/add_header Strict-Transport-Security/d' /etc/nginx/includes/misp - - if [[ -n "$HSTS_MAX_AGE" ]]; then - # If $HSTS_MAX_AGE is defined, add the HSTS header - echo "... setting HSTS to 'max-age=$HSTS_MAX_AGE; includeSubdomains'" - sed -i "/add_header X-Download-Options/a add_header Strict-Transport-Security \"max-age=$HSTS_MAX_AGE; includeSubdomains\";" /etc/nginx/includes/misp - else - # Otherwise, do nothing, keeping without the HSTS header - echo "... no HSTS header will be set as HSTS_MAX_AGE is not defined" - fi - - # Testing for files also test for links, and generalize better to mounted files - if [[ ! -f "/etc/nginx/sites-enabled/misp80" ]]; then - echo "... enabling port 80 redirect" - ln -s /etc/nginx/sites-available/misp80 /etc/nginx/sites-enabled/misp80 - else - echo "... port 80 already enabled" - fi - if [[ "$DISABLE_IPV6" = "true" ]]; then - echo "... disabling IPv6 on port 80" - sed -i "s/[^#] listen \[/ # listen \[/" /etc/nginx/sites-enabled/misp80 - else - echo "... enabling IPv6 on port 80" - sed -i "s/# listen \[/listen \[/" /etc/nginx/sites-enabled/misp80 - fi - if [[ "$DISABLE_SSL_REDIRECT" = "true" ]]; then - echo "... disabling SSL redirect" - sed -i "s/[^#] return / # return /" /etc/nginx/sites-enabled/misp80 - sed -i "s/# include /include /" /etc/nginx/sites-enabled/misp80 - else - echo "... enabling SSL redirect" - sed -i "s/[^#] include / # include /" /etc/nginx/sites-enabled/misp80 - sed -i "s/# return /return /" /etc/nginx/sites-enabled/misp80 - fi - - # Testing for files also test for links, and generalize better to mounted files - if [[ ! -f "/etc/nginx/sites-enabled/misp443" ]]; then - echo "... enabling port 443" - ln -s /etc/nginx/sites-available/misp443 /etc/nginx/sites-enabled/misp443 - else - echo "... port 443 already enabled" - fi - if [[ "$DISABLE_IPV6" = "true" ]]; then - echo "... disabling IPv6 on port 443" - sed -i "s/[^#] listen \[/ # listen \[/" /etc/nginx/sites-enabled/misp443 - else - echo "... enabling IPv6 on port 443" - sed -i "s/# listen \[/listen \[/" /etc/nginx/sites-enabled/misp443 - fi - - if [[ ! -f /etc/nginx/certs/cert.pem || ! -f /etc/nginx/certs/key.pem ]]; then - echo "... generating new self-signed TLS certificate" - openssl req -x509 -subj '/CN=localhost' -nodes -newkey rsa:4096 -keyout /etc/nginx/certs/key.pem -out /etc/nginx/certs/cert.pem -days 365 \ - -addext "subjectAltName = DNS:localhost, IP:127.0.0.1, IP:::1" - else - echo "... TLS certificates found" - fi - - if [[ "$FASTCGI_STATUS_LISTEN" != "" ]]; then - echo "... enabling php-fpm status page" - ln -s /etc/nginx/sites-available/php-fpm-status /etc/nginx/sites-enabled/php-fpm-status - sed -i -E "s/ listen [^;]+/ listen $FASTCGI_STATUS_LISTEN/" /etc/nginx/sites-enabled/php-fpm-status - elif [[ -f /etc/nginx/sites-enabled/php-fpm-status ]]; then - echo "... disabling php-fpm status page" - rm /etc/nginx/sites-enabled/php-fpm-status - fi - - flip_nginx false false -} - -# Hinders further execution when sourced from other scripts -if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then - return -fi - -# Initialize MySQL -echo "INIT | Initialize MySQL ..." && init_mysql - -# Initialize NGINX -echo "INIT | Initialize NGINX ..." && init_nginx -nginx -g 'daemon off;' & master_pid=$! - -# Initialize MISP -echo "INIT | Initialize MISP files and configurations ..." && init_misp_data_files -echo "INIT | Update MISP app/files directory ..." && update_misp_data_files -echo "INIT | Enforce MISP permissions ..." && enforce_misp_data_permissions -echo "INIT | Flip NGINX live ..." && flip_nginx true true - -# Run configure MISP script -echo "INIT | Configure MISP installation ..." -/configure_misp.sh - -if [[ -x /custom/files/customize_misp.sh ]]; then - echo "INIT | Customize MISP installation ..." - /custom/files/customize_misp.sh -fi +echo "INIT | Initialize MISP files and configurations ..." +init_misp_data_files -# Restart PHP workers -echo "INIT | Configure PHP ..." -supervisorctl restart php-fpm -echo "INIT | Done ..." +echo "INIT | Update MISP app/files directory ..." +update_misp_data_files -# Wait for it -wait "$master_pid" +echo "INIT | Enforce MISP permissions ..." +enforce_misp_data_permissions diff --git a/core/files/kubernetes/entrypoint_fpm.sh b/core/files/kubernetes/entrypoint_fpm.sh index 4a4e90a4..124a8925 100755 --- a/core/files/kubernetes/entrypoint_fpm.sh +++ b/core/files/kubernetes/entrypoint_fpm.sh @@ -1,25 +1,30 @@ #!/bin/bash -e + echo "INIT | Loading environment and functions" + source /entrypoint.sh -export PHP_LISTEN_FPM=true -source /entrypoint_nginx.sh source /entrypoint_fpm.sh +# Configure supervisord for kubernetes echo "INIT | Configuring supervisord for kubernetes" mv /etc/supervisor/conf.d/10-supervisor.conf{.kubernetes,} mv /etc/supervisor/conf.d/50-workers.conf{.kubernetes,} +# Starting supervisord echo "INIT | Starting supervisord" /usr/local/bin/supervisord -c /etc/supervisor/supervisord.conf & # Initialize MySQL -echo "INIT | Initialize MySQL ..." && init_mysql +echo "INIT | Initialize MySQL ..." +init_mysql # Initialize MISP -echo "INIT | Initialize MISP files and configurations ..." && init_misp_data_files -echo "INIT | Update MISP app/files directory ..." && update_misp_data_files -echo "INIT | Mirror file logs to stdout ..." && redirect_logs -echo "INIT | Enforce MISP permissions ..." && enforce_misp_data_permissions +echo "INIT | Initialize MISP installation ..." +/init_misp.sh + +# Mirror logs to stdout +echo "INIT | Mirror file logs to stdout ..." +redirect_logs # Run configure MISP script echo "INIT | Configure MISP installation ..." @@ -30,8 +35,9 @@ if [[ -x /custom/files/customize_misp.sh ]]; then /custom/files/customize_misp.sh fi -echo "Configure PHP | Change PHP values ..." && change_php_vars - -echo "Configure PHP | Starting PHP FPM" +# Configure PHP +echo "Configure PHP | Change PHP values ..." +change_php_vars +echo "MISP | Starting PHP FPM" exec /usr/bin/tini -- /usr/local/sbin/php-fpm -R -F diff --git a/docker-bake.hcl b/docker-bake.hcl index 865ca9ac..dad968af 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -100,6 +100,7 @@ group "default" { "misp-modules-slim", "misp-core", "misp-core-slim", + "misp-nginx", "misp-guard", ] } @@ -108,6 +109,7 @@ group "slim" { targets = [ "misp-modules-slim", "misp-core-slim", + "misp-nginx", "misp-guard", ] } @@ -115,6 +117,7 @@ group "standard" { targets = [ "misp-modules", "misp-core", + "misp-nginx", "misp-guard", ] } @@ -197,6 +200,17 @@ target "misp-core-slim" { platforms = "${PLATFORMS}" } +target "misp-nginx" { + context = "nginx/." + dockerfile = "Dockerfile" + tags = flatten(["${NAMESPACE}/misp-nginx:latest", "${NAMESPACE}/misp-nginx:${COMMIT_HASH}", CORE_TAG != "" ? ["${NAMESPACE}/misp-nginx:${CORE_TAG}"] : []]) + args = { + "CORE_TAG": "${CORE_TAG}", + "CORE_COMMIT": "${CORE_COMMIT}", + } + platforms = "${PLATFORMS}" +} + target "misp-guard" { context = "guard/." dockerfile = "Dockerfile" diff --git a/docker-compose.yml b/docker-compose.yml index 075b38c6..718a419f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,8 +90,6 @@ services: misp-core: image: ${REGISTRY_MIRROR_URL:-}ghcr.io/misp/misp-docker/misp-core:${CORE_RUNNING_TAG:-latest} restart: always - cap_add: - - AUDIT_WRITE build: context: core/. args: @@ -120,22 +118,22 @@ services: misp-modules: condition: service_healthy healthcheck: - test: curl -ks ${BASE_URL:-https://localhost}/users/heartbeat > /dev/null || exit 1 + test: bash -c 'echo > /dev/tcp/127.0.0.1/9002' || exit 1 interval: 2s timeout: 1s retries: 3 start_period: 30s start_interval: 30s - ports: - - "${CORE_HTTP_PORT:-80}:80" - - "${CORE_HTTPS_PORT:-443}:443" + expose: + - 9002 volumes: - "./configs/:/var/www/MISP/app/Config/:Z" - "./logs/:/var/www/MISP/app/tmp/logs/:Z" - "./files/:/var/www/MISP/app/files/:Z" - - "./ssl/:/etc/nginx/certs/:Z" - "./gnupg/:/var/www/MISP/.gnupg/:Z" - "misp_guard_ca:/usr/local/share/ca-certificates/misp_guard:Z" + cap_add: + - AUDIT_WRITE # customize by replacing ${CUSTOM_PATH} with a path containing 'files/customize_misp.sh' # - "${CUSTOM_PATH}/:/custom/:Z" # mount custom ca root certificates @@ -252,10 +250,6 @@ services: - "CUSTOM_AUTH_DISABLE_LOGOUT=${CUSTOM_AUTH_DISABLE_LOGOUT}" - "CUSTOM_AUTH_CUSTOM_PASSWORD_RESET=${CUSTOM_AUTH_CUSTOM_PASSWORD_RESET}" - "CUSTOM_AUTH_CUSTOM_LOGOUT=${CUSTOM_AUTH_CUSTOM_LOGOUT}" - # nginx settings - - "NGINX_X_FORWARDED_FOR=${NGINX_X_FORWARDED_FOR}" - - "NGINX_SET_REAL_IP_FROM=${NGINX_SET_REAL_IP_FROM}" - - "NGINX_CLIENT_MAX_BODY_SIZE=${NGINX_CLIENT_MAX_BODY_SIZE:-50M}" # proxy settings - "PROXY_ENABLE=${PROXY_ENABLE}" - "PROXY_HOST=${PROXY_HOST}" @@ -308,12 +302,9 @@ services: # SMTP setting - "SMTP_FQDN=${SMTP_FQDN}" - "SMTP_PORT=${SMTP_PORT:-25}" - # NGINX settings - - "FASTCGI_READ_TIMEOUT=${FASTCGI_READ_TIMEOUT:-300s}" - - "FASTCGI_SEND_TIMEOUT=${FASTCGI_SEND_TIMEOUT:-300s}" - - "FASTCGI_CONNECT_TIMEOUT=${FASTCGI_CONNECT_TIMEOUT:-300s}" - - "FASTCGI_STATUS_LISTEN=${FASTCGI_STATUS_LISTEN}" # PHP settings + - "FASTCGI_LISTEN_STATUS=${FASTCGI_LISTEN_STATUS:-misp-core:9003}" + - "PHP_LISTEN_FPM=${PHP_LISTEN_FPM:-true}" - "PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-2048M}" - "PHP_MAX_EXECUTION_TIME=${PHP_MAX_EXECUTION_TIME:-300}" - "PHP_UPLOAD_MAX_FILESIZE=${PHP_UPLOAD_MAX_FILESIZE:-50M}" @@ -334,13 +325,55 @@ services: - "PHP_SESSION_COOKIE_SECURE=${PHP_SESSION_COOKIE_SECURE:-true}" - "PHP_SESSION_COOKIE_DOMAIN=${PHP_SESSION_COOKIE_DOMAIN}" - "PHP_SESSION_COOKIE_SAMESITE=${PHP_SESSION_COOKIE_SAMESITE:-Lax}" - # security settings - - "HSTS_MAX_AGE=${HSTS_MAX_AGE}" - - "X_FRAME_OPTIONS=${X_FRAME_OPTIONS}" - - "CONTENT_SECURITY_POLICY=${CONTENT_SECURITY_POLICY}" # compose profiles - "COMPOSE_PROFILES=${COMPOSE_PROFILES}" + misp-nginx: + image: ${REGISTRY_MIRROR_URL:-}ghcr.io/misp/misp-docker/misp-nginx:${CORE_RUNNING_TAG:-latest} + restart: always + build: + context: nginx/. + args: + - CORE_TAG=${CORE_TAG:?Missing or outdated .env file, see README.md for instructions} + - CORE_COMMIT=${CORE_COMMIT} + depends_on: + misp-core: + condition: service_healthy + ports: + - "${NGINX_HTTP_PORT:-80}:8080" + - "${NGINX_HTTPS_PORT:-443}:8443" + volumes: + - "./ssl:/etc/nginx/certs:ro" + - "./gnupg/:/var/www/MISP/.gnupg/:Z" + tmpfs: + - /var/www/MISP/cache:mode=0755,uid=101,gid=101 + - /etc/nginx/conf.d:mode=0755,uid=101,gid=101 + - /var/cache/nginx:mode=0755,uid=101,gid=101 + - /tmp:mode=1777 + user: nginx + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + environment: + - "TZ=${TZ}" + - "BASE_URL=${BASE_URL}" + - "DISABLE_IPV6=${DISABLE_IPV6}" + # NGINX settings + - "NGINX_X_FORWARDED_FOR=${NGINX_X_FORWARDED_FOR}" + - "NGINX_SET_REAL_IP_FROM=${NGINX_SET_REAL_IP_FROM}" + - "NGINX_CLIENT_MAX_BODY_SIZE=${NGINX_CLIENT_MAX_BODY_SIZE:-50M}" + - "FASTCGI_LISTEN=${FASTCGI_LISTEN:-misp-core:9002}" + - "FASTCGI_LISTEN_STATUS=${FASTCGI_LISTEN_STATUS:-misp-core:9003}" + - "FASTCGI_READ_TIMEOUT=${FASTCGI_READ_TIMEOUT:-300s}" + - "FASTCGI_SEND_TIMEOUT=${FASTCGI_SEND_TIMEOUT:-300s}" + - "FASTCGI_CONNECT_TIMEOUT=${FASTCGI_CONNECT_TIMEOUT:-300s}" + # security settings + - "NGINX_HSTS_MAX_AGE=${NGINX_HSTS_MAX_AGE}" + - "NGINX_X_FRAME_OPTIONS=${NGINX_X_FRAME_OPTIONS}" + - "NGINX_CONTENT_SECURITY_POLICY=${NGINX_CONTENT_SECURITY_POLICY}" + misp-modules: image: ${REGISTRY_MIRROR_URL:-}ghcr.io/misp/misp-docker/misp-modules:${MODULES_RUNNING_TAG:-latest} restart: always @@ -363,7 +396,7 @@ services: - "./custom/expansion/:/custom/expansion/:Z" - "./custom/export_mod/:/custom/export_mod/:Z" - "./custom/import_mod/:/custom/import_mod/:Z" - environment: + environment: - "TZ=${TZ:-UTC}" misp-guard: diff --git a/kubernetes/helm-chart/templates/deployment.yaml b/kubernetes/helm-chart/templates/deployment.yaml index ecdad745..4491384c 100644 --- a/kubernetes/helm-chart/templates/deployment.yaml +++ b/kubernetes/helm-chart/templates/deployment.yaml @@ -215,8 +215,7 @@ spec: echo "CA Certificates updated." {{- end }} - exec /entrypoint.sh - + exec /kubernetes/entrypoint_fpm.sh env: - name: ADMIN_EMAIL value: {{ .Values.misp.mispConfig.initialAdminUsername | quote }} @@ -311,8 +310,8 @@ spec: {{- include "common.tplvalues.render" (dict "value" .Values.misp.extraEnvVarsSecret "context" $) | nindent 12 }} {{- end }} ports: - - name: http - containerPort: {{ .Values.misp.service.port }} + - name: misp-php + containerPort: 9002 protocol: TCP {{- if .Values.misp.liveness.enabled }} livenessProbe: @@ -332,11 +331,8 @@ spec: periodSeconds: {{ .Values.misp.readiness.period }} successThreshold: {{ .Values.misp.readiness.success }} timeoutSeconds: {{ .Values.misp.readiness.timeout }} - exec: - command: - - "/bin/sh" - - "-c" - - "grep -i 'MISP is ready' /misp/readiness/ready.log" + tcpSocket: + port: 9002 {{- end }} {{- if .Values.misp.startup.enabled }} startupProbe: @@ -398,6 +394,52 @@ spec: resources: {{- toYaml . | nindent 12 }} {{- end }} + + - name: misp-nginx + image: "{{ .Values.misp.nginx.image.repository }}:{{ .Values.misp.nginx.image.tag | default "latest" }}" + imagePullPolicy: {{ .Values.misp.nginx.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.misp.service.port }} + protocol: TCP + env: + - name: BASE_URL + {{- if and .Values.misp.ingress.enabled .Values.misp.ingress.hosts }} + {{- with (first .Values.misp.ingress.hosts) }} + value: "http{{ if $.Values.misp.ingress.tls }}s{{ end }}://{{ .host }}" + {{- end }} + {{- else }} + value: "{{ .Values.misp.hostname | default (printf "http://%s.%s" (include "misp.fullname" .) .Release.Namespace) }}{{ if .Values.misp.nodePort }}:{{ .Values.misp.nodePort }}{{ end }}" + {{- end }} + {{- range $key, $value := .Values.misp.nginx.extraEnvVars }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- with .Values.misp.nginx.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/conf.d + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-tmp + mountPath: /tmp + {{- if .Values.misp.nginx.readiness.enabled }} + readinessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: {{ .Values.misp.nginx.readiness.delay }} + timeoutSeconds: {{ .Values.misp.nginx.readiness.timeout }} + periodSeconds: {{ .Values.misp.nginx.readiness.period }} + successThreshold: {{ .Values.misp.nginx.readiness.success }} + failureThreshold: {{ .Values.misp.nginx.readiness.failure }} + {{- end }} + {{- with .Values.misp.nginx.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} volumes: {{- if .Values.misp.vault.enabled }} {{- range .Values.misp.vault.secrets }} @@ -424,6 +466,12 @@ spec: {{- end }} {{- end }} {{- end }} + - name: nginx-config + emptyDir: {} + - name: nginx-cache + emptyDir: {} + - name: nginx-tmp + emptyDir: {} {{- if .Values.misp.keycloakSecret.enabled }} - name: keycloak-secret secret: diff --git a/kubernetes/helm-chart/values.yaml b/kubernetes/helm-chart/values.yaml index 8a23b8d1..5ad3188c 100644 --- a/kubernetes/helm-chart/values.yaml +++ b/kubernetes/helm-chart/values.yaml @@ -593,15 +593,6 @@ misp: ## @param extraEnvVars.PASSWORD_COMPLEXITY Password complexity regex. ## Specifies a regex pattern enforcing password complexity rules. PASSWORD_COMPLEXITY: "/^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).{16,}$/" - ## @param extraEnvVars.NGINX_LIVE Enable live Nginx updates. - ## If `true`, Nginx will update configurations without restarting. - NGINX_LIVE: "false" - ## @param extraEnvVars.NGINX_RELOAD Force Nginx to reload configurations. - ## If `true`, Nginx will be reloaded after config changes. - NGINX_RELOAD: "false" - ## @param extraEnvVars.DISABLE_SSL_REDIRECT Disable automatic SSL redirection. - ## If `true`, HTTP traffic will not be automatically redirected to HTTPS. - DISABLE_SSL_REDIRECT: "true" ## @param extraEnvVars.BACKUP_COUNT Number of backups to retain. ## Defines how many backup versions are stored before older ones are deleted. BACKUP_COUNT: "90" @@ -798,9 +789,6 @@ misp: - name: fpm mountPath: /etc/php/7.4/fpm rsync: true - - name: nginx - mountPath: /etc/nginx - rsync: true - name: log mountPath: /var/log rsync: true @@ -810,9 +798,6 @@ misp: - name: tmp mountPath: /tmp rsync: false - - name: body - mountPath: /var/lib/nginx - rsync: false - name: certs mountPath: /etc/ssl/certs rsync: false @@ -994,6 +979,94 @@ misp: ## updateCaCertificatesOnStart: false + nginx: + ## MISP image + ## @param image.registry [default: REGISTRY_NAME] MISP image registry + ## @param image.tag MISP image tag (immutable tags are recommended) + ## @param image.pullPolicy MISP image pull policy + ## + image: + repository: path/to/misp/image + pullPolicy: Always + tag: 2.5.27 + + ## @param extraEnvVars List with extra environment variables to add to backend containers + ## e.g: + ## extraEnvVars: + ## - FOO: bar + ## + extraEnvVars: + FASTCGI_LISTEN: 127.0.0.1:9002 + FASTCGI_READ_TIMEOUT: 300s + FASTCGI_SEND_TIMEOUT: 300s + FASTCGI_CONNECT_TIMEOUT: 300s + NGINX_CLIENT_MAX_BODY_SIZE: 100M + NGINX_X_FORWARDED_FOR: "true" + NGINX_SET_REAL_IP_FROM: 127.0.0.1 + NGINX_X_FRAME_OPTIONS: SAMEORIGIN + NGINX_HSTS_MAX_AGE: "0" + + # yamllint disable + ## @param resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) + ## Example: + ## resources: + ## requests: + ## cpu: 2 + ## memory: 512Mi + ## limits: + ## cpu: 3 + ## memory: 1024Mi + ## + # yamllint enable + resources: + requests: + memory: "512Mi" + cpu: "400m" + limits: + memory: "1024Mi" + cpu: "1" + + ## Configure extra options for readiness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param readinessProbe.enabled Enable readinessProbe + ## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param readinessProbe.successThreshold Success threshold for readinessProbe + ## + readiness: + enabled: true + delay: 10 + timeout: 1 + period: 2 + success: 1 + failure: 3600 + + ## MISP nginx container security context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param containerSecurityContext.enabled MISP nginx container securityContext + ## @param containerSecurityContext.seLinuxOptions [object,nullable] Set SELinux options in container + ## @param containerSecurityContext.runAsUser User ID for the MISP container + ## @param containerSecurityContext.runAsGroup Group ID for the MISP container + ## @param containerSecurityContext.runAsNonRoot Set MISP container's Security Context runAsNonRoot + ## @param containerSecurityContext.allowPrivilegeEscalation Set container's privilege escalation + ## @param containerSecurityContext.capabilities.drop Set container's Security Context runAsNonRoot + ## @param containerSecurityContext.seccompProfile.type Set container's Security Context seccomp profile + ## @param containerSecurityContext.readOnlyRootFilesystem Set container's Security Context read-only root filesystem + # yamllint enable + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + capabilities: + drop: + - ALL + ## valkey: enabled: true diff --git a/kubernetes/kustomization.yaml b/kubernetes/kustomization.yaml index 1e067979..74870f94 100644 --- a/kubernetes/kustomization.yaml +++ b/kubernetes/kustomization.yaml @@ -8,7 +8,6 @@ resources: - ./manifests/mysql.yaml - ./manifests/services.yaml - ./manifests/ingress.yaml -- ./manifests/nginx-cm.yaml - ./manifests/cronjobs/cache-all-feeds.yaml - ./manifests/cronjobs/fetch-all-feeds.yaml - ./manifests/cronjobs/pull-all-servers.yaml @@ -28,3 +27,8 @@ secretGenerator: literals: - MYSQL_DATABASE=misp - MYSQL_USER=misp + +configMapGenerator: + - name: nginx-config + envs: + - ./nginx-config.env diff --git a/kubernetes/manifests/deployment-nginx.yaml b/kubernetes/manifests/deployment-nginx.yaml index ecbaf87d..2cc99e23 100644 --- a/kubernetes/manifests/deployment-nginx.yaml +++ b/kubernetes/manifests/deployment-nginx.yaml @@ -18,19 +18,12 @@ spec: spec: enableServiceLinks: false securityContext: - # We want imported files to be owned by www-data group - fsGroup: 33 - runAsUser: 33 + runAsUser: 101 + runAsGroup: 101 + runAsNonRoot: true containers: - name: nginx - command: - - /usr/bin/tini - args: - - nginx - - -- - - -g - - daemon off; - image: "ghcr.io/misp/misp-docker/misp-core:latest" + image: "ghcr.io/misp/misp-docker/misp-nginx:latest" imagePullPolicy: IfNotPresent readinessProbe: tcpSocket: @@ -40,34 +33,51 @@ spec: timeoutSeconds: 5 failureThreshold: 2 successThreshold: 1 - env: - # We only expose https anyway - - name: DISABLE_SSL_REDIRECT - value: "true" + - name: BASE_URL + valueFrom: + secretKeyRef: + name: instance-secrets + key: BASE_URL + envFrom: + - configMapRef: + name: nginx-config ports: - name: misp-http containerPort: 8080 protocol: TCP + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL resources: limits: memory: 2048Mi + cpu: 250m requests: memory: 512Mi cpu: 50m volumeMounts: - name: nginx-config - mountPath: /etc/nginx/nginx.conf - subPath: nginx.conf - - name: nginx-body - mountPath: /var/lib/nginx/ + mountPath: /etc/nginx/conf.d + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-tmp + mountPath: /tmp volumes: - name: nginx-config - configMap: - name: nginx-conf - items: - - key: nginx.conf - path: nginx.conf + emptyDir: + sizeLimit: 50Mi + - name: nginx-cache + emptyDir: + sizeLimit: 50Mi + - name: nginx-tmp + emptyDir: + sizeLimit: 50Mi - name: nginx-body emptyDir: sizeLimit: 50Mi diff --git a/kubernetes/manifests/nginx-cm.yaml b/kubernetes/manifests/nginx-cm.yaml deleted file mode 100644 index a72044e9..00000000 --- a/kubernetes/manifests/nginx-cm.yaml +++ /dev/null @@ -1,113 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: nginx-conf -data: - nginx.conf: | - user www-data; - worker_processes auto; - worker_cpu_affinity auto; - pid /tmp/nginx.pid; - error_log stderr warn; - - events { - worker_connections 768; - # multi_accept on; - } - - http { - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - types_hash_max_size 2048; - server_tokens off; # Recommended practice is to turn this off - - # server_names_hash_bucket_size 64; - # server_name_in_redirect off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # SSL Settings - ## - - ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1 - ssl_prefer_server_ciphers off; # Don't force server cipher order. - - ## - # Logging Settings - ## - - access_log /var/log/nginx/access.log; - - ## - # Gzip Settings - ## - - gzip on; - - # gzip_vary on; - # gzip_proxied any; - # gzip_comp_level 6; - # gzip_buffers 16 8k; - # gzip_http_version 1.1; - # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - ## - # Virtual Host Configs - ## - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; - - server { - listen 8080 default_server; - listen [::]:8080 default_server; - - # disable access logs - access_log off; - log_not_found off; - error_log /dev/stderr error; - - - # define the root dir - root /var/www/MISP/app/webroot; - index index.php; - - # incrase the maximum body size - client_max_body_size 50M; - - # added headers for hardening browser security - add_header X-Content-Type-Options "nosniff" always; - add_header X-Download-Options "noopen" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; - - # remove X-Powered-By and nginx version, which is an information leak - fastcgi_hide_header X-Powered-By; - server_tokens off; - - location / { - try_files $uri $uri/ /index.php$is_args$query_string; - } - - location ~ ^/[^/]+\.php(/|$) { - include snippets/fastcgi-php.conf; - fastcgi_pass misp-php:9002; - fastcgi_read_timeout 300s; - fastcgi_send_timeout 300s; - fastcgi_connect_timeout 300s; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - set $path_info $fastcgi_path_info; - fastcgi_param PATH_INFO $path_info; - } - - # return 301 https://$host$request_uri; - } - } diff --git a/kubernetes/nginx-config.env b/kubernetes/nginx-config.env new file mode 100644 index 00000000..e05ff2ce --- /dev/null +++ b/kubernetes/nginx-config.env @@ -0,0 +1,9 @@ +FASTCGI_LISTEN=misp-php:9002 +FASTCGI_READ_TIMEOUT=300s +FASTCGI_SEND_TIMEOUT=300s +FASTCGI_CONNECT_TIMEOUT=300s +NGINX_CLIENT_MAX_BODY_SIZE=50M +NGINX_X_FRAME_OPTIONS=SAMEORIGIN +# NGINX_X_FORWARDED_FOR="true" +# NGINX_SET_REAL_IP_FROM=127.0.0.1 +# NGINX_HSTS_MAX_AGE="0" diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 00000000..f1ade9a7 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,65 @@ +# --- sources stage +FROM ghcr.io/nginx/nginx-unprivileged:1.31.2-trixie AS sources + +ARG CORE_TAG +ARG CORE_COMMIT + +# environment +ENV DEBIAN_FRONTEND=noninteractive + +# ensure directory exists with permissions for nginx user +WORKDIR /var/www + +# install git +USER root +RUN apt-get update && \ + apt-get install -y --no-install-recommends git && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* +USER nginx + +# clone source code into /var/www directory +RUN <<-EOF + if [ ! -z "${CORE_COMMIT}" ]; then + git clone https://github.com/MISP/MISP.git /var/www/MISP && cd /var/www/MISP && git checkout "${CORE_COMMIT}" + else + git clone --branch "${CORE_TAG}" --depth 1 https://github.com/MISP/MISP.git /var/www/MISP + fi + + cd /var/www/MISP || exit; git submodule update --init --recursive ./app/webroot +EOF + +# --- final stage +FROM ghcr.io/nginx/nginx-unprivileged:1.31.2-trixie + +# environment +ENV NGINX_INTERNAL_HTTP_PORT=8080 +ENV NGINX_INTERNAL_HTTPS_PORT=8443 +ENV NGINX_INTERNAL_STATUS_PORT=8081 +ENV NGINX_INCLUDE_DIR=/etc/nginx/conf.d/include + +# remove default page +RUN rm -rf /etc/nginx/conf.d/* + +# copy entrypoints +COPY files/00-misp.envsh /docker-entrypoint.d/00-misp.envsh +COPY files/01-listen.sh /docker-entrypoint.d/01-listen.sh +COPY files/02-real-ip.sh /docker-entrypoint.d/02-real-ip.sh +COPY files/03-conditional-headers.sh /docker-entrypoint.d/03-conditional-headers.sh +COPY files/04-ssl.sh /docker-entrypoint.d/04-ssl.sh +COPY files/05-gpg-key.sh /docker-entrypoint.d/05-gpg-key.sh + +# copy nginx configs +COPY --chown=nginx:nginx files/misp.conf /etc/nginx/templates/misp.conf.template +COPY --chown=nginx:nginx files/status.conf /etc/nginx/templates/status.conf.template + +# copy source code +COPY --from=sources --chown=nginx:nginx /var/www/MISP/app/webroot /var/www/MISP/app/webroot + +# expose port +EXPOSE ${NGINX_INTERNAL_HTTP_PORT} ${NGINX_INTERNAL_HTTPS_PORT} + +# health check via status page +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD bash -c 'echo > /dev/tcp/127.0.0.1/${NGINX_INTERNAL_STATUS_PORT}' || exit 1 diff --git a/nginx/files/00-misp.envsh b/nginx/files/00-misp.envsh new file mode 100755 index 00000000..46e7f3a1 --- /dev/null +++ b/nginx/files/00-misp.envsh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +# -*- coding: utf-8 -*- + +export TZ="${TZ:-UTC}" +export BASE_URL="${BASE_URL:-https://localhost}" +export DISABLE_IPV6="${DISABLE_IPV6:-false}" + +export NGINX_X_FORWARDED_FOR="${NGINX_X_FORWARDED_FOR:-false}" +export NGINX_SET_REAL_IP_FROM="${NGINX_SET_REAL_IP_FROM}" +export NGINX_CLIENT_MAX_BODY_SIZE="${NGINX_CLIENT_MAX_BODY_SIZE:-50M}" +export NGINX_X_FRAME_OPTIONS="${NGINX_X_FRAME_OPTIONS:-SAMEORIGIN}" +export NGINX_CONTENT_SECURITY_POLICY="${NGINX_CONTENT_SECURITY_POLICY}" +export NGINX_HSTS_MAX_AGE="${NGINX_HSTS_MAX_AGE}" + +export FASTCGI_LISTEN="${FASTCGI_LISTEN:-misp-core:9002}" +export FASTCGI_LISTEN_STATUS="${FASTCGI_LISTEN_STATUS:-misp-core:9003}" +export FASTCGI_READ_TIMEOUT="${FASTCGI_READ_TIMEOUT:-300s}" +export FASTCGI_SEND_TIMEOUT="${FASTCGI_SEND_TIMEOUT:-300s}" +export FASTCGI_CONNECT_TIMEOUT="${FASTCGI_CONNECT_TIMEOUT:-300s}" diff --git a/nginx/files/01-listen.sh b/nginx/files/01-listen.sh new file mode 100755 index 00000000..6a219887 --- /dev/null +++ b/nginx/files/01-listen.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env sh +# -*- coding: utf-8 -*- + +set -eu + +CONF="${NGINX_INCLUDE_DIR}/listen.conf" + +# ensure include file exists +mkdir -p "${NGINX_INCLUDE_DIR}" && : > "$CONF" + +if [ -f "/etc/nginx/certs/cert.pem" ] && [ -f "/etc/nginx/certs/key.pem" ]; then + # ipv4-ssl + printf 'listen %s ssl;\n' "${NGINX_INTERNAL_HTTPS_PORT}" >> "$CONF" + + # ipv6-ssl + if [ "${DISABLE_IPV6}" != "true" ]; then + printf 'listen [::]:%s ssl;\n' "${NGINX_INTERNAL_HTTPS_PORT}" >> "$CONF" + fi +else + # ipv4 + printf 'listen %s;\n' "${NGINX_INTERNAL_HTTP_PORT}" >> "$CONF" + + # ipv6 + if [ "${DISABLE_IPV6}" != "true" ]; then + printf 'listen [::]:%s;\n' "${NGINX_INTERNAL_HTTP_PORT}" >> "$CONF" + fi +fi diff --git a/nginx/files/02-real-ip.sh b/nginx/files/02-real-ip.sh new file mode 100755 index 00000000..8719e915 --- /dev/null +++ b/nginx/files/02-real-ip.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env sh +# -*- coding: utf-8 -*- + +set -eu + +CONF="${NGINX_INCLUDE_DIR}/real-ip.conf" + +# ensure include file exists +mkdir -p "${NGINX_INCLUDE_DIR}" && : > "$CONF" + +# early-exit if disabled +if [ "${NGINX_X_FORWARDED_FOR:-false}" != "true" ]; then + exit 0 +fi + +IFS=',' + +# enable forwarding headers +cat >> "$CONF" <> "$CONF" +done + +# reset separator +unset IFS diff --git a/nginx/files/03-conditional-headers.sh b/nginx/files/03-conditional-headers.sh new file mode 100755 index 00000000..b4da3d1c --- /dev/null +++ b/nginx/files/03-conditional-headers.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +# -*- coding: utf-8 -*- + +set -eu + +CONF="${NGINX_INCLUDE_DIR}/conditional-headers.conf" + +# ensure include file exists +mkdir -p "${NGINX_INCLUDE_DIR}" && : > "$CONF" + +# configure content security policy header +if [ -n "${NGINX_CONTENT_SECURITY_POLICY:-}" ]; then + printf 'add_header Content-Security-Policy "%s";\n' "${NGINX_CONTENT_SECURITY_POLICY}" >> "$CONF" +fi + +# configure strict transport security header +if [ -n "${NGINX_HSTS_MAX_AGE:-}" ]; then + printf 'add_header Strict-Transport-Security "max-age=%s; includeSubdomains";\n' "${NGINX_HSTS_MAX_AGE}" >> "$CONF" +fi diff --git a/nginx/files/04-ssl.sh b/nginx/files/04-ssl.sh new file mode 100755 index 00000000..867618d7 --- /dev/null +++ b/nginx/files/04-ssl.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env sh +# -*- coding: utf-8 -*- + +set -eu + +REDIRECT_CONF="${NGINX_INCLUDE_DIR}/redirect.conf" +SSL_CONF="${NGINX_INCLUDE_DIR}/ssl.conf" + +# ensure include files exists +mkdir -p "${NGINX_INCLUDE_DIR}" && : > "$REDIRECT_CONF" && : > "$SSL_CONF" + +if [ ! -f "/etc/nginx/certs/cert.pem" ] || [ ! -f "/etc/nginx/certs/key.pem" ]; then + exit 0 +fi + +# normalize redirect url +REDIRECT_URL=${BASE_URL#http://} +REDIRECT_URL=${REDIRECT_URL#https://} +REDIRECT_URL="https://${REDIRECT_URL}" + +# build redirect config +cat >> "$REDIRECT_CONF" <> "$SSL_CONF" < Default: SAMEORIGIN -# X_FRAME_OPTIONS= +# NGINX_X_FRAME_OPTIONS= # NGINX maximum allowed size of the client request body. # NGINX_CLIENT_MAX_BODY_SIZE=50M # Content-Security-Policy (CSP) configuration: defines allowed resources and prevents attacks like XSS. # Example: "frame-src 'self' https://*.example.com; frame-ancestors 'self' https://*.example.com; object-src 'none'; report-uri https://example.com/cspReport" -# CONTENT_SECURITY_POLICY= +# NGINX_CONTENT_SECURITY_POLICY=