Skip to content

Commit 0dcbe57

Browse files
YLChen-007cyl-auth
andauthored
Fix that Sensitive information logged in SshHelper.sshExecute method (#12026)
* Sensitive information logged in SshHelper.sshExecute method * Fix that Sensitive information logged in SshHelper.sshExecute method2 * Fix sensitive information handling in SshHelper and its tests --------- Co-authored-by: chenyoulong20g@ict.ac.cn <chenyoulong20g@ict.ac.cn>
1 parent 70d4c9d commit 0dcbe57

File tree

2 files changed

+129
-4
lines changed

2 files changed

+129
-4
lines changed

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

Lines changed: 69 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,23 @@ 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+
Pattern.compile("(?i)(\\s+-s\\s+['\"])([^'\"]*)(['\"])"),
59+
Pattern.compile("(?i)(\\s+-s\\s+)([^\\s]+)"),
60+
61+
};
4362

4463
protected static Logger LOGGER = LogManager.getLogger(SshHelper.class);
4564

@@ -145,7 +164,7 @@ public static void scpTo(String host, int port, String user, File pemKeyFile, St
145164
}
146165

147166
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 {
167+
int connectTimeoutInMs, int kexTimeoutInMs) throws Exception {
149168

150169
com.trilead.ssh2.Connection conn = null;
151170
com.trilead.ssh2.SCPClient scpClient = null;
@@ -291,13 +310,16 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
291310
}
292311

293312
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));
313+
// Exit status is NOT available. Returning failure result.
314+
LOGGER.error(String.format("SSH execution of command %s has no exit status set. Result output: %s",
315+
sanitizeForLogging(command), sanitizeForLogging(result)));
296316
return new Pair<Boolean, String>(false, result);
297317
}
298318

299319
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));
320+
LOGGER.error(String.format(
321+
"SSH execution of command %s has an error status code in return. Result output: %s",
322+
sanitizeForLogging(command), sanitizeForLogging(result)));
301323
return new Pair<Boolean, String>(false, result);
302324
}
303325
return new Pair<Boolean, String>(true, result);
@@ -366,4 +388,47 @@ protected static void throwSshExceptionIfStdoutOrStdeerIsNull(InputStream stdout
366388
throw new SshException(msg);
367389
}
368390
}
391+
392+
private static String sanitizeForLogging(String value) {
393+
if (value == null) {
394+
return null;
395+
}
396+
String masked = maskSensitiveValue(value);
397+
String cleaned = com.cloud.utils.StringUtils.cleanString(masked);
398+
if (StringUtils.isBlank(cleaned)) {
399+
return masked;
400+
}
401+
return cleaned;
402+
}
403+
404+
private static String maskSensitiveValue(String value) {
405+
String masked = value;
406+
for (Pattern pattern : SENSITIVE_COMMAND_PATTERNS) {
407+
masked = replaceWithMask(masked, pattern);
408+
}
409+
return masked;
410+
}
411+
412+
private static String replaceWithMask(String value, Pattern pattern) {
413+
Matcher matcher = pattern.matcher(value);
414+
if (!matcher.find()) {
415+
return value;
416+
}
417+
418+
StringBuffer buffer = new StringBuffer();
419+
do {
420+
StringBuilder replacement = new StringBuilder();
421+
replacement.append(matcher.group(1));
422+
if (matcher.groupCount() >= 3) {
423+
replacement.append(MASKED_VALUE);
424+
replacement.append(matcher.group(matcher.groupCount()));
425+
} else {
426+
replacement.append(MASKED_VALUE);
427+
}
428+
matcher.appendReplacement(buffer, Matcher.quoteReplacement(replacement.toString()));
429+
} while (matcher.find());
430+
431+
matcher.appendTail(buffer);
432+
return buffer.toString();
433+
}
369434
}

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)