Skip to main content

Managing Failover Between Containers

In most production environments it is expected that the IBM Application Gateway (IAG) service will be replicated for load balancing and high availability purposes. When a user is switched from one replicated IAG service to another it is important to make this a seamless experience for the user, without requiring them to provide their authentication information again.

There are currently two mechanisms by which the user can be re-authenticated to the IAG service:

Single Sign On from the Identity Provider

If the environment is using an external identity provider (for example, IBM Security Verify) it is possible to re-establish the IAG session from the current session at the Identity Provider, as depicted in the following diagram:

IDP SSO Flow

Some of the disadvantages of this approach include:

  1. It relies on the user session at the identity provider still being active and available;
  2. Numerous redirects are required during the re-authentication which can slow down the authentication process for the user;
  3. A new session will be established at the replicated IAG which means that the lifetime of the original session will be lost. The session at each replicated IAG service will potentially have a different lifetime.

Another option is to enable the IAG failover cookie. This failover cookie will contain a copy of the established user credential, along with the time at which the current session will expire, encrypted as a JSON Web Encryption (JWE) token.

The following diagram depicts the flow when the JWE failover cookie is enabled:

JWE Failover Cookie SSO Flow

The main advantages of using the JWE failover cookie are:

  1. There is no dependency on the identity provider, which means that the flow is much simpler;
  2. The failover cookie embeds the session expiry time which means that the lifetime of the session is consistent across the various replicated services.

The main disadvantage of this approach is that the failover cookie can potentially be quite large (a typical failover cookie might be 2-3 Kb in size) and this cookie will be sent by the client on every request.

Note: The failover cookie may be larger than what is permitted by any intermediate devices that inspect the HTTP traffic. Settings on these devices may need to be adjusted.

Configuration

In order to enable the JWE failover cookie the failover node must be added to the IAG configuration yaml file:

server:

  # Configuration related to failover support for the IAG.
  failover:
  
    # The key which is used to protect the failover JWE cookie.  
    key: "@oct-512-bit.bin"
    
    # The name of the cookie which will be used to store the failover JWE.
    cookie_name: IAG-JWE
    
    # Should the failover cookie be created as a domain cookie?  A domain
    # cookie will allow the failover cookie to be sent back to any
    # server within the same domain.  The name of the domain which is
    # used in the failover cookie is derived from the Host header in
    # the HTTP request.
    domain_cookie: false

The key which is used is a symmetric key which must be 512 bits (or 64 bytes) in length. The key can be an English pass-phrase or random characters. If the provided key is longer than 64 bytes it will be truncated so that only the first 64 bytes are used. If the provided key is less than 64 bytes it will be right-padded with 0's to produce a 64-byte key.

The openssl command can be used to produce a key file which contains a random number of bytes:

openssl rand -out oct-512-bit.bin 64

All IAG instances which want to share JWE failover cookies must be configured with the same key.

Schema

The fact that the JWE cookie is based on a well known standard ( RFC7516: JSON Web Encryption ) means that it is possible for a third-party to create a JWE cookie, providing they know the key which is being used to protect the cookie.

When creating the JWE it is important to note the following:

  1. The key will always be 64-bytes in length. If the provided key is less than 64-bytes it must be right padded with 0x00's, and if it is longer than 64-bytes it must be truncated;
  2. The JWE should be created with 'Compact Serialization' and not 'JSON Serialization' (refer to section 3 of the JWE RFC for details);
  3. The JWE header should contain the following values:
Name Description Value
enc The content encryption algorithm. A256CBC-HS512
alg The cryptographic algorithm used to encrypt or determine the value of the CEK. dir
exp The expiration time for the token - in Epoch time format - as a string.
zip The "zip" (compression algorithm) applied to the plaintext before encryption. This header should only be set if the body of the JWE has been compressed. DEF
  1. The JWE body:

    1. may be compressed using the RFC1951 compression algorithm;
    2. should contain the claims to be added to the session (which will be used in the authorization rules);
    3. should contain a claim with the name: 'AZN_CRED_PRINCIPAL_NAME' which contains the name of the user.

An example python script which can be used to generate a JWE failover cookie is illustrated below:

#!/usr/local/bin/python3

def generateCookie(failover_key):
    """
    This function will create a custom JWE failover cookie using the 
    specified failover key.
    """

    from jwcrypto import jwe 
    from jwcrypto import jwk

    import time
    import json
    import base64

    # Correct the length of the failover key.  If it is less than 64 bytes
    # we right-pad with 0x00's and if it is longer than 64 bytes we truncate
    # it to 64-bytes.

    keySize = 64

    if len(failover_key) > keySize:
        key = failover_key[0:keySize]
    elif len(failover_key) < keySize:
        key = failover_key.ljust(keySize, '\0')
    else:
        key = failover_key

    # Build up the key which is to be used.
    jwk_ = jwk.JWK.from_json(
        json.dumps({
            "k": str(base64.urlsafe_b64encode(key.encode("utf-8")), "utf-8"),
            "kty": "oct"
        })
    )

    # Set up the protected header.
    hdr = {
        "alg": "dir", 
        "enc": "A256CBC-HS512",
        "exp": str(int(time.time()) + 3600)
    }

    # Build up the body.  We also want to compress the body.
    json_body = { 'AZN_CRED_PRINCIPAL_NAME' : 'testuser' }
    body      = json.dumps(json_body).encode("utf-8")

    # We can finally generate the JWE using the key, header and body.
    jwe_ = jwe.JWE(body, json.dumps(hdr))

    jwe_.add_recipient(jwk_)

    return jwe_.serialize(compact=True)

# The key which will be used to generate the JWE
key = "This is only a test key!"

# Generate and display the JWE
print("Cookie: {0}".format(generateCookie(key)))

The output from running this script is as follows:

cmd> python3 generate_jwe_cookie.py 
Cookie: eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2Q0JDLUhTNTEyIiwgImV4cCI6ICIxNTc0NDExNzE2In0..--BovSXb9VrF90xVFQYQIQ.kjLZdCnKqDwTOSfhzb4JDCmciUCIgW0-f0Zj5bl7cSHQEKm-lkmEUHBipxVg42ok.4Aj2c8aiJZaMt4JwYxuInk2sTNAiGnEZRalbsDCI5dQ