Overview
IBM App Connect Enterprise (ACE) acts as an Enterprise Service Bus (ESB) to fulfil its integration purposes.
In a recent case we had to tackle a situation where ACE had to run on Red Hat Enterprise Linux (RHEL) and authenticate against a SOAP Web Service secured by Kerberos running on Microsoft IIS. The Windows Domain Administrator had enabled the Negotiation authentication provider on the IIS Application Pool, so our Kerberos journey commenced!
Note: ACE running on Windows does support Integrated Windows Authentication (IWA) hence Kerberos/SPNEGO authentication will work out of the box. On Linux we had to be a bit more resourceful and find another solution that would fit the client requirements on using Linux.
Use Case
Kerberos authentication is a network authentication protocol designed to provide strong authentication for client-server applications through secret-key cryptography. Here's a diagram on the steps of Kerberos authentication, including the specific scenario of converting a Ticket-Granting Service (TGS) response ticket to a Negotiation Authorization token for Internet Information Services (IIS) and the role of SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) in this process:
- AS REQ (Authentication Service Request): The Kerberos authentication process begins with the client sending an AS REQ to the Authentication Service (AS). This request typically includes the client's identifier and the target service's identifier. The purpose is to obtain a Ticket-Granting Ticket (TGT), which is used in later steps to request service-specific tickets.
- AS REP (Authentication Service Response): In response to the AS REQ, the AS sends back an AS REP. This response includes the TGT and a session key, both of which are encrypted with the client's secret key. The client uses its secret key to decrypt the session key and the TGT, which is still encrypted with the Ticket-Granting Service's (TGS) secret key.
- TGS REQ (Ticket-Granting Service Request): With the TGT in hand, the client can now request access to a specific service. The client sends a TGS REQ to the TGS, including the encrypted TGT and an authenticator (which includes the client's identifier and a timestamp), encrypted with the session key obtained from the AS REP.
- TGS RES (Ticket-Granting Service Response): The TGS decrypts the TGT and verifies the client's authenticator. If everything checks out, the TGS issues a service ticket for the requested service. This service ticket is encrypted with the service's secret key and sent back to the client in the TGS RES.
- SOAP Request: Conversion to a Negotiation Authorization Token for IIS: In the context of IIS, the service ticket obtained from the TGS can be used for authenticating to a web server running IIS. IIS supports several authentication mechanisms, including Negotiate, which is a security package that selects between Kerberos and NTLM (NT LAN Manager) based on the client and server capabilities. Negotiate is often associated with SPNEGO, which is a mechanism that enables the client and server to negotiate the choice of security protocol. SPNEGO is used to wrap the security tokens (like the Kerberos ticket) and provide a framework where the best common security mechanism is chosen between the client and server. In the case of Kerberos, the service ticket (TGS RES) is presented to IIS as part of the SPNEGO negotiation process. When a client communicates with IIS and requests access to a protected resource, it can use the Negotiate authentication method. The client sends an HTTP request with an Authorization header containing a Negotiation token, which, in the case of Kerberos, includes the Kerberos ticket. IIS, upon receiving this token, uses SPNEGO to unwrap the token, extract the Kerberos ticket, and authenticate the client based on the ticket.
In summary, the Negotiation authentication type in IIS leverages SPNEGO to enable the choice between Kerberos and NTLM. When Kerberos is used, the TGS RES (service ticket) obtained from the Kerberos TGS is converted into a SPNEGO token, which is then presented to IIS for authentication. This process ensures that secure, encrypted, and mutually authenticated communication is established between the client and the IIS server.
Putting Everything to Practice
The following sections provide technical guidance on how to setup the Linux (as a Kerberos client), ACE Java Source Code on converting the Kerberos Ticket to a Negotiate SPNEGO Token and ESQL Sample on adding the token as an HTTP Header. Finally, we provide references where needed to external Microsoft Links for IIS specific tasks.
Linux Packages Setup
Install Kerberos packages for RHEL to act as a client (as root):
[root@host aceroot]# yum install krb5-workstation krb5-libs krb5-devel
Linux Kerberos Configuration
Edit /etc/krb5.conf
file which is the global Kerberos configuration file:
includedir /etc/krb5.conf.d/
[libdefaults]
allow_weak_crypto = true
default_realm = INTRANET.DOMAIN.COM
default_keytab_name = FILE:/home/aceroot/krb5.keytab
default_tkt_enctypes = rc4-hmac aes128-cts aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha384-192 camellia256-cts-cmac aes128-cts-hmac-sha1-96 aes128-cts-hmac-sha256-128 camellia128-cts-cmac
default_tgs_enctypes = rc4-hmac aes128-cts aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha384-192 camellia256-cts-cmac aes128-cts-hmac-sha1-96 aes128-cts-hmac-sha256-128 camellia128-cts-cmac
permitted_enctypes = rc4-hmac aes128-cts aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha384-192 camellia256-cts-cmac aes128-cts-hmac-sha1-96 aes128-cts-hmac-sha256-128 camellia128-cts-cmac
dns_lookup_realm = false
dns_lookup_kdc = false
ticket_lifetime = 1h
renew_lifetime = 1h
forwardable = false
proxiable = false
clockskew = 12000
kdc_default_options = 1342177296
udp_preference_limit = 1
[domain_realm]
.intranet.domain.com = INTRANET.DOMAIN.COM
athsrv22new.intranet.domain.com = INTRANET.DOMAIN.COM
[realms]
INTRANET.DOMAIN.COM = {
kdc = dax-dc3.intranet.domain.com
default_domain = intranet.selonda.com
}
Here's a description of each attribute in the provided configuration above:
- includedir /etc/krb5.conf.d/: This line specifies that the Kerberos configuration should include all files within the /etc/krb5.conf.d/ directory.
- [libdefaults]: This section sets default values for various Kerberos library options.
- allow_weak_crypto = true: This option allows the use of weak cryptographic algorithms.
- default_realm = INTRANET.DOMAIN.COM: Specifies the default Kerberos realm for the client.
- default_keytab_name = FILE:/home/aceroot/krb5.keytab: Points to the default keytab file that contains the client's secret keys. This file contains the username and the password of the user that will connect to the target realm to retrieve the Kerberos ticket. It also defines as we will see later the encryption types/salt that will be used to retrieve the ticket from Kerberos.
- default_tkt_enctypes, default_tgs_enctypes, permitted_enctypes: These specify the encryption types to be used for ticket-granting tickets (TGTs), service tickets, and permitted encryption types, respectively. They list various encryption algorithms that Kerberos is allowed to use.
- dns_lookup_realm = false: Disables DNS lookups to determine the realm name from the domain name.
- dns_lookup_kdc = false: Disables DNS lookups for KDC (Key Distribution Center) address.
- ticket_lifetime = 1h: Sets the lifetime of the Kerberos ticket to 1 hour.
- renew_lifetime = 1h: Specifies the duration for which a ticket may be renewed.
- forwardable = false: Indicates whether forwardable tickets are allowed.
- proxiable = false: Specifies if the client is allowed to get tickets with the proxiable flag set.
- clockskew = 12000: Sets the allowed clock skew in seconds (here it is 12000 seconds). Please note the Linux Server + KDC must be in time synchronisation using NTP.
- kdc_default_options = 1342177296: Specifies the default KDC options bit mask.
- udp_preference_limit = 1: Sets the maximum size of UDP packet the client will send, which is 1 byte here, essentially forcing TCP to be used. That will allow us to have a clean firewall communication procedure and avoid increasing the UDP packet size length.
- [domain_realm]: This section maps domain names to Kerberos realms.
- .intranet.domain.com = INTRANET.DOMAIN.COM: This line specifies that any host within the
- .intranet.domain.comdomain is part of the INTRANET.DOMAIN.COM` Kerberos realm.
- server.intranet.domain.com = INTRANET.DOMAIN.COM: Specifies that the specific host server.intranet.domain.com belongs to the INTRANET.DOMAIN.COM realm.
- [realms]: This section contains information about specific Kerberos realms.
- INTRANET.DOMAIN.COM = {: Begins the realm-specific configuration for INTRANET.DOMAIN.COM.
- kdc = kdc.intranet.domain.com: Defines the KDC for the realm INTRANET.DOMAIN.COM.
- default_domain = intranet.domain.com: Sets the default domain for the realm INTRANET.DOMAIN.COM.
The attributes and values in a krb5.conf
file are crucial for configuring Kerberos to work correctly within a specific environment. They determine how the Kerberos clients will interact with the KDC, including what encryption algorithms will be used, realm definitions, and domain-to-realm mappings, among other settings.
Linux Keytab File Creation
To create the keytab file you can follow this procedure using the ktutil
cmd line tool:
[root@host aceroot]# ktutil
ktutil: list
slot KVNO Principal
---- ---- ---------------------------------------------------------------------
ktutil: clear
ktutil: l
slot KVNO Principal
---- ---- ---------------------------------------------------------------------
ktutil: addent
usage: addent (-key | -password) -p principal -k kvno [-e enctype] [-f|-s salt]
ktutil: addent -password -p user@INTRANET.DOMAIN.COM -k 1 -e rc4-hmac
Password for user@INTRANET.DOMAIN.COM: <PUT YOUR PASSWORD HERE>
ktutil: l
slot KVNO Principal
---- ---- ---------------------------------------------------------------------
1 1 user@INTRANET.DOMAIN.COM
ktutil: wkt /home/aceroot/krb5.keytab
ktutil: exit
To read the contents of the keytab you can use the ktutil cmd line tool as follows:
[root@host aceroot]# ktutil
ktutil: rkt /home/aceroot/krb5.keytab
tutil: l
slot KVNO Principal
---- ---- ---------------------------------------------------------------------
1 1 user@INTRANET.DOMAIN.COM
ktutil: exit
Linux Firewall Communications
As shown in previous section for /etc/krb5.conf we enforce the use of TCP protocol for Kerberos communications instead of UDP protocol. Please ensure that the firewall communication from the Linux server towards to the KDC has open port 88 using TCP protocol.
Linux Client Kerberos Login
Pre-requisites: The KDC Domain Administrator must ensure that an SPN is setup on the Domain Controller for the remote IIS Web Service that we need to have access. Also the IIS Negotiate MUST be enabled as well.
Here is a reference URL from Microsoft® on how implement both: https://techcommunity.microsoft.com/t5/iis-support-blog/setting-up-kerberos-authentication-for-a-website-in-iis/ba-p/347882. (see: Method 2: Registering an SPN to a domain account.)
To test the Kerberos setup is working from the Linux Client point of view we can perform a manual Kerberos login using the following command:
[root@host aceroot]# sss_cache -E && KRB5_TRACE=/dev/stdout kinit -kt /home/aceroot/krb5.keytab -S "HTTP/server.intranet.domain.com" user
[776599] 1704718204.661146: Resolving unique ccache of type KCM
[776599] 1704718204.661147: Getting initial credentials for user@INTRANET.DOMAIN.COM
[776599] 1704718204.661148: Setting initial creds service to HTTP/server.intranet.domain.com
[776599] 1704718204.661149: Looked up etypes in keytab: rc4-hmac
[776599] 1704718204.661151: Sending unauthenticated request
[776599] 1704718204.661152: Sending request (240 bytes) to INTRANET.DOMAIN.COM
[776599] 1704718204.661153: Resolving hostname kdc.intranet.domain.com
[776599] 1704718204.661154: Initiating TCP connection to stream 10.31.1.30:88
[776599] 1704718204.661155: Sending TCP request to stream 10.31.1.30:88
[776599] 1704718204.661156: Received answer (232 bytes) from stream 10.31.1.30:88
[776599] 1704718204.661157: Terminating TCP connection to stream 10.31.1.30:88
[776599] 1704718204.661158: Response was not from master KDC
[776599] 1704718204.661159: Received error from KDC: -1765328359/Additional pre-authentication required
[776599] 1704718204.661162: Preauthenticating using KDC method data
[776599] 1704718204.661163: Processing preauth types: PA-PK-AS-REQ (16), PA-PK-AS-REP_OLD (15), PA-ETYPE-INFO2 (19), PA-ENC-TIMESTAMP (2)
[776599] 1704718204.661164: Selected etype info: etype rc4-hmac, salt "", params ""
[776599] 1704718204.661165: Retrieving user@INTRANET.DOMAIN.COM from FILE:/home/aceroot/krb5.keytab (vno 0, enctype rc4-hmac) with result: 0/Success
[776599] 1704718204.661166: AS key obtained for encrypted timestamp: rc4-hmac/B8F7
[776599] 1704718204.661168: Encrypted timestamp (for 1704718204.584958): plain 301AA011180F32303234303130383132353030345AA105020308ECFE, encrypted C2A4A906EAA5D5481130204406D8DA22E3249742704AF402FD3D5359106B4D1B99A62D0B417967136953936F856F641D711E76
[776599] 1704718204.661169: Preauth module encrypted_timestamp (2) (real) returned: 0/Success
[776599] 1704718204.661170: Produced preauth for next request: PA-ENC-TIMESTAMP (2)
[776599] 1704718204.661171: Sending request (316 bytes) to INTRANET.DOMAIN.COM
[776599] 1704718204.661172: Resolving hostname kdc.intranet.domain.com
[776599] 1704718204.661173: Initiating TCP connection to stream 10.31.1.30:88
[776599] 1704718204.661174: Sending TCP request to stream 10.31.1.30:88
[776599] 1704718204.661175: Received answer (1648 bytes) from stream 10.31.1.30:88
[776599] 1704718204.661176: Terminating TCP connection to stream 10.31.1.30:88
[776599] 1704718204.661177: Response was not from master KDC
[776599] 1704718204.661178: Salt derived from principal: INTRANET.DOMAIN.COMuser
[776599] 1704718204.661179: AS key determined by preauth: rc4-hmac/B8F7
[776599] 1704718204.661180: Decrypted AS reply; session key is: rc4-hmac/DFB3
[776599] 1704718204.661181: FAST negotiation: unavailable
[776599] 1704718204.661182: Initializing KCM:0:41956 with default princ user@INTRANET.DOMAIN.COM
[776599] 1704718204.661183: Storing user@INTRANET.DOMAIN.COM -> HTTP/server.intranet.domain.com@INTRANET.DOMAIN.COM in KCM:0:41956
[776599] 1704718204.661184: Storing config in KCM:0:41956 for HTTP/server.intranet.domain.com@INTRANET.DOMAIN.COM: pa_type: 2
[776599] 1704718204.661185: Storing user@INTRANET.DOMAIN.COM -> krb5_ccache_conf_data/pa_type/HTTP\/server.intranet.domain.com\@INTRANET.DOMAIN.COM@X-CACHECONF: in KCM:0:41956
[root@ACEDEV01 aceroot]# klist
Ticket cache: KCM:0:41956
Default principal: user@INTRANET.DOMAIN.COM
Valid starting Expires Service principal
01/08/2024 12:50:04 01/08/2024 13:50:04 HTTP/server.intranet.domain.com@INTRANET.DOMAIN.COM
renew until 01/08/2024 13:50:04
[root@host aceroot]# date
Mon Jan 8 12:50:11 UTC 2024
The above section shows a successful Kerberos login using user “user” requesting access to the service SPN “HTTP/server.intranet.domain.com” the keytab file is used by the command to retrieve the password or user and the cipher for preauth.
Once the above section is a pass, we can continue with the Java source code required on creating the Negotiation token to pass the authentication enforced by IIS.
Programmatic Kerberos Authentication and SPNEGO Token Generation
package com.ibm.ace.common.kerberos;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import org.ietf.jgss.*;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class KerberosTokenGenerator {
private static final String REALM = "INTRANET.DOMAIN.COM";
private static final String PRINCIPAL = "user@” + REALM;
private static final String PASSWD = "put_your_pass_here";
private static final String SERVICE = "HTTP/server.intranet.domain.com@" + REALM;
private static final String KDC = "kdc.intranet.selonda.com";
public static String generate(final String temp) {
try {
// Set up Kerberos properties
System.setProperty("java.security.krb5.realm", REALM);
System.setProperty("java.security.krb5.kdc", KDC);
// Custom CallbackHandler to supply the password otherwise use keytab
CallbackHandler callbackHandler = new CallbackHandler() {
@Override
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nc = (NameCallback) callback;
nc.setName(PRINCIPAL);
} else if (callback instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback) callback;
pc.setPassword(PASSWD.toCharArray()); // Password
} else {
throw new UnsupportedCallbackException(callback, "Unrecognized Callback");
}
}
}
};
// Programmatic JAAS Configuration
Configuration config = new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<>();
options.put("renewable", "false");
options.put("credsType", "both");
options.put("principal", PRINCIPAL);
options.put("debug", "true"); // Set to false in production
options.put("forwardable", "false");
options.put("proxiable", "false");
return new AppConfigurationEntry[]{
new AppConfigurationEntry(
"com.ibm.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options)
};
}
};
// Create a LoginContext with the custom configuration
LoginContext lc = new LoginContext("KerberosLogin", null, callbackHandler, config);
lc.login();
// Generate SPNEGO token
byte[] spnegoToken = createSpnegoToken(lc.getSubject());
// Create HTTP Authorization Header
String authHeader = "Negotiate " + Base64.getEncoder().encodeToString(spnegoToken);
System.out.println("Authorization: " + authHeader);
// Cleanup
lc.logout();
return authHeader;
} catch (LoginException | GSSException | PrivilegedActionException e) {
e.printStackTrace();
}
return "Error in creating spnego token";
}
private static byte[] createSpnegoToken(Subject subject) throws PrivilegedActionException, GSSException {
return Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() {
@Override
public byte[] run() throws Exception {
GSSManager manager = GSSManager.getInstance();
GSSName serverName = manager.createName(SERVICE, null);
Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
GSSContext context = manager.createContext(serverName, spnegoOid, null, GSSContext.DEFAULT_LIFETIME);
byte[] token = new byte[0];
token = context.initSecContext(token, 0, token.length);
context.dispose();
return token;
}
});
}
}
This Java source code shows a fully programmatic way to setup everything you need in one place to talk to Kerberos and convert the ticket to a SPNEGO Token. This Java class must belong to a Java Project, and it must be referenced in a common shared library. If the developer needs to make use of the keytab
file we created before they can do so by using the appropriate useKeytab
option of the IBM Krb5LoginModule as described here: https://www.ibm.com/docs/en/sdk-java-technology/8?topic=jgss-krb5loginmodule in order to eliminate the user providing the password in the source code and thus avoid the specification of the callback handler.
One can also eliminate the need to provide the KDC and REALM inside the source code if the /etc/krb5.conf
is setup property. See here: https://docs.oracle.com/en/java/javase/11/security/kerberos-requirements.html#GUID-8B30CD5C-64B6-48DE-9CD5-0E44D3A434A7.
Now that the Java source code is in place all we have left is to make the appropriate wiring in the ACE Toolkit and reference it from an ESQL module.
Use Java from ESQL to retrieve the Authorization Header
An ESQL node will be used prior to the SOAP Request Node to inject the Authorization Header.
- Add in a common (shared) library the following ESQL function:
BROKER SCHEMA com.ibm.ace.common.kerberos
CREATE PROCEDURE kerberosTokenGenerator(IN temp CHARACTER)
RETURNS CHARACTER
LANGUAGE JAVA
EXTERNAL NAME "com.ibm.ace.common.kerberos.KerberosTokenGenerator.generate";
Note: Keep in mind that in this example, the Java class above and kerberosTokenGenerator()
ESQL function are in the same library.
- Add in the ESQL module that is preparing the request the following:
PATH com.ibm.ace.common.kerberos;
CREATE COMPUTE MODULE PrepareRequest
CREATE FUNCTION Main() RETURNS BOOLEAN
BEGIN
…
DECLARE httpHeader CHARACTER '';
SET httpHeader = kerberosTokenGenerator('temp');
SET OutputRoot.HTTPRequestHeader.Authorization = httpHeader;
…
PROPAGATE TO TERMINAL ‘out’;
RETURN FALSE;
END;
END MODULE;
The component (API/Application), that is preparing and making the request, must have a reference to the common library. That will allow you to use PATH
statement to import the procedure that is calling the Java code responsible for creating the SPNEGO Authorization Token.
Conclusion
In this article we have provided a programmatic way to setup Kerberos/SPNEGO Negotiation and Authentication using IBM App Connect Enterprise running on Linux. We have shown how to setup the Linux (as a Kerberos client), ACE Java Source Code on converting the Kerberos Ticket to a Negotiate SPNEGO Token and ESQL Sample on adding the token as an HTTP Header. We've also provided references where needed to external Microsoft Links for IIS specific tasks.
Acknowledgement and thanks to Konstantinos Karas (Software Engineer, ACE Developer) and Chris Tsatsampas (Software Engineer, ACE Developer) for providing valuable input to this article.
#AppConnectEnterprise(ACE)#Linux#Kerberos