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 --build
to 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.com
to 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 ./amster
or use the web configurator wizard at http://am.example.com:8080/am to set it up, do follow the image fileconfigurator_summary_details.png
for 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_name
andhostname
to be the same Fully Qualified Domain Name (FQDN)build
must containcontext: buildfiles/ds
image
is can be anything you want to call the DS imageports
require external ports for the internal ports80, 443, 389, 646, 4444
to be configuredvolumes
will require persistent volume for the internal path/root/.opendj/
to be configurednetworks
give anyipv4_address
that is available, preferably in the same subnet.env_file
leave it as.env
environment
:
HOST
must be the same ascontainer_name
TYPE
is a list of types that can be delimited by any character, it can contain the few keywords:
Starting withdirectory
orreplication
: directory or replication-only server
Containsreplication
: to configure replication
RequiresMASTER_HOST
andMASTER_ROOT_PASS
environment variables set to the FQDN and root password of the DS you wish to replicate this instance with.
RequiresSLAVE_HOST
environment variable to be the same asHOST
Containscts
ortokens
: setup using Core Token Service (CTS) profile
RequiresDS_CTS_PASS
environment variable to set the CTS data store admin password
Containscfg
orconfig
: setup using AM configuration profile
RequiresDS_CONFIG_PASS
environment variable to set the config data store admin password
Containsids
oruser
: setup using identity store profile
RequiresDS_IDS_PASS
environment variable to set the config data
DS_ROOT_PASS
is the root usercn=Directory Manager
's password
DS_MONITOR_PASS
is the monitor user's password
i.e.directory-replication-cts-cfg-ids
Access Manager
container_name
andhostname
to be the same Fully Qualified Domain Name (FQDN)build
must containcontext: buildfiles/am
image
is can be anything you want to call the AM imageports
require external ports for the internal ports8080
to be configuredvolumes
will require persistent volume for the internal path/root/am and/root/.openamcfg
to be configured./tmp/treenodes
is optional for additional tree nodes.networks
give anyipv4_address
that is available, preferably in the same subnet.env_file
leave it as.env
environment
:
DIRECTORIES
a 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.out
bootstrap.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"* ]]; then
directory.
# 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"* ]]; then
replication
# 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"* ]]; then
replication
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/null
Disclaimer
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.