This is an automated email from the ASF dual-hosted git repository.

jialiang pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 99630e03e0 AMBARI-26240: Fix alter dispatcher (#3896)
99630e03e0 is described below

commit 99630e03e0d9315bafbc0f81c0bcd2a94b34058d
Author: jialiang <[email protected]>
AuthorDate: Tue Nov 26 13:45:08 2024 +0800

    AMBARI-26240: Fix alter dispatcher (#3896)
---
 .../dispatchers/AlertScriptDispatcher.java         |  33 ++++
 .../dispatchers/AlertScriptDispatcherTest.java     | 166 ++++++++++++++++++++-
 2 files changed, 195 insertions(+), 4 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcher.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcher.java
index 2dfcaa58f7..4766aeb171 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcher.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcher.java
@@ -18,6 +18,9 @@
 package org.apache.ambari.server.notifications.dispatchers;
 
 import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -279,6 +282,36 @@ public class AlertScriptDispatcher implements 
NotificationDispatcher {
    * @return
    */
   ProcessBuilder getProcessBuilder(String script, AlertNotification 
notification) {
+    if (script == null || script.isEmpty()) {
+      throw new IllegalArgumentException("Script path cannot be null or 
empty");
+    }
+
+    String unixPathRegex = "^(/[a-zA-Z0-9._-]+)+$";
+    String windowsPathRegex = "^[a-zA-Z]:\\\\([a-zA-Z0-9._-]+\\\\?)+$";
+
+    boolean isValidPath = SystemUtils.IS_OS_WINDOWS
+            ? script.matches(windowsPathRegex)
+            : script.matches(unixPathRegex);
+
+    if (!isValidPath) {
+      throw new IllegalArgumentException("Invalid script path format: " + 
script);
+    }
+
+    Path scriptPath = Paths.get(script);
+
+    if (!scriptPath.isAbsolute()) {
+      throw new IllegalArgumentException("Script path must be an absolute 
path: " + script);
+    }
+
+    if (!Files.exists(scriptPath)) {
+      throw new IllegalArgumentException("Script does not exist: " + script);
+    }
+
+    if (!Files.isExecutable(scriptPath)) {
+      throw new IllegalArgumentException("Script is not executable: " + 
script);
+    }
+
+
     final String shellCommand;
     final String shellCommandOption;
     if (SystemUtils.IS_OS_WINDOWS) {
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcherTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcherTest.java
index 4b1480ca81..f3fb7e22c8 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcherTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/notifications/dispatchers/AlertScriptDispatcherTest.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.notifications.dispatchers;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.Collections;
 import java.util.HashMap;
@@ -38,7 +40,9 @@ import 
org.apache.ambari.server.state.services.AlertNoticeDispatchService.AlertI
 import org.apache.ambari.server.state.stack.OsFamily;
 import org.easymock.EasyMock;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.powermock.api.easymock.PowerMock;
 import org.powermock.core.classloader.annotations.PrepareForTest;
@@ -70,12 +74,162 @@ public class AlertScriptDispatcherTest {
   @Inject
   private Configuration m_configuration;
 
+  @Rule
+  public TemporaryFolder tempFolder = new TemporaryFolder();
+
+  private final AlertNotification mockNotification = 
EasyMock.createNiceMock(AlertNotification.class);
+  private final AlertScriptDispatcher dispatcher = new AlertScriptDispatcher();
+
+
   @Before
   public void before() throws Exception {
     m_injector = Guice.createInjector(new MockModule());
     m_injector.injectMembers(this);
   }
 
+
+  @Test
+  public void testNullScriptPath() {
+    try {
+      dispatcher.getProcessBuilder(null, mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Script path cannot be null or empty", 
e.getMessage());
+    }
+  }
+
+  @Test
+  public void testEmptyScriptPath() {
+    try {
+      dispatcher.getProcessBuilder("", mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Script path cannot be null or empty", 
e.getMessage());
+    }
+  }
+
+  @Test
+  public void testRelativeScriptPath() {
+    try {
+      dispatcher.getProcessBuilder("relative/path/to/script.sh", 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Invalid script path format: 
relative/path/to/script.sh", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testNonExistentScriptPath() {
+    try {
+      dispatcher.getProcessBuilder("/non/existent/path/to/script.sh", 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Script does not exist: 
/non/existent/path/to/script.sh", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testNonExecutableScriptPath() throws IOException {
+    // Create a temp file that is not executable
+    File nonExecutableFile = tempFolder.newFile("non_executable_script.sh");
+
+    try {
+      dispatcher.getProcessBuilder(nonExecutableFile.getAbsolutePath(), 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Script is not executable: " + 
nonExecutableFile.getAbsolutePath(), e.getMessage());
+    }
+  }
+
+  @Test
+  public void testValidScriptPath() throws IOException {
+    File validScript = tempFolder.newFile("valid_script.sh");
+    validScript.setExecutable(true);
+    String scriptAbsolutePath = validScript.getAbsolutePath();
+
+    final String ALERT_DEFINITION_NAME = "mock_alert_with_quotes";
+    final String ALERT_DEFINITION_LABEL = "Mock alert with Quotes";
+    final String ALERT_LABEL = "Alert Label";
+    final String ALERT_SERVICE_NAME = "FOO_SERVICE";
+    final String ALERT_TEXT = "Did you know, \"Quotes are hard!!!\"";
+    final String ALERT_TEXT_ESCAPED = "Did you know, \\\"Quotes are 
hard\\!\\!\\!\\\"";
+    final String ALERT_HOST = "mock_host";
+    final long ALERT_TIMESTAMP = 1111111l;
+
+    DispatchCallback callback = 
EasyMock.createNiceMock(DispatchCallback.class);
+    AlertNotification notification = new AlertNotification();
+    notification.Callback = callback;
+    notification.CallbackIds = 
Collections.singletonList(UUID.randomUUID().toString());
+
+    AlertDefinitionEntity definition = new AlertDefinitionEntity();
+    definition.setDefinitionName(ALERT_DEFINITION_NAME);
+    definition.setLabel(ALERT_DEFINITION_LABEL);
+
+    AlertHistoryEntity history = new AlertHistoryEntity();
+    history.setAlertDefinition(definition);
+    history.setAlertLabel(ALERT_LABEL);
+    history.setAlertText(ALERT_TEXT);
+    history.setAlertState(AlertState.OK);
+    history.setServiceName(ALERT_SERVICE_NAME);
+    history.setHostName(ALERT_HOST);
+    history.setAlertTimestamp(ALERT_TIMESTAMP);
+
+
+    AlertInfo alertInfo = new AlertInfo(history);
+    notification.setAlertInfo(alertInfo);
+
+    AlertScriptDispatcher dispatcher = new AlertScriptDispatcher();
+    m_injector.injectMembers(dispatcher);
+
+    ProcessBuilder processBuilder = 
dispatcher.getProcessBuilder(scriptAbsolutePath, notification);
+
+    Assert.assertNotNull(processBuilder);
+    Assert.assertEquals("sh", processBuilder.command().get(0));
+    Assert.assertEquals("-c", processBuilder.command().get(1));
+    
Assert.assertTrue(processBuilder.command().get(2).contains(scriptAbsolutePath));
+
+  }
+
+  @Test
+  public void testInjectionAttemptWithSemicolon() {
+    try {
+      dispatcher.getProcessBuilder("/path/to/script.sh; rm -rf /", 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Invalid script path format: /path/to/script.sh; rm 
-rf /", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testInjectionAttemptWithAndOperator() {
+    try {
+      dispatcher.getProcessBuilder("/path/to/script.sh && touch /tmp/hacked", 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Invalid script path format: /path/to/script.sh && 
touch /tmp/hacked", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testInjectionAttemptWithPipeOperator() {
+    try {
+      dispatcher.getProcessBuilder("/path/to/script.sh | ls", 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Invalid script path format: /path/to/script.sh | 
ls", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testInjectionAttemptWithBackticks() {
+    try {
+      dispatcher.getProcessBuilder("/path/to/script.sh `rm -rf /`", 
mockNotification);
+      Assert.fail("Expected IllegalArgumentException was not thrown");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("Invalid script path format: /path/to/script.sh `rm 
-rf /`", e.getMessage());
+    }
+  }
+
   /**
    * Tests that a callback error happens when the notification is not an
    * {@link AlertNotification}.
@@ -307,6 +461,10 @@ public class AlertScriptDispatcherTest {
     final String ALERT_HOST = "mock_host";
     final long ALERT_TIMESTAMP = 1111111l;
 
+    File validScript = tempFolder.newFile("valid_script.sh");
+    validScript.setExecutable(true);
+    String scriptAbsolutePath = validScript.getAbsolutePath();
+
     DispatchCallback callback = 
EasyMock.createNiceMock(DispatchCallback.class);
     AlertNotification notification = new AlertNotification();
     notification.Callback = callback;
@@ -332,14 +490,14 @@ public class AlertScriptDispatcherTest {
     AlertScriptDispatcher dispatcher = new AlertScriptDispatcher();
     m_injector.injectMembers(dispatcher);
 
-    ProcessBuilder processBuilder = 
dispatcher.getProcessBuilder(SCRIPT_CONFIG_VALUE, notification);
+    ProcessBuilder processBuilder = 
dispatcher.getProcessBuilder(scriptAbsolutePath, notification);
     List<String> commands = processBuilder.command();
     Assert.assertEquals(3, commands.size());
     Assert.assertEquals("sh", commands.get(0));
     Assert.assertEquals("-c", commands.get(1));
 
     StringBuilder buffer = new StringBuilder();
-    buffer.append(SCRIPT_CONFIG_VALUE).append(" ");
+    buffer.append(scriptAbsolutePath).append(" ");
     buffer.append(ALERT_DEFINITION_NAME).append(" ");
     buffer.append("\"").append(ALERT_DEFINITION_LABEL).append("\"").append(" 
");
     buffer.append(ALERT_SERVICE_NAME).append(" ");
@@ -355,10 +513,10 @@ public class AlertScriptDispatcherTest {
     alertInfo = new AlertInfo(history);
     notification.setAlertInfo(alertInfo);
 
-    processBuilder = dispatcher.getProcessBuilder(SCRIPT_CONFIG_VALUE, 
notification);
+    processBuilder = dispatcher.getProcessBuilder(scriptAbsolutePath, 
notification);
     commands = processBuilder.command();
     buffer = new StringBuilder();
-    buffer.append(SCRIPT_CONFIG_VALUE).append(" ");
+    buffer.append(scriptAbsolutePath).append(" ");
     buffer.append(ALERT_DEFINITION_NAME).append(" ");
     buffer.append("\"").append(ALERT_DEFINITION_LABEL).append("\"").append(" 
");
     buffer.append(ALERT_SERVICE_NAME).append(" ");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to