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
202 changes: 188 additions & 14 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -523,17 +523,28 @@ jobs:
shell: bash
run: |
trap 'code=$?; echo "::group::container logs (test)"; docker logs test 2>&1 || true; echo "::endgroup::"; exit $code' ERR
docker run --rm -it -d --memory="512m" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
docker run -it -d --memory="512m" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done'
docker exec test 'sh' '-c' '/opt/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com /opt/opendj/data/config/MakeLDIF/example.template'
docker exec test 'sh' '-c' '/opt/opendj/bin/stop-ds'
docker exec test 'sh' '-c' '/opt/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2'
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/start-ds'
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/import-ldif --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --ldifFile /tmp/test.ldif --backendID=example2 --trustAll'
# the server is PID 1 of the container, so stopping it stops the container; the
# container that bootstrapped the instance has to stop the server on SIGTERM, not
# sit out the timeout and be killed. The server run in the foreground reports its
# shutdown only in the error log, which is read once the container is back up
stopped=$(docker exec test grep -c "The Directory Server is now stopped" /opt/opendj/data/logs/errors || true)
start=$SECONDS
docker stop -t 60 test
echo "stopped in $((SECONDS - start)) s, exit code $(docker inspect --format='{{.State.ExitCode}}' test)"
test $((SECONDS - start)) -lt 50
test "$(docker inspect --format='{{.State.ExitCode}}' test)" -ne 137
# a restart runs the server of the instance already there
docker start test
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done'
test "$(docker exec test grep -c "The Directory Server is now stopped" /opt/opendj/data/logs/errors)" -gt "$stopped"
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000'
docker kill test
docker rm -f test
- name: Docker test custom password
shell: bash
run: |
Expand Down Expand Up @@ -603,6 +614,82 @@ jobs:
if [ -n "$left" ]; then echo "::error::The root password is left in $left of $c"; false; fi
done
cleanup
- name: Docker test secret volume
# a keystore mounted at SECRET_VOLUME is what LDAPS serves from the first start on,
# a renewed one reaches the data volume while the server runs and is served after a
# restart, and the server is PID 1 of the container, stopping on SIGTERM (#1087, #1085);
# one with a new password is left by the watcher to the next start, which copies it
# over the instance already there, and SECRET_VOLUME_REFRESH=0 runs no watcher
shell: bash
env:
IMAGE: localhost:5000/${{ env.image_repository }}:${{ env.release_version }}
run: |
set -E
trap 'code=$?; echo "::group::container logs (test_secret)"; docker logs test_secret 2>&1 || true; echo "::endgroup::"; exit $code' ERR
SECRETS=$(mktemp -d)
chmod 777 "$SECRETS"
# a key under an alias of its own for each version, since setup binds no connection
# handler to an alias, and a truststore holding its certificate; the passwords are
# written first, as the watcher looks at them before it copies anything
keystore() {
local pass=${2:-changeit}
docker run --rm --entrypoint keytool -v "$SECRETS":/secrets "$IMAGE" -genkeypair -alias "$1" \
-keyalg RSA -keysize 2048 -validity 30 -dname "CN=$1" -storetype PKCS12 \
-keystore /secrets/keystore.new -storepass "$pass" -keypass "$pass"
docker run --rm --entrypoint keytool -v "$SECRETS":/secrets "$IMAGE" -exportcert -rfc -alias "$1" \
-keystore /secrets/keystore.new -storepass "$pass" -file /secrets/cert.pem
rm -f "$SECRETS/truststore.new"
docker run --rm --entrypoint keytool -v "$SECRETS":/secrets "$IMAGE" -importcert -noprompt -alias "$1" \
-file /secrets/cert.pem -keystore /secrets/truststore.new -storetype JKS -storepass changeit
rm -f "$SECRETS/cert.pem"
printf %s "$pass" > "$SECRETS/keystore.pin"
printf changeit > "$SECRETS/truststore.pin"
mv -f "$SECRETS/truststore.new" "$SECRETS/truststore"
mv -f "$SECRETS/keystore.new" "$SECRETS/keystore"
}
served() { echo | openssl s_client -connect 127.0.0.1:1637 2>/dev/null | openssl x509 -noout -subject -nameopt RFC2253; }
healthy() { timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_secret | grep -q \"healthy\"; do sleep 10; done'; }
copies() { docker logs test_secret 2>&1 | grep -c "^Copied $1 from the secret volume$" || true; }
keystore secret-v1
docker run -d --memory="512m" -e SECRET_VOLUME_REFRESH=5 -p 127.0.0.1:1637:1636 --name=test_secret \
-v "$SECRETS":/var/secrets/opendj:ro -v test_secret_data:/opt/opendj/data "$IMAGE"
healthy
grep -q "CN=secret-v1" <<< "$(served)"
test "$(docker exec test_secret cat /proc/1/comm)" = java
test "$(docker exec test_secret stat -c %a /opt/opendj/data/config/keystore.pin)" = 600
docker exec test_secret cmp -s /var/secrets/opendj/truststore /opt/opendj/data/config/truststore
# the bootstrap's server is stopped before the instance is marked bootstrapped
test "$(docker logs test_secret 2>&1 | grep -e '^Stopping Server' -e '^The instance is bootstrapped' | paste -sd '|' -)" \
= "Stopping Server...|The instance is bootstrapped, the health check may probe it"
keystore secret-v2
timeout 1m bash -c 'until docker exec test_secret cmp -s /var/secrets/opendj/keystore /opt/opendj/data/config/keystore; do sleep 5; done'
# the watcher has looked at the volume every 5 s since the start, and copied the
# keystore only when it changed: on the start and once more for v2
sleep 6
test "$(copies keystore)" = 2
start=$SECONDS
docker stop -t 60 test_secret
test $((SECONDS - start)) -lt 30
test "$(docker inspect --format='{{.State.ExitCode}}' test_secret)" = 143
docker start test_secret
healthy
grep -q "CN=secret-v2" <<< "$(served)"
keystore secret-v3 changeit2
sleep 12
docker exec test_secret sh -c '! cmp -s /var/secrets/opendj/keystore /opt/opendj/data/config/keystore'
docker stop -t 60 test_secret
docker rm test_secret
docker run -d --memory="512m" -e SECRET_VOLUME_REFRESH=0 -p 127.0.0.1:1637:1636 --name=test_secret \
-v "$SECRETS":/var/secrets/opendj:ro -v test_secret_data:/opt/opendj/data "$IMAGE"
healthy
grep -q "CN=secret-v3" <<< "$(served)"
# no watcher, whose sleep is nearly always there, and nothing copied while the server runs
docker exec test_secret sh -c '! grep -sqx sleep /proc/[0-9]*/comm'
keystore secret-v4 changeit2
sleep 15
docker exec test_secret sh -c '! cmp -s /var/secrets/opendj/keystore /opt/opendj/data/config/keystore'
docker rm -f test_secret
docker volume rm test_secret_data
- name: Scan image for vulnerabilities (Trivy)
# trivy resolves the image from the local Docker daemon, so only the runner's
# linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from
Expand Down Expand Up @@ -736,17 +823,28 @@ jobs:
shell: bash
run: |
trap 'code=$?; echo "::group::container logs (test)"; docker logs test 2>&1 || true; echo "::endgroup::"; exit $code' ERR
docker run --rm -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine
docker run -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done'
docker exec test 'sh' '-c' '/opt/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com /opt/opendj/data/config/MakeLDIF/example.template'
docker exec test 'sh' '-c' '/opt/opendj/bin/stop-ds'
docker exec test 'sh' '-c' '/opt/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2'
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/start-ds'
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/import-ldif --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --ldifFile /tmp/test.ldif --backendID=example2 --trustAll'
# the server is PID 1 of the container, so stopping it stops the container; the
# container that bootstrapped the instance has to stop the server on SIGTERM, not
# sit out the timeout and be killed. The server run in the foreground reports its
# shutdown only in the error log, which is read once the container is back up
stopped=$(docker exec test grep -c "The Directory Server is now stopped" /opt/opendj/data/logs/errors || true)
start=$SECONDS
docker stop -t 60 test
echo "stopped in $((SECONDS - start)) s, exit code $(docker inspect --format='{{.State.ExitCode}}' test)"
test $((SECONDS - start)) -lt 50
test "$(docker inspect --format='{{.State.ExitCode}}' test)" -ne 137
# a restart runs the server of the instance already there
docker start test
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done'
test "$(docker exec test grep -c "The Directory Server is now stopped" /opt/opendj/data/logs/errors)" -gt "$stopped"
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll'
docker exec test 'sh' '-c' '/opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000'
docker kill test
docker rm -f test
- name: Docker test custom password
shell: bash
run: |
Expand Down Expand Up @@ -816,6 +914,82 @@ jobs:
if [ -n "$left" ]; then echo "::error::The root password is left in $left of $c"; false; fi
done
cleanup
- name: Docker test secret volume
# a keystore mounted at SECRET_VOLUME is what LDAPS serves from the first start on,
# a renewed one reaches the data volume while the server runs and is served after a
# restart, and the server is PID 1 of the container, stopping on SIGTERM (#1087, #1085);
# one with a new password is left by the watcher to the next start, which copies it
# over the instance already there, and SECRET_VOLUME_REFRESH=0 runs no watcher
shell: bash
env:
IMAGE: localhost:5000/${{ env.image_repository }}:${{ env.release_version }}-alpine
run: |
set -E
trap 'code=$?; echo "::group::container logs (test_secret)"; docker logs test_secret 2>&1 || true; echo "::endgroup::"; exit $code' ERR
SECRETS=$(mktemp -d)
chmod 777 "$SECRETS"
# a key under an alias of its own for each version, since setup binds no connection
# handler to an alias, and a truststore holding its certificate; the passwords are
# written first, as the watcher looks at them before it copies anything
keystore() {
local pass=${2:-changeit}
docker run --rm --entrypoint keytool -v "$SECRETS":/secrets "$IMAGE" -genkeypair -alias "$1" \
-keyalg RSA -keysize 2048 -validity 30 -dname "CN=$1" -storetype PKCS12 \
-keystore /secrets/keystore.new -storepass "$pass" -keypass "$pass"
docker run --rm --entrypoint keytool -v "$SECRETS":/secrets "$IMAGE" -exportcert -rfc -alias "$1" \
-keystore /secrets/keystore.new -storepass "$pass" -file /secrets/cert.pem
rm -f "$SECRETS/truststore.new"
docker run --rm --entrypoint keytool -v "$SECRETS":/secrets "$IMAGE" -importcert -noprompt -alias "$1" \
-file /secrets/cert.pem -keystore /secrets/truststore.new -storetype JKS -storepass changeit
rm -f "$SECRETS/cert.pem"
printf %s "$pass" > "$SECRETS/keystore.pin"
printf changeit > "$SECRETS/truststore.pin"
mv -f "$SECRETS/truststore.new" "$SECRETS/truststore"
mv -f "$SECRETS/keystore.new" "$SECRETS/keystore"
}
served() { echo | openssl s_client -connect 127.0.0.1:1637 2>/dev/null | openssl x509 -noout -subject -nameopt RFC2253; }
healthy() { timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_secret | grep -q \"healthy\"; do sleep 10; done'; }
copies() { docker logs test_secret 2>&1 | grep -c "^Copied $1 from the secret volume$" || true; }
keystore secret-v1
docker run -d --memory="1g" -e SECRET_VOLUME_REFRESH=5 -p 127.0.0.1:1637:1636 --name=test_secret \
-v "$SECRETS":/var/secrets/opendj:ro -v test_secret_data:/opt/opendj/data "$IMAGE"
healthy
grep -q "CN=secret-v1" <<< "$(served)"
test "$(docker exec test_secret cat /proc/1/comm)" = java
test "$(docker exec test_secret stat -c %a /opt/opendj/data/config/keystore.pin)" = 600
docker exec test_secret cmp -s /var/secrets/opendj/truststore /opt/opendj/data/config/truststore
# the bootstrap's server is stopped before the instance is marked bootstrapped
test "$(docker logs test_secret 2>&1 | grep -e '^Stopping Server' -e '^The instance is bootstrapped' | paste -sd '|' -)" \
= "Stopping Server...|The instance is bootstrapped, the health check may probe it"
keystore secret-v2
timeout 1m bash -c 'until docker exec test_secret cmp -s /var/secrets/opendj/keystore /opt/opendj/data/config/keystore; do sleep 5; done'
# the watcher has looked at the volume every 5 s since the start, and copied the
# keystore only when it changed: on the start and once more for v2
sleep 6
test "$(copies keystore)" = 2
start=$SECONDS
docker stop -t 60 test_secret
test $((SECONDS - start)) -lt 30
test "$(docker inspect --format='{{.State.ExitCode}}' test_secret)" = 143
docker start test_secret
healthy
grep -q "CN=secret-v2" <<< "$(served)"
keystore secret-v3 changeit2
sleep 12
docker exec test_secret sh -c '! cmp -s /var/secrets/opendj/keystore /opt/opendj/data/config/keystore'
docker stop -t 60 test_secret
docker rm test_secret
docker run -d --memory="1g" -e SECRET_VOLUME_REFRESH=0 -p 127.0.0.1:1637:1636 --name=test_secret \
-v "$SECRETS":/var/secrets/opendj:ro -v test_secret_data:/opt/opendj/data "$IMAGE"
healthy
grep -q "CN=secret-v3" <<< "$(served)"
# no watcher, whose sleep is nearly always there, and nothing copied while the server runs
docker exec test_secret sh -c '! grep -sqx sleep /proc/[0-9]*/comm'
keystore secret-v4 changeit2
sleep 15
docker exec test_secret sh -c '! cmp -s /var/secrets/opendj/keystore /opt/opendj/data/config/keystore'
docker rm -f test_secret
docker volume rm test_secret_data
- name: Scan image for vulnerabilities (Trivy)
# trivy resolves the image from the local Docker daemon, so only the runner's
# linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from
Expand Down
3 changes: 2 additions & 1 deletion opendj-packages/opendj-docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ ENV BASE_DN="dc=example,dc=com"
ENV ROOT_USER_DN="cn=Directory Manager"
# ROOT_PASSWORD should be passed at runtime via: docker run -e ROOT_PASSWORD=...
# Default value if not provided: "password"
#ENV SECRET_VOLUME
#ENV SECRET_VOLUME="/var/secrets/opendj"
#ENV SECRET_VOLUME_REFRESH=60
ENV OPENDJ_SSL_OPTIONS="--generateSelfSignedCertificate"
#ENV MASTER_SERVER
#ENV OPENDJ_REPLICATION_TYPE
Expand Down
3 changes: 2 additions & 1 deletion opendj-packages/opendj-docker/Dockerfile-alpine
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ ENV BASE_DN="dc=example,dc=com"
ENV ROOT_USER_DN="cn=Directory Manager"
# ROOT_PASSWORD should be passed at runtime via: docker run -e ROOT_PASSWORD=...
# Default value if not provided: "password"
#ENV SECRET_VOLUME
#ENV SECRET_VOLUME="/var/secrets/opendj"
#ENV SECRET_VOLUME_REFRESH=60
ENV OPENDJ_SSL_OPTIONS="--generateSelfSignedCertificate"
#ENV MASTER_SERVER
#ENV OPENDJ_REPLICATION_TYPE
Expand Down
Loading
Loading