Skip to content

Commit

Permalink
[KYUUBI #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 ac8b195
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 51 deletions.
4 changes: 3 additions & 1 deletion docs/deployment/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,14 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co

Key | Default | Meaning | Type | Since
--- | --- | --- | --- | ---
kyuubi\.authentication|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>NONE</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Client authentication types.<ul> <li>NOSASL: raw transport.</li> <li>NONE: no authentication check.</li> <li>KERBEROS: Kerberos/GSSAPI authentication.</li> <li>CUSTOM: User-defined authentication.</li> <li>LDAP: Lightweight Directory Access Protocol authentication.</li></ul></div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.0.0</div>
kyuubi\.authentication<br>\.custom\.class|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>&lt;undefined&gt;</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>User-defined authentication implementation of org.apache.kyuubi.service.authentication.PasswdAuthenticationProvider</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.3.0</div>
kyuubi\.authentication<br>\.ldap\.base\.dn|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>&lt;undefined&gt;</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>LDAP base DN.</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.0.0</div>
kyuubi\.authentication<br>\.ldap\.domain|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>&lt;undefined&gt;</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>LDAP domain.</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.0.0</div>
kyuubi\.authentication<br>\.ldap\.guidKey|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>uid</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>LDAP attribute name whose values are unique in this LDAP server.For example:uid or cn.</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.2.0</div>
kyuubi\.authentication<br>\.ldap\.url|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>&lt;undefined&gt;</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>SPACE character separated LDAP connection URL(s).</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.0.0</div>
kyuubi\.authentication<br>\.sasl\.enabled|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>true</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Whether enable SASL(GSSAPI or PLAIN) mechanism for authentication</div>|<div style='width: 30pt'>boolean</div>|<div style='width: 20pt'>1.4.0</div>
kyuubi\.authentication<br>\.sasl\.kerberos\.enabled|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>false</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Whether enable KERBEROS: Kerberos/GSSAPI authentication</div>|<div style='width: 30pt'>boolean</div>|<div style='width: 20pt'>1.4.0</div>
kyuubi\.authentication<br>\.sasl\.plain\.auth\.type|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>&lt;undefined&gt;</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>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></div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.4.0</div>
kyuubi\.authentication<br>\.sasl\.qop|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>auth</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Sasl QOP enable higher levels of protection for Kyuubi communication with clients.<ul> <li>auth - authentication only (default)</li> <li>auth-int - authentication plus integrity protection</li> <li>auth-conf - authentication plus integrity and confidentiality protection. This is applicable only if Kyuubi is configured to use Kerberos authentication.</li> </ul></div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.0.0</div>


Expand Down
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,18 +374,30 @@ object KyuubiConf {
.version("1.4.0")
.fallbackConf(FRONTEND_LOGIN_BACKOFF_SLOT_LENGTH)

val AUTHENTICATION_METHOD: ConfigEntry[String] = buildConf("authentication")
.doc("Client authentication types.<ul>" +
" <li>NOSASL: raw transport.</li>" +
" <li>NONE: no authentication check.</li>" +
" <li>KERBEROS: Kerberos/GSSAPI authentication.</li>" +
" <li>CUSTOM: User-defined authentication.</li>" +
" <li>LDAP: Lightweight Directory Access Protocol authentication.</li></ul>")
.version("1.0.0")
.stringConf
.transform(_.toUpperCase(Locale.ROOT))
.checkValues(AuthTypes.values.map(_.toString))
.createWithDefault(AuthTypes.NONE.toString)
val AUTHENTICATION_SASL_ENABLED: ConfigEntry[Boolean] = buildConf("authentication.sasl.enabled")
.doc("Whether enable SASL(GSSAPI or PLAIN) 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 KERBEROS: Kerberos/GSSAPI 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_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,22 @@ 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_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_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,15 @@ 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).toString)
confsToPublish += ("hive.server2.authentication.sasl.kerberos.enabled" ->
conf.get(KyuubiConf.AUTHENTICATION_SASL_KERBEROS_ENABLED).toString)
conf.get(KyuubiConf.AUTHENTICATION_SASL_PLAIN_AUTH_TYPE).foreach { plainAuthType =>
confsToPublish += ("hive.server2.authentication.sasl.plain.auth.type" -> plainAuthType)
}
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 ac8b195

Please sign in to comment.