Skip to content

Commit

Permalink
[KYUUBI apache#1262] Support multiple kinds of SASL authentication type
Browse files Browse the repository at this point in the history
  • Loading branch information
turboFei committed Oct 20, 2021
1 parent d0d5fb6 commit 1286bcb
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import scala.collection.JavaConverters._

import org.apache.kyuubi.{Logging, Utils}
import org.apache.kyuubi.engine.ShareLevel
import org.apache.kyuubi.service.authentication.{AuthTypes, SaslQOP}
import org.apache.kyuubi.service.authentication.{PlainAuthTypes, SaslQOP}

case class KyuubiConf(loadSysDefault: Boolean = true) extends Logging {
import KyuubiConf._
Expand Down Expand Up @@ -136,7 +136,9 @@ case class KyuubiConf(loadSysDefault: Boolean = true) extends Logging {
FRONTEND_THRIFT_BINARY_BIND_PORT,
FRONTEND_REST_BIND_HOST,
FRONTEND_REST_BIND_PORT,
AUTHENTICATION_METHOD,
AUTHENTICATION_SASL_ENABLED,
AUTHENTICATION_SASL_KERBEROS_ENABLED,
AUTHENTICATION_SASL_PLAIN_AUTH_TYPE,
SERVER_KEYTAB,
SERVER_PRINCIPAL,
KINIT_INTERVAL)
Expand Down Expand Up @@ -372,6 +374,31 @@ object KyuubiConf {
.version("1.4.0")
.fallbackConf(FRONTEND_LOGIN_BACKOFF_SLOT_LENGTH)

val AUTHENTICATION_SASL_ENABLED: ConfigEntry[Boolean] = buildConf("authentication.sasl.enabled")
.doc("Whether enable SASL mechanism for authentication")
.version("1.4.0")
.booleanConf
.createWithDefault(true)

val AUTHENTICATION_SASL_KERBEROS_ENABLED: ConfigEntry[Boolean] =
buildConf("authentication.sasl.kerberos.enabled")
.doc("Whether enable GSSAPI/kerberos mechanism for SASL authentication")
.version("1.4.0")
.booleanConf
.createWithDefault(false)

val AUTHENTICATION_SASL_PLAIN_AUTH_TYPE: OptionalConfigEntry[String] =
buildConf("authentication.sasl.plain.auth.type")
.doc("Client authentication types for PLAIN mechanism.<ul>" +
" <li>NONE: no authentication check.</li>" +
" <li>CUSTOM: User-defined authentication.</li>" +
" <li>LDAP: Lightweight Directory Access Protocol authentication.</li></ul>")
.version("1.4.0")
.stringConf
.transform(_.toUpperCase(Locale.ROOT))
.checkValues(PlainAuthTypes.values.map(_.toString))
.createOptional

val AUTHENTICATION_METHOD: ConfigEntry[String] = buildConf("authentication")
.doc("Client authentication types.<ul>" +
" <li>NOSASL: raw transport.</li>" +
Expand All @@ -382,8 +409,8 @@ object KyuubiConf {
.version("1.0.0")
.stringConf
.transform(_.toUpperCase(Locale.ROOT))
.checkValues(AuthTypes.values.map(_.toString))
.createWithDefault(AuthTypes.NONE.toString)
.checkValues(PlainAuthTypes.values.map(_.toString))
.createWithDefault(PlainAuthTypes.NONE.toString)

val AUTHENTICATION_CUSTOM_CLASS: OptionalConfigEntry[String] =
buildConf("authentication.custom.class")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,26 @@ import org.apache.thrift.transport.{TTransportException, TTransportFactory}
import org.apache.kyuubi.KyuubiSQLException
import org.apache.kyuubi.config.KyuubiConf
import org.apache.kyuubi.config.KyuubiConf._
import org.apache.kyuubi.service.authentication.AuthTypes._
import org.apache.kyuubi.service.authentication.PlainAuthTypes._

class KyuubiAuthenticationFactory(conf: KyuubiConf) {
private val saslEnabled: Boolean = conf.get(AUTHENTICATION_SASL_ENABLED)
private val kerberosEnabled: Boolean = conf.get(AUTHENTICATION_SASL_KERBEROS_ENABLED)
private val plainAuthType: Option[PlainAuthType] =
conf.get(AUTHENTICATION_SASL_PLAIN_AUTH_TYPE).map(PlainAuthTypes.withName)

private val authType: AuthType = AuthTypes.withName(conf.get(AUTHENTICATION_METHOD))

private val saslServer: Option[HadoopThriftAuthBridgeServer] = authType match {
case KERBEROS =>
private val hadoopAuthServer: Option[HadoopThriftAuthBridgeServer] = {
if (saslEnabled && kerberosEnabled) {
val secretMgr = KyuubiDelegationTokenManager(conf)
try {
secretMgr.startThreads()
} catch {
case e: IOException => throw new TTransportException("Failed to start token manager", e)
}
Some(new HadoopThriftAuthBridgeServer(secretMgr))
case _ => None
} else {
None
}
}

private def getSaslProperties: java.util.Map[String, String] = {
Expand All @@ -59,34 +63,40 @@ class KyuubiAuthenticationFactory(conf: KyuubiConf) {
}

def getTTransportFactory: TTransportFactory = {
saslServer match {
case Some(server) =>
val serverTransportFactory = try {
server.createSaslServerTransportFactory(getSaslProperties)
} catch {
case e: TTransportException => throw new LoginException(e.getMessage)
}
if (!saslEnabled || (!kerberosEnabled && plainAuthType.isEmpty)) {
new TTransportFactory()
} else {
val kerberosTransportFactory = hadoopAuthServer match {
case Some(server) =>
val transportFactory = try {
server.createSaslServerTransportFactory(getSaslProperties)
} catch {
case e: TTransportException => throw new LoginException(e.getMessage)
}
Some(transportFactory)

case _ => None
}

server.wrapTransportFactory(serverTransportFactory)
val transportFactory = plainAuthType.map { authType =>
PlainSASLHelper.getTransportFactory(authType.toString, conf, kerberosTransportFactory)
}.orElse(kerberosTransportFactory).orNull

case _ => authType match {
case NOSASL => new TTransportFactory
case _ => PlainSASLHelper.getTransportFactory(authType.toString, conf)
}
hadoopAuthServer.map(_.wrapTransportFactory(transportFactory)).getOrElse(transportFactory)
}
}

def getTProcessorFactory(fe: Iface): TProcessorFactory = saslServer match {
def getTProcessorFactory(fe: Iface): TProcessorFactory = hadoopAuthServer match {
case Some(server) => FEServiceProcessorFactory(server, fe)
case _ => PlainSASLHelper.getProcessFactory(fe)
}

def getRemoteUser: Option[String] = {
saslServer.map(_.getRemoteUser).orElse(Option(TSetIpAddressProcessor.getUserName))
hadoopAuthServer.map(_.getRemoteUser).orElse(Option(TSetIpAddressProcessor.getUserName))
}

def getIpAddress: Option[String] = {
saslServer.map(_.getRemoteAddress).map(_.getHostAddress)
hadoopAuthServer.map(_.getRemoteAddress).map(_.getHostAddress)
.orElse(Option(TSetIpAddressProcessor.getUserIpAddress))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

package org.apache.kyuubi.service.authentication

object AuthTypes extends Enumeration {
type AuthType = Value
object PlainAuthTypes extends Enumeration {
type PlainAuthType = Value

val NOSASL, NONE, LDAP, KERBEROS, CUSTOM = Value
val NONE, LDAP, CUSTOM = Value
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ object PlainSASLHelper {
SQLPlainProcessorFactory(service)
}

def getTransportFactory(authTypeStr: String, conf: KyuubiConf): TTransportFactory = {
val saslFactory = new TSaslServerTransport.Factory()
def getTransportFactory(
authTypeStr: String,
conf: KyuubiConf,
transportFactory: Option[TSaslServerTransport.Factory] = None): TTransportFactory = {
val saslFactory = transportFactory.getOrElse(new TSaslServerTransport.Factory())
try {
val handler = new PlainServerCallbackHandler(authTypeStr, conf)
val props = new java.util.HashMap[String, String]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ThriftFrontendServiceSuite extends KyuubiFunSuite {
protected val conf = KyuubiConf()
.set(KyuubiConf.FRONTEND_THRIFT_BINARY_BIND_PORT, 0)
.set("kyuubi.test.server.should.fail", "false")
.set(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE, "NONE")

val user: String = System.getProperty("user.name")
val sessionConf: util.Map[String, String] = new util.HashMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import org.apache.kyuubi.config.KyuubiConf
import org.apache.kyuubi.service.authentication.PlainSASLServer.SaslPlainProvider
import org.apache.kyuubi.util.KyuubiHadoopUtils


class KyuubiAuthenticationFactorySuite extends KyuubiFunSuite {
import KyuubiAuthenticationFactory._

Expand All @@ -45,7 +44,7 @@ class KyuubiAuthenticationFactorySuite extends KyuubiFunSuite {
}

test("AuthType NONE") {
val kyuubiConf = KyuubiConf()
val kyuubiConf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE, "NONE")
val auth = new KyuubiAuthenticationFactory(kyuubiConf)
auth.getTTransportFactory
assert(Security.getProviders.exists(_.isInstanceOf[SaslPlainProvider]))
Expand All @@ -55,22 +54,24 @@ class KyuubiAuthenticationFactorySuite extends KyuubiFunSuite {
}

test("AuthType Other") {
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_METHOD, "INVALID")
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE, "INVALID")
val e = intercept[IllegalArgumentException](new KyuubiAuthenticationFactory(conf))
assert(e.getMessage === "The value of kyuubi.authentication should be one of" +
" CUSTOM, KERBEROS, LDAP, NONE, NOSASL, but was INVALID")
assert(e.getMessage === "The value of kyuubi.authentication.sasl.plain.auth.type should be" +
" one of CUSTOM, LDAP, NONE, but was INVALID")
}

test("AuthType LDAP") {
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_METHOD, "LDAP")
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_SASL_ENABLED, true)
.set(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE, "LDAP")
val authFactory = new KyuubiAuthenticationFactory(conf)
authFactory.getTTransportFactory
assert(Security.getProviders.exists(_.isInstanceOf[SaslPlainProvider]))
}


test("AuthType KERBEROS w/o keytab/principal") {
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_METHOD, "KERBEROS")
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_SASL_ENABLED, true)
.set(KyuubiConf.AUTHENTICATION_SASL_KERBEROS_ENABLED, true)

val factory = new KyuubiAuthenticationFactory(conf)
val e = intercept[LoginException](factory.getTTransportFactory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SessionManagerSuite extends ThriftFrontendServiceSuite with Eventually {
.set(KyuubiConf.OPERATION_IDLE_TIMEOUT, Duration.ofSeconds(20).toMillis)
.set(KyuubiConf.SESSION_CONF_RESTRICT_LIST, Seq("spark.*"))
.set(KyuubiConf.SESSION_CONF_IGNORE_LIST, Seq("session.engine.*"))
.set(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE, "NONE")

test("close expired operations") {
withSessionHandle{ (client, handle) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,17 @@ object ServiceDiscovery extends Logging {
confsToPublish += ("hive.server2.thrift.port" -> hostPort(1))
confsToPublish += ("hive.server2.thrift.sasl.qop" -> conf.get(KyuubiConf.SASL_QOP))
// Auth specific confs
val authenticationMethod = conf.get(KyuubiConf.AUTHENTICATION_METHOD)
confsToPublish += ("hive.server2.authentication" -> authenticationMethod)
if (authenticationMethod.equalsIgnoreCase("KERBEROS")) {
confsToPublish += ("hive.server2.authentication.sasl.enabled" ->
conf.get(KyuubiConf.AUTHENTICATION_SASL_ENABLED))
confsToPublish += ("hive.server2.authentication.sasl.kerberos.enabled" ->
conf.get(KyuubiConf.AUTHENTICATION_SASL_KERBEROS_ENABLED))
conf.get(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE).foreach { plainAuthType =>
confsToPublish += ("hive.server2.authentication.sasl.plain.auth.type" -> plainAuthType)
}
confsToPublish += ("hive.server2.authentication.sasl.plain.auth.type" ->
conf.get(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE))
if (conf.get(KyuubiConf.AUTHENTICATION_SASL_ENABLED) &&
conf.get(KyuubiConf.AUTHENTICATION_SASL_KERBEROS_ENABLED)) {
confsToPublish += ("hive.server2.authentication.kerberos.principal" ->
conf.get(KyuubiConf.SERVER_PRINCIPAL).map(KyuubiHadoopUtils.getServerPrincipal)
.getOrElse(""))
Expand Down

0 comments on commit 1286bcb

Please sign in to comment.