Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security optimizations for the container base image #10672

Merged
merged 16 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading