Module Maverics WebSphere TAI

Le module Maverics TAI est un WebSphere Trust Association Interceptor (TAI). Ce module valide le fait que la requête provient d’un proxy d’authentification tiers fiable, lit l’identité de l’utilisateur à partir de l’en-tête de la requête et renvoie un nom distinctif (DN) ou un nom court que Websphere associera à un utilisateur dans son registre.

Exigences relatives au serveur

  • IBM Websphere 8.5
  • Java 8

Installation

Copiez le dernier fichier JAR Maverics TAI dans le répertoire d’installation WebSphere, <WEBSPHERE_ROOT>/AppServer/lib/ext.

Dans la console d’administration WebSphere :

  • Sous Security dans le menu de gauche, sélectionnez Global Security
  • Sous Authentication, développez la Web and SIP security et sélectionnez Trust association
  • Assurez-vous que l’option Enable trust association est sélectionnée et accédez à Interceptors
  • Cliquez sur New… et ajoutez un nouvel intercepteur avec le nom de classe io.strata.TAIModule
  • Ajoutez des propriétés pour configurer l’intercepteur
  • Redémarrez Websphere

Mise à niveau

Supprimez le fichier JAR Maverics TAI existant du répertoire d’installation de WebSphere, et ajoutez la dernière version au fichier <WEBSPHERE_ROOT>/AppServer/lib/ext.

Une fois le dernier fichier JAR ajouté, redémarrez Websphere.

Options de configuration

headerName

headerName correspond au nom de l’en-tête utilisé pour lire de nom d’utilisateur. Si aucune n’est fournie, la valeur par défaut MAVERICS_USERNAME est utilisée. Par défaut, le module TAI suppose que la valeur de l’en-tête est un JWT signé. Le nom d’utilisateur sera extrait de la sous-réclamation dans le JWT.

signatureVerificationKey

signatureVerificationKey correspond à la clé publique RSA utilisée pour vérifier la signature de la valeur de l’en-tête JWT. La clé publique doit être encodée au format base64 avant d’être ajoutée en tant que propriété. Suivez les instructions fournies dans l’exemple ci-dessous pour générer une paire de clés et encoder la clé publique au format base64.

insecurePlainTextHeader

insecurePlainTextHeader peut être utilisé pour remplacer le comportement par défaut du TAI de sorte que la valeur du MAVERICS_USERNAME soit un nom d’utilisateur en texte brut au lieu d’un JWT signé. Lorsque cette option est utilisée, l’intercepteur devient réceptif au trafic latéral.

⚠️
Cette option ne doit être utilisée qu’à des fins de test.

Exemple

Cet exemple illustre la manière dont l’orchestrateur peut être utilisé pour authentifier un utilisateur et acheminer le trafic à travers un proxy vers WebSphere. Lorsqu’une requête est reçue par WebSphere, le module TAI valide la valeur de l’en-tête MAVERICS_USERNAME et construit le contexte d’identité dans WebSphere.

La commande suivante suivante peut être exécutée afin de générer une clé privée qui sera utilisée pour signer les jetons JWT. Dans cet exemple, la clé privée générée est stockée dans une base de données secrète dont le nom est taiPrivateKey.

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

Une fois la clé privée générée, la clé publique utilisée pour vérifier la signature peut être extraite à l’aide de la commande suivante.

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

Une fois la clé publique extraite, elle doit être encodée au format base64 avant d’être ajoutée en tant que propriété dans WebSphere. L’encodage Base64 de la clé est nécessaire car WebSphere n’autorise pas les propriétés multi-lignes.

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

Exemple de la propriété de clé encodée au format Base64 ajoutée dans WebSphere. Propriété de la clé WebSphere encodée au format Base64

Maintenant que la paire de clés a été générée et que les propriétés nécessaires ont été ajoutées à Websphere, le fichier de configuration Maverics ci-dessous peut être utilisé comme référence pour transmettre une valeur d’en-tête JWT à 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
}