Kerberos Authentication with Play 2.5.9 WS API


#1

Hello, I am attempting to call an HttpFS service which requires Kerberos authentication from a Play web app.

I have a krb5.conf and keytab file included in my docker container and setting these jvm options:

javaOptions in Universal := Seq(
      "-Djava.security.krb5.conf=/opt/docker/conf/krb5.conf",
      "-Dsun.security.krb5.debug=true",
      "-Dsun.security.spnego.debug=true")

This is the block of code throwing an exception:

ws.url("https://httpfsUrl:14000/webhdfs/v1/development/test?op=MKDIRS")
        .withAuth("username", "password", WSAuthScheme.KERBEROS)
        .put(Map("test" -> Seq("test")))
        .map { response =>
          Ok(response.toString)
        }
        .recover { case thrown =>
          val sw: StringWriter = new StringWriter()
          val pw: PrintWriter = new PrintWriter(sw, true)
          thrown.printStackTrace(pw)
          BadRequest(thrown.getMessage + "\n" + sw.getBuffer.toString)
        }

These are my detailed logs of the error I’m getting:

14:25:13.995 [application-akka.actor.default-dispatcher-6] DEBUG org.asynchttpclient.spnego.SpnegoEngine - init usrhdphttpfs.rxcorp.com
Java config name: /opt/docker/conf/krb5.conf
Loaded from Java config
14:25:14.010 [application-akka.actor.default-dispatcher-6] ERROR org.asynchttpclient.spnego.SpnegoEngine - generateToken
org.ietf.jgss.GSSException: No valid credentials provided (Mechanism level: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt))
	at sun.security.jgss.spnego.SpNegoContext.initSecContext(SpNegoContext.java:454)
	at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248)
	at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
	at org.asynchttpclient.spnego.SpnegoEngine.generateToken(SpnegoEngine.java:141)
	at org.asynchttpclient.util.AuthenticatorUtils.perConnectionAuthorizationHeader(AuthenticatorUtils.java:173)
	at org.asynchttpclient.netty.request.NettyRequestSender.sendRequestWithNewChannel(NettyRequestSender.java:253)
	at org.asynchttpclient.netty.request.NettyRequestSender.sendRequestWithCertainForceConnect(NettyRequestSender.java:136)
	at org.asynchttpclient.netty.request.NettyRequestSender.sendRequest(NettyRequestSender.java:107)
	at org.asynchttpclient.DefaultAsyncHttpClient.execute(DefaultAsyncHttpClient.java:216)
	at org.asynchttpclient.DefaultAsyncHttpClient.executeRequest(DefaultAsyncHttpClient.java:184)
	at play.api.libs.ws.ahc.AhcWSClient.executeRequest(AhcWS.scala:45)
	at play.api.libs.ws.ahc.AhcWSRequest$.execute(AhcWS.scala:90)
	at play.api.libs.ws.ahc.AhcWSRequest$$anon$2.execute(AhcWS.scala:166)
	at play.api.libs.ws.ahc.AhcWSRequest.execute(AhcWS.scala:168)
	at play.api.libs.ws.WSRequest$class.put(WS.scala:541)
	at play.api.libs.ws.ahc.AhcWSRequest.put(AhcWS.scala:107)
	at controllers.ContractManager$$anonfun$testHdfs$1.apply(ContractManager.scala:63)
	at controllers.ContractManager$$anonfun$testHdfs$1.apply(ContractManager.scala:58)
	at play.api.mvc.Action$.invokeBlock(Action.scala:498)
	at play.api.mvc.Action$.invokeBlock(Action.scala:495)
	at play.api.mvc.ActionBuilder$$anon$2.apply(Action.scala:458)
	at play.api.mvc.Action$$anonfun$apply$2$$anonfun$apply$5$$anonfun$apply$6.apply(Action.scala:112)
	at play.api.mvc.Action$$anonfun$apply$2$$anonfun$apply$5$$anonfun$apply$6.apply(Action.scala:112)
	at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
	at play.api.mvc.Action$$anonfun$apply$2$$anonfun$apply$5.apply(Action.scala:111)
	at play.api.mvc.Action$$anonfun$apply$2$$anonfun$apply$5.apply(Action.scala:110)
	at scala.Option.map(Option.scala:146)
	at play.api.mvc.Action$$anonfun$apply$2.apply(Action.scala:110)
	at play.api.mvc.Action$$anonfun$apply$2.apply(Action.scala:103)
	at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:253)
	at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:251)
	at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:36)
	at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
	at akka.dispatch.BatchingExecutor$BlockableBatch$$anonfun$run$1.apply$mcV$sp(BatchingExecutor.scala:91)
	at akka.dispatch.BatchingExecutor$BlockableBatch$$anonfun$run$1.apply(BatchingExecutor.scala:91)
	at akka.dispatch.BatchingExecutor$BlockableBatch$$anonfun$run$1.apply(BatchingExecutor.scala:91)
	at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:72)
	at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:90)
	at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:39)
	at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:415)
	at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
	at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
	at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
	at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: org.ietf.jgss.GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)
	at sun.security.jgss.krb5.Krb5InitCredential.getInstance(Krb5InitCredential.java:147)
	at sun.security.jgss.krb5.Krb5MechFactory.getCredentialElement(Krb5MechFactory.java:122)
	at sun.security.jgss.krb5.Krb5MechFactory.getMechanismContext(Krb5MechFactory.java:187)
	at sun.security.jgss.GSSManagerImpl.getMechanismContext(GSSManagerImpl.java:224)
	at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:212)
	at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
	at sun.security.jgss.spnego.SpNegoContext.GSS_initSecContext(SpNegoContext.java:882)
	at sun.security.jgss.spnego.SpNegoContext.initSecContext(SpNegoContext.java:317)
	... 43 common frames omitted

Is my use of the Play WS API correct here? I am able to successfully authenticate and call the REST API from bash like so:

kinit -kt user.keytab -V user@DOMAIN.COM
curl -k -X PUT --negotiate -u : "https://httpfsUrl:14000/webhdfs/v1/development/test?op=MKDIRS"