Search code examples
javakerberoskerberos-delegation

How to delegate the kerberos client credentials on a linux server running Tomcat?


This is nearly the same question as How to delegate the kerberos client credentials to the server?

But there is no answer. That's why I raise the question again. Hopefully someone can help.

It's possible to get a service ticket for the client (remote user) in the server side in order to use that ticket to authenticate against another backend?

Scenario: User (IE) ==> AppServer (Tomcat, under Linux) ==> Backend (webservice - REST service on Windows)

  • We have SPNEGO auth running and working in the AppServer

  • The AD user in the keytab file on in the AppServer has the rights to do the delegation (hopefully)

  • What are the preconditions that the GSSManager can create a credential that can be used for delegation? (´context.getDelegCred()´ should not fail after ´GSSManager.getInstance().createContext(this.serverCredentials)´?

There must be someone who has solved this problem?

Does "Forwardable Ticket true" mean that user from keytab file has delegation rights? Does anyone know this?

Thanks in advance

Output from HelloKDC.java (see extract from bellow)

Client Principal = HTTP/[email protected]
Server Principal = krbtgt/[email protected]
Session Key = EncryptionKey: keyType=23 keyBytes (hex dump)=
0000: xx xx xx xx xx xx xx xx   xx xx xx xx xx xx xx xx  ................

Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Wed Dec 20 16:52:03 CET 2017
Start Time = Wed Dec 20 16:52:03 CET 2017
End Time = Thu Dec 21 02:52:03 CET 2017
Renew Till = null
Client Addresses  Null
        Private Credential: /opt/app/tomcat/ssoad1/servername.domain.com.keytab for HTTP/[email protected]

Connection test successful.

Extract from HelloKDC.java (also from net.sourceforge.spnego):

// Name of our krb5 config file
final String krbfile = "/opt/app/tomcat/ssoad1/krb5.ini";

// Name of our login config file
final String loginfile = "/opt/app/tomcat/ssoad1/jaas.conf";

// Name of our login module
//final String module = "spnego-client";
final String module = "com.sun.security.jgss.krb5.initiate";

// set some system properties
System.setProperty("java.security.krb5.conf", krbfile);
System.setProperty("java.security.auth.login.config", loginfile);
System.setProperty("sun.security.krb5.debug", "true");

final LoginContext loginContext = new LoginContext(module);

// attempt to login
loginContext.login();

// output some info
System.out.println("Subject=" + loginContext.getSubject());

// logout
loginContext.logout();

System.out.println("Connection test successful.");

jaas.conf:

com.sun.comcurity.jgss.krb5.initiate {
    com.sun.comcurity.auth.module.Krb5LoginModule required
    doNotPrompt=true
    principal="HTTP/[email protected]"
    useKeyTab=true
    keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
    storeKey=true;
};  

com.sun.security.jgss.krb5.accept {
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt=true
    principal="HTTP/[email protected]"
    useKeyTab=true
    keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
    storeKey=true
    useTicketCache=true
    isInitiator=true
    refreshKrb5Config=true
    moduleBanner=true
    storePass=true;
};

spnego-client {
    com.sun.security.auth.module.Krb5LoginModule required;
};

spnego-server {
    com.sun.security.auth.module.Krb5LoginModule required
    principal="HTTP/[email protected]"
    useKeyTab=true
    keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
    storeKey=true
    useTicketCache=true
    isInitiator=false
    refreshKrb5Config=true
    moduleBanner=true
;
};

krb5.ini

[libdefaults]
        default_realm = CORP1.AD1.COMPANY.NET
        default_keytab_name = FILE:/opt/app/tomcat/ssoad1/servername.domain.com.keytab
        default_tkt_enctypes = rc4-hmac
        default_tgs_enctypes = rc4-hmac
        forwardable  = true
        renewable  = true
        noaddresses = true
        clockskew  = 300
        udp_preference_limit = 1

[realms]
        CORP1.AD1.COMPANY.NET = {
                kdc = ndcr001k.corp1.ad1.company.net:88
                default_domain = domain.com
        }

[domain_realm]
        .domain.com = CORP1.AD1.COMPANY.NET

from net.sourceforge.spnego.SpnegoAuthenticator.java

    SpnegoAuthenticator.LOCK.lock();
    try {
        LOGGER.fine("create context");
        LOGGER.fine("serverCredentials="+this.serverCredentials.toString());
        context = SpnegoAuthenticator.MANAGER.createContext(this.serverCredentials);
        context.requestCredDeleg(true);
        LOGGER.fine("clientModuleName="+clientModuleName.toString());
        LOGGER.fine("context.getCredDelegState()="+context.getCredDelegState());
        token = context.acceptSecContext(gss, 0, gss.length); // When I understand right : gss contains the token from the authorized client (IE Windows user)
        LOGGER.fine("token="+token);
        LOGGER.fine("context.getDelegCred()="+context.getDelegCred());
    } finally {
        SpnegoAuthenticator.LOCK.unlock();
    }

creates the following Exception:

javax.servlet.ServletException: GSSException: No valid credentials provided
    net.sourceforge.spnego.SpnegoHttpFilter.doFilter(SpnegoHttpFilter.java:287)
Root Cause

GSSException: No valid credentials provided
    sun.security.jgss.krb5.Krb5Context.getDelegCred(Krb5Context.java:511)
    sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)
    sun.security.jgss.spnego.SpNegoContext.getDelegCred(SpNegoContext.java:1064)
    sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)
    net.sourceforge.spnego.SpnegoAuthenticator.doSpnegoAuth(SpnegoAuthenticator.java:503)

Solution

  • As recommended I answer my own question:
    First make sure to have delegation allowed in active directory for the user that is listed in the keytab file. For delegation we add a full qualified hostname and a username under which the service is running on the second sever (delegation target server - here windows). AD Delegation tab
    The AD admins should know how to create a keytab file and hand it over to you.
    Create a jaas.conf and a krb5.ini file like described in the question.

    Use the library from http://spnego.sourceforge.net/.
    Add the filter to the web.xml:

    <filter>
        <filter-name>SpnegoHttpFilter</filter-name>
        <filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>
        ....
        ....
        <init-param>
            <param-name>spnego.allow.delegation</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>SpnegoHttpFilter</filter-name>
        <url-pattern>/sc2</url-pattern>
    </filter-mapping>
    

    with all needed init parameters like described on the library web page and the filter mapping of course.

    Start Tomcat with the following options:

    CATALINA_OPTS="-Dsun.security.krb5.debug=[true|false]
    -Djava.security.auth.login.config=/opt/app/tomcat/ssoad1/jaas.conf
    -Djava.security.krb5.conf=/opt/app/tomcat/ssoad1/krb5.ini
    -Djavax.security.auth.useSubjectCredsOnly=false" 
    

    The last option

    -Djavax.security.auth.useSubjectCredsOnly=false

    is very important - without that it doesn't work. That is not mentioned on the spnego.sourceforge.net website.


    And then the magic really works:
    The application acts as a http client with the credentials from the user who called the application from the browser.

    private void doServiceCall(HttpServletRequest request, StringBuilder sb) throws GSSException, MalformedURLException, PrivilegedActionException, IOException {
           if (request instanceof DelegateServletRequest) {
                DelegateServletRequest dsr = (DelegateServletRequest) request;
                GSSCredential creds = dsr.getDelegatedCredential();
                if (null == creds) {
                    sb.append("No delegated creds.");
                } else {
                    sb.append(creds.getName().toString());
    
                    SpnegoHttpURLConnection spnego =
                        new SpnegoHttpURLConnection(creds);
    
                    HttpURLConnection con = spnego.connect(new URL("https://server.domain.com/ServiceFactory/servicenamexyz/Get?KeyConditionValue=ACTION_OUTPUT"));
    
                    sb.append("<br />HTTP Status Code: " + spnego.getResponseCode());
                    sb.append("<br />HTTP Status Message: " + spnego.getResponseMessage());
    
                    String contentType = con.getContentType();
                    sb.append("<br />HTTP Content Type: " + contentType);
    
                    StringBuilder result = new StringBuilder();
                    String line;
                    BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
                    while ((line = reader.readLine()) != null) {
                        result.append(line);
                    }
                    reader.close();
    
                    sb.append("<br />HTTP Content: " + result.toString());
    
                    spnego.disconnect();
                }
    
            } else {
                sb.append("Request not a delegate.");
            }
           br(sb);
    }