2222import java .net .UnknownHostException ;
2323import java .util .Arrays ;
2424import java .util .HashMap ;
25+ import java .util .HashSet ;
2526import java .util .List ;
2627import java .util .Map ;
28+ import java .util .regex .Pattern ;
29+ import java .util .Set ;
2730
2831import javax .inject .Inject ;
2932import javax .servlet .ServletConfig ;
4649import org .apache .cloudstack .context .CallContext ;
4750import org .apache .cloudstack .managed .context .ManagedContext ;
4851import org .apache .cloudstack .utils .consoleproxy .ConsoleAccessUtils ;
52+ import org .apache .commons .collections .MapUtils ;
4953
5054import org .apache .logging .log4j .Logger ;
5155import org .apache .logging .log4j .LogManager ;
@@ -78,6 +82,39 @@ public class ApiServlet extends HttpServlet {
7882 private static final Logger ACCESSLOGGER = LogManager .getLogger ("apiserver." + ApiServlet .class .getName ());
7983 private static final String REPLACEMENT = "_" ;
8084 private static final String LOGGER_REPLACEMENTS = "[\n \r \t ]" ;
85+ private static final Pattern GET_REQUEST_COMMANDS = Pattern .compile ("^(get|list|query|find)(\\ w+)+$" );
86+ private static final HashSet <String > GET_REQUEST_COMMANDS_LIST = new HashSet <>(Set .of ("isaccountallowedtocreateofferingswithtags" ,
87+ "readyforshutdown" , "cloudianisenabled" , "quotabalance" , "quotasummary" , "quotatarifflist" , "quotaisenabled" , "quotastatement" , "verifyoauthcodeandgetuser" ));
88+ private static final HashSet <String > POST_REQUESTS_TO_DISABLE_LOGGING = new HashSet <>(Set .of (
89+ "login" ,
90+ "oauthlogin" ,
91+ "createaccount" ,
92+ "createuser" ,
93+ "updateuser" ,
94+ "forgotpassword" ,
95+ "resetpassword" ,
96+ "importrole" ,
97+ "updaterolepermission" ,
98+ "updateprojectrolepermission" ,
99+ "createstoragepool" ,
100+ "addhost" ,
101+ "updatehostpassword" ,
102+ "addcluster" ,
103+ "addvmwaredc" ,
104+ "configureoutofbandmanagement" ,
105+ "uploadcustomcertificate" ,
106+ "addciscovnmcresource" ,
107+ "addnetscalerloadbalancer" ,
108+ "createtungstenfabricprovider" ,
109+ "addnsxcontroller" ,
110+ "configtungstenfabricservice" ,
111+ "createnetworkacl" ,
112+ "updatenetworkaclitem" ,
113+ "quotavalidateactivationrule" ,
114+ "quotatariffupdate" ,
115+ "listandswitchsamlaccount" ,
116+ "uploadresourceicon"
117+ ));
81118
82119 @ Inject
83120 ApiServerService apiServer ;
@@ -193,11 +230,24 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
193230
194231 utf8Fixup (req , params );
195232
233+ final Object [] commandObj = params .get (ApiConstants .COMMAND );
234+ final String command = commandObj == null ? null : (String ) commandObj [0 ];
235+
196236 // logging the request start and end in management log for easy debugging
197237 String reqStr = "" ;
198238 String cleanQueryString = StringUtils .cleanString (req .getQueryString ());
199239 if (LOGGER .isDebugEnabled ()) {
200240 reqStr = auditTrailSb .toString () + " " + cleanQueryString ;
241+ if (req .getMethod ().equalsIgnoreCase ("POST" ) && org .apache .commons .lang3 .StringUtils .isNotBlank (command )) {
242+ if (!POST_REQUESTS_TO_DISABLE_LOGGING .contains (command .toLowerCase ()) && !reqParams .containsKey (ApiConstants .USER_DATA )) {
243+ String cleanParamsString = getCleanParamsString (reqParams );
244+ if (org .apache .commons .lang3 .StringUtils .isNotBlank (cleanParamsString )) {
245+ reqStr += "\n " + cleanParamsString ;
246+ }
247+ } else {
248+ reqStr += " " + command ;
249+ }
250+ }
201251 LOGGER .debug ("===START=== " + reqStr );
202252 }
203253
@@ -213,8 +263,6 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
213263 responseType = (String )responseTypeParam [0 ];
214264 }
215265
216- final Object [] commandObj = params .get (ApiConstants .COMMAND );
217- final String command = commandObj == null ? null : (String ) commandObj [0 ];
218266 final Object [] userObj = params .get (ApiConstants .USERNAME );
219267 String username = userObj == null ? null : (String )userObj [0 ];
220268 if (LOGGER .isTraceEnabled ()) {
@@ -317,6 +365,19 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
317365 }
318366 }
319367
368+ if (apiServer .isPostRequestsAndTimestampsEnforced () && !isStateChangingCommandUsingPOST (command , req .getMethod (), params )) {
369+ String errorText = String .format ("State changing command %s needs to be sent using POST request" , command );
370+ if (command .equalsIgnoreCase ("updateConfiguration" ) && params .containsKey ("name" )) {
371+ errorText = String .format ("Changes for configuration %s needs to be sent using POST request" , params .get ("name" )[0 ]);
372+ }
373+ auditTrailSb .append (" " + HttpServletResponse .SC_BAD_REQUEST + " " + errorText );
374+ final String serializedResponse =
375+ apiServer .getSerializedApiError (new ServerApiException (ApiErrorCode .BAD_REQUEST , errorText ), params ,
376+ responseType );
377+ HttpUtils .writeHttpResponse (resp , serializedResponse , HttpServletResponse .SC_BAD_REQUEST , responseType , ApiServer .JSONcontentType .value ());
378+ return ;
379+ }
380+
320381 Long userId = null ;
321382 if (!isNew ) {
322383 userId = (Long )session .getAttribute ("userid" );
@@ -407,6 +468,15 @@ private boolean checkIfAuthenticatorIsOf2FA(String command) {
407468 return verify2FA ;
408469 }
409470
471+ private boolean isStateChangingCommandUsingPOST (String command , String method , Map <String , Object []> params ) {
472+ if (command == null || (!GET_REQUEST_COMMANDS .matcher (command .toLowerCase ()).matches () && !GET_REQUEST_COMMANDS_LIST .contains (command .toLowerCase ())
473+ && !command .equalsIgnoreCase ("updateConfiguration" ) && !method .equals ("POST" ))) {
474+ return false ;
475+ }
476+ return !command .equalsIgnoreCase ("updateConfiguration" ) || method .equals ("POST" ) || (params .containsKey ("name" )
477+ && params .get ("name" )[0 ].toString ().equalsIgnoreCase (ApiServer .EnforcePostRequestsAndTimestamps .key ()));
478+ }
479+
410480 protected boolean skip2FAcheckForAPIs (String command ) {
411481 boolean skip2FAcheck = false ;
412482
@@ -644,4 +714,45 @@ private static String getCorrectIPAddress(String ip) {
644714 }
645715 return null ;
646716 }
717+
718+ private String getCleanParamsString (Map <String , String []> reqParams ) {
719+ if (MapUtils .isEmpty (reqParams )) {
720+ return "" ;
721+ }
722+
723+ StringBuilder cleanParamsString = new StringBuilder ();
724+ for (Map .Entry <String , String []> reqParam : reqParams .entrySet ()) {
725+ if (org .apache .commons .lang3 .StringUtils .isBlank (reqParam .getKey ())) {
726+ continue ;
727+ }
728+
729+ cleanParamsString .append (reqParam .getKey ());
730+ cleanParamsString .append ("=" );
731+
732+ if (reqParam .getKey ().toLowerCase ().contains ("password" )
733+ || reqParam .getKey ().toLowerCase ().contains ("privatekey" )
734+ || reqParam .getKey ().toLowerCase ().contains ("accesskey" )
735+ || reqParam .getKey ().toLowerCase ().contains ("secretkey" )) {
736+ cleanParamsString .append ("\n " );
737+ continue ;
738+ }
739+
740+ if (reqParam .getValue () == null || reqParam .getValue ().length == 0 ) {
741+ cleanParamsString .append ("\n " );
742+ continue ;
743+ }
744+
745+ for (String param : reqParam .getValue ()) {
746+ if (org .apache .commons .lang3 .StringUtils .isBlank (param )) {
747+ continue ;
748+ }
749+ String cleanParamString = StringUtils .cleanString (param .trim ());
750+ cleanParamsString .append (cleanParamString );
751+ cleanParamsString .append (" " );
752+ }
753+ cleanParamsString .append ("\n " );
754+ }
755+
756+ return cleanParamsString .toString ();
757+ }
647758}
0 commit comments