Skip to main content
Broadpin

SSO and MFA for Oracle E-Business Suite with Entra ID, OCI IAM and the EBS Asserter, Part 3: Going Live and Surviving the Clone

This blog post was written by Johannes Michler (Chief Technology Officer (CTO) @ Broadpin & Oracle Ace Director).

Part 1 covered the architecture, Part 2 the setup of the containerized EBS Asserter. In this third post we put it in front of real users: the OCI Load Balancer configuration that serves the Asserter under the EBS hostname, the profile options that enforce SSO without locking anyone out, and, in my view the most valuable piece of the whole project, the automation that makes the Asserter survive EBS clones without a single manual step.

The series:

SSO and MFA for Oracle E-Business Suite with Entra ID, OCI IAM and the EBS Asserter, Part 2: Setting Up the Asserter

Step 1: Serving the Asserter Under the EBS Hostname

I found it easiest if the Asserter is reachable under the same hostname as E-Business Suite itself (https://ebs.example.com:4443/ebs). Since our EBS environments already sit behind an OCI Load Balancer, this is a routing exercise rather than an infrastructure project.

Backend set

Create a new backend set per environment (e.g. DEV-ASSERTER) with the Docker host and the environment's Asserter port as its only backend, for example dockerhost:7501. For the health check, an HTTP check against /ebs/about with a response body regex does the job:

.*(Asserter for E-Business Suite).*

That string is the title of the Asserter landing page you saw in the Part 2 smoke test. If the container is down, unhealthy or still deploying, the load balancer takes the backend out of rotation instead of presenting users a WebLogic error page.

Security lists

Two rules are needed: an egress rule on the load balancer subnet's security list towards the Docker host's Asserter port, and a matching ingress rule on the Docker host's security list with the LB subnet as source. Nothing else on the Docker host needs to be reachable from the LB.

Routing policy

On the existing HTTPS listener of the environment, attach a routing policy that sends path /ebs* to the new Asserter backend set, while everything else continues to the EBS apps tier backend set as before. From the user's perspective there is exactly one URL; the split into two backends is invisible.

Step 2: Enforcing SSO via Profile Options

Three profile options control the switch. Set them in this order, and read the whole section before touching production.

  1. Application Authenticate Agent, set on server level, pointing to the Asserter: https://ebs.example.com:4443/ebs. Unauthenticated requests are now redirected to the Asserter instead of the local login page.

  2. Applications SSO Type, from SSWA to SSWA w/SSO.

  3. Applications SSO Login Types, set to SSO. This is the lockdown step: it disables password login via AppsLocalLogin.jsp entirely.

Why server level matters: selective SSO

Setting Application Authenticate Agent on server level rather than site level is not an accident. Our external apps tier node serves iSupplier to external business partners who do not exist in Entra ID, and it keeps running with local EBS logins (for now). Only the internal node redirects to the Asserter. The Oracle FAQ describes exactly this pattern for the iSupplier scenario, and it has worked flawlessly for us in production.

Don't lock yourself out

Once Applications SSO Login Types is set to SSO, nobody logs in with an EBS password anymore. Two safety nets before you flip that switch:

  • For selected IT accounts, set Applications SSO Login Types to Both on user level. These break-glass accounts can still use AppsLocalLogin.jsp if the SSO chain (Asserter, IAM domain or Entra ID) is ever unavailable.

  • Verify the e-mail addresses one last time. Every user whose EBS e-mail address is missing, wrong, or not unique (see the SQL check in Part 2) is effectively locked out the moment SSO becomes mandatory. Run the check, fix the data, then enforce.

With that, the environment is live: users hit the EBS URL, authenticate at Microsoft including MFA, and land on their EBS homepage. Time to talk about what happens three weeks later, when the environment gets cloned.

Step 3: The Clone Problem

E-Business Suite test environments get refreshed from production regularly; in our case fully automated via the EBS Cloud Manager. And after the very first refresh, SSO on the cloned environment was broken.

The reason sits in a single line of the DBC file: APPL_SERVER_ID. This identifier is regenerated when the DBC file is created for the target environment, and the Asserter carries a copy of it in bridge.properties (app.serverid). EBS validates that the ID presented by the Asserter matches its own registration. After a clone, the two no longer match: the container starts fine, the health check is green, but every login attempt fails.

So after every clone, three things must happen: regenerate the DBC file on the new apps tier, patch app.serverid inside the WAR, and redeploy. Doing this manually is a recipe for a Monday morning ticket. We automated it end to end.

Step 4: The Post-Clone Automation

The automation is a chain of three small scripts, triggered by the Cloud Manager's post-clone hook. All hostnames below are anonymized; adapt paths and names to your environment.

Hook in the post-clone script (Cloud Manager host)

Our generic post-clone script simply calls the Asserter deployment for the environments that have SSO enabled:

# EBS Asserter; only for DEV and QAT
if [ "$SID" = "DEV" ] || [ "$SID" = "QAT" ]; then
  deploy_ebs_asserter.sh $IP_APPS $SID
fi

deploy_ebs_asserter.sh (Cloud Manager host)

The orchestrator: create a fresh DBC file on the apps tier, ship it to the Docker host, and trigger the redeployment there.

#!/bin/bash
IP_APPS=$1
SID=$2
ssh $IP_APPS "~/createEbsAsserterDBC.sh"
scp $IP_APPS:~/${SID}_EBSASSERTER.EXAMPLE.COM.dbc .
scp ${SID}_EBSASSERTER.EXAMPLE.COM.dbc opc@dockerhost:/tmp/
rm ${SID}_EBSASSERTER.EXAMPLE.COM.dbc
ssh opc@dockerhost "sudo /root/ebs_asserter/update_environment.sh $SID"

createEbsAsserterDBC.sh (EBS apps tier)

Regenerates the DBC file for the Asserter node, then fixes the database hostname. The clone source is production, so the freshly generated file still references the production DB host; a sed replaces it with the host of the cloned environment (derived from the SID):

#!/bin/bash
source /u01/install/APPS/EBSapps.env run
java oracle.apps.fnd.security.AdminDesktop apps/$APPS_PWD CREATE \
  NODE_NAME=ebsasserter.example.com DBC=$FND_SECURE/$TWO_TASK.dbc

cp ${TWO_TASK}_EBSASSERTER.EXAMPLE.COM.dbc \ ${TWO_TASK}_EBSASSERTER.EXAMPLE.COM.dbc.org

sed -i -e 's/prodebsdb/'${TWO_TASK,,}'ebsdb/g'  \ ${TWO_TASK}_EBSASSERTER.EXAMPLE.COM.dbc

update_environment.sh (Docker host)

The heart of the automation. It extracts the new APPL_SERVER_ID from the DBC file, patches bridge.properties, rebuilds the WAR from a pristine copy, and runs the exact deployment sequence from Part 2, health checks included:

#!/bin/bash
SID=$1
SID_LOWER=${SID,,}
COMPOSE_DIR=/root/ebs_asserter/${SID_LOWER}/
CONTAINER_HOME=/root/ebs_asserter/home_oracle_${SID_LOWER}
CONTAINER_NAME=ebs_asserter_${SID_LOWER}

echo "Patching environment $SID"
cd $COMPOSE_DIR
docker compose down

# 1. Move the fresh DBC file into place
DBC_FILE=${CONTAINER_HOME}/${SID}_EBSASSERTER.EXAMPLE.COM.dbc
mv /tmp/${SID}_EBSASSERTER.EXAMPLE.COM.dbc $DBC_FILE
chmod 644 $DBC_FILE

# 2. Extract the new APPL_SERVER_ID and patch bridge.properties
NEW_APPL_SERVER_ID=$(sed -n 's/^APPL_SERVER_ID=//p' "$DBC_FILE" | tr -d '\r' | head -n1)
echo "NEW_APPL_SERVER_ID: $NEW_APPL_SERVER_ID"
sed -i "s/^app\.serverid=.*/app.serverid=${NEW_APPL_SERVER_ID}/" \
  "${CONTAINER_HOME}/WEB-INF/bridge.properties"

# 3. Rebuild ebs.war from a pristine copy
cd $CONTAINER_HOME
cp ../software_ebsasserter/build/libs/ebs.war .
zip -u ebs.war WEB-INF/*

# 4. Start the container and wait until healthy
cd $COMPOSE_DIR
docker compose up -d --wait
until [ "$(docker inspect -f '{{.State.Health.Status}}' ${CONTAINER_NAME})" = "healthy" ]; do
  echo "waiting for ebs_asserter container ${CONTAINER_NAME}..."
  sleep 2
done
sleep 5

# 5. fndext.jar into the domain lib, restart, wait again
docker exec $CONTAINER_NAME cp /home/oracle/ebs_asserter/fndext-2.0.9.jar \
  /u01/oracle/user_projects/domains/asserter_domain/lib

docker restart $CONTAINER_NAME

until [ "$(docker inspect -f '{{.State.Health.Status}}' ${CONTAINER_NAME})" = "healthy" ]; do
  echo "waiting for ebs_asserter container ${CONTAINER_NAME}..."
  sleep 2
done
sleep 5

# 6. Deploy datasource and Asserter via autodeploy

docker exec $CONTAINER_NAME cp /home/oracle/ebs_asserter/visionDS-jdbc-fndext.xml \ /u01/oracle/user_projects/domains/asserter_domain/autodeploy/visionDS-jdbc.xml

docker exec $CONTAINER_NAME cp /home/oracle/ebs_asserter/ebs.war \
  /u01/oracle/user_projects/domains/asserter_domain/autodeploy/

A few design decisions worth pointing out:

  • The WAR is always rebuilt from a pristine copy of the downloaded Asserter, with bridge.properties maintained outside the archive and merged in via zip -u. The script is idempotent; running it twice does no harm.

  • docker compose down first, then up. The container is disposable by design (Part 1 pays off here): no state worth preserving, so a clean start beats patching a running domain.

  • Health checks gate every step. The autodeploy directory only works reliably once the AdminServer is fully up, hence the wait loops after each start. docker compose up -d --wait covers the first start, the loop covers the restart after the fndext.jar copy.

  • No WebLogic console, no wlst, no manual step. The same mechanism deploys a brand-new environment: create the home directory, drop in wallet and compose file, run the script.

Finally post cloning the relevant profile options have to be adjusted:

declare
  stat boolean;
  m_node_id fnd_nodes.node_id%type := 0;
  l_tmp_url  varchar2(100);
begin
  SELECT vals.profile_option_value||'/ebs/', node_id
    into l_tmp_url, m_node_id
  FROM
    fnd_profile_option_values vals, fnd_nodes nodes
  WHERE
    vals.profile_option_id = 4177
    AND vals.level_id = 10007
    and vals.level_value2=nodes.node_id
    and nodes.node_name like '%APP01'
  ;
  stat:=fnd_profile.save('APPS_AUTH_AGENT',
                         l_tmp_url,
                         'SERVER',
                         m_node_id);
  stat:=fnd_profile.save('APPS_SSO',
                         'SSWA_SSO',
                         'SERVER',
                         m_node_id);
  stat:=fnd_profile.save('APPS_SSO_LOCAL_LOGIN',
                         'SSO',
                         'SITE');
exception when no_data_found then
   null;
end;

Since this automation went live, cloned environments come back with working SSO before anyone has had their first coffee. The clone notification mail arrives, the environment is ready, and nobody thinks about APPL_SERVER_ID anymore, which is exactly how it should be.

What's Next

In the final Part 4 we move up the identity chain: federating the OCI IAM domain with Microsoft Entra ID via SAML, provisioning users with SCIM (including the deprovisioning detail that saves real money), keeping MFA policy in exactly one place, and what the setup means for the systems around EBS, from integrated APEX applications to standalone ones.

Questions about SSO/MFA for Oracle E-Business Suite?

Feel free to reach out: At Broadpin we've taken this from PoC to production.