From cb3c236528e0a273185071ab6bade95ad2a13225 Mon Sep 17 00:00:00 2001 From: dheeraj12347 Date: Thu, 23 Jul 2026 08:35:13 +0000 Subject: [PATCH] Redact sensitive duplicate query parameter values in ApiServlet logs --- .../main/java/com/cloud/api/ApiServlet.java | 37 ++++++++++++++++++- .../java/com/cloud/api/ApiServletTest.java | 25 +++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index 64308cc9e6bb..f7efcddff0ff 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.inject.Inject; import javax.servlet.ServletConfig; @@ -78,7 +79,19 @@ public class ApiServlet extends HttpServlet { protected static Logger LOGGER = LogManager.getLogger(ApiServlet.class); private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServlet.class.getName()); private static final String REPLACEMENT = "_"; - private static final String LOGGER_REPLACEMENTS = "[\n\r\t]"; +private static final String REDACTED = "REDACTED"; +private static final String LOGGER_REPLACEMENTS = "[\n\r\t]"; + +private static final Set SENSITIVE_PARAMETER_KEYWORDS = Set.of( + "password", + "privatekey", + "accesskey", + "secretkey", + "apikey", + "signature", + "sessionkey", + "token" +); @Inject ApiServerService apiServer; @@ -161,12 +174,32 @@ private void checkSingleQueryParameterValue(Map params) { params.forEach((k, v) -> { if (v.length > 1) { String message = String.format("Query parameter '%s' has multiple values %s. Only the last value will be respected." + - "It is advised to pass only a single parameter", k, Arrays.toString(v)); + "It is advised to pass only a single parameter", k, formatValuesForLog(k, v)); LOGGER.warn(message); } }); } + static boolean isSensitiveParameter(String parameterName) { + if (parameterName == null) { + return false; + } + + String lowerCaseParameter = parameterName.toLowerCase(); + return SENSITIVE_PARAMETER_KEYWORDS.stream() + .anyMatch(lowerCaseParameter::contains); +} + +static String formatValuesForLog(String parameterName, String[] values) { + if (!isSensitiveParameter(parameterName)) { + return Arrays.toString(values); + } + + String[] masked = new String[values.length]; + Arrays.fill(masked, REDACTED); + return Arrays.toString(masked); +} + void processRequestInContext(final HttpServletRequest req, final HttpServletResponse resp) { InetAddress remoteAddress = null; try { diff --git a/server/src/test/java/com/cloud/api/ApiServletTest.java b/server/src/test/java/com/cloud/api/ApiServletTest.java index 4d4f0a12098c..234394adf35e 100644 --- a/server/src/test/java/com/cloud/api/ApiServletTest.java +++ b/server/src/test/java/com/cloud/api/ApiServletTest.java @@ -432,4 +432,29 @@ public void testVerify2FAWhenExpectedCommandIsNotCalled() throws UnknownHostExce Assert.assertEquals(false, result); } + @Test + public void testFormatValuesForLogMasksSensitiveParameters() { + String result = ApiServlet.formatValuesForLog( + "password", + new String[]{"SECRET_ONE", "SECRET_TWO"}); + + Assert.assertEquals("[REDACTED, REDACTED]", result); + } + + @Test + public void testFormatValuesForLogDoesNotMaskNormalParameters() { + String result = ApiServlet.formatValuesForLog( + "username", + new String[]{"alice", "bob"}); + + Assert.assertEquals("[alice, bob]", result); + } + + @Test + public void testIsSensitiveParameter() { + Assert.assertTrue(ApiServlet.isSensitiveParameter("adminpassword")); + Assert.assertTrue(ApiServlet.isSensitiveParameter("usersecretkey")); + Assert.assertTrue(ApiServlet.isSensitiveParameter("sessiontoken")); + Assert.assertFalse(ApiServlet.isSensitiveParameter("username")); + } }