Uploaded image for project: 'OpenAM'
  1. OpenAM
  2. OPENAM-12128

IdP-Proxy - SP initiated single logout fails in multi-instance deployment with SAML2 failover enabled

    Details

    • Type: Bug
    • Status: In Progress
    • Priority: Major
    • Resolution: Unresolved
    • Affects Version/s: 11.0.0, 11.0.1, 11.0.2, 11.0.3, 12.0.0, 12.0.1, 12.0.2, 12.0.3, 12.0.4, 13.0.0, 13.5.0, 13.5.1, 14.0.0
    • Fix Version/s: None
    • Component/s: SAML
    • Labels:
    • Environment:
      Apache Tomcat 8

      AM 5.0.0 multi instance IdP-Proxy
    • Target Version/s:
    • Sprint:
      AM Sustaining Sprint 46, AM Sustaining Sprint 47
    • Story Points:
      5
    • Support Ticket IDs:

      Description

      Bug description

      the IdP-Proxy does not proxy the SingleLogout Request message to the upstream IdP in case of multi-instance IdP-Proxy deployment without guraranteed stickiness

      How to reproduce the issue

      Details steps outlining how to recreate the issue (remove this text)

      1. Configure 2 AM 5.0.0 as IdP-Proxies behind a loadbalancer
      2. Configure another OpenAM instance as IdP
      3. Configure yet another OpenAM instance as SP
      4. Remove all 'backchannel bindings' for SP and IdP
      5. Exchange SAML meta data accordingly for IdP-Proxy scenario
      6. Perform SP-initiated SSO
      7. Authenticate at the IdP
      8. Perform SP-initiated SLO
      9. Perform SP-initiated SSO again
      Expected behaviour
      Authentication at the IdP should take place again
      
      Current behaviour
      User is not prompted for authentication although Single Logout was being reported as successful

      Work around

      none

      Code analysis

      AM 5.0.0 code ...

      com.sun.identity.saml2.profile.SPACSUtils.java
      ...
          public static Object processResponse(
              HttpServletRequest request, HttpServletResponse response, PrintWriter out,
              String metaAlias, Object session, ResponseInfo respInfo,
              String realm, String hostEntityId, SAML2MetaManager metaManager, SAML2EventLogger auditor
          ) throws SAML2Exception {
      ....
              String requestID = respInfo.getResponse().getInResponseTo();
              // save info in memory for logout
              saveInfoInMemory(sessionProvider, session, sessionIndex, metaAlias,
                  info, IDPProxyUtil.isIDPProxyEnabled(requestID), isTransient);
      ....
        }
      
          public static void saveInfoInMemory(SessionProvider sessionProvider,
              Object session, String sessionIndex, String metaAlias,
              NameIDInfo info, boolean isIDPProxy, boolean isTransient)
              throws SAML2Exception {
      
      ....
                   if (!SPCache.isFedlet) {
                  List fedSessions = (List) SPCache.fedSessionListsByNameIDInfoKey.get(infoKeyString);
                  if (isIDPProxy) {
                      IDPSession idpSess = IDPCache.idpSessionsBySessionID.get(tokenID);
                      if (idpSess == null) {
                          idpSess = new IDPSession(session);
                          IDPCache.idpSessionsBySessionID.put(tokenID, idpSess);
                      }
                      SAML2Utils.debug.message("Add Session Partner: {}", info.getRemoteEntityID());
                      idpSess.addSessionPartner(new SAML2SessionPartner(info.getRemoteEntityID(), true));
                  }
      
                  if (fedSessions == null) {
                      synchronized (SPCache.fedSessionListsByNameIDInfoKey) {
                          fedSessions = (List)
                          SPCache.fedSessionListsByNameIDInfoKey.get(infoKeyString);
                          if (fedSessions == null) {
                              fedSessions = new ArrayList();
                          }
                      }  
                      synchronized (fedSessions) {
                          fedSessions.add(new SPFedSession(sessionIndex, tokenID,
                              info, metaAlias));
                          SPCache.fedSessionListsByNameIDInfoKey.put(
                              infoKeyString, fedSessions);
                      }
                      if ((agent != null) && agent.isRunning() && (saml2Svc != null)){
                          saml2Svc.setFedSessionCount(
      		        (long)SPCache.fedSessionListsByNameIDInfoKey.size());
                      }
                  } else {
                      synchronized (fedSessions) {
                          Iterator iter = fedSessions.iterator();
                          boolean found = false;
                          while (iter.hasNext()) {
                              SPFedSession temp = (SPFedSession) iter.next();
                              String idpSessionIndex = null;
                              if(temp != null) {
                                 idpSessionIndex = temp.idpSessionIndex; 
                              }
                              if ((idpSessionIndex != null) &&
                                      (idpSessionIndex.equals(sessionIndex))) {
                                  temp.spTokenID = tokenID;
                                  temp.info = info;
                                  found = true;
                                  break;
                              }
                          }    
                          if (!found) {
                              fedSessions.add(
                                  new SPFedSession(sessionIndex, tokenID, info,
                                                   metaAlias));
                              SPCache.fedSessionListsByNameIDInfoKey.put(
                                  infoKeyString, fedSessions);
                              if ((agent != null) &&
                                  agent.isRunning() &&
                                  (saml2Svc != null))
                              {
                                  saml2Svc.setFedSessionCount(
      		                (long)SPCache.fedSessionListsByNameIDInfoKey.
      				    size());
                              }
                          }
                     }    
                  }
                  SPCache.fedSessionListsByNameIDInfoKey.put(infoKeyString,
                          fedSessions);
                  if ((agent != null) && agent.isRunning() && (saml2Svc != null)) {
                      saml2Svc.setFedSessionCount(
      		    (long)SPCache.fedSessionListsByNameIDInfoKey.size());
                  }
              }
      ....
          }
      
      

      The session partner added to the IDPsession in the a local cache.

      If the LogoutRequest then hits the AM instance where this cache entry is not populated, the session partner can not be retrieved anymore, hence the LogoutRequest is not proxied to the upstream IdP

      com.sun.identity.saml2.profile.IDPSingleLogout.java
      ....
          public static LogoutResponse processLogoutRequest(LogoutRequest logoutReq, HttpServletRequest request,
                  HttpServletResponse response, PrintWriter out, String binding, String relayState, String idpEntityID,
                  String realm, boolean isVerified) throws SAML2Exception {
      ....
                      IDPSession idpSession = IDPCache.idpSessionsByIndices.get(sessionIndex);
      
                      if (idpSession == null && SAML2FailoverUtils.isSAML2FailoverEnabled()) {
                          // Read from SAML2 Token Repository
                          IDPSessionCopy idpSessionCopy = null;
                          try {
                              idpSessionCopy = (IDPSessionCopy) SAML2FailoverUtils.retrieveSAML2Token(sessionIndex);
                          } catch (SAML2TokenRepositoryException se) {
                              debug.error("IDPSingleLogout.processLogoutRequest: Error while deleting token from " +
                                      "SAML2 Token Repository for sessionIndex:" + sessionIndex, se);
                          }
                          // Copy back to IDPSession
                          if (idpSessionCopy != null) {
                              idpSession = new IDPSession(idpSessionCopy);
                          } else {
                              SAML2Utils.debug.error("IDPSessionCopy is NULL!!!");
                          }
                      }
      ....
                      List partners = idpSession.getSessionPartners();
                      boolean cleanUp = true;
                      if (CollectionUtils.isNotEmpty(partners)) {
                          //IdP Proxy case: store the original LogoutRequest from the remote SP, so that once the proxy had
                          //its SLO round-trip with the remote IdP we can send back a LogoutResponse as a reply.
                          IDPCache.logoutRequestById.put(logoutReq.getID(), logoutReq);
                          cleanUp = false;
                      }
      
                      n = list.size();
                      if (n == 0) {
                          // this is the case where there is no other
                          // session participant
                          status = destroyTokenAndGenerateStatus(
                              sessionIndex, idpSession.getSession(),
                              request, response, cleanUp);
                          if (cleanUp) {
                             IDPCache.idpSessionsByIndices.remove(sessionIndex);
                             if ((agent != null) &&
                                 agent.isRunning() &&
                                 (saml2Svc != null)) {
                                 saml2Svc.setIdpSessionCount( (long)IDPCache.idpSessionsByIndices.size() );
                             }
                             if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
                                 try {
                                      SAML2FailoverUtils.deleteSAML2Token(sessionIndex);
                                 } catch (SAML2TokenRepositoryException se) {
                                     debug.error("IDPSingleLogout.processLogoutRequest: Error while deleting token from " +
                                             "SAML2 Token Repository for sessionIndex:" + sessionIndex, se);
                                 }
                             }
                             IDPCache.authnContextCache.remove(sessionIndex);
                          }
                          break;
                      }
      ...
      

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                sfraser Sam Fraser
                Reporter:
                bthalmayr Bernhard Thalmayr
              • Votes:
                0 Vote for this issue
                Watchers:
                4 Start watching this issue

                Dates

                • Created:
                  Updated: