Skip to content

Commit 1b3e81e

Browse files
committed
add kyuubi.session.conf.display.mode config (REDACTED/ORIGINAL/NONE)
1 parent c5a1b50 commit 1b3e81e

3 files changed

Lines changed: 80 additions & 8 deletions

File tree

kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3336,6 +3336,21 @@ object KyuubiConf {
33363336
.regexConf
33373337
.createOptional
33383338

3339+
val SESSION_CONF_DISPLAY_MODE: ConfigEntry[String] =
3340+
buildConf("kyuubi.session.conf.display.mode")
3341+
.serverOnly
3342+
.doc("Controls how session configurations are returned in REST API responses. " +
3343+
"Supported values: " +
3344+
"<ul>" +
3345+
"<li>REDACTED: Mask values that match kyuubi.server.redaction.regex (default).</li>" +
3346+
"<li>ORIGINAL: Return the raw config values as-is.</li>" +
3347+
"<li>NONE: Omit the conf map from responses entirely.</li>" +
3348+
"</ul>")
3349+
.version("1.12.0")
3350+
.stringConf
3351+
.checkValues(Set("REDACTED", "ORIGINAL", "NONE"))
3352+
.createWithDefault("REDACTED")
3353+
33393354
val SERVER_PERIODIC_GC_INTERVAL: ConfigEntry[Long] =
33403355
buildConf("kyuubi.server.periodicGC.interval")
33413356
.doc("How often to trigger the periodic garbage collection. 0 will disable it.")

kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/ApiUtils.scala

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,28 @@ import scala.collection.JavaConverters._
2222
import org.apache.kyuubi.{Logging, Utils}
2323
import org.apache.kyuubi.client.api.v1.dto
2424
import org.apache.kyuubi.client.api.v1.dto.{OperationData, OperationProgress, ServerData, SessionData}
25-
import org.apache.kyuubi.config.KyuubiConf.SERVER_SECRET_REDACTION_PATTERN
25+
import org.apache.kyuubi.config.KyuubiConf.{SERVER_SECRET_REDACTION_PATTERN, SESSION_CONF_DISPLAY_MODE}
2626
import org.apache.kyuubi.ha.client.ServiceNodeInfo
2727
import org.apache.kyuubi.operation.KyuubiOperation
2828
import org.apache.kyuubi.session.KyuubiSession
2929

3030
object ApiUtils extends Logging {
31+
32+
private def buildConf(
33+
rawConf: Map[String, String],
34+
session: KyuubiSession): java.util.Map[String, String] = {
35+
session.sessionManager.getConf.get(SESSION_CONF_DISPLAY_MODE) match {
36+
case "NONE" => Map.empty[String, String].asJava
37+
case "ORIGINAL" => rawConf.asJava
38+
case _ =>
39+
val pattern = session.sessionManager.getConf.get(SERVER_SECRET_REDACTION_PATTERN)
40+
Utils.redact(pattern, rawConf.toSeq).toMap.asJava
41+
}
42+
}
43+
3144
def sessionEvent(session: KyuubiSession): dto.KyuubiSessionEvent = {
3245
session.getSessionEvent.map { event =>
33-
val redactionPattern = session.sessionManager.getConf.get(SERVER_SECRET_REDACTION_PATTERN)
34-
val redactedConf = Utils.redact(redactionPattern, event.conf.toSeq).toMap.asJava
46+
val conf = buildConf(event.conf, session)
3547
dto.KyuubiSessionEvent.builder()
3648
.sessionId(event.sessionId)
3749
.clientVersion(event.clientVersion)
@@ -40,7 +52,7 @@ object ApiUtils extends Logging {
4052
.user(event.user)
4153
.clientIp(event.clientIP)
4254
.serverIp(event.serverIP)
43-
.conf(redactedConf)
55+
.conf(conf)
4456
.remoteSessionId(event.remoteSessionId)
4557
.engineId(event.engineId)
4658
.engineName(event.engineName)
@@ -57,14 +69,13 @@ object ApiUtils extends Logging {
5769

5870
def sessionData(session: KyuubiSession): SessionData = {
5971
val sessionEvent = session.getSessionEvent
60-
val redactionPattern = session.sessionManager.getConf.get(SERVER_SECRET_REDACTION_PATTERN)
61-
val redactedConf = Utils.redact(redactionPattern, session.conf.toSeq).toMap.asJava
72+
val conf = buildConf(session.conf, session)
6273
new SessionData(
6374
session.handle.identifier.toString,
6475
sessionEvent.map(_.remoteSessionId).getOrElse(""),
6576
session.user,
6677
session.ipAddress,
67-
redactedConf,
78+
conf,
6879
session.createTime,
6980
session.lastAccessTime - session.createTime,
7081
session.getNoOperationTime,

kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ class SessionsResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper {
396396
assert(sessionHandle.toString.equals(operations.head.getSessionId))
397397
}
398398

399-
test("get /sessions returns redacted spark confs") {
399+
test("get /sessions returns redacted spark confs when mode is REDACTED") {
400400
val sensitiveKey = "spark.password"
401401
val sensitiveValue = "superSecret123"
402402
val requestObj = new SessionOpenRequest(Map(sensitiveKey -> sensitiveValue).asJava)
@@ -418,4 +418,50 @@ class SessionsResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper {
418418
val delResp = webTarget.path(s"api/v1/sessions/$sessionHandle").request().delete()
419419
assert(200 == delResp.getStatus)
420420
}
421+
422+
test("get /sessions returns empty conf when mode is NONE") {
423+
withSessionConfDisplayMode("NONE") {
424+
val requestObj =
425+
new SessionOpenRequest(Map("spark.password" -> "secret", "key" -> "val").asJava)
426+
val r = webTarget.path("api/v1/sessions")
427+
.request(MediaType.APPLICATION_JSON_TYPE)
428+
.post(Entity.entity(requestObj, MediaType.APPLICATION_JSON_TYPE))
429+
assert(200 == r.getStatus)
430+
val sessionHandle = r.readEntity(classOf[SessionHandle]).getIdentifier
431+
432+
val r2 = webTarget.path("api/v1/sessions").request().get()
433+
assert(200 == r2.getStatus)
434+
val sessions = r2.readEntity(new GenericType[Seq[SessionData]]() {})
435+
val sessionConf = sessions.find(_.getIdentifier == sessionHandle.toString).get.getConf
436+
assert(sessionConf.isEmpty)
437+
438+
webTarget.path(s"api/v1/sessions/$sessionHandle").request().delete()
439+
}
440+
}
441+
442+
test("get /sessions returns raw conf when mode is ORIGINAL") {
443+
withSessionConfDisplayMode("ORIGINAL") {
444+
val sensitiveKey = "spark.password"
445+
val sensitiveValue = "plainVisible"
446+
val requestObj = new SessionOpenRequest(Map(sensitiveKey -> sensitiveValue).asJava)
447+
val r = webTarget.path("api/v1/sessions")
448+
.request(MediaType.APPLICATION_JSON_TYPE)
449+
.post(Entity.entity(requestObj, MediaType.APPLICATION_JSON_TYPE))
450+
assert(200 == r.getStatus)
451+
val sessionHandle = r.readEntity(classOf[SessionHandle]).getIdentifier
452+
453+
val r2 = webTarget.path("api/v1/sessions").request().get()
454+
assert(200 == r2.getStatus)
455+
val sessions = r2.readEntity(new GenericType[Seq[SessionData]]() {})
456+
val sessionConf = sessions.find(_.getIdentifier == sessionHandle.toString).get.getConf
457+
assert(sessionConf.get(sensitiveKey) == sensitiveValue)
458+
459+
webTarget.path(s"api/v1/sessions/$sessionHandle").request().delete()
460+
}
461+
}
462+
463+
private def withSessionConfDisplayMode(mode: String)(f: => Unit): Unit = {
464+
conf.set(KyuubiConf.SESSION_CONF_DISPLAY_MODE, mode)
465+
try f finally conf.set(KyuubiConf.SESSION_CONF_DISPLAY_MODE, "REDACTED")
466+
}
421467
}

0 commit comments

Comments
 (0)