So what is SPNEGO? SPNEGO stands for Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO). It is a mechanism by which an authenticating body negotiates with the authenticator what security protocol to use, for example Kerberos, NTLM, Digest or Basic

Originally posted on DZone

PART 1 In The Same Forest

Introduction

I’d like to start by explaining some of the terms that will be used throughout this article.

So what is SPNEGO?

SPNEGO stands for Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO). It is a mechanism by which an authenticating body negotiates with the authenticator what security protocol to use, for example Kerberos, NTLM, Digest or Basic

What is the difference between Authentication and Authorization?

Authentication is the act of verifying a user is who they say they are. Whereas authorization is the act of verifying that the user has sufficient permissions to access the data they are requesting. The difference is quite important so I wanted to introduce it early on.

Tomcat

Single Sign-on in Tomcat is handled as a two step process. First authentication is handled by a valve component. A valve component is an element in the request processing chain. And then the authorization is handled by the Realm. The Realm is essentially a user database that contains a collection of usernames, groups and their associated roles.

Single Sign-on

When you logon to a computer you are authenticating with either that local machine or some central server. Under normal circumstances if you then try and connect to an internal web application, that requires you be a user with certain rights, you would be prompted to login. However in the case of single sign-on this should all be transparent.

Kerberos Single Sign-on

Kerberos

The user logs into Windows, they are authenticated with the Key Distribution Centre (KDC) in the case of Windows this would be the Primary Domain Controller. The OS of the client receives a TGT token for the user. When they try to connect to the Tomcat server the authentication mechanism is negotiated and their token is passed to Tomcat who then verifies it with the KDC. Once they have been authenticated Tomcat then retrieves their roles from the LDAP server in the case of Windows the Active Directory and decides if they have access to the resource they have requested on the server. The sequence diagram below shows the interactions that take place in more detail.

Kerberos sequence diagram

Both the user and the Tomcat server have their own TGT token (credential associated with their identity). In the sequence diagram you can see outlined the division of responsibility between the Tomcat valve and the Tomcat realm. The valve is taking care of the authentications and the realm is taking care of the authorizations. Some internal interactions have been excluded to reduce the complexity of the diagram in particular related to Pre-Authentication Data PADATA which is used as part of the key sharing mechanism.

LDAP

Entries in LDAP are hierarchical and have each entry has a Distinguished Name (DN) that reflects that hierarchy. For example the DN for a user might be CN=Brett Crawley, OU=Development, DC=mydomain, DC=com where CN stands for common name, OU stands for organizational unit and DC stands for domain component. There will be one DC for each component of the domain. In many organizations Users are split up by organization unit within LDAP and in some LDAP repositories this happens at the root of the tree. This can cause problems when searching for users because it is necessary to state the entry point at which to start searching. This entry point can only be one and therefore if your LDAP repository is organized in this manner it is necessary to user what is known as the Global Catalogue.


NOTE: This requires binding to be carried out at the root and it is necessary to use a different port to connect to the LDAP server


  1. Create a technical user for Tomcat in LDAP.

  2. Create your user groups in LDAP.

  3. Ensure that forward and reverse DNS resolutions are correctly configured for all servers involved.

  4. Associate a Service Principal Name (SPN) on the domain controller with the technical user, that is, associate 4 principals:

    • HTTP/ FQDN @ REALM
    • HTTP/ FQDN
    • HTTP/ HOSTNAME @ REALM
    • HTTP/ HOSTNAME
    • setspn -a PRINCIPAL TECHNICAL_USER
  5. On the Domain Controller and on the Tomcat Server (if Windows), open the local security policy administration tool

    1. Go to “> local policies > security options”
    2. Look for “Network Security: Configure encryption types allowed for Kerberos”

    If the value is not defined, this means that all types are enabled, contrary to what is written.

    However, if some value is set, either retain or add

    • RC4_HMAC
    • AES128
    • AES256
    • Future Encryption Types

    If you inadvertently set this value, you can remove it by going into the registry and deleting the value in the following hive of the registry:

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters
    

  6. On the Domain Controller, create a keytab file with the following command. The path is where the tomcat.keytab is written, and is a temporary location because the keytab is copied to the host machines:

    ktpass /out PATH/tomcat.keytab 
    /mapuser TECHNICAL_USER@REALM 
    /princ HTTP/ FQDN@REALM /Pass PASSWORD 
    /crypto AES256-SHA1 ptype KRB5_NT_PRINCIPAL
    

    Now copy the keytab file to the Tomcat server(s) into the /conf folder

  7. In the Active directory, open the technical user properties and go to the account tab. Flag the following values:

    • Password never expires = true
    • User cannot change password = true
    • This account supports Kerberos AES128
    • This account supports Kerberos AES256
    • Use Kerberos DES encryption types for this account = should preferably be false
    • The other settings are at your discretion as the are not pertinent to this setup.

  8. Still in the technical user properties, go to the delegation tab (which appeared as a consequence of step 6) and set the following value to true:

    Trust this user for delegation to any service (Kerberos only).

  9. Create the krb5.ini or krb5.conf file (depending on your platform) in the folder:

    CATALINA_BASE /conf
    

    Alternatively, you can point to another file containing this configuration by setting the property:

    -Djava.security.krb5.conf=PATH_TO_KRB_CONF
    

    The contents of the file should look like the following:

    [libdefaults]
    default_realm=REALM
    default_keytab_name="CATALINA_BASE/conf/tomcat.keytab"
    default_tkt_enctypes=aes256-cts-hmac-shal-96,aes128-cts-hmac-shal-96
    default_tgs_enctypes=aes256-cts-hmac-shal-96,aes128-cts-hmac-shal-96
    forwardable=true
    
    [realms]
    REALM={
    	kdc=DOMAIN_CONTROLLER_FQDN:88
    }
    
    [domain_realm]
    yourdomain.com=REALM
    .yourdomain.com=REALM
    

    Note: Make sure there are no spaces between forwardable, equals and true. Redundancy: In the krb5.ini, add multiple KDCs and, in case of a load-balanced set-up, define the load balancer KDC as the master_kdc and the admin_server (defines the host, which is typically the master_kdc):

    [realms]
    REALM={
    	kdc = DOMAIN_CONTROLLER_FQDN_1:88
    	kdc = DOMAIN_CONTROLLER_FQDN_2:88
    	master_kdc = DOMAIN_CONTROLLER_FQDN_LoadBalancer:88
    	admin_server = DOMAIN_CONTROLLER_FQDN_LoadBalancer
    }
    
  10. Add the following Java startup property to the environment variables.

    -Djavax.security.auth.useSubjectCredsOnly=false
    

    Create/edit the jaas.conf in the Tomcat conf (CATALINA_BASE/conf) or you could set the following property and point to a different file such as login.conf as is often referred to in other Kerberos documentation:

    -Djava.security.auth.login.conf=PATH_TO_LOGIN_CONF
    

    The content of the file (jaas.conf/login.conf) must include the accept and initiate sections as in the example below.

    com.sun.security.jgss.krb5.accept {
    	com.sun.security.auth.module.Krb5LoginModule
    	required
    	doNotPrompt=true
    	principal="HTTP/FQDN@REALM"
    	keyTab="DATA_HOME/conf/tomcat.keytab"
    	storeKey=true
    	useKeyTab=true
    	useTicketCache=true
    	isInitiator=true
    	refreshKrb5Config=true
    	moduleBanner=true
    	storePass=true;
    };
    com.sun.security.jgss.krb5.initiate {
    	com.sun.security.auth.module.Krb5LoginModule
    	required
    	doNotPrompt=true
    	principal="HTTP/FQDN@REALM"
    	keyTab="DATA_HOME/conf/tomcat.keytab"
    	storeKey=true
    	useKeyTab=true
    	useTicketCache=true
    	isInitiator=true
    	refreshKrb5Config=true
    	moduleBanner=true
    	storePass=true
    	debug=true;
    };
    
  11. Update the Java security libraries (Java Cryptography Extension (JCE) Unlimited Strength) to those for Strong Encryption. These libraries can be downloaded here:

    Java 6: http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html

    Java 7: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

    Java 8: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

    Put them in jre/lib/security and jdk/jre/lib/security

  12. Set the registry settings on host:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters
    
    Value Name: allowtgtsessionkey 
    Value Type: reg_sz or reg_dword 
    Value: 1
    

  13. Configure environment variables JAVA_OPTS, CATALINA_HOME and CATALINA_OPTS. In Windows, this can be done here:

    On Linux, this can be modified in the file:

    /etc/sysconfig/tomcat
    
  14. Enable Kerberos authentication in the browser. In Internet Explorer, you can find this setting in the Internet options menu:

    In Firefox, you must type “about:config” as the address, and then click on the button that says “I’ll be careful, I promise!”. Once you have access to the settings, type “neg” in the search field and edit the fields highlighted below:

  15. Modify the login-conf section of your web.xml file to use the SPNEGO auth method.

    <login-config>
    	<auth-method>SPNEGO</auth-method>
    </login-config>
    

    Add security roles for the roles you added to LDAP in step 2.

    <security-role>
    	<description>Users</description>
        	<role-name>WebAppUsers</role-name>
    </security-role>
    <security-role>
    	<description>Admins</description>
    	<role-name>WebAppAdmins</role-name>
    </security-role>
    
  16. Then modify your security constraint sections of the web.xml, setting the auth-constraint roles to match the ones you specified in step 2. as shown below.

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Common Area</web-resource-name>
            <url-pattern>/common/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>PUT</http-method>
            <http-method>HEAD</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>WebAppUser</role-name>
            <role-name>WebAppAdmin</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>User Area</web-resource-name>
            <url-pattern>/user/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>PUT</http-method>
            <http-method>HEAD</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>WebAppUser</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Admin Area</web-resource-name>
            <url-pattern>/admin/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>PUT</http-method>
            <http-method>HEAD</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>WebAppAdmin</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    
  17. Modify the server.xml to use the SPNEGO valve and the JNDI Realm.


NOTE: In the JNDI realm you should not include either the username or password as they will be ignored when using SPNEGO as the implementation of the realm does not support this configuration. It is assumed you will be using the GSSAPI


NOTE: Do not use the userPattern because this also is not supported by Tomcat when using SPNEGO

Replace property values as required.

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
	<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off"/>
	<Listener className="org.apache.catalina.core.JasperListener"/>
	<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
	<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
   	<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
	<Service name="Catalina">
   		<Connector port="8080" maxSavePostSize="2097152" URIEncoding="UTF-8" maxHttpHeaderSize="65536"/>
		<Engine name="Catalina" defaultHost="localhost">
       		<Realm className="org.apache.catalina.realm.JNDIRealm" 
       			connectionURL="ldap://dc.mydomain.com:3268" userSubtree="true"
           		userBase="cn=Users,dc=mydomain,dc=com"
           		userSearch="(sAMAccountName={0})" userRoleName="memberOf"
           		roleBase="cn=Users,dc=mydomain,dc=com" roleName="cn"
           		roleSearch="(member={0})" roleSubtree="true" roleNested="true"/>
			<Host name="localhost" appBase="webapps">
				<Context docBase="ROOT.war" path="">
					<Valve className="org.apache.catalina.authenticator.SpnegoAuthenticator"
						storeDelegatedCredential="true"/>
				</Context>
			</Host>
		</Engine>
	</Service>
</Server>

Testing your Kerberos Config

kinit

On Windows, copy your krb5.ini to the Windows directory. On Linux, copy your krb5.conf file to the /etc folder.

kinit -k -t PATH_TO_KEYTAB SERVICE_PRINCIPAL (HTTP/ FQDN @ REALM )

kinit is for testing that krb5 config is correct.

kinit.exe in $JAVA_HOME/bin

Troubleshooting

BAD RESPONSE

Issues related to Bad Response could be caused by packet length restrictions. This is related to the token and ticket lengths in the HTTP headers. There are three possible solutions to this:

  1. Add the maxHttpHeaderSize attribute to the connector in the server.xml with the value of 65535

  2. Run the following command on the servers and the machines suffering this problem.

    reg add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters" /v "MaxPacketSize" /t REG_DWORD /d 1
    
  3. Add the following to the krb5 config file in the [libdefaults] section so that TCP is used instead of UDP:

    udp_preference_limit=1
    
-Dsun.security.krb5.debug=true

For additional debugging in Apache, you can add the following lines to the logging.properties in the conf directory of Tomcat:

org.apache.catalina.realm.level = ALL
org.apache.catalina.realm.useParentHandlers = true
org.apache.catalina.authenticator.level = ALL
org.apache.catalina.authenticator.useParentHandlers = true

org.apache.juli.logging.UserDataHelper.CONFIG = INFO_ALL
org.apache.coyote.http11.level = DEBUG

For additional logging from the Krb5LoginModule, you can add the following lines to each section of the login.conf

debug=true
moduleBanner=true

In the server config, you can also add the following attribute to the realm and valve:

debug="9"

Cleanup

In some cases, it may be necessary to empty the ticket cache because it contains tickets that are no longer valid (for example, if you have created a new keytab). Clearing the ticket cache in Windows:

klist purge

Clearing the ticket cache in Linux:

kdestroy

Resources

MIT Kerberos Documentation

Oracle About Kerberos Authentication

Tomcat Windows Authentication How-To

The Valve Component - SPNEGO Valve

Realm Configuration HOW-TO - JNDI Realm

The Realm Component - JNDI Directory Realm

Common Kerberos Error Messages (A-M)

Common Kerberos Error Messages (N-Z)

Troubleshooting Kerberos Errors

Troubleshooting Kerberos Delegation

Enabling Strict KDC Validation in Windows Kerberos

** Next week I will cover how to configure cross forest authentication and the algorithm necessary to implement cross forest authorization when the root domain components of two trusted domains are different e.g. you have mydomain.com and mydomain.info **