How to Quickly Bootstrap a Development Environment for ForgeRock Access Manager (AM) using Docker Compose
This post will guide you through on how one of our employees set up an entire ForgeRock AM development environment connecting to replicated external Directory Services (DS) and a resource to protect with Apache web agent on different containers with the help of Docker and Docker Compose. This can help simulate a production environment where there is separation of services on different servers.
Setup
- Ensure Docker and Docker Compose is installed on your machine, instructions to install it can be located on the Docker website at https://docs.docker.com/compose/install/
- Pull the source codes from our GitHub at: https://github.com/Nebulas-Tree/ForgeRock-Docker-Compose
- Download the official Access Manager (AM) WAR file from ForgeRock at https://backstage.forgerock.com/downloads/browse/am/latest and place it at
[Path to Source Codes] -> buildfiles -> am -> AM.war - Download the official Amster ZIP file from ForgeRock from the same link as step 2 and place it at
[Path to Source Codes] -> buildfiles -> am -> Amster.zip - Download the official Linux Apache Web Agent (WA) from https://backstage.forgerock.com/downloads/browse/am/latest/web-agents and place it at
[Path to Source Codes] -> buildfiles -> httpd -> web-agent.zip - Download the official Directory Services (DS) ZIP file ForgeRock at https://backstage.forgerock.com/downloads/browse/ds/latest and place it at
[Path to Source Codes] -> buildfiles -> ds -> DS.zip - Run
docker-compose up --buildto start building the environment and eventually it will start. - Set your computer's local hosts file to point
am.example.com,cts.example.com,cts2.example.com,cfg1.example.com,cfg2.example.comto your localhost127.0.0.1. - Run this command if you wish to use Amster to bootstrap the initial configuration:
docker exec am.example.com ./bootstrap.sh (desired AMAdmin password) cfg.example.com. Alternatively, you can run Amster interactively using:docker exec -it am.example.com ./amsteror use the web configurator wizard at http://am.example.com:8080/am to set it up, do follow the image fileconfigurator_summary_details.pngfor the values for configuration. - Finally, you can access AM at http://am.example.com:8080/am and tinker away.
Docker-Compose guide
.env file
DS_BASEDN: the base DN of all DS profiles
Directory Service
container_nameandhostnameto be the same Fully Qualified Domain Name (FQDN)buildmust containcontext: buildfiles/dsimageis can be anything you want to call the DS imageportsrequire external ports for the internal ports80, 443, 389, 646, 4444to be configuredvolumeswill require persistent volume for the internal path/root/.opendj/to be configurednetworksgive anyipv4_addressthat is available, preferably in the same subnet.env_fileleave it as.envenvironment:
HOSTmust be the same ascontainer_name
TYPEis a list of types that can be delimited by any character, it can contain the few keywords:
Starting withdirectoryorreplication: directory or replication-only server
Containsreplication: to configure replication
RequiresMASTER_HOSTandMASTER_ROOT_PASSenvironment variables set to the FQDN and root password of the DS you wish to replicate this instance with.
RequiresSLAVE_HOSTenvironment variable to be the same asHOST
Containsctsortokens: setup using Core Token Service (CTS) profile
RequiresDS_CTS_PASSenvironment variable to set the CTS data store admin password
Containscfgorconfig: setup using AM configuration profile
RequiresDS_CONFIG_PASSenvironment variable to set the config data store admin password
Containsidsoruser: setup using identity store profile
RequiresDS_IDS_PASSenvironment variable to set the config data
DS_ROOT_PASSis the root usercn=Directory Manager's password
DS_MONITOR_PASSis the monitor user's password
i.e.directory-replication-cts-cfg-ids
Access Manager
container_nameandhostnameto be the same Fully Qualified Domain Name (FQDN)buildmust containcontext: buildfiles/amimageis can be anything you want to call the AM imageportsrequire external ports for the internal ports8080to be configuredvolumeswill require persistent volume for the internal path/root/am and/root/.openamcfgto be configured./tmp/treenodesis optional for additional tree nodes.networksgive anyipv4_addressthat is available, preferably in the same subnet.env_fileleave it as.envenvironment:
DIRECTORIESa list of FQDN and port of DS LDAPS ports delimited by a space to automatically query and trust certificates with.
Explanation of Shell Scripts
Access Manager
docker.sh
The shell script the Docker container will initialize with.
# Setup fake group
getent group fakegroup >/dev/null || addgroup --gid ${GID:-1000} fakegroup && chgrp -R fakegroup /root && chmod -R g=rXs /root# Copy all custom treenodes into AM
cp -urf /tmp/treenodes/* ${CATALINA_HOME}/webapps/am/WEB-INF/lib/[Path to Source Codes] -> volumes -> am -> treenodes to AM itself.# Trust all directory certificates specified in DIRECTORIES with space as delimiter
IFS=' '
read -ra ARR_DIRECTORIES <<< "${DIRECTORIES}"
for DIRECTORY in "${ARR_DIRECTORIES[@]}"; do
until openssl s_client -connect "${DIRECTORY}" -showcerts >/dev/null 2>/dev/null; do
>&2 echo "Waiting for ${DIRECTORY} to start..."
sleep 5
done
echo "" | openssl s_client -connect "${DIRECTORY}" -showcerts 2>/dev/null | openssl x509 -out /tmp/cert
keytool -importcert -alias "${DIRECTORY}" -file /tmp/cert -trustcacerts -keystore ${JAVA_HOME}/jre/lib/security/cacerts -storetype JKS -storepass changeit -noprompt
echo -e "\t- ${DIRECTORY}"
done# Show bootstrap.sh guide if not set up
if [[ ! -f /root/am/install.log ]]; then
echo `
until [[ "$(cat ${CATALINA_HOME}/logs/catalina.out | grep 'org.apache.catalina.startup.Catalina.start')" != "" ]];
do
sleep 5
done
echo -e "\n\nRun 'docker exec ${HOSTNAME} ./bootstrap.sh (PASSWORD) (DS/CFG HOSTNAME) (IDS HOSTNAME [OPTIONAL])' to quickly bootstrap using Amster."
` & 2>/dev/null
fi# Start Tomcat and AM
startup.sh
tail -f ${CATALINA_HOME}/logs/catalina.outbootstrap.sh
Used to quickly configure a basic AM instance using Amster.
# Check number of variables
if [[ $# -lt 2 ]]; then
echo "Usage: $0 (PASSWORD) (DS/CFG HOSTNAME) (IDS HOSTNAME [OPTIONAL])"
exit 1
fi# Execute Amster install-openam
./amster <<< "install-openam \
--serverUrl http://${HOSTNAME}:8080/am \
--adminPwd $1 \
--acceptLicense \
--cfgDir /root/am \
--cfgStore dirServer \
--cfgStoreHost $2 \
--cfgStorePort 636 \
--cfgStoreDirMgr uid=am-config,ou=admins,ou=am-config,${DS_BASEDN} \
--cfgStoreRootSuffix ou=am-config,${DS_BASEDN} \
--cfgStoreSsl SSL \
--userStoreType LDAPv3ForOpenDS \
--userStoreHost ${3:-$2} \
--userStorePort 636 \
--userStoreDirMgr uid=am-identity-bind-account,ou=admins,ou=identities,${DS_BASEDN} \
--userStoreRootSuffix ou=identities,${DS_BASEDN} \
--userStoreDirMgrPwd $1 \
--userStoreSsl SSL
:exit"setenv.sh
CATALINA_OPTS="-server -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:+UseConcMarkSweepGC"
CATALINA_PID="$CATALINA_BASE/bin/catalina.pid"Directory Service
docker.sh
if [[ ! -f /root/.opendj/instance.loc ]]; then
rm -f instance.loc if [[ "${TYPE}" == "directory"* ]]; thendirectory. # Directory server
params+=(
directory-server \
--instancePath "/root/.opendj" \
--monitorUserDn "uid=Monitor" \
--monitorUserPassword ${DS_MONITOR_PASS} \
--ldapPort 389 \
--enableStartTls \
--ldapsPort 636 \
--httpPort 80 \
--httpsPort 443 \
) # Config store
[[ "${TYPE}" == *"cfg"* || "${TYPE}" == *"config"* ]] && params+=(
--profile am-config \
--set am-config/amConfigAdminPassword:${DS_CONFIG_PASS} \
--set am-config/baseDn:ou=am-config,${DS_BASEDN} \
)
# CTS
[[ "${TYPE}" == *"cts"* || "${TYPE}" == *"tokens"* ]] && params+=(
--profile am-cts \
--set am-cts/amCtsAdminPassword:${DS_CTS_PASS} \
--set am-cts/baseDn:ou=tokens,${DS_BASEDN} \
)
# User store
[[ "${TYPE}" == *"ids"* || "${TYPE}" == *"user"* ]] && params+=(
--profile am-identity-store \
--set am-identity-store/amIdentityStoreAdminPassword:${DS_IDS_PASS} \
--set am-identity-store/baseDn:ou=identities,${DS_BASEDN} \
)cfg, config, cts, tokens, ids, user, use profiles feature to quickly set up the respective keyword store. elif [[ "${TYPE}" == "replication"* ]]; thenreplication # Replication only
params+=(
replication-server \
--replicationPort 8989 \
) # Common parameters
params+=(
--rootUserDn "cn=Directory Manager" \
--rootUserPassword ${DS_ROOT_PASS} \
--hostname ${HOSTNAME} \
--adminConnectorPort 4444 \
--productionMode \
--acceptLicense \
--doNotStart
)
./setup "${params[@]}"
echo "./setup ${params[@]}" # Tweaks and hardening
bin/dsconfig set-password-policy-prop \
--offline \
--policy-name "Default Password Policy" \
--set skip-validation-for-administrators:true \
--no-prompt bin/dsconfig set-connection-handler-prop \
--offline \
--handler-name LDAPS \
--set ssl-protocol:TLSv1.2 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA \
--no-prompt
bin/dsconfig set-administration-connector-prop \
--offline \
--set ssl-protocol:TLSv1.2 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA \
--no-prompt
bin/dsconfig set-crypto-manager-prop \
--offline \
--set ssl-protocol:TLSv1.2 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 \
--set ssl-cipher-suite:TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA \
--set ssl-cipher-suite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA \
--no-prompt cp instance.loc /root/.opendj/instance.loc# Start DS
bin/start-ds --noDetach &# Setup replication
if [[ "${TYPE}" == *"replication"* ]]; thenreplication exists anywhere in the TYPE environment variable until bin/status -h ${MASTER_HOST} -p 4444 --bindPassword ${MASTER_ROOT_PASS} -s --trustAll --no-prompt > /dev/null; do
>&2 echo "Waiting for master to start..."
sleep 5
done params=(
configure \
--host1 ${MASTER_HOST} \
--port1 4444 \
--bindDn1 "cn=Directory Manager" \
--bindPassword1 ${MASTER_ROOT_PASS} \
--host2 ${SLAVE_HOST:-$HOSTNAME} \
--port2 4444 \
--bindDn2 "cn=Directory Manager" \
--bindPassword2 ${DS_ROOT_PASS} \
--replicationPort1 8989 \
--secureReplication1 \
--replicationPort2 8989 \
--secureReplication2 \
--baseDn ou=am-config,${DS_BASEDN} \
--baseDn ou=identities,${DS_BASEDN} \
--baseDn ou=tokens,${DS_BASEDN} \
--baseDn uid=Monitor \
--adminUid "cn=Directory Manager" \
--adminPassword ${MASTER_ROOT_PASS} \
--no-prompt --trustAll
)
bin/dsreplication "${params[@]}"
echo "bin/dsreplication ${params[@]}" params=(
initialize \
--hostSource ${MASTER_HOST} \
--portSource 4444 \
--hostDestination ${SLAVE_HOST:-$HOST} \
--portDestination 4444 \
--baseDn ou=am-config,${DS_BASEDN} \
--baseDn ou=identities,${DS_BASEDN} \
--baseDn ou=tokens,${DS_BASEDN} \
--baseDn uid=Monitor \
--adminUid "cn=Directory Manager" \
--adminPassword ${MASTER_ROOT_PASS} \
--no-prompt --trustAll
)
bin/dsreplication "${params[@]}"
echo "bin/dsreplication ${params[@]}"# Setup fake group
getent group fakegroup >/dev/null || addgroup --gid ${GID:-1000} fakegroup && chgrp -R fakegroup /root && chmod -R g=rXs /root# Keep Docker container alive
tail -f /dev/null/dev/nullDisclaimer
The document and content made available by Nebulas Tree in no way conveys any right, title, interest or license in any intellectual property rights (including but not limited to patents, copyrights, trade secrets or trademarks) contained herein. Nebulas Tree reserves the right to vary the terms of the document and content in response to changes to the specifications or information made available to Nebulas Tree.
Nebulas Tree does not assume liability for any errors or omissions in the content of this document or any referenced or associated third party document, including, but not limited to, typographical errors, inaccuracies or outdated information. This document and all information within it are provided on an "as is" basis without any warranties of any kind, express or implied. Any communication required or permitted in terms of this document shall be valid and effective only if submitted in writing. Reliance of any information provided herein this document is solely at your own risk.