Kerberos & Apigee configuration that works

deniska
Participant V

Hi team,

I'm struggling with sample kerberos code provided in the API samples.

No matter what we are trying, we get same error from java:

GSSException: No credential found for: 1.3.6.1.5.5.2 usage: Accept

* 1.3.6.1.5.5.2 are the OID of SPNEGO protocol.

My environment:

Apigee AIO profile, with OpenJDK, Centos 7.5

Both WIN2016 and WIN2012R2 with configured KDC:

kdc: KRB.COM, kdc server - krb1.krb.com, Apigee added to DNS: api.krb.com, bi-directional DNS resolve working.

Keytab generation process:

1. Create user APIX

2. Add SPN: setspn -A HTTP/apix.krb.com apix

3. Generate keytab: ktpass -princ HTTP/apix.krb.com@KRB.COM -pass Master123 -mapuser apix -mapOp set -crypto all -pType KRB5_NT_PRINCIPAL -out apix.keytab

Made needed configuration listed here (added relevant string to MP conf-file):

conf/system.properties+java.security.auth.login.config=/opt/login.conf
conf/system.properties+java.security.krb5.conf=/opt/krb5.conf

My krb5.conf file:

[logging]
 default = FILE:/var/log/krb5libs.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log
[libdefaults]
 default_realm = KRB.COM
 dns_lookup_realm = false
 dns_lookup_kdc = false
 ticket_lifetime = 24h
 renew_lifetime = 7d
 forwardable = true
[realms]
 KRB.COM = {
  kdc = krb1.krb.com
  admin_server = krb1.krb.com
 }
[domain_realm]
 .krb.com = KRB.COM
 krb.com = KRB.COM

My login.conf file:

ServicePrincipalLoginContext
{
      com.sun.security.auth.module.Krb5LoginModule required
      principal="HTTP/apix.krb.com@KRB.COM"
      doNotPrompt=true
      useTicketCache=true
      keyTab="/opt/apix.keytab"
      useKeyTab=true
      storeKey=true
      debug=true;
};

Inside Apigee, configuration file for java-callout:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout async="false" continueOnError="false" enabled="true" name="credentialdelegation">
    <DisplayName>CredentialDelegation</DisplayName>
    <FaultRules/>
    <Properties>
        <Property name="krb5Conf">/opt/krb5.conf</Property>
        <Property name="loginConf">/opt/login.conf</Property>
        <Property name="loginModule">ServicePrincipalLoginContext</Property>
        <Property name="serverPrincipal">HTTP/apix.krb.com@KRB.COM</Property>
    </Properties>
    <ClassName>krb5.apigee.CredentialMediation</ClassName>
    <ResourceURL>java://kerberos-credential-mediation.jar</ResourceURL>
</JavaCallout>

We have chown apigee:apigee on all files.

After so many things I've checked, I came to conclusion that keytab is OK, KDC config also OK, but something missing in Java code(maybe). The original Javacode are lost, but @Anil Sagar @ Google was kind to decompile and provided us with class logic inside:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//


package krb5.apigee;


import com.apigee.flow.execution.ExecutionContext;
import com.apigee.flow.execution.ExecutionResult;
import com.apigee.flow.execution.spi.Execution;
import com.apigee.flow.message.MessageContext;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.xml.bind.DatatypeConverter;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;


public class CredentialMediation implements Execution {
    Map<String, String> properties;


    public CredentialMediation(Map<String, String> props) {
        this.properties = props;
    }


    public ExecutionResult execute(MessageContext messageContext, ExecutionContext arg1) {
        //System.setProperty("java.security.krb5.conf", (String)this.properties.get("krb5Conf"));
        //System.setProperty("java.security.auth.login.config", (String)this.properties.get("loginConf"));
        String loginModule = (String)this.properties.get("loginModule");


        try {
            LoginContext loginContext = new LoginContext(loginModule);
            loginContext.login();
            Subject serviceSubject = loginContext.getSubject();
            GSSManager MANAGER = GSSManager.getInstance();
            GSSContext context = MANAGER.createContext(this.getServerCredential(MANAGER, serviceSubject));
            String header = (String)messageContext.getVariable("request.header.Authorization");
            messageContext.setVariable("hello", "world");
            messageContext.setVariable("incomingHeader", header);
            String header1 = header.split("Negotiate ")[1];
            messageContext.setVariable("incomingToken", header1);
            byte[] token = DatatypeConverter.parseBase64Binary(header1);
            context.acceptSecContext(token, 0, token.length);
            System.out.println("After SecContext");
            GSSCredential incomingCreds = context.getDelegCred();
            System.out.println("After getDelegCred");
            messageContext.setVariable("incomingSubject", context.getSrcName().toString());
            System.out.println("incoming Subject");
            System.out.println(context.getSrcName().toString());
            System.out.println("incoming Subject printed..");
            String newToken = (String)Subject.doAs(serviceSubject, new CredentialMediation.ServiceTicketGen(incomingCreds, (String)this.properties.get("serverPrincipal")));
            messageContext.setVariable("newToken", newToken);
            messageContext.setVariable("request.header.Authorization", "Negotiate " + newToken);
            return ExecutionResult.SUCCESS;
        } catch (Exception var12) {
            throw new RuntimeException(var12);
        }
    }


    private GSSCredential getServerCredential(final GSSManager MANAGER, Subject subject) throws PrivilegedActionException {
        PrivilegedExceptionAction<GSSCredential> action = new PrivilegedExceptionAction<GSSCredential>() {
            public GSSCredential run() throws GSSException {
                return MANAGER.createCredential((GSSName)null, 28800, new Oid("1.2.840.113554.1.2.2"), 0);
            }
        };
        return (GSSCredential)Subject.doAs(subject, action);
    }


    class ServiceTicketGen implements PrivilegedAction {
        private GSSCredential user;
        private String servicePrincipal;


        public ServiceTicketGen(GSSCredential user, String servicePrincipal) {
            this.user = user;
            this.servicePrincipal = servicePrincipal;
        }


        private Oid createKerberosOid() {
            try {
                return new Oid("1.2.840.113554.1.2.2");
            } catch (GSSException var2) {
                var2.printStackTrace();
                return null;
            }
        }


        public Object run() {
            byte[] outToken = (byte[])null;


            try {
                GSSManager manager = GSSManager.getInstance();
                GSSName serverName = manager.createName(this.servicePrincipal, GSSName.NT_HOSTBASED_SERVICE);
                GSSContext context = manager.createContext(serverName, this.createKerberosOid(), this.user, 0);
                context.requestMutualAuth(false);
                context.requestConf(false);
                context.requestInteg(true);
                outToken = context.initSecContext(new byte[0], 0, 0);
                String out = DatatypeConverter.printBase64Binary(outToken);
                System.out.println(out);
                context.dispose();
                return out;
            } catch (GSSException var6) {
                throw new RuntimeException(var6);
            }
        }
    }
}


No matter what we did, we always get the same error:

GSSException: No credential found for: 1.3.6.1.5.5.2 usage: Accept

Google search providing absolutely no matches for this exact error.

So I recorded some WireShark to see the packets exchange: (attached .pcap)

As we can see we have a request from the client to KDC and response from KDC to the client, and then something happens inside the java (exception) and TGT ticket exchange stops. From the .pcap I don't see anything 'wrong' with the req\resp, so what's the problem?

8354-krb-req.png

8355-krb-resp.png

8356-krb-exch.png

Can some1 help to examine the problem? Maybe some1 got kerberos working on Apigee and can share?

We used this proxy as an example.

Calling all experts to help :). @Dino-at-Google @Anil Sagar @ Google @Jared Williams @Rahul

0 1 1,159
1 REPLY 1

@Denis Kalitviansky : Hello Denis,

Are you able to solve this, I am getting same error.