This is an automated email from the ASF dual-hosted git repository.
wangzhen pushed a commit to branch branch-1.9
in repository https://gitbox.apache.org/repos/asf/kyuubi.git
The following commit(s) were added to refs/heads/branch-1.9 by this push:
new 63b67a56f [KYUUBI #6216] Support to deny some client ips to make
connection
63b67a56f is described below
commit 63b67a56f12f97a13343d65318cffbdafada4972
Author: wforget <[email protected]>
AuthorDate: Sun Apr 7 16:32:00 2024 +0800
[KYUUBI #6216] Support to deny some client ips to make connection
# :mag: Description
## Issue References ๐
This pull request fixes #6216
## Describe Your Solution ๐ง
Similar to #4540, sometimes we need to quickly deny requests from some
clients, so I added `kyuubi.server.limit.connections.ip.deny.list` to limit
client ips.
## Types of changes :bookmark:
- [ ] Bugfix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
## Test Plan ๐งช
#### Behavior Without This Pull Request :coffin:
#### Behavior With This Pull Request :tada:
#### Related Unit Tests
SessionLimiterSuite.test("test session limiter with ip deny list")
---
# Checklist ๐
- [X] This patch was not authored or co-authored using [Generative
Tooling](https://www.apache.org/legal/generative-tooling.html)
**Be nice. Be informative.**
Closes #6217 from wForget/KYUUBI-6216.
Closes #6216
e0a058c1d [wforget] update ctl
f66426eda [wforget] typo
5c5308ad8 [wforget] typo
959242e9b [wforget] Support to deny some client ips to make connection
Authored-by: wforget <[email protected]>
Signed-off-by: wforget <[email protected]>
(cherry picked from commit a4c3c0b75912ee60d1186c7531bf1d0da8ddda01)
Signed-off-by: wforget <[email protected]>
---
docs/configuration/settings.md | 1 +
.../org/apache/kyuubi/config/KyuubiConf.scala | 9 +++++++
.../ctl/cmd/refresh/RefreshConfigCommand.scala | 4 ++-
.../apache/kyuubi/ctl/opt/AdminCommandLine.scala | 2 +-
.../kyuubi/ctl/AdminControlCliArgumentsSuite.scala | 11 +++++++-
.../org/apache/kyuubi/client/AdminRestApi.java | 5 ++++
.../org/apache/kyuubi/server/KyuubiServer.scala | 8 ++++++
.../kyuubi/server/api/v1/AdminResource.scala | 19 ++++++++++++++
.../kyuubi/session/KyuubiSessionManager.scala | 26 +++++++++++++++----
.../org/apache/kyuubi/session/SessionLimiter.scala | 30 +++++++++++++++++++---
.../kyuubi/session/SessionLimiterSuite.scala | 15 +++++++++++
11 files changed, 119 insertions(+), 11 deletions(-)
diff --git a/docs/configuration/settings.md b/docs/configuration/settings.md
index 2ea09228f..fec770917 100644
--- a/docs/configuration/settings.md
+++ b/docs/configuration/settings.md
@@ -421,6 +421,7 @@ You can configure the Kyuubi properties in
`$KYUUBI_HOME/conf/kyuubi-defaults.co
| kyuubi.server.limit.batch.connections.per.user | <undefined>
| Maximum kyuubi server batch connections per user. Any user exceeding this
limit will not be allowed to connect.
| int | 1.7.0 |
| kyuubi.server.limit.batch.connections.per.user.ipaddress | <undefined>
| Maximum kyuubi server batch connections per user:ipaddress combination. Any
user-ipaddress exceeding this limit will not be allowed to connect.
| int | 1.7.0 |
| kyuubi.server.limit.client.fetch.max.rows | <undefined>
| Max rows limit for getting result row set operation. If the max rows
specified by client-side is larger than the limit, request will fail directly.
| int | 1.8.0 |
+| kyuubi.server.limit.connections.ip.deny.list
|| The client ip in the deny list will be denied to connect to kyuubi server.
| set | 1.9.1 |
| kyuubi.server.limit.connections.per.ipaddress | <undefined>
| Maximum kyuubi server connections per ipaddress. Any user exceeding this
limit will not be allowed to connect.
| int | 1.6.0 |
| kyuubi.server.limit.connections.per.user | <undefined>
| Maximum kyuubi server connections per user. Any user exceeding this limit
will not be allowed to connect.
| int | 1.6.0 |
| kyuubi.server.limit.connections.per.user.ipaddress | <undefined>
| Maximum kyuubi server connections per user:ipaddress combination. Any
user-ipaddress exceeding this limit will not be allowed to connect.
| int | 1.6.0 |
diff --git
a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
index 8305ddfd7..f06647ab7 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
@@ -2857,6 +2857,15 @@ object KyuubiConf {
.toSet()
.createWithDefault(Set.empty)
+ val SERVER_LIMIT_CONNECTIONS_IP_DENY_LIST: ConfigEntry[Set[String]] =
+ buildConf("kyuubi.server.limit.connections.ip.deny.list")
+ .doc("The client ip in the deny list will be denied to connect to kyuubi
server.")
+ .version("1.9.1")
+ .serverOnly
+ .stringConf
+ .toSet()
+ .createWithDefault(Set.empty)
+
val SERVER_LIMIT_BATCH_CONNECTIONS_PER_USER: OptionalConfigEntry[Int] =
buildConf("kyuubi.server.limit.batch.connections.per.user")
.doc("Maximum kyuubi server batch connections per user." +
diff --git
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
index 1cda224df..a7704ca47 100644
---
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
+++
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
@@ -21,7 +21,7 @@ import org.apache.kyuubi.KyuubiException
import org.apache.kyuubi.client.AdminRestApi
import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
import org.apache.kyuubi.ctl.cmd.AdminCtlCommand
-import
org.apache.kyuubi.ctl.cmd.refresh.RefreshConfigCommandConfigType.{DENY_USERS,
HADOOP_CONF, KUBERNETES_CONF, UNLIMITED_USERS, USER_DEFAULTS_CONF}
+import
org.apache.kyuubi.ctl.cmd.refresh.RefreshConfigCommandConfigType.{DENY_IPS,
DENY_USERS, HADOOP_CONF, KUBERNETES_CONF, UNLIMITED_USERS, USER_DEFAULTS_CONF}
import org.apache.kyuubi.ctl.opt.CliConfig
import org.apache.kyuubi.ctl.util.{Tabulator, Validator}
@@ -39,6 +39,7 @@ class RefreshConfigCommand(cliConfig: CliConfig) extends
AdminCtlCommand[String]
case KUBERNETES_CONF => adminRestApi.refreshKubernetesConf()
case UNLIMITED_USERS => adminRestApi.refreshUnlimitedUsers()
case DENY_USERS => adminRestApi.refreshDenyUsers()
+ case DENY_IPS => adminRestApi.refreshDenyIps()
case configType => throw new KyuubiException(s"Invalid config
type:$configType")
}
}
@@ -54,4 +55,5 @@ object RefreshConfigCommandConfigType {
final val KUBERNETES_CONF = "kubernetesConf"
final val UNLIMITED_USERS = "unlimitedUsers"
final val DENY_USERS = "denyUsers"
+ final val DENY_IPS = "denyIps"
}
diff --git
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala
index c7e367405..b6bcef0b2 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala
@@ -109,6 +109,6 @@ object AdminCommandLine extends CommonCommandLine {
.action((v, c) => c.copy(adminConfigOpts =
c.adminConfigOpts.copy(configType = v)))
.text("The valid config type can be one of the following: " +
s"$HADOOP_CONF, $USER_DEFAULTS_CONF, $KUBERNETES_CONF, " +
- s"$UNLIMITED_USERS, $DENY_USERS."))
+ s"$UNLIMITED_USERS, $DENY_USERS, $DENY_IPS."))
}
}
diff --git
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
index 52a2796f4..fba36f85e 100644
---
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
+++
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
@@ -101,6 +101,15 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite
with TestPrematureExi
assert(opArgs5.cliConfig.resource === ControlObject.CONFIG)
assert(opArgs5.cliConfig.adminConfigOpts.configType === DENY_USERS)
+ args = Array(
+ "refresh",
+ "config",
+ "denyIps")
+ val opArgs6 = new AdminControlCliArguments(args)
+ assert(opArgs6.cliConfig.action === ControlAction.REFRESH)
+ assert(opArgs6.cliConfig.resource === ControlObject.CONFIG)
+ assert(opArgs6.cliConfig.adminConfigOpts.configType === DENY_IPS)
+
args = Array(
"refresh",
"config",
@@ -183,7 +192,7 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite
with TestPrematureExi
| Refresh the resource.
|Command: refresh config [<configType>]
| Refresh the config with specified type.
- | <configType> The valid config type can be one of the
following: $HADOOP_CONF, $USER_DEFAULTS_CONF, $KUBERNETES_CONF,
$UNLIMITED_USERS, $DENY_USERS.
+ | <configType> The valid config type can be one of the
following: $HADOOP_CONF, $USER_DEFAULTS_CONF, $KUBERNETES_CONF,
$UNLIMITED_USERS, $DENY_USERS, $DENY_IPS.
|
| -h, --help Show help message and exit.""".stripMargin
// scalastyle:on
diff --git
a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
index 834b508de..002f6b46d 100644
---
a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
+++
b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
@@ -60,6 +60,11 @@ public class AdminRestApi {
return this.getClient().post(path, null, client.getAuthHeader());
}
+ public String refreshDenyIps() {
+ String path = String.format("%s/%s", API_BASE_PATH, "refresh/deny_ips");
+ return this.getClient().post(path, null, client.getAuthHeader());
+ }
+
public String deleteEngine(
String engineType, String shareLevel, String subdomain, String
hs2ProxyUser) {
Map<String, Object> params = new HashMap<>();
diff --git
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
index 453ae0b79..b5f135339 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
@@ -161,6 +161,14 @@ object KyuubiServer extends Logging {
val refreshedDenyUsers = sessionMgr.getDenyUsers
info(s"Refreshed deny users from $existingDenyUsers to
$refreshedDenyUsers")
}
+
+ private[kyuubi] def refreshDenyIps(): Unit = synchronized {
+ val sessionMgr =
kyuubiServer.backendService.sessionManager.asInstanceOf[KyuubiSessionManager]
+ val existingDenyIps = sessionMgr.getDenyIps
+ sessionMgr.refreshDenyIps(KyuubiConf().loadFileDefaults())
+ val refreshedDenyIps = sessionMgr.getDenyIps
+ info(s"Refreshed deny client ips from $existingDenyIps to
$refreshedDenyIps")
+ }
}
class KyuubiServer(name: String) extends Serverable(name) {
diff --git
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
index ca35a1b6b..e31c792c3 100644
---
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
+++
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
@@ -141,6 +141,25 @@ private[v1] class AdminResource extends ApiRequestContext
with Logging {
Response.ok(s"Refresh the deny users successfully.").build()
}
+ @ApiResponse(
+ responseCode = "200",
+ content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)),
+ description = "refresh the deny ips")
+ @POST
+ @Path("refresh/deny_ips")
+ def refreshDenyIp(): Response = {
+ val userName = fe.getSessionUser(Map.empty[String, String])
+ val ipAddress = fe.getIpAddress
+ info(s"Receive refresh deny ips request from $userName/$ipAddress")
+ if (!fe.isAdministrator(userName)) {
+ throw new NotAllowedException(
+ s"$userName is not allowed to refresh the deny ips")
+ }
+ info(s"Reloading deny ips")
+ KyuubiServer.refreshDenyIps()
+ Response.ok(s"Refresh the deny ips successfully.").build()
+ }
+
@ApiResponse(
responseCode = "200",
content = Array(new Content(
diff --git
a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
index 924145882..2297cc02c 100644
---
a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
+++
b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
@@ -338,12 +338,14 @@ class KyuubiSessionManager private (name: String) extends
SessionManager(name) {
val userUnlimitedList =
conf.get(SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST).filter(_.nonEmpty)
val userDenyList =
conf.get(SERVER_LIMIT_CONNECTIONS_USER_DENY_LIST).filter(_.nonEmpty)
+ val ipDenyList =
conf.get(SERVER_LIMIT_CONNECTIONS_IP_DENY_LIST).filter(_.nonEmpty)
limiter = applySessionLimiter(
userLimit,
ipAddressLimit,
userIpAddressLimit,
userUnlimitedList,
- userDenyList)
+ userDenyList,
+ ipDenyList)
val userBatchLimit =
conf.get(SERVER_LIMIT_BATCH_CONNECTIONS_PER_USER).getOrElse(0)
val ipAddressBatchLimit =
conf.get(SERVER_LIMIT_BATCH_CONNECTIONS_PER_IPADDRESS).getOrElse(0)
@@ -354,7 +356,8 @@ class KyuubiSessionManager private (name: String) extends
SessionManager(name) {
ipAddressBatchLimit,
userIpAddressBatchLimit,
userUnlimitedList,
- userDenyList)
+ userDenyList,
+ ipDenyList)
}
private[kyuubi] def getUnlimitedUsers: Set[String] = {
@@ -378,19 +381,32 @@ class KyuubiSessionManager private (name: String) extends
SessionManager(name) {
batchLimiter.foreach(SessionLimiter.resetDenyUsers(_, denyUsers))
}
+ private[kyuubi] def getDenyIps: Set[String] = {
+
limiter.orElse(batchLimiter).map(SessionLimiter.getDenyIps).getOrElse(Set.empty)
+ }
+
+ private[kyuubi] def refreshDenyIps(conf: KyuubiConf): Unit = {
+ val denyIps =
conf.get(SERVER_LIMIT_CONNECTIONS_IP_DENY_LIST).filter(_.nonEmpty)
+ limiter.foreach(SessionLimiter.resetDenyIps(_, denyIps))
+ batchLimiter.foreach(SessionLimiter.resetDenyIps(_, denyIps))
+ }
+
private def applySessionLimiter(
userLimit: Int,
ipAddressLimit: Int,
userIpAddressLimit: Int,
userUnlimitedList: Set[String],
- userDenyList: Set[String]): Option[SessionLimiter] = {
- if (Seq(userLimit, ipAddressLimit, userIpAddressLimit).exists(_ > 0) ||
userDenyList.nonEmpty) {
+ userDenyList: Set[String],
+ ipDenyList: Set[String]): Option[SessionLimiter] = {
+ if (Seq(userLimit, ipAddressLimit, userIpAddressLimit).exists(_ > 0) ||
+ userDenyList.nonEmpty || ipDenyList.nonEmpty) {
Some(SessionLimiter(
userLimit,
ipAddressLimit,
userIpAddressLimit,
userUnlimitedList,
- userDenyList))
+ userDenyList,
+ ipDenyList))
} else {
None
}
diff --git
a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala
b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala
index 8a1ebedf1..688154d00 100644
---
a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala
+++
b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala
@@ -107,7 +107,8 @@ class SessionLimiterWithAccessControlListImpl(
ipAddressLimit: Int,
userIpAddressLimit: Int,
var unlimitedUsers: Set[String],
- var denyUsers: Set[String])
+ var denyUsers: Set[String],
+ var denyIps: Set[String])
extends SessionLimiterImpl(userLimit, ipAddressLimit, userIpAddressLimit) {
override def increment(userIpAddress: UserIpAddress): Unit = {
val user = userIpAddress.user
@@ -116,6 +117,12 @@ class SessionLimiterWithAccessControlListImpl(
s"Connection denied because the user is in the deny user list. (user:
$user)"
throw KyuubiSQLException(errorMsg)
}
+ val ip = userIpAddress.ipAddress
+ if (StringUtils.isNotBlank(ip) && denyIps.contains(ip)) {
+ val errorMsg =
+ s"Connection denied because the client ip is in the deny ip list.
(ipAddress: $ip)"
+ throw KyuubiSQLException(errorMsg)
+ }
if (!unlimitedUsers.contains(user)) {
super.increment(userIpAddress)
@@ -129,6 +136,10 @@ class SessionLimiterWithAccessControlListImpl(
private[kyuubi] def setDenyUsers(denyUsers: Set[String]): Unit = {
this.denyUsers = denyUsers
}
+
+ private[kyuubi] def setDenyIps(denyIps: Set[String]): Unit = {
+ this.denyIps = denyIps
+ }
}
object SessionLimiter {
@@ -138,13 +149,15 @@ object SessionLimiter {
ipAddressLimit: Int,
userIpAddressLimit: Int,
unlimitedUsers: Set[String] = Set.empty,
- denyUsers: Set[String] = Set.empty): SessionLimiter = {
+ denyUsers: Set[String] = Set.empty,
+ denyIps: Set[String] = Set.empty): SessionLimiter = {
new SessionLimiterWithAccessControlListImpl(
userLimit,
ipAddressLimit,
userIpAddressLimit,
unlimitedUsers,
- denyUsers)
+ denyUsers,
+ denyIps)
}
def resetUnlimitedUsers(limiter: SessionLimiter, unlimitedUsers:
Set[String]): Unit =
@@ -168,4 +181,15 @@ object SessionLimiter {
case l: SessionLimiterWithAccessControlListImpl => l.denyUsers
case _ => Set.empty
}
+
+ def resetDenyIps(limiter: SessionLimiter, denyIps: Set[String]): Unit =
+ limiter match {
+ case l: SessionLimiterWithAccessControlListImpl => l.setDenyIps(denyIps)
+ case _ =>
+ }
+
+ def getDenyIps(limiter: SessionLimiter): Set[String] = limiter match {
+ case l: SessionLimiterWithAccessControlListImpl => l.denyIps
+ case _ => Set.empty
+ }
}
diff --git
a/kyuubi-server/src/test/scala/org/apache/kyuubi/session/SessionLimiterSuite.scala
b/kyuubi-server/src/test/scala/org/apache/kyuubi/session/SessionLimiterSuite.scala
index 775239f9b..47c33d63e 100644
---
a/kyuubi-server/src/test/scala/org/apache/kyuubi/session/SessionLimiterSuite.scala
+++
b/kyuubi-server/src/test/scala/org/apache/kyuubi/session/SessionLimiterSuite.scala
@@ -152,6 +152,21 @@ class SessionLimiterSuite extends KyuubiFunSuite {
"Connection denied because the user is in the deny user list. (user:
user002)"))
}
+ test("test session limiter with ip deny list") {
+ val ipAddress = "127.0.0.1"
+ val denyIps = Set(ipAddress)
+ val limiter =
+ SessionLimiter(100, 100, 100, Set.empty, Set.empty, denyIps)
+
+ val caught = intercept[KyuubiSQLException] {
+ val userIpAddress = UserIpAddress("user001", ipAddress)
+ limiter.increment(userIpAddress)
+ }
+
+ assert(caught.getMessage.equals(
+ "Connection denied because the client ip is in the deny ip list.
(ipAddress: 127.0.0.1)"))
+ }
+
test("test refresh unlimited users and deny users") {
val random: Random = new Random()
val latch = new CountDownLatch(600)