Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
136 changes: 122 additions & 14 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions opendj-packages/opendj-docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ RUN printf 'Acquire::ForceIPv4 "true";\nAcquire::Retries "5";\n' > /etc/apt/apt
# root. The scripts copied below are only read and run, so they just keep the same group.
COPY --chown=$OPENDJ_USER:0 bootstrap/ /opt/opendj/bootstrap/
COPY --chown=$OPENDJ_USER:0 run.sh /opt/opendj/run.sh
COPY --chown=$OPENDJ_USER:0 healthcheck.sh /opt/opendj/healthcheck.sh

RUN chmod +x /opt/opendj/run.sh /opt/opendj/bootstrap/setup.sh /opt/opendj/bootstrap/replicate.sh
RUN chmod +x /opt/opendj/run.sh /opt/opendj/healthcheck.sh /opt/opendj/bootstrap/setup.sh /opt/opendj/bootstrap/replicate.sh

EXPOSE $PORT/tcp $LDAPS_PORT/tcp $ADMIN_PORT/tcp

Expand All @@ -75,10 +76,11 @@ USER $OPENDJ_USER
# "healthy" has to mean the instance is ready to serve, not just that it answers: setup
# starts the server in the middle of the bootstrap, before the backend of BASE_DN is
# created and its entries imported, so probing the root DSE alone reports ready while a
# search of BASE_DN still fails with "No Such Entry". Testing the marker first also keeps
# the probe from launching a JVM every interval until the bootstrap is through. The start
# period is what a bootstrap importing SAMPLE_DATA into a small container can take; a
# probe that succeeds ends it early, and a bootstrap that failed never writes the marker.
HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 CMD test -f "$BOOTSTRAP_COMPLETE" && opendj/bin/ldapsearch --hostname localhost --port $LDAPS_PORT --bindDN "$ROOT_USER_DN" --bindPassword "${ROOT_PASSWORD:-password}" --useSsl --trustAll --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
# search of BASE_DN still fails with "No Such Entry". healthcheck.sh tests the marker
# first, then searches the root DSE without binding as the root user, whose password the
# operator is expected to change. The start period is what a bootstrap importing
# SAMPLE_DATA into a small container can take; a probe that succeeds ends it early, and a
# bootstrap that failed never writes the marker.
HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 CMD ["/opt/opendj/healthcheck.sh"]

ENTRYPOINT ["/opt/opendj/run.sh"]
14 changes: 8 additions & 6 deletions opendj-packages/opendj-docker/Dockerfile-alpine
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ RUN apk add --update --no-cache --virtual builddeps curl unzip \
# root. The scripts copied below are only read and run, so they just keep the same group.
COPY --chown=$OPENDJ_USER:0 bootstrap/ /opt/opendj/bootstrap/
COPY --chown=$OPENDJ_USER:0 run.sh /opt/opendj/run.sh
COPY --chown=$OPENDJ_USER:0 healthcheck.sh /opt/opendj/healthcheck.sh

RUN chmod +x /opt/opendj/run.sh /opt/opendj/bootstrap/setup.sh /opt/opendj/bootstrap/replicate.sh
RUN chmod +x /opt/opendj/run.sh /opt/opendj/healthcheck.sh /opt/opendj/bootstrap/setup.sh /opt/opendj/bootstrap/replicate.sh

EXPOSE $PORT/tcp $LDAPS_PORT/tcp $ADMIN_PORT/tcp

Expand All @@ -79,10 +80,11 @@ USER $OPENDJ_USER
# "healthy" has to mean the instance is ready to serve, not just that it answers: setup
# starts the server in the middle of the bootstrap, before the backend of BASE_DN is
# created and its entries imported, so probing the root DSE alone reports ready while a
# search of BASE_DN still fails with "No Such Entry". Testing the marker first also keeps
# the probe from launching a JVM every interval until the bootstrap is through. The start
# period is what a bootstrap importing SAMPLE_DATA into a small container can take; a
# probe that succeeds ends it early, and a bootstrap that failed never writes the marker.
HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 CMD test -f "$BOOTSTRAP_COMPLETE" && opendj/bin/ldapsearch --hostname localhost --port $LDAPS_PORT --bindDN "$ROOT_USER_DN" --bindPassword "${ROOT_PASSWORD:-password}" --useSsl --trustAll --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
# search of BASE_DN still fails with "No Such Entry". healthcheck.sh tests the marker
# first, then searches the root DSE without binding as the root user, whose password the
# operator is expected to change. The start period is what a bootstrap importing
# SAMPLE_DATA into a small container can take; a probe that succeeds ends it early, and a
# bootstrap that failed never writes the marker.
HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 CMD ["/opt/opendj/healthcheck.sh"]

ENTRYPOINT ["/opt/opendj/run.sh"]
24 changes: 23 additions & 1 deletion opendj-packages/opendj-docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ without `ADD_BASE_ENTRY` nothing creates the base entry, so `BASE_DN` is an empt
a healthy container - the health check itself searches the root DSE, which every instance
serves whatever it was set up to hold.

The health check does not bind as the root user: `ROOT_PASSWORD` is only the initial root
password, and a probe binding with it would turn the container `unhealthy` once that password
is changed. It reads the root DSE anonymously instead. An instance that rejects
unauthenticated requests (`reject-unauthenticated-requests:true`) answers that search with
`53 (Unwilling to Perform)`; for such an instance set `HEALTHCHECK_BIND_DN` to an account the
probe may bind as and `HEALTHCHECK_BIND_PASSWORD_FILE` to a file in the container holding its
password - the probe reads it from there, so it never shows on a command line:

```bash
docker run -d --name opendj -v /path/to/secrets:/var/secrets/healthcheck:ro \
-e HEALTHCHECK_BIND_DN="uid=monitor,ou=people,dc=example,dc=com" \
-e HEALTHCHECK_BIND_PASSWORD_FILE=/var/secrets/healthcheck/password \
openidentityplatform/opendj
```

Images before this one probed as the root user, so an existing instance that rejects
unauthenticated requests was healthy with them. Started on this image without these two
variables, the same instance is probed anonymously and turns `unhealthy` although it serves:
set them before the upgrade.

A bootstrap that imports `SAMPLE_DATA` can take minutes on a small container, which is what
the start period allows for. A bootstrap that fails - or an upgrade that fails when starting
over an instance that is already there - never reports healthy: what failed is in `docker
Expand All @@ -56,4 +76,6 @@ turning `unhealthy` once the start period is over.
| OPENDJ_JAVA_ARGS | -server | extra instance java args |
| BACKEND_TYPE | je | OpenDJ backend type, see [dsconfig create-backend](https://doc.openidentityplatform.org/opendj/reference/dsconfig-subcommands-ref#dsconfig-create-backend) documentation |
| BACKEND_DB_DIRECTORY | db | OpenDJ `db-directory` attribute for backend |
| SETUP_ARGS | - | extra setup args |
| SETUP_ARGS | - | extra setup args |
| HEALTHCHECK_BIND_DN | - | DN the health check binds as, for an instance that rejects unauthenticated requests; unset, the health check searches the root DSE anonymously |
| HEALTHCHECK_BIND_PASSWORD_FILE | - | file in the container holding the password of `HEALTHCHECK_BIND_DN` |
23 changes: 16 additions & 7 deletions opendj-packages/opendj-docker/bootstrap/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@

echo "Setting up default OpenDJ instance"

# The tools read the root password from a file, so that it shows on no command line while they
# run, where the ps of the Docker host lists it to every user of the host. As in replicate.sh,
# mktemp creates the file readable by its owner only, on the tmpfs of /dev/shm where there is
# one, and run.sh removes a file a killed setup.sh leaves there. run.sh runs this script with
# sh, so it keeps to POSIX sh.
PASSWORD_FILE=$(mktemp -p /dev/shm opendj-setup.XXXXXX 2>/dev/null || mktemp) || exit 1
trap 'rm -f "$PASSWORD_FILE"' EXIT
printf '%s\n' "$ROOT_PASSWORD" >"$PASSWORD_FILE" || exit 1

# If any optional LDIF files are present load them

# There are multiple types of ldif files.
Expand All @@ -45,7 +54,7 @@ fi
--enableStartTLS $OPENDJ_SSL_OPTIONS \
--adminConnectorPort $ADMIN_PORT \
--rootUserDN "$ROOT_USER_DN" \
--rootUserPassword "$ROOT_PASSWORD" \
--rootUserPasswordFile "$PASSWORD_FILE" \
--acceptLicense \
--no-prompt \
--noPropertiesFile \
Expand All @@ -55,7 +64,7 @@ BACKEND_TYPE=${BACKEND_TYPE:-je}
BACKEND_DB_DIRECTORY=${BACKEND_DB_DIRECTORY:-db}
echo "creating backend: $BACKEND_TYPE db-directory: ${BACKEND_DB_DIRECTORY}"

/opt/opendj/bin/dsconfig create-backend -h localhost -p $ADMIN_PORT --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD" \
/opt/opendj/bin/dsconfig create-backend -h localhost -p $ADMIN_PORT --bindDN "$ROOT_USER_DN" --bindPasswordFile "$PASSWORD_FILE" \
--backend-name=userRoot --type $BACKEND_TYPE --set base-dn:$BASE_DN --set "db-directory:$BACKEND_DB_DIRECTORY" \
--set enabled:true --no-prompt --trustAll || exit 1

Expand All @@ -65,13 +74,13 @@ if [ "$ADD_BASE_ENTRY" = "--addBaseEntry" ]; then
echo "generating sample data..."
/opt/opendj/bin/makeldif -o $BASE_TEMPLATE -c suffix="$BASE_DN" -c numusers=$SAMPLE_DATA /opt/opendj/template/config/MakeLDIF/example.template || exit 1
/opt/opendj/bin/import-ldif --ldifFile $BASE_TEMPLATE \
--backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD" || exit 1
--backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPasswordFile "$PASSWORD_FILE" || exit 1
else
echo "creating base entry..."
BASE_TEMPLATE=$(mktemp)
echo "branch: $BASE_DN" > $BASE_TEMPLATE
/opt/opendj/bin/import-ldif --templateFile $BASE_TEMPLATE \
--backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD" || exit 1
--backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPasswordFile "$PASSWORD_FILE" || exit 1
fi
rm $BASE_TEMPLATE
fi
Expand All @@ -85,7 +94,7 @@ if [ -d /opt/opendj/bootstrap/schema/ ]; then
echo "Loading initial schema:"
for file in /opt/opendj/bootstrap/schema/*; do
echo "Loading $file ..."
/opt/opendj/bin/ldapmodify -D "$ROOT_USER_DN" -h localhost -p $PORT -w $ROOT_PASSWORD -f $file
/opt/opendj/bin/ldapmodify -D "$ROOT_USER_DN" -h localhost -p $PORT --bindPasswordFile "$PASSWORD_FILE" -f $file
done
fi

Expand All @@ -94,14 +103,14 @@ if [ -d /opt/opendj/bootstrap/data/ ]; then
/opt/opendj/bin/dsconfig \
set-password-policy-prop \
--bindDN "$ROOT_USER_DN" \
--bindPassword "$ROOT_PASSWORD" \
--bindPasswordFile "$PASSWORD_FILE" \
--policy-name "Default Password Policy" \
--set allow-pre-encoded-passwords:true \
--trustAll \
--no-prompt

for file in /opt/opendj/bootstrap/data/*; do
echo "Loading $file ..."
/opt/opendj/bin/ldapmodify -D "$ROOT_USER_DN" -h localhost -p $PORT -w $ROOT_PASSWORD -f $file --continueOnError
/opt/opendj/bin/ldapmodify -D "$ROOT_USER_DN" -h localhost -p $PORT --bindPasswordFile "$PASSWORD_FILE" -f $file --continueOnError
done
fi
44 changes: 44 additions & 0 deletions opendj-packages/opendj-docker/healthcheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions copyright [year] [name of copyright owner]".
#
# Copyright 2026 3A Systems, LLC.

# The container health check
#
# The marker comes first: run.sh writes it only once the bootstrap has succeeded, and testing
# it also keeps the probe from launching a JVM every interval until then.
#
# The probe then reads the root DSE with the attribute list 1.1, which needs no bind. It must
# not bind as the root user: ROOT_PASSWORD is only the initial root password, so the probe
# would turn the container unhealthy for good once an operator changes it, and it would put
# the password on a command line every interval. An instance configured to reject
# unauthenticated requests answers the anonymous search with 53 (Unwilling to Perform); for
# such an instance HEALTHCHECK_BIND_DN names an account to bind with, and its password is
# read from HEALTHCHECK_BIND_PASSWORD_FILE, never passed on a command line. --noPropertiesFile
# keeps a tools.properties in the user's home from turning the probe into a bind of its own.
#
# Docker reserves exit code 2, so whatever failed is reported as 1.

test -f "${BOOTSTRAP_COMPLETE:-/opt/opendj/.bootstrap-complete}" || exit 1

BIND_ARGS=()
if [ -n "${HEALTHCHECK_BIND_DN}" ]; then
if [ ! -r "${HEALTHCHECK_BIND_PASSWORD_FILE}" ]; then
echo "HEALTHCHECK_BIND_DN is set, but HEALTHCHECK_BIND_PASSWORD_FILE '${HEALTHCHECK_BIND_PASSWORD_FILE}' is not a readable file"
exit 1
fi
BIND_ARGS=(--bindDN "${HEALTHCHECK_BIND_DN}" --bindPasswordFile "${HEALTHCHECK_BIND_PASSWORD_FILE}")
fi

/opt/opendj/bin/ldapsearch --noPropertiesFile --hostname localhost --port "${LDAPS_PORT:-1636}" --useSsl --trustAll \
"${BIND_ARGS[@]}" --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
2 changes: 2 additions & 0 deletions opendj-packages/opendj-docker/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
information: "Portions Copyright [year] [name of copyright owner]".

Copyright 2018-2019 Open Identity Platform Community.
Portions Copyright 2026 3A Systems, LLC.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -81,6 +82,7 @@
<include name="../opendj-openshift-template/*.yaml" />
<include name="bootstrap/**" />
<include name="run.sh" />
<include name="healthcheck.sh" />
</fileset>
</zip>
</target>
Expand Down
7 changes: 4 additions & 3 deletions opendj-packages/opendj-docker/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ cd /opt/opendj
BOOTSTRAP_COMPLETE=${BOOTSTRAP_COMPLETE:-/opt/opendj/.bootstrap-complete}
rm -f "$BOOTSTRAP_COMPLETE"

# A replicate.sh killed before its EXIT trap ran leaves the root password in /dev/shm, and on
# Kubernetes that outlives the container: the pod keeps its /dev/shm across container restarts
rm -f /dev/shm/opendj-replicate.*
# A setup.sh or replicate.sh killed before its EXIT trap ran leaves the root password in
# /dev/shm, and on Kubernetes that outlives the container: the pod keeps its /dev/shm across
# container restarts
rm -f /dev/shm/opendj-setup.* /dev/shm/opendj-replicate.*

#if default data folder exists do not change it
if [ ! -d ./db ]; then
Expand Down
Loading