Skip to content

Commit 26a4aa8

Browse files
committed
Sensitive information logged in SshHelper.sshExecute method
1 parent e90e436 commit 26a4aa8

File tree

2 files changed

+123
-4
lines changed

2 files changed

+123
-4
lines changed

utils/src/main/java/com/cloud/utils/ssh/SshHelper.java

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.io.IOException;
2424
import java.io.InputStream;
2525
import java.nio.charset.StandardCharsets;
26+
import java.util.regex.Matcher;
27+
import java.util.regex.Pattern;
2628

2729
import org.apache.commons.io.IOUtils;
2830
import org.apache.commons.lang3.StringUtils;
@@ -40,6 +42,20 @@ public class SshHelper {
4042
private static final int DEFAULT_CONNECT_TIMEOUT = 180000;
4143
private static final int DEFAULT_KEX_TIMEOUT = 60000;
4244
private static final int DEFAULT_WAIT_RESULT_TIMEOUT = 120000;
45+
private static final String MASKED_VALUE = "*****";
46+
47+
private static final Pattern[] SENSITIVE_COMMAND_PATTERNS = new Pattern[] {
48+
Pattern.compile("(?i)(\\s+-p\\s+['\"])([^'\"]*)(['\"])"),
49+
Pattern.compile("(?i)(\\s+-p\\s+)([^\\s]+)"),
50+
Pattern.compile("(?i)(\\s+-p=['\"])([^'\"]*)(['\"])"),
51+
Pattern.compile("(?i)(\\s+-p=)([^\\s]+)"),
52+
Pattern.compile("(?i)(--password=['\"])([^'\"]*)(['\"])"),
53+
Pattern.compile("(?i)(--password=)([^\\s]+)"),
54+
Pattern.compile("(?i)(--password\\s+['\"])([^'\"]*)(['\"])"),
55+
Pattern.compile("(?i)(--password\\s+)([^\\s]+)"),
56+
Pattern.compile("(?i)(\\s+-u\\s+['\"][^,'\":]+[,:])([^'\"]*)(['\"])"),
57+
Pattern.compile("(?i)(\\s+-u\\s+[^\\s,:]+[,:])([^\\s]+)")
58+
};
4359

4460
protected static Logger LOGGER = LogManager.getLogger(SshHelper.class);
4561

@@ -145,7 +161,7 @@ public static void scpTo(String host, int port, String user, File pemKeyFile, St
145161
}
146162

147163
public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, String[] localFiles, String fileMode,
148-
int connectTimeoutInMs, int kexTimeoutInMs) throws Exception {
164+
int connectTimeoutInMs, int kexTimeoutInMs) throws Exception {
149165

150166
com.trilead.ssh2.Connection conn = null;
151167
com.trilead.ssh2.SCPClient scpClient = null;
@@ -291,13 +307,16 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
291307
}
292308

293309
if (sess.getExitStatus() == null) {
294-
//Exit status is NOT available. Returning failure result.
295-
LOGGER.error(String.format("SSH execution of command %s has no exit status set. Result output: %s", command, result));
310+
// Exit status is NOT available. Returning failure result.
311+
LOGGER.error(String.format("SSH execution of command %s has no exit status set. Result output: %s",
312+
sanitizeForLogging(command), sanitizeForLogging(result)));
296313
return new Pair<Boolean, String>(false, result);
297314
}
298315

299316
if (sess.getExitStatus() != null && sess.getExitStatus().intValue() != 0) {
300-
LOGGER.error(String.format("SSH execution of command %s has an error status code in return. Result output: %s", command, result));
317+
LOGGER.error(String.format(
318+
"SSH execution of command %s has an error status code in return. Result output: %s",
319+
sanitizeForLogging(command), sanitizeForLogging(result)));
301320
return new Pair<Boolean, String>(false, result);
302321
}
303322
return new Pair<Boolean, String>(true, result);
@@ -366,4 +385,44 @@ protected static void throwSshExceptionIfStdoutOrStdeerIsNull(InputStream stdout
366385
throw new SshException(msg);
367386
}
368387
}
388+
389+
private static String sanitizeForLogging(String value) {
390+
if (value == null) {
391+
return null;
392+
}
393+
String masked = maskSensitiveValue(value);
394+
String cleaned = com.cloud.utils.StringUtils.cleanString(masked);
395+
return cleaned != null ? cleaned : masked;
396+
}
397+
398+
private static String maskSensitiveValue(String value) {
399+
String masked = value;
400+
for (Pattern pattern : SENSITIVE_COMMAND_PATTERNS) {
401+
masked = replaceWithMask(masked, pattern);
402+
}
403+
return masked;
404+
}
405+
406+
private static String replaceWithMask(String value, Pattern pattern) {
407+
Matcher matcher = pattern.matcher(value);
408+
if (!matcher.find()) {
409+
return value;
410+
}
411+
412+
StringBuffer buffer = new StringBuffer();
413+
do {
414+
StringBuilder replacement = new StringBuilder();
415+
replacement.append(matcher.group(1));
416+
if (matcher.groupCount() >= 3) {
417+
replacement.append(MASKED_VALUE);
418+
replacement.append(matcher.group(matcher.groupCount()));
419+
} else {
420+
replacement.append(MASKED_VALUE);
421+
}
422+
matcher.appendReplacement(buffer, Matcher.quoteReplacement(replacement.toString()));
423+
} while (matcher.find());
424+
425+
matcher.appendTail(buffer);
426+
return buffer.toString();
427+
}
369428
}

utils/src/test/java/com/cloud/utils/ssh/SshHelperTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.io.IOException;
2323
import java.io.InputStream;
24+
import java.lang.reflect.Method;
2425

2526
import org.junit.Assert;
2627
import org.junit.Test;
@@ -140,4 +141,63 @@ public void openConnectionSessionTest() throws IOException, InterruptedException
140141

141142
Mockito.verify(conn).openSession();
142143
}
144+
145+
@Test
146+
public void sanitizeForLoggingMasksShortPasswordFlag() throws Exception {
147+
String command = "/opt/cloud/bin/script -v 10.0.0.1 -p superSecret";
148+
String sanitized = invokeSanitizeForLogging(command);
149+
150+
Assert.assertTrue("Sanitized command should retain flag", sanitized.contains("-p *****"));
151+
Assert.assertFalse("Sanitized command should not contain original password", sanitized.contains("superSecret"));
152+
}
153+
154+
@Test
155+
public void sanitizeForLoggingMasksQuotedPasswordFlag() throws Exception {
156+
String command = "/opt/cloud/bin/script -v 10.0.0.1 -p \"super Secret\"";
157+
String sanitized = invokeSanitizeForLogging(command);
158+
159+
Assert.assertTrue("Sanitized command should retain quoted flag", sanitized.contains("-p \"*****\""));
160+
Assert.assertFalse("Sanitized command should not contain original password",
161+
sanitized.contains("super Secret"));
162+
}
163+
164+
@Test
165+
public void sanitizeForLoggingMasksLongPasswordAssignments() throws Exception {
166+
String command = "tool --password=superSecret";
167+
String sanitized = invokeSanitizeForLogging(command);
168+
169+
Assert.assertTrue("Sanitized command should retain assignment", sanitized.contains("--password=*****"));
170+
Assert.assertFalse("Sanitized command should not contain original password", sanitized.contains("superSecret"));
171+
}
172+
173+
@Test
174+
public void sanitizeForLoggingMasksUsernamePasswordPairs() throws Exception {
175+
String command = "/opt/cloud/bin/vpn_l2tp.sh -u alice,topSecret";
176+
String sanitized = invokeSanitizeForLogging(command);
177+
178+
Assert.assertTrue("Sanitized command should retain username and mask password",
179+
sanitized.contains("-u alice,*****"));
180+
Assert.assertFalse("Sanitized command should not contain original password", sanitized.contains("topSecret"));
181+
}
182+
183+
@Test
184+
public void sanitizeForLoggingMasksUsernamePasswordPairsWithColon() throws Exception {
185+
String command = "curl -u alice:topSecret https://example.com";
186+
String sanitized = invokeSanitizeForLogging(command);
187+
188+
Assert.assertTrue("Sanitized command should retain username and mask password",
189+
sanitized.contains("-u alice:*****"));
190+
Assert.assertFalse("Sanitized command should not contain original password", sanitized.contains("topSecret"));
191+
}
192+
193+
@Test
194+
public void sanitizeForLoggingHandlesNullValues() throws Exception {
195+
Assert.assertNull(invokeSanitizeForLogging(null));
196+
}
197+
198+
private String invokeSanitizeForLogging(String value) throws Exception {
199+
Method method = SshHelper.class.getDeclaredMethod("sanitizeForLogging", String.class);
200+
method.setAccessible(true);
201+
return (String) method.invoke(null, value);
202+
}
143203
}

0 commit comments

Comments
 (0)