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]

Reply via email to