Skip to content

Commit

Permalink
Merge pull request #10672 from IQSS/10508-base-image-fixes
Browse files Browse the repository at this point in the history
Security optimizations for the container base image
  • Loading branch information
pdurbin authored Jul 25, 2024
2 parents 93afea5 + 4258900 commit 1e7e9f1
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 115 deletions.
12 changes: 12 additions & 0 deletions doc/release-notes/10508-base-image-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Security and Compatibility Fixes to the Container Base Image

- Switch "wait-for" to "wait4x", aligned with the Configbaker Image
- Update "jattach" to v2.2
- Install AMD64 / ARM64 versions of tools as necessary
- Run base image as unprivileged user by default instead of `root` - this was an oversight from OpenShift changes
- Linux User, Payara Admin and Domain Master passwords:
- Print hints about default, public knowledge passwords in place for
- Enable replacing these passwords at container boot time
- Enable building with updates Temurin JRE image based on Ubuntu 24.04 LTS
- Fix entrypoint script troubles with pre- and postboot script files
- Unify location of files at CONFIG_DIR=/opt/payara/config, avoid writing to other places
22 changes: 18 additions & 4 deletions doc/sphinx-guides/source/container/base-image.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The base image provides:
- CLI tools necessary to run Dataverse (i. e. ``curl`` or ``jq`` - see also :doc:`../installation/prerequisites` in Installation Guide)
- Linux tools for analysis, monitoring and so on
- `Jattach <https://github.com/apangin/jattach>`__ (attach to running JVM)
- `wait-for <https://github.com/eficode/wait-for>`__ (tool to "wait for" a service to be available)
- `wait4x <https://github.com/atkrad/wait4x>`__ (tool to "wait for" a service to be available)
- `dumb-init <https://github.com/Yelp/dumb-init>`__ (see :ref:`below <base-entrypoint>` for details)

This image is created as a "multi-arch image", see :ref:`below <base-multiarch>`.
Expand Down Expand Up @@ -85,7 +85,7 @@ Some additional notes, using Maven parameters to change the build and use ...:
(See also `Docker Hub search example <https://hub.docker.com/_/eclipse-temurin/tags?page=1&name=11-jre>`_)
- ... a different Java Distribution: add ``-Djava.image="name:tag"`` with precise reference to an
image available local or remote.
- ... a different UID/GID for the ``payara`` user/group: add ``-Dbase.image.uid=1234`` (or ``.gid``)
- ... a different UID/GID for the ``payara`` user/group (default ``1000:1000``): add ``-Dbase.image.uid=1234`` (or ``.gid``)

Automated Builds & Publishing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -151,12 +151,12 @@ provides. These are mostly based on environment variables (very common with cont
- [preboot]_
- Abs. path
- Provide path to file with ``asadmin`` commands to run **before** boot of application server.
See also `Pre/postboot script docs`_.
See also `Pre/postboot script docs`_. Must be writeable by Payara Linux user!
* - ``POSTBOOT_COMMANDS``
- [postboot]_
- Abs. path
- Provide path to file with ``asadmin`` commands to run **after** boot of application server.
See also `Pre/postboot script docs`_.
See also `Pre/postboot script docs`_. Must be writeable by Payara Linux user!
* - ``JVM_ARGS``
- (empty)
- String
Expand Down Expand Up @@ -231,6 +231,18 @@ provides. These are mostly based on environment variables (very common with cont
- See :ref:`:ApplicationServerSettings` ``http.request-timeout-seconds``.

*Note:* can also be set using any other `MicroProfile Config Sources`_ available via ``dataverse.http.timeout``.
* - ``PAYARA_ADMIN_PASSWORD``
- ``admin``
- String
- Set to secret string to change `Payara Admin Console`_ Adminstrator User ("admin") password.
* - ``LINUX_PASSWORD``
- ``payara``
- String
- Set to secret string to change the Payara Linux User ("payara", default UID=1000) password.
* - ``DOMAIN_PASSWORD``
- ``changeit``
- String
- Set to secret string to change the `Domain Master Password`_.


.. [preboot] ``${CONFIG_DIR}/pre-boot-commands.asadmin``
Expand Down Expand Up @@ -374,3 +386,5 @@ from `run-java-sh recommendations`_.
.. _Pre/postboot script docs: https://docs.payara.fish/community/docs/Technical%20Documentation/Payara%20Micro%20Documentation/Payara%20Micro%20Configuration%20and%20Management/Micro%20Management/Asadmin%20Commands/Pre%20and%20Post%20Boot%20Commands.html
.. _MicroProfile Config Sources: https://docs.payara.fish/community/docs/Technical%20Documentation/MicroProfile/Config/Overview.html
.. _run-java-sh recommendations: https://github.com/fabric8io-images/run-java-sh/blob/master/TUNING.md#recommandations
.. _Domain Master Password: https://docs.payara.fish/community/docs/Technical%20Documentation/Payara%20Server%20Documentation/Security%20Guide/Administering%20System%20Security.html#to-change-the-master-password
.. _Payara Admin Console: https://docs.payara.fish/community/docs/Technical%20Documentation/Payara%20Server%20Documentation/General%20Administration/Overview.html#administration-console
85 changes: 54 additions & 31 deletions modules/container-base/src/main/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,18 @@ ENV PAYARA_DIR="${HOME_DIR}/appserver" \
STORAGE_DIR="/dv" \
SECRETS_DIR="/secrets" \
DUMPS_DIR="/dumps" \
PASSWORD_FILE="${HOME_DIR}/passwordFile" \
ADMIN_USER="admin" \
ADMIN_PASSWORD="admin" \
PAYARA_ADMIN_USER="admin" \
# This is a public default, easy to change via this env var at runtime
PAYARA_ADMIN_PASSWORD="admin" \
DOMAIN_NAME="domain1" \
PAYARA_ARGS=""
# This is the public default as per https://docs.payara.fish/community/docs/Technical%20Documentation/Payara%20Server%20Documentation/Security%20Guide/Administering%20System%20Security.html#to-change-the-master-password
# Can be changed at runtime via this env var
DOMAIN_PASSWORD="changeit" \
PAYARA_ARGS="" \
LINUX_USER="payara" \
LINUX_GROUP="payara" \
# This is a public default and can be changed at runtime using this env var
LINUX_PASSWORD="payara"
ENV PATH="${PATH}:${PAYARA_DIR}/bin:${SCRIPT_DIR}" \
DOMAIN_DIR="${PAYARA_DIR}/glassfish/domains/${DOMAIN_NAME}" \
DEPLOY_PROPS="" \
Expand All @@ -69,6 +76,10 @@ ENV PATH="${PATH}:${PAYARA_DIR}/bin:${SCRIPT_DIR}" \
### PART 1: SYSTEM ###
ARG UID=1000
ARG GID=1000
# Auto-populated by BuildKit / buildx
#ARG TARGETARCH="amd64"
ARG TARGETARCH

USER root
WORKDIR /
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
Expand All @@ -78,23 +89,25 @@ RUN <<EOF
# Create pathes
mkdir -p "${HOME_DIR}" "${PAYARA_DIR}" "${DEPLOY_DIR}" "${CONFIG_DIR}" "${SCRIPT_DIR}"
mkdir -p "${STORAGE_DIR}" "${SECRETS_DIR}" "${DUMPS_DIR}"
# Remove the default user if present (do not fail build if not, introduced by Ubuntu 24.04)
userdel --force --remove ubuntu || true
groupdel -f ubuntu || true # for some reason, groupdel on Ubuntu 22.04 does not like --force
# Create user
addgroup --gid ${GID} payara
adduser --system --uid ${UID} --no-create-home --shell /bin/bash --home "${HOME_DIR}" --gecos "" --ingroup payara payara
echo payara:payara | chpasswd
groupadd --gid "${GID}" "${LINUX_GROUP}"
useradd --system --uid "${UID}" --no-create-home --shell /bin/false --home "${HOME_DIR}" --gid "${LINUX_GROUP}" "${LINUX_USER}"
echo "${LINUX_USER}:$LINUX_PASSWORD" | chpasswd
# Set permissions
# Note: Following OpenShift best practices for arbitrary user id support:
# https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#use-uid_create-images
chown -R payara:0 "${HOME_DIR}" "${STORAGE_DIR}" "${SECRETS_DIR}" "${DUMPS_DIR}"
chown -R "${LINUX_USER}:0" "${HOME_DIR}" "${STORAGE_DIR}" "${SECRETS_DIR}" "${DUMPS_DIR}"
chmod -R g=u "${HOME_DIR}" "${STORAGE_DIR}" "${SECRETS_DIR}" "${DUMPS_DIR}"

EOF

ARG JATTACH_VERSION="v2.1"
ARG JATTACH_CHECKSUM="07885fdc782e02e7302c6d190f54c3930afa10a38140365adf54076ec1086a8e"
ARG WAIT_FOR_VERSION="v2.2.3"
ARG WAIT_FOR_CHECKSUM="70271181be69cd2c7265b2746f97fccfd7e8aa1059894138a775369c23589ff4"
ARG PKGS="jq imagemagick curl unzip wget acl dirmngr gpg lsof procps netcat dumb-init"
ARG JATTACH_VERSION="v2.2"
ARG JATTACH_TGZ_CHECKSUM_AMD64="acd9e17f15749306be843df392063893e97bfecc5260eef73ee98f06e5cfe02f"
ARG JATTACH_TGZ_CHECKSUM_ARM64="288ae5ed87ee7fe0e608c06db5a23a096a6217c9878ede53c4e33710bdcaab51"
ARG WAIT4X_VERSION="v2.14.0"
ARG PKGS="jq imagemagick curl unzip wget acl lsof procps netcat-openbsd dumb-init"

# Installing the packages in an extra container layer for better caching
RUN <<EOF
Expand All @@ -103,40 +116,48 @@ RUN <<EOF
apt-get install -qqy --no-install-recommends ${PKGS}
rm -rf "/var/lib/apt/lists/*"

# Install jattach
curl -sSfL -o /usr/bin/jattach "https://github.com/apangin/jattach/releases/download/${JATTACH_VERSION}/jattach"
echo "${JATTACH_CHECKSUM} /usr/bin/jattach" | sha256sum -c -
chmod +x /usr/bin/jattach
# Install jattach & wait4x
if [ "${TARGETARCH}" = "amd64" ]; then
curl -sSfL -o /usr/bin/jattach.tgz "https://github.com/jattach/jattach/releases/download/${JATTACH_VERSION}/jattach-linux-x64.tgz"
echo "${JATTACH_TGZ_CHECKSUM_AMD64} /usr/bin/jattach.tgz" | sha256sum -c -
elif [ "${TARGETARCH}" = "arm64" ]; then
curl -sSfL -o /usr/bin/jattach.tgz "https://github.com/jattach/jattach/releases/download/${JATTACH_VERSION}/jattach-linux-arm64.tgz"
echo "${JATTACH_TGZ_CHECKSUM_ARM64} /usr/bin/jattach.tgz" | sha256sum -c -
fi
tar -xzf /usr/bin/jattach.tgz -C /usr/bin && chmod +x /usr/bin/jattach

# Install wait-for
curl -sSfL -o /usr/bin/wait-for "https://github.com/eficode/wait-for/releases/download/${WAIT_FOR_VERSION}/wait-for"
echo "${WAIT_FOR_CHECKSUM} /usr/bin/wait-for" | sha256sum -c -
chmod +x /usr/bin/wait-for
# Install wait4x
curl -sSfL -o /usr/bin/wait4x.tar.gz "https://github.com/atkrad/wait4x/releases/download/${WAIT4X_VERSION}/wait4x-linux-${TARGETARCH}.tar.gz"
curl -sSfL -o /tmp/w4x-checksum "https://github.com/atkrad/wait4x/releases/download/${WAIT4X_VERSION}/wait4x-linux-${TARGETARCH}.tar.gz.sha256sum"
echo "$(cat /tmp/w4x-checksum | cut -f1 -d" ") /usr/bin/wait4x.tar.gz" | sha256sum -c -
tar -xzf /usr/bin/wait4x.tar.gz -C /usr/bin && chmod +x /usr/bin/wait4x
EOF

### PART 2: PAYARA ###
# After setting up system, now configure Payara

ARG ASADMIN="${PAYARA_DIR}/bin/asadmin --user=${ADMIN_USER} --passwordfile=${PASSWORD_FILE}"

USER payara
USER ${LINUX_USER}
WORKDIR ${HOME_DIR}

# Copy Payara from build context (cached by Maven)
COPY --chown=payara:payara maven/appserver ${PAYARA_DIR}/
COPY --chown=${LINUX_USER}:${LINUX_GROUP} maven/appserver ${PAYARA_DIR}/

# Copy the system (appserver level) scripts like entrypoint, etc
COPY --chown=payara:payara maven/scripts ${SCRIPT_DIR}/
COPY --chown=${LINUX_USER}:${LINUX_USER} maven/scripts ${SCRIPT_DIR}/

# Configure the domain to be container and production ready
# -- This is mostly inherited from the "production domain template", experience with Dataverse and
# https://blog.payara.fish/fine-tuning-payara-server-5-in-production
RUN <<EOF
# Set admin password
echo "AS_ADMIN_PASSWORD=" > /tmp/password-change-file.txt
echo "AS_ADMIN_NEWPASSWORD=${ADMIN_PASSWORD}" >> /tmp/password-change-file.txt
echo "AS_ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> ${PASSWORD_FILE}
asadmin --user=${ADMIN_USER} --passwordfile=/tmp/password-change-file.txt change-admin-password --domain_name=${DOMAIN_NAME}
echo "AS_ADMIN_NEWPASSWORD=${PAYARA_ADMIN_PASSWORD}" >> /tmp/password-change-file.txt
asadmin --user=${PAYARA_ADMIN_USER} --passwordfile=/tmp/password-change-file.txt change-admin-password --domain_name=${DOMAIN_NAME}

# Prepare shorthand
PASSWORD_FILE=$(mktemp)
echo "AS_ADMIN_PASSWORD=${PAYARA_ADMIN_PASSWORD}" >> ${PASSWORD_FILE}
ASADMIN="${PAYARA_DIR}/bin/asadmin --user=${PAYARA_ADMIN_USER} --passwordfile=${PASSWORD_FILE}"

# Start domain for configuration
${ASADMIN} start-domain ${DOMAIN_NAME}
# Allow access to admin with password only
Expand Down Expand Up @@ -213,6 +234,7 @@ RUN <<EOF
${SCRIPT_DIR}/removeExpiredCaCerts.sh
# Delete generated files
rm -rf \
"$PASSWORD_FILE" \
"/tmp/password-change-file.txt" \
"${PAYARA_DIR}/glassfish/domains/${DOMAIN_NAME}/osgi-cache" \
"${PAYARA_DIR}/glassfish/domains/${DOMAIN_NAME}/logs"
Expand All @@ -224,6 +246,7 @@ USER root
RUN true && \
chgrp -R 0 "${DOMAIN_DIR}" && \
chmod -R g=u "${DOMAIN_DIR}"
USER ${LINUX_USER}

# Set the entrypoint to tini (as a process supervisor)
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
Expand Down
12 changes: 8 additions & 4 deletions modules/container-base/src/main/docker/scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@

# We do not define these variables within our Dockerfile so the location can be changed when trying to avoid
# writes to the overlay filesystem. (CONFIG_DIR is defined within the Dockerfile, but might be overridden.)
${PREBOOT_COMMANDS:="${CONFIG_DIR}/pre-boot-commands.asadmin"}
export PREBOOT_COMMANDS
${POSTBOOT_COMMANDS:="${CONFIG_DIR}/post-boot-commands.asadmin"}
export POSTBOOT_COMMANDS
PREBOOT_COMMANDS_FILE=${PREBOOT_COMMANDS:-"${CONFIG_DIR}/pre-boot-commands.asadmin"}
export PREBOOT_COMMANDS_FILE
POSTBOOT_COMMANDS_FILE=${POSTBOOT_COMMANDS:-"${CONFIG_DIR}/post-boot-commands.asadmin"}
export POSTBOOT_COMMANDS_FILE

# Remove existing POSTBOOT/PREBOOT files if they exist. Anything to be done needs to be injected by a script
rm -rf "$POSTBOOT_COMMANDS_FILE" || exit 1
rm -rf "$PREBOOT_COMMANDS_FILE" || exit 1

# Execute any scripts BEFORE the appserver starts
for f in "${SCRIPT_DIR}"/init_* "${SCRIPT_DIR}"/init.d/*; do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
set -euo pipefail

# NOTE: ALL PASSWORD ENV VARS WILL BE SCRAMBLED IN startInForeground.sh FOR SECURITY!
# This is to avoid possible attack vectors where someone could extract the sensitive information
# from within an env var dump inside an application!

# Someone set the env var for passwords - get the new password in. Otherwise print warning.
# https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#avoid-default-passwords
if [ "$LINUX_PASSWORD" != "payara" ]; then
echo -e "$LINUX_USER\n$LINUX_PASSWORD\n$LINUX_PASSWORD" | passwd
else
echo "IMPORTANT: THIS CONTAINER USES THE DEFAULT PASSWORD FOR USER \"${LINUX_USER}\"! ('payara')"
echo " To change the password, set the LINUX_PASSWORD env var."
fi

# Change the domain admin password if necessary
if [ "$PAYARA_ADMIN_PASSWORD" != "admin" ]; then
PASSWORD_FILE=$(mktemp)
echo "AS_ADMIN_PASSWORD=admin" > "$PASSWORD_FILE"
echo "AS_ADMIN_NEWPASSWORD=${PAYARA_ADMIN_PASSWORD}" >> "$PASSWORD_FILE"
asadmin --user="${PAYARA_ADMIN_USER}" --passwordfile="$PASSWORD_FILE" change-admin-password --domain_name="${DOMAIN_NAME}"
rm "$PASSWORD_FILE"
else
echo "IMPORTANT: THIS CONTAINER USES THE DEFAULT PASSWORD FOR PAYARA ADMIN \"${PAYARA_ADMIN_USER}\"! ('admin')"
echo " To change the password, set the PAYARA_ADMIN_PASSWORD env var."
fi

# Change the domain master password if necessary
# > The master password is not tied to a user account, and it is not used for authentication.
# > Instead, Payara Server strictly uses the master password to ONLY encrypt the keystore and truststore used to store keys and certificates for the DAS and instances usage.
# It will be requested when booting the application server!
# https://docs.payara.fish/community/docs/Technical%20Documentation/Payara%20Server%20Documentation/Security%20Guide/Administering%20System%20Security.html#to-change-the-master-password
if [ "$DOMAIN_PASSWORD" != "changeit" ]; then
PASSWORD_FILE=$(mktemp)
echo "AS_ADMIN_MASTERPASSWORD=changeit" >> "$PASSWORD_FILE"
echo "AS_ADMIN_NEWMASTERPASSWORD=${DOMAIN_PASSWORD}" >> "$PASSWORD_FILE"
asadmin --user="${PAYARA_ADMIN_USER}" --passwordfile="$PASSWORD_FILE" change-master-password --savemasterpassword false "${DOMAIN_NAME}"
rm "$PASSWORD_FILE"
else
echo "IMPORTANT: THIS CONTAINER USES THE DEFAULT DOMAIN \"MASTER\" PASSWORD! ('changeit')"
echo " To change the password, set the DOMAIN_PASSWORD env var."
fi
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ set -euo pipefail

# Check required variables are set
if [ -z "$DEPLOY_DIR" ]; then echo "Variable DEPLOY_DIR is not set."; exit 1; fi
if [ -z "$PREBOOT_COMMANDS" ]; then echo "Variable PREBOOT_COMMANDS is not set."; exit 1; fi
if [ -z "$POSTBOOT_COMMANDS" ]; then echo "Variable POSTBOOT_COMMANDS is not set."; exit 1; fi

# Create pre and post boot command files if they don't exist
touch "$POSTBOOT_COMMANDS"
touch "$PREBOOT_COMMANDS"
if [ -z "$PREBOOT_COMMANDS_FILE" ]; then echo "Variable PREBOOT_COMMANDS_FILE is not set."; exit 1; fi
if [ -z "$POSTBOOT_COMMANDS_FILE" ]; then echo "Variable POSTBOOT_COMMANDS_FILE is not set."; exit 1; fi
# Test if files are writeable for us, exit otherwise
touch "$PREBOOT_COMMANDS_FILE" || exit 1
touch "$POSTBOOT_COMMANDS_FILE" || exit 1

deploy() {

Expand All @@ -50,14 +49,14 @@ deploy() {
fi

DEPLOY_STATEMENT="deploy $DEPLOY_PROPS $1"
if grep -q "$1" "$POSTBOOT_COMMANDS"; then
echo "post boot commands already deploys $1";
if grep -q "$1" "$POSTBOOT_COMMANDS_FILE"; then
echo "Post boot commands already deploys $1, skip adding";
else
if [ -n "$SKIP_DEPLOY" ] && { [ "$SKIP_DEPLOY" = "1" ] || [ "$SKIP_DEPLOY" = "true" ]; }; then
echo "Skipping deployment of $1 as requested.";
else
echo "Adding deployment target $1 to post boot commands";
echo "$DEPLOY_STATEMENT" >> "$POSTBOOT_COMMANDS";
echo "$DEPLOY_STATEMENT" >> "$POSTBOOT_COMMANDS_FILE";
fi
fi
}
Expand Down
Loading

0 comments on commit 1e7e9f1

Please sign in to comment.