This is an automated email from the ASF dual-hosted git repository.
starocean999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 979e053f080 [Enhance](resource) Restrict AI resource creation by user
identity (#68147)
979e053f080 is described below
commit 979e053f0802fbc48f83dafe7f713c02968415a8
Author: linrrarity <[email protected]>
AuthorDate: Fri Sep 18 20:25:29 2026 +0800
[Enhance](resource) Restrict AI resource creation by user identity (#68147)
Rstrict AI resource creation by user identity
---
.../main/java/org/apache/doris/common/Config.java | 4 ++
.../plans/commands/info/CreateResourceInfo.java | 10 +++
.../plans/commands/CreateResourceCommandTest.java | 73 ++++++++++++++++++++++
3 files changed, 87 insertions(+)
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index ba3b27d8b6e..7d020f42c35 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2713,6 +2713,10 @@ public class Config extends ConfigBase {
+ "this configuration to prevent command injection attacks.")
public static boolean enable_python_udf = true;
+ @ConfField(description = "The user identity allowed to create AI
resources, in the form 'user'@'host'. "
+ + "The default value '*' allows any user that satisfies the
existing privilege checks.")
+ public static String ai_resource_allowed_user = "*";
+
@ConfField(description = "Whether to ignore unknown modules in Image file.
If true, metadata modules not in "
+ "PersistMetaModules.MODULE_NAMES will be ignored and skipped.
Default is false, if Image "
+ "file contains unknown modules, Doris will throw exception. This
parameter is mainly "
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateResourceInfo.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateResourceInfo.java
index 9375e8b4b14..1b768a324f6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateResourceInfo.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateResourceInfo.java
@@ -18,9 +18,11 @@
package org.apache.doris.nereids.trees.plans.commands.info;
import org.apache.doris.analysis.ResourceTypeEnum;
+import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.Resource.ResourceType;
import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.FeNameFormat;
@@ -75,6 +77,14 @@ public class CreateResourceInfo {
}
analyzeResourceType();
+
+ if (resourceType == ResourceType.AI &&
!"*".equals(Config.ai_resource_allowed_user)) {
+ UserIdentity allowedUser =
UserIdentity.fromString(Config.ai_resource_allowed_user);
+ if
(!ConnectContext.get().getCurrentUserIdentity().equals(allowedUser)) {
+ throw new AnalysisException("Current user does not have
permission to create AI resources",
+ ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR);
+ }
+ }
}
/**
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CreateResourceCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CreateResourceCommandTest.java
index aecb79f37ff..fe01dd4c5e4 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CreateResourceCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CreateResourceCommandTest.java
@@ -17,9 +17,12 @@
package org.apache.doris.nereids.trees.plans.commands;
+import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Env;
import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.jmockit.Deencapsulation;
import org.apache.doris.mysql.privilege.AccessControllerManager;
import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -108,6 +111,76 @@ public class CreateResourceCommandTest extends
TestWithFeService {
Assertions.assertDoesNotThrow(() -> createResource(jdbc));
}
+ @Test
+ public void testAiResourceCreationRequiresConfiguredUserIdentity() {
+ allowAdminPrivilege();
+ String originalAllowedUser = Config.ai_resource_allowed_user;
+ UserIdentity originalUser = connectContext.getCurrentUserIdentity();
+ try {
+ Config.ai_resource_allowed_user = "'root'@'%'";
+
+ connectContext.setCurrentUserIdentity(UserIdentity.ROOT);
+ Assertions.assertDoesNotThrow(() ->
createResourceInfo("ai").validate());
+
+ connectContext.setCurrentUserIdentity(
+ UserIdentity.createAnalyzedUserIdentWithIp("root",
"10.0.%"));
+ AnalysisException exception = Assertions.assertThrows(
+ AnalysisException.class, () ->
createResourceInfo("ai").validate());
+ Assertions.assertEquals(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+ exception.getMysqlErrorCode());
+ Assertions.assertEquals("errCode = 2, detailMessage = "
+ + "Current user does not have permission to create
AI resources",
+ exception.getMessage());
+ } finally {
+ Config.ai_resource_allowed_user = originalAllowedUser;
+ connectContext.setCurrentUserIdentity(originalUser);
+ }
+ }
+
+ @Test
+ public void testAiResourceCreationAllowsAnyAdminForWildcard() {
+ allowAdminPrivilege();
+ String originalAllowedUser = Config.ai_resource_allowed_user;
+ UserIdentity originalUser = connectContext.getCurrentUserIdentity();
+ try {
+ Config.ai_resource_allowed_user = "*";
+ connectContext.setCurrentUserIdentity(UserIdentity.ADMIN);
+
+ Assertions.assertDoesNotThrow(() ->
createResourceInfo("ai").validate());
+ } finally {
+ Config.ai_resource_allowed_user = originalAllowedUser;
+ connectContext.setCurrentUserIdentity(originalUser);
+ }
+ }
+
+ @Test
+ public void testAllowedUserDoesNotRestrictNonAiResourceCreation() {
+ allowAdminPrivilege();
+ String originalAllowedUser = Config.ai_resource_allowed_user;
+ UserIdentity originalUser = connectContext.getCurrentUserIdentity();
+ try {
+ Config.ai_resource_allowed_user = "'root'@'%'";
+ connectContext.setCurrentUserIdentity(UserIdentity.ADMIN);
+
+ Assertions.assertDoesNotThrow(() ->
createResourceInfo("jdbc").validate());
+ } finally {
+ Config.ai_resource_allowed_user = originalAllowedUser;
+ connectContext.setCurrentUserIdentity(originalUser);
+ }
+ }
+
+ private void allowAdminPrivilege() {
+ Env env = Env.getCurrentEnv();
+ AccessControllerManager spyAcm = Mockito.spy(env.getAccessManager());
+ Mockito.doReturn(true).when(spyAcm).checkGlobalPriv(
+ Mockito.nullable(ConnectContext.class),
Mockito.eq(PrivPredicate.ADMIN));
+ Deencapsulation.setField(env, "accessManager", spyAcm);
+ }
+
+ private CreateResourceInfo createResourceInfo(String type) {
+ return new CreateResourceInfo(true, false, "test_" + type,
ImmutableMap.of("type", type));
+ }
+
private void createResource(String sql) throws Exception {
LogicalPlan plan = new NereidsParser().parseSingle(sql);
Assertions.assertTrue(plan instanceof CreateResourceCommand);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]