From 5b70409deac041b1e2f7d7f8f4ea78c2114b03fd Mon Sep 17 00:00:00 2001 From: Justin Bertram Date: Tue, 5 May 2026 15:50:22 -0500 Subject: [PATCH] ARTEMIS-6049 mask cluster-password input from CLI --- .../activemq/artemis/cli/commands/Create.java | 11 ++++- .../artemis/cli/commands/InputAbstract.java | 12 +++++- .../CreateTestIndividualSettings.java | 40 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java index 32f70c1c89a..1eb5e6d9b25 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java @@ -35,6 +35,7 @@ import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext; import org.apache.activemq.artemis.nativo.jlibaio.LibaioFile; import org.apache.activemq.artemis.utils.FileUtil; +import org.apache.activemq.artemis.utils.PasswordMaskingUtil; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -444,9 +445,17 @@ private String getClusterUser() { return clusterUser; } - private String getClusterPassword() { + protected String getClusterPassword() { if (clusterPassword == null) { clusterPassword = inputPassword("--cluster-password", "What is the cluster password?", "password-admin"); + try { + clusterPassword = PasswordMaskingUtil.wrap(PasswordMaskingUtil.getDefaultCodec().encode(clusterPassword)); + getActionContext().out.println("Using masked cluster-password: " + clusterPassword); + } catch (Exception e) { + getActionContext().err.println("Warning: Failed to mask password."); + getActionContext().err.println("Reason: " + e.getMessage()); + e.printStackTrace(); + } } return clusterPassword; } diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java index 602d81cdf30..c320be6f22b 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java @@ -31,13 +31,17 @@ public class InputAbstract extends ActionAbstract { private static boolean inputEnabled = false; /** - * Test cases validating or using the CLI cannot deal with inputs, so they are generally disabled, however the main - * method from the CLI will enable it back. + * Test cases validating or using the CLI usually don't deal with inputs, so they are generally disabled, however + * the main method from the CLI will enable it back. */ public static void enableInput() { inputEnabled = true; } + public static void disableInput() { + inputEnabled = false; + } + @Option(names = "--silent", description = "Disable all the inputs, and make a best guess for any required input.") private boolean silentInput = false; @@ -145,4 +149,8 @@ public Object execute(ActionContext context) throws Exception { return null; } + + public void setLineReader(InputReader lineReader) { + this.lineReader = lineReader; + } } diff --git a/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateTestIndividualSettings.java b/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateTestIndividualSettings.java index 20a88683589..56df22a03fd 100644 --- a/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateTestIndividualSettings.java +++ b/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateTestIndividualSettings.java @@ -20,14 +20,20 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.Map; +import org.apache.activemq.artemis.cli.commands.util.input.InputReader; import org.apache.activemq.artemis.tests.extensions.TargetTempDirFactory; +import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec; +import org.apache.activemq.artemis.utils.PasswordMaskingUtil; import org.apache.activemq.artemis.utils.RandomUtil; import org.apache.activemq.cli.test.TestActionContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -84,4 +90,38 @@ private boolean fileContains(File file, String search) { } return false; } + + @Test + public void testMaskClusterPassword() throws Exception { + final String password = RandomUtil.randomUUIDString(); + + Create c = new Create(); + Create.enableInput(); + try { + InputReader inputReader = Mockito.mock(InputReader.class); + Mockito.when(inputReader.readPassword(Mockito.anyString())).thenReturn(password); + c.setLineReader(inputReader); + + assertEquals(PasswordMaskingUtil.wrap(PasswordMaskingUtil.getDefaultCodec().encode(password)), c.getClusterPassword()); + } finally { + Create.disableInput(); + } + } + + @Test + public void testMaskAdminPassword() throws Exception { + final String password = RandomUtil.randomUUIDString(); + + Create c = new Create(); + Create.enableInput(); + try { + InputReader inputReader = Mockito.mock(InputReader.class); + Mockito.when(inputReader.readPassword(Mockito.anyString())).thenReturn(password); + c.setLineReader(inputReader); + + assertTrue(PasswordMaskingUtil.getDefaultCodec(Map.of(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.ONE_WAY)).verify(password.toCharArray(), PasswordMaskingUtil.unwrap(c.getPassword()))); + } finally { + Create.disableInput(); + } + } } \ No newline at end of file