Java, Kerberos/SPNEGO und Cross-Realm-Authentification bzw. Domain-Forrests

Single-Sign-On ist in Windows Domänen ein gern gesehen Feature und wie hier bereits vorgestellt via SPNEGO auch für Java Webanwendungen realisierbar. Ohne zusätzliche Konfiguration funktioniert SPNEGO nur dann, wenn sowohl der Browser als auch die Webanwendung innerhalb der gleichen Domäne arbeiten. Dies ist immer dann kritisch, wenn in großen Installation aus Last- und/oder Managementgründen mehrere Windows Domänen parallel betrieben werden. Dieser Artikel stellt die Hintergründe vor, um SPNEGO auch Domänenübergreifend einzusetzen, so dass Browser und Server in unterschiedlichen Domänen arbeiten können

Als erstes ist es notwendig zu verstehen, wie das Verhältnis zwischen unterschiedlichen Domänen definiert ist. Eine Domäne, die einer Identifikation vertraut, welche in einer anderen Domäne durchgeführt wurde, wird als “Trust” bezeichnet. Wichtig ist zu verstehen, dass es zwei Arten gibt, wie dieser Trust zustande kommen kann:

  1. Direct Trust
    Ein direkter Trust ist explizit durch die jeweiligen Domain Admin definiert. Ein typisches Anwendungsbeispiel wäre von COMPANY1.COM zu COMPANY2.COM
  2. Indirect Trust / Domain Forrest Trust
    Alternativ kann ein Trust automatisch, d.h. ohne zusätzlichen Eingriff des Domain Admins, indirekt durch einen Domain Forrest entstehen. Ein Beispiel hierzu wäre:

    • COMPANY.COM als Forrest Root
      • REALM003.COMPANY.COM als Kind-Domäne
      • REALM007.COMPANY.COM als Kind-Domäne
  3. Wenn der Browser in REALM003 ist und der Server in REALM007 ist, entsteht zunächst ein Problem, da weder REALM003 direkt zu REALM007 ein Trust Verhältniss hat, noch umgekehrt.
    Indirekt vertraut aber REALM007 dem Forrest Root COMPANY.COM und dieser wiederrum REALM003.

Das im letzten Beispiel angeführte indirekte Trust Verhältniss muss nun der SPNEGO Implementierung auf dem Server bekannt gegeben werden. Hierzu dient die Kerberos-Konfigurationsdatei krb5.ini. Eine Beispieldatei, passend zu oberen Szenario ist im folgenden angeführt:

[libdefaults]
ticket_lifetime = 600
default_tgt_enctypes = aes256-cts aes128-cts des3-cbc-sha1 rc4-hmac des-cbc-md5 des-cbc-crc
default_tgs_enctypes = aes256-cts aes128-cts des3-cbc-sha1 rc4-hmac des-cbc-md5 des-cbc-crc
forwardable = true
default_realm = REALM007.COMPANY.COM

[realms]
COMPANY.COM = {
 kdc = xyz.company.com:88
 default_domain = company.com
}
REALM007.COMPANY.COM = {
 kdc = xyz.realm007.company.com:88
 default_domain = realm007.company.com
}
REALM003.COMPANY.COM = {
 kdc = xyz.realm003.company.com:88
 default_domain = realm003.company.com
}

Wenn nun die SPNEGO Serverimplementierung ein Kerberos Ticket von dem fremden REALM003 bekommt, kann Sie sich über den Forrest-Root zum zuständigen KDC für REALM003 durchfragen.

Es bleibt allerdings noch eine Ausnahme, falls der Forrest nicht streng hierarchisch aufgebaut ist. Dies kann ebenfalls in der krb5.ini Datei abgebildet werden, allerdings muss dann zusätzlich mit [capaths] Elementen gearbeitet werden.

Hierzu gibt es einen sehr hilfreichen Artikel bei Redhat, wo das Thema auch nochmals ausführlich erklärt wird.

Posted in Java, Kerberos | Tagged , | Leave a comment

Kerberos Keytab überprüfen mit Java-Boardmitteln

Nachdem eine Kerberos keytab Datei erstellt wurde (siehe Beschriebung im vorherigen Posting), stellt sich die Frage, wie diese möglichst einfach getestet werden kann. Hierzu bringt das Standard JDK bereits alles nötige mit.

Im Standard JDK ist auf allen Plattformen ein kinit-Utility enthalten. Für Diagnose Zwecke besteht nun der entscheidende Trick darin, an diesem Utility die Kerberos-Debug Option zu aktivieren. Hier ein Beispiel:

SET KINIT=c:\Program Files\Java\jdk1.6.0_20\bin\kinit
SET KEYTAB=d:\webapps\app1\WEB-INF\serverhostname.keytab
SET SPN=HTTP/app1.intranet.company.com@REALM002.COMPANY.COM
SET KRB5INI=d:\webapps\app1\WEB-INF\krb5.ini
"%KINIT%" -k -t "%KEYTAB%" %SPN% -J-Dsun.security.krb5.debug=true "-J-Djava.security.krb5.conf=%KRB5INI%"

Das Utility versucht ein TGT (Ticket Granting Ticket) vom KDC (Key Distribution Cewnter in Form des Windows Active Directory) anzufordern. Dies entspricht genau dem Vorgang, den eine SPNEGO Implementierung beim Start des Tomcat durchführt.

Durch die aktivierte Debug Option lassen sich Fehler einfacher aufspüren und da das Utility autark von der eigentlichen Web-Anwendung läuft, scheidet diese schon mal als Fehlerquelle aus.

Sobald das Utility erfolgreich ein Ticket bezogen hat, erscheint als letzte Zeile die Meldung:

New ticket is stored in cache file C:\Documents and Settings\myuser\krb5cc_myuser
Posted in Java, Kerberos | Tagged , | Leave a comment

Kerberos/SPNEGO keytab-Dateien und Windows Server 2008

Seit der Erstellung der JGSS Dokumentation für Kerberos in Java haben sich mit Windows Server 2008 hilfreiche Vereinfachungen ergeben. Diese werden hier kurz vorgestellt.

Die Konfiguration von JGSS für Kerberos ist hinreichend dokumentiert. Beispiele hierzu sind:

Diese laufen alle auf die Erstellung einer Keytab Datei durch den Windows Domänen Administrator hinaus. Seit Windows 2003 hat sich hier eine wichtige Verbesserung ergeben, nämlich der optionale Parameter “/mapOp set”. Beispiel:

ktpass
   /out serverhostname.keytab
   /mapuser realm002\username
   /princ HTTP/app1.intranet.company.com@REALM002.COMPANY.COM
   /crypto RC4-HMAC-NT
   /pass XXXXXXXX
   /ptype KRB5_NT_PRINCIPAL
   /mapOp set

Der Parameter “/mapOp set” überschreibt gegebenenfalls existierende SPN Mappings automatisch, was vorher manuell und fehleranfällig durch einen seperaten “setspn -D” Aufruf erledigt werden musste. Hierdurch wird immer ein richtiges SPN-Mapping sicher gestellt, was die weitere Inbetriebnahme erleichtert da es eine Fehlerquelle eliminiert.

Natürlich ist dies nur ab dem zweiten ktpass-Versuch relevant. Die Erfahrung zeigt aber, dass meist mehr wie ein ktpass Lauf notwendig ist, bevor wirklich alle Parameter stimmen. ;-)

Posted in Java, Kerberos | Tagged , | Leave a comment

Tomcat und extern generierte SSL Zertifikate

Das in der Zusammenarbeit mit externen CAs verwendete PEM Dateiformat für SSL Zertifikate ist für Java und somit für Tomcat eher exotisch. Was ist zu tun, in Situationen, wo die zu verwendede CA festgelegt ist und ausschliesslich PEM Dateien akzeptiert? In diesem BLog Eintrag zeige ich ein kleines Shell-Skript von mir, welches mit Hilfe von openSSL und entsprechenden Parametern die Generierung automatisiert.

Anbei zunächst das Skript, weiter unten folgt die Benutzeranleitung:

#!/bin/bash

SERVERS="www.company.com \
         www2.company.com"

function genkeypair {

 if [ -f $1-key.pem ]; then
  echo "$1-key.pem bereits da, überspringe ..."
 else
  echo "------------Hilfestellung Start------------------------"
  echo "Country Name (2 letter code) [AU]:DE  (z.B.)"
  echo "State or Province Name (full name) [Some-State]:Bavaria (z.B.)"
  echo "Locality Name (eg, city) []:Nuernberg (z.B.)"
  echo "Organization Name (eg, company) [Internet Widgits Pty Ltd]:Musterfirma"
  echo "Organizational Unit Name (eg, section) []:IT  (z.B.)"
  echo "Common Name []:$1    (exakt!!!)"
  echo "Email Address []:mail@company.com  (z.B.)"
  echo "A challenge password []:     (leer - exakt !!!)"
  echo "An optional company name []:   (leer z.B.)"
  echo "------------Hilfestellung Stop------------------------"
  openssl req -newkey rsa:1024 -keyout $1-key.pem -new -nodes -out $1-csr.pem
 fi
}

function pem2der {
 if [ -f $1-key.pem ] && [ -f $1-cert.pem ]; then
  openssl pkcs8 -topk8 -nocrypt -in $1-key.pem -inform PEM -out $1-key.der -outform DER
  openssl x509 -in $1-cert.pem -inform PEM -out $1-cert.der -outform DER
 else
  echo "$1-key.pem bzw. $1-cert.pem (geliefert von CA) nicht vorhanden, überspringe ..."
 fi
}

function createKeyStore {
 if [ -f $1-key.der ] && [ -f $1-cert.der ]; then
  java -Dkeystore=$1.keystore ImportKey $1-key.der $1-cert.der tomcat
 else
  echo "$1-key.der bzw. $1-cert.der nicht vorhanden, überspringe ..."
 fi
}

for server in $SERVERS; do
 genkeypair $server
done

for server in $SERVERS; do
 pem2der $server
done

for server in $SERVERS; do
 createKeyStore $server
done

Schritt 1: Request für CA erzeugen

Beim ersten Aufruf des Skript wird das Keyfile mit dem Private key und ein zugehöriger Certificate-Signing-Request erzeugt. Der Certificate-Signing-Request muss an die CA zur Unterschrift über den dort dokumentierten Weg eingereicht werden.

Erzeugte Dateien:
www.company.com-csr.pem   (Certificate-Signing-Request)
www.company.com-key.pem  (Private Key)

Schritt 2: Das unterschriebene Zertifikat

Nachdem der Prozess mit der CA durchlaufen ist, kann von dort das unterschriebene Zertifikat herunterladen werden.

Dieses muss dann unter der Datei www.company.com-cert.pem abgelegt werden, damit das Skript das neue Zertifikat von dort auslesen kann.

Schritt 3: Java Keystore bauen

Sowohl der Private Key als auch das von der CA unterschriebene Zertifikat liegen gespeichert in PEM Containern vor. Java und damit Tomcat erwartet hingegen die gleichen Inhalte innerhalb eines Java Keystores, in dem sie nun umkopiert werden müssen.

Der naheliegendste Weg über das Java keytool Utility scheitert, da aus mir unbekannten Gründen mit diesem Utility zwar eigene Private Keys generiert werden können, allerdings der Import von externen Private Keys in einen Keystore mangels Kommandozeilenoption nicht unterstützt wird. Die Lösung wird in diesem Blog-Artikel beschrieben. Zwar fehlt die Kommandozeilenoption, allerdings lässt sich der Vorgang durch den direkten Aufruf der Java-API trotzdem durchführen.

Hierfür erzeugt das Skript zunächst folgende zwei Hilfsdateien:
www.company.com-key.der  (Private Schlüssel im PKCS#8 Container)
www.company.com-cert.der  (Zertifikat im PKCS#8 Container)

Anschliessend wird ein Programm des oben erwähnten Blog Autors aufgerufen (Download), welches den Keystore als Ausgabe erstellt.

Ausgabe:
www.company.com.keystore

Schritt 4: Tomcat anpassen

Als letzter Schritt muss noch dem Tomcat bekannt gegeben werden, dass er SSL-Verschlüsselung verwenden soll. Dies geschieht innerhalb der server.xml:

<Connector port="443"
 protocol="org.apache.coyote.http11.Http11NioProtocol"
 SSLEnabled="true"
 scheme="https"
 secure="true"
 clientAuth="false"
 sslProtocol="TLS"
 keystoreFile="D:\absolute\path\to\www.company.com.keystore"
 keystorePass="importkey"
 />

Aus Performance Gründen ist der Einsatz des Nio-Connectors dringend angeraten. Wie hier nachzulesen ist, verfügt nur dieser über die Eigenschaft SSL non-blocking abzuwickeln, was gerade auf den heute marktüblichen Mehrprozessor-Systemen einen großen Performance Sprung bringt.

Posted in Java, Tomcat | Tagged , | Leave a comment

Tomcat, AJP, Kerberos, 8kB Header-Limit und große Windows-Domain-Forrests

Eine geläufige Installation ist, den Tomcat hinter einen Apache oder IIS Webserver zu schalten. Dieser Aufbau hat einen gravierenden Nachteil sobald, es darum geht Single-Sign-On (SSO) via Kerberos/SPNEGO für die Anwendung zu implementieren.

Auf der Apache Seite ist eine Unterstützung für das hierzu verwendete AJP Protokoll im Rahmen von mod_proxy vorhanden. Für den IIS gibt es entsprechend eine isapi_redirect.dll bei den regulären Tomcat Downloads.

Typischer weise gibt es in Konzern-Intranets mehrere Windows Domains, die sich über Domain Forrests gegenseitig vertrauen. Dies hat die unangenehme Nebeneigenschaft, dass die Größe des Kerberos Ticketes, welches letztendlich in einem SPNEGO Paket verpackt ist, entsprechend anschwillt. In der Praxis kommen hier sehr leicht Werte größer wie 8kB pro Ticket zusammen. Beispielsweise kann die Anzahl der Windows-Gruppenmitgliedschaften die Ticketgröße aufblähen.

Da Testuser in der Praxis selten so feingranulare Rechtedefinitionen haben, führt dies zu dem sehr unglücklichen Phänomen, dass nicht alle Anwender von diesem Technischen k.o. betroffen sind und es erst sehr spät in Tests bzw. überhaupt erst in der Produktion bemerkt werden kann.

Gibt es also eine entsprechende Abhilfe? Zunächst könnte man vermuten, dass eine Erweiterung des Tomcat Connectors um das Attribut maxHttpHeaderSize Abhilfe bringt. Beispiel:

<Connector maxHttpHeaderSize="65536"/>

Eine weitere Recherche führt dann allerdings zu diesem Posting auf der Tomcat Maillingliste:

“I’m afraid that the 8KB limit is part of the AJP/1.3 protocol, so it is very unlikely to get “fixed” until AJP/1.4″

Weil das AJP Protokoll ein Binär-Protokoll ist, ist damit gleichzeitig die maximale Paketgröße bereits in der Protokolldefinition festgeschrieben und das Kerberos Ticket findet schlicht und ergreifend keinen Platz mehr innerhalb des Paketes. Für AJP ist mir keine explizite Protokoll Definiton bekannt. Es findet sich nur eine reverse-Engeneering Doku mit folgendem Inhalt:

According to much of the code, the max packet size is 8 * 1024 bytes (8K). The actual length of the packet is encoded in the header.

Eine Manipulation der Größe der Packete erschien mir zu riskant, da abseits des Mainstream. So bestand die Abhilfe darin, den Apache bzw. IIS aus der Installation zu entfernen. Natürlich muss dann der Tomcat Themen wie SSL und GZIP Komprimierung übernehmen. Aus diesem Grund wird es weitere Blog Postings hierzu geben.

Posted in Java, Kerberos, Tomcat | Tagged , | Leave a comment