2020import java .net .InetAddress ;
2121import java .net .URLDecoder ;
2222import java .net .UnknownHostException ;
23+ import java .util .ArrayList ;
2324import java .util .Arrays ;
2425import java .util .HashMap ;
2526import java .util .HashSet ;
@@ -85,10 +86,12 @@ public class ApiServlet extends HttpServlet {
8586 private static final Logger ACCESSLOGGER = LogManager .getLogger ("apiserver." + ApiServlet .class .getName ());
8687 private static final String REPLACEMENT = "_" ;
8788 private static final String LOGGER_REPLACEMENTS = "[\n \r \t ]" ;
89+ private static final Pattern SENSITIVE_QUERY_PARAMETER = Pattern .compile (
90+ ".*(?:password|privatekey|accesskey|secretkey)|sessionkey|secretcode|token|userdata" , Pattern .DOTALL );
8891 private static final Pattern GET_REQUEST_COMMANDS = Pattern .compile ("^(get|list|query|find)(\\ w+)+$" );
8992 private static final HashSet <String > GET_REQUEST_COMMANDS_LIST = new HashSet <>(Set .of ("isaccountallowedtocreateofferingswithtags" ,
9093 "readyforshutdown" , "cloudianisenabled" , "quotabalance" , "quotasummary" , "quotatarifflist" , "quotaisenabled" , "quotastatement" , "verifyoauthcodeandgetuser" ));
91- private static final HashSet <String > REQUESTS_TO_DISABLE_PARAMETER_LOGGING = new HashSet <>(Set .of (
94+ private static final HashSet <String > POST_REQUESTS_TO_DISABLE_LOGGING = new HashSet <>(Set .of (
9295 "login" ,
9396 "oauthlogin" ,
9497 "createaccount" ,
@@ -173,7 +176,7 @@ void utf8Fixup(final HttpServletRequest req, final Map<String, Object[]> params)
173176 String value = decodeUtf8 (paramTokens [1 ]);
174177 params .put (name , new String [] {value });
175178 } else {
176- LOGGER .debug ("Invalid parameter in URL found. param: " + param );
179+ LOGGER .debug ("Invalid parameter in URL found (missing equals sign)." );
177180 }
178181 }
179182 }
@@ -200,8 +203,8 @@ public void run() {
200203 private void checkSingleQueryParameterValue (Map <String , String []> params ) {
201204 params .forEach ((k , v ) -> {
202205 if (v .length > 1 ) {
203- String message = String .format ("Query parameter '%s' has multiple values %s . Only the last value will be respected." +
204- "It is advised to pass only a single parameter" , k , Arrays . toString ( v ) );
206+ String message = String .format ("Query parameter '%s' has %d values. Only the last value will be respected. " +
207+ "It is advised to pass only a single parameter" , saveLogString ( k ), v . length );
205208 LOGGER .warn (message );
206209 }
207210 });
@@ -587,7 +590,7 @@ protected void setClientAddressForConsoleEndpointAccess(String command, Map<Stri
587590
588591 @ Nullable
589592 private String saveLogString (String stringToLog ) {
590- return stringToLog == null ? null : stringToLog .replace (LOGGER_REPLACEMENTS , REPLACEMENT );
593+ return stringToLog == null ? null : stringToLog .replaceAll (LOGGER_REPLACEMENTS , REPLACEMENT );
591594 }
592595
593596 /**
@@ -773,14 +776,49 @@ private String getCleanParamsString(Map<String, String[]> reqParams) {
773776
774777 protected boolean shouldLogRequestParameters (String command , Map <String , String []> reqParams ) {
775778 return (org .apache .commons .lang3 .StringUtils .isBlank (command )
776- || !REQUESTS_TO_DISABLE_PARAMETER_LOGGING .contains (command .toLowerCase (java .util .Locale .ROOT )))
779+ || !POST_REQUESTS_TO_DISABLE_LOGGING .contains (command .toLowerCase (java .util .Locale .ROOT )))
777780 && !reqParams .containsKey (ApiConstants .USER_DATA );
778781 }
779782
780783 protected String getCleanQueryString (String command , String queryString , Map <String , String []> reqParams ) {
781- if (!shouldLogRequestParameters (command , reqParams )) {
782- return org .apache .commons .lang3 .StringUtils .isBlank (command ) ? "" : "command=" + saveLogString (command );
784+ boolean sensitiveRequest = !shouldLogRequestParameters (command , reqParams );
785+ boolean includeCommand = sensitiveRequest && org .apache .commons .lang3 .StringUtils .isNotBlank (command );
786+ if (org .apache .commons .lang3 .StringUtils .isEmpty (queryString )) {
787+ return includeCommand ? "command=" + saveLogString (command ) : "" ;
783788 }
784- return StringUtils .cleanString (queryString );
789+
790+ List <String > cleanParameters = new ArrayList <>();
791+ try {
792+ // Split before decoding so an encoded ampersand remains part of its original value.
793+ // Read the query only: reqParams also contains the POST body.
794+ for (String parameter : queryString .split ("&" , -1 )) {
795+ int separator = parameter .indexOf ('=' );
796+ if (separator < 0 ) {
797+ continue ;
798+ }
799+ String name = decodeUtf8 (parameter .substring (0 , separator ));
800+ if (ApiConstants .COMMAND .equals (name )) {
801+ includeCommand = false ;
802+ }
803+ String lowerName = name .toLowerCase (java .util .Locale .ROOT );
804+ if (SENSITIVE_QUERY_PARAMETER .matcher (lowerName ).matches ()) {
805+ continue ;
806+ }
807+ // Sensitive requests can carry credentials in indexed maps. Omit those entries
808+ // rather than trust descriptors that may be in the POST body or use different case.
809+ if (sensitiveRequest && (name .indexOf ('[' ) >= 0 || ApiConstants .DETAILS .equals (lowerName ))) {
810+ continue ;
811+ }
812+ cleanParameters .add (parameter );
813+ }
814+ } catch (IllegalArgumentException e ) {
815+ // Formatting must not expose an undecodable query or change request processing.
816+ return includeCommand ? "command=" + saveLogString (command ) : "" ;
817+ }
818+ if (includeCommand ) {
819+ cleanParameters .add (0 , "command=" + saveLogString (command ));
820+ }
821+ // Preserve the existing cleaning of nested URLs and JSON in the retained query fields.
822+ return StringUtils .cleanString (String .join ("&" , cleanParameters ));
785823 }
786824}
0 commit comments