How to implement Kerberos authentication with a Simple REST Web App

Version 4

    In this "how to" guide I will go over the steps I took to make Kerberos authentication work with a Simple REST based webapp. My setup includes two laptop machines running Fedora OS. They both are connected to my wireless router under same subnet. Note that most of the time you do not need to do configure KDC server at all, your enterprise already may be configured with it, you need to ask your operation guys on how to get the keytabs or how to use SSO without keytabs.

     

    For simplicity sake, I edited "/etc/hosts" file added following lines

     

    192.168.1.76 primary.example.com
    192.168.1.98 secondary.example.com
    
    
    
    
    

     

    Most of you already may have your enterprise Kerberos system installed and configured to go, however in for my testing I had to install Kerberos server. I took instructions from http://fedoraproject.org/wiki/Kerberos_KDC_Quickstart_Guide here. On my "secondary.example.com" machine, with root permissions

     

    yum -y install krb5-libs krb5-server krb5-workstation

     

    Once the installation is complete, I needed to create database for the Kerberos credentials, typically enterprises may attach to LDAP etc, for my test case that is not important, so a simple local store is sufficient. To achieve that execute

     

    kdb5_util create -s

     

    Typically when the time difference between machine is off, Kerberos gives issues, so you can synchronize time using NTP, for that execute

     

    yum install ntp
    service ntpd restart
    
    
    
    
    

     

    Edit the "/var/kerberos/krb5kdc/kadm5.acl" to look like

     

    */admin *

     

    now edit the "/etc/krb5.conf" file, and it should look like

     

    [logging]
    default = FILE:/var/log/krb5libs.log
    kdc = FILE:/var/log/krb5kdc.log
    admin_server = FILE:/var/log/kadmind.log
    
    [libdefaults]
    dns_lookup_realm = false
    ticket_lifetime = 24h
    renew_lifetime = 7d
    forwardable = true
    rdns = false
    default_realm = EXAMPLE.COM
    
    [realms]
    EXAMPLE.COM = {
      kdc = secondary.example.com:88
      admin_server = secondary.example.com:749
    }
    
    [domain_realm]
    .example.com = EXAMPLE.COM
    example.com = EXAMPLE.COM
    
    
    
    
    

     

    Now edit firewall permissions and allow "kerberos", for my setting, I used graphical management tool that come with fedora to accomplish that. Then start the Kerberos server using following commands

     

    /sbin/service krb5kdc start
    /sbin/service kadmin start
    
    
    
    
    

     

    Now we are ready to create users in the Kerberos, to begin with we need a "root" user, you can add that by issuing

     

    kadmin.local -q "addprinc root/admin"
    
    
    
    
    

     

    Now login with "root" user in kadmin to create another user

     

    kadmin.local -p root/admin
    
    
    
    
    

     

    Then at the command prompt, to add a user say "rareddy", issue command

     

    ank rareddy
    ank HTTP/primary.example.com
    
    
    
    
    

     

    You can also view all the users in the system by issuing

     

    listprincs
    
    
    
    
    

     

    you will see users like

     

    root/admin@EXAMPLE.COM
    rareddy@EXAMPLE.COM
    krbtgt/EXAMPLE.COM@EXAMPLE.COM
    HTTP/primary.example.com@EXAMPLE.COM
    
    
    
    
    

     

    Here I am going to use "HTTP/primary.example.com@EXAMPLE.COM" user as Service Provider Principle. In Kerberos, there are three systems, one is client user (that is you, ex:rareddy), second is where the service you wants access to is running (that is Teiid server), and then the kerberos server itself. In order for the Service provider system to authenticate with Kerberos system, you do not want configure using your user token, but need to use a trusted user called SPN. Initially, the client machine talks directly to the Kerberos system gets a token, and then uses that token with Service system to negotiate a login. In back end, the service provider system, based on its SPN credentials with authorize the client or not.

     

    To proceed we need to create a keytab, which holds the credentials. To create a keytab, execute below on kadmin console

     

    ktadd -k /path/to/http.keytab  HTTP/primary.example.com
    ktadd -k /path/to/rareddy.keytab rareddy
    
    
    
    
    

     

    Now kerberoes is all setup, copy the "path/to/http.keytab" to the system where JBoss EAP is running, to a known location. In my case it was "primary.example.com" machine. Install JBoss EAP on this machine.https://docs.jboss.org/author/display/TEIID/Installation+GuideCopy the "/etc/krb5.conf" file to this machine too in the same location.

     

    Now edit the standalone.xml file, add


        <system-properties>
            <property name="java.security.krb5.conf" value="/etc/krb5.conf"/>
            <property name="java.security.krb5.debug" value="true"/>
            <property name="java.security.disable.secdomain.option" value="true"/>
        </system-properties>
    
    
    
    
    

     

    right after the "<extensions>" element. Add the following in the "security-domains" configuration

     

        <security-domain name="host" cache-type="default">
            <authentication>
                <login-module code="Kerberos" flag="required">
                    <module-option name="storeKey" value="true"/>
                    <module-option name="useKeyTab" value="true"/>
                    <module-option name="refreshKrb5Config" value="true"/>
                    <module-option name="keyTab" value="/path/to/krbtgt.keytab"/>
                    <module-option name="principal" value="HTTP/primary.example.com@EXAMPLE.COM"/>
                    <module-option name="doNotPrompt" value="true"/>
                    <module-option name="debug" value="true"/>
                </login-module>
            </authentication>
        </security-domain>
        <security-domain name="EXAMPLE.COM" cache-type="default">
            <authentication>
                <login-module code="SPNEGO" flag="requisite">
                    <module-option name="password-stacking" value="useFirstPass"/>
                    <module-option name="serverSecurityDomain" value="host"/>
                </login-module>
                <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="requisite">
                    <module-option name="password-stacking" value="useFirstPass"/>
                    <module-option name="usersProperties" value="${jboss.server.config.dir}/teiid-security-users.properties"/>
                    <module-option name="rolesProperties" value="${jboss.server.config.dir}/teiid-security-roles.properties"/>
                </login-module>
            </authentication>
        </security-domain>
    
    
    
    
    

     

    teiid-security-roles.properties

     

    # A roles.properties file for use with the UsersRolesLoginModule
    # username=role1,role2
    rareddy@EXAMPLE.COM=krb5
    HTTP/primary.example.com@EXAMPLE.COM=krb5
    
    
    
    

     

    Save the file, and start the JBoss EAP  server using

     

    <jboss-as>/bin/standalone.sh -c standalone.xml -b primary.example.com
    
    
    
    
    

     

    Web Application

    HelloWorld.java


    package org.teiid.rest.webapp.kerberoes;
    
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.core.Application;
    import javax.ws.rs.core.Context;
    import javax.ws.rs.core.SecurityContext;
    
    @Path("/hello")
    public class HelloWorld extends Application {
    
      @Context
      private SecurityContext mySecurityContext;
    
        @GET
        @Path("world")
        public String helloworld() {
            return "<message>Hello World! User="+mySecurityContext.getUserPrincipal().getName()+"</message>";
        }
    }
    
    
    
    
    

     

    WEB-INF/web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
      <display-name>resteasy</display-name>
      <session-config>
        <session-timeout>30</session-timeout>
      </session-config>
      <listener>
        <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
      </listener>
      <servlet>
        <servlet-name>Resteasy</servlet-name>
        <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
      </servlet>
      <servlet-mapping>
        <servlet-name>Resteasy</servlet-name>
        <url-pattern>/*</url-pattern>
      </servlet-mapping>
      <context-param>
        <param-name>resteasy.scan</param-name>
        <param-value>true</param-value>
      </context-param>
       
        <security-constraint>
            <display-name>require valid user</display-name>
            <web-resource-collection>
                <web-resource-name>Kerberos Application</web-resource-name>
                <url-pattern>/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>krb5</role-name>
            </auth-constraint>
        </security-constraint>
         <security-role>
            <description>security role</description>
            <role-name>krb5</role-name>
        </security-role>
    
    </web-app>
    
    
    
    
    

     

    WEB-INF/jboss-web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <jboss-web>
        <security-domain>java:/jaas/EXAMPLE.COM</security-domain>
        <valve>
        <class-name>org.jboss.security.negotiation.NegotiationAuthenticator</class-name>
        </valve>
        <context-root>kerberoes</context-root>
    </jboss-web>
    
    
    
    
    

     

    META-INF/jboss-deployment-structure.xml

     

    <jboss-deployment-structure>
      <deployment>
      <dependencies>
      <module name="org.jboss.security.negotiation" />
      </dependencies>
      </deployment>
    </jboss-deployment-structure>
    
    
    
    
    

     

    A sample project is attached. Build the WAR file, and deploy to the JBoss EAP instance.

     

    Testing:

    For testing I chose curl, as I did not find correct way to configure chrome or firefox, that I can see how they issue the negotiate call for kerberos. CURL was much more clear and simple

     

    Before you can issue the http call, execute the following

     

    $kinit -f -k -t /path/to/rareddy.keytab rareddy@EXAMPLE.COM
    
    $klist
    Ticket cache: DIR::/run/user/1000/krb5cc/tktgaFBoW
    Default principal: rareddy@EXAMPLE.COM
    
    Valid starting       Expires              Service principal
    06/16/2014 13:20:48  06/17/2014 13:20:47  krbtgt/EXAMPLE.COM@EXAMPLE.COM
      renew until 06/16/2014 13:20:48
    
    $curl --negotiate -u foo:bar http://primary.example.com:8080/kerberoes/hello/world
    <message>Hello World! User=rareddy@EXAMPLE.COM</message>
    
    
    
    
    

     

    References:

    http://www.slideshare.net/josef.cacek/dev-conf2013-ltkerberosas7

    http://doc.opensuse.org/products/draft/SLES/SLES-security_sd_draft/cha.net.kerberos.html

    http://cxf.apache.org/docs/jaxrs-kerberos.html

    How to implement Kerberos authentication with Teiid over JDBC

     

    Ramesh..