Maverics WebSphere TAI Module

The Maverics TAI module is a WebSphere Trust Association Interceptor (TAI). This module validates that the request comes from a legitimate third-party authentication proxy, reads in a users’ identity from the request header, and returns either a distinguished name (DN) or a short name which Websphere will map to a user in its registry.

Server requirements

  • IBM Websphere 8.5
  • Java 8

Install

Copy the latest Maverics TAI jar file to the WebSphere installation directory, <WEBSPHERE_ROOT>/AppServer/lib/ext.

In the WebSphere admin console:

  • Under Security on the left menu, select Global Security
  • Under Authentication, expand Web and SIP security and select Trust association
  • Ensure Enable trust association is selected and navigate to Interceptors
  • Click New… and add a new interceptor with the class name io.strata.TAIModule
  • Add properties to configure the interceptor
  • Restart Websphere

Upgrade

Remove the existing Maverics TAI JAR file from the WebSphere installation directory, and add the latest version to <WEBSPHERE_ROOT>/AppServer/lib/ext.

After the latest JAR has been added, restart Websphere.

Configuration options

headerName

headerName is the name of the header used to read the user’s username. If none is provided, a default of MAVERICS_USERNAME is used. By default, the TAI module assumes the value of the header is a signed JWT. The user’s username will be extracted from the sub claim in the JWT.

signatureVerificationKey

signatureVerificationKey is the RSA public key used to verify the signature of the JWT header value. The public key should be a base64 encoded before it is added as a property. Follow the instructions in the below example for how to generate a key pair and base64 encode the public key.

insecurePlainTextHeader

insecurePlainTextHeader can be used to override the default TAI behavior such that the value of the MAVERICS_USERNAME is a plain text username instead of a signed JWT. When this option is used, the interceptor becomes susceptible to side-channel traffic.

⚠️
This option should only be used for testing.

Example

This example demonstrates how the Orchestrator can be used to authenticate a user and proxy traffic to WebSphere. Once a request is received by WebSphere, the TAI module will validate the MAVERICS_USERNAME header value and build the identity context in WebSphere.

In order to generate a private key that will be used to sign JWTs, the following command can be run. This example stores the generated private key in a secret store with a name of taiPrivateKey.

openssl genrsa -out tai-private-key.pem 2048

After a private key has been generated, the public key used for verifying the signature can be extracted with the following command.

openssl rsa -in tai-private-key.pem -outform PEM -pubout -out tai-public-key.pem

After the public key has been extracted, base64 encode it before adding it as a property in WebSphere. Base64 encoding the key is a requirement since WebSphere does not permit multi-line properties.

openssl base64 -A -in tai-public-key.pem

Example of the Base64 encoded key property added in WebSphere. WebSphere Base64 encoded key property

Now that the key pair has been generated and the necessary properties have been added to Websphere, the below Maverics config file can be used as a reference for how to pass a JWT header value to WebSphere.

apps:
  - name: exampleTAI
    type: proxy
    routePatterns:
      - /
    upstream: https://app-internal.example.com
    headers:
      - createHeaderSE:
          funcName: CreateHeader
          file: /etc/maverics/tai-createHeader.go
          metadata:
            # userLookupKey is the key that will be used to look up the user's
            # username from the session store. The corresponding value will be passed
            # as the 'sub' claim in the JWT.
            userLookupKey: azure.name
            # privateKeyLookupKey is the key that will be used to retrieve the
            # signing key from the secret store.
            privateKeyLookupKey: taiPrivateKey
            # jwtLifetime is the lifetime of the JWT in hours. The value should be
            # set to match the lifetime of a user's session.
            jwtLifetime: 24

    policies:
      - location: /
        authentication:
          idps:
            - azure
        authorization:
          allowAll: true

/etc/maverics/tai-createHeader.go

package main

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/strata-io/service-extension/orchestrator"
	"github.com/strata-io/service-extension/tai"
)

// CreateHeader creates an HTTP header that will be consumed and validated by the TAI
// module. The header has a name of 'MAVERICS_USERNAME' and a signed JWT as the value.
func CreateHeader(
	api orchestrator.Orchestrator,
	_ http.ResponseWriter,
	_ *http.Request,
) (http.Header, error) {
	logger := api.Logger()
	logger.Debug("se", "creating header for TAI")

	metadata := api.Metadata()
	userLookupKey, _ := metadata["userLookupKey"].(string)
	privateKeyLookupKey, _ := metadata["privateKeyLookupKey"].(string)
	jwtLifetime, _ := metadata["jwtLifetime"].(int)

	session, err := api.Session()
	if err != nil {
		return nil, fmt.Errorf("failed to get session: %w", err)
	}
	name, err := session.GetString(userLookupKey)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve username from session: %w", err)
	}

	secretProvider, err := api.SecretProvider()
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve secret provider: %w", err)
	}
	privateKey := secretProvider.GetString(privateKeyLookupKey)
	if len(privateKey) == 0 {
		return nil, errors.New("private key not found in secret provider")
	}

	jwt, err := api.TAI().NewSignedJWT(tai.Config{
		RSAPrivateKeyPEM: privateKey,
		Subject:          name,
		Lifetime:         time.Duration(jwtLifetime) * time.Hour,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to construct JWT: %w", err)
	}

	header := make(http.Header)
	header["MAVERICS_USERNAME"] = []string{jwt}
	return header, nil
}