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 ;
2728import java .util .Collections ;
29+ import java .util .regex .Pattern ;
30+ import java .util .Set ;
31+
2832import javax .inject .Inject ;
2933import javax .servlet .ServletConfig ;
3034import javax .servlet .ServletException ;
4650import org .apache .cloudstack .context .CallContext ;
4751import org .apache .cloudstack .managed .context .ManagedContext ;
4852import org .apache .cloudstack .utils .consoleproxy .ConsoleAccessUtils ;
53+ import org .apache .commons .collections .MapUtils ;
4954
5055import org .apache .logging .log4j .Logger ;
5156import org .apache .logging .log4j .LogManager ;
@@ -78,6 +83,39 @@ public class ApiServlet extends HttpServlet {
7883 private static final Logger ACCESSLOGGER = LogManager .getLogger ("apiserver." + ApiServlet .class .getName ());
7984 private static final String REPLACEMENT = "_" ;
8085 private static final String LOGGER_REPLACEMENTS = "[\n \r \t ]" ;
86+ private static final Pattern GET_REQUEST_COMMANDS = Pattern .compile ("^(get|list|query|find)(\\ w+)+$" );
87+ private static final HashSet <String > GET_REQUEST_COMMANDS_LIST = new HashSet <>(Set .of ("isaccountallowedtocreateofferingswithtags" ,
88+ "readyforshutdown" , "cloudianisenabled" , "quotabalance" , "quotasummary" , "quotatarifflist" , "quotaisenabled" , "quotastatement" , "verifyoauthcodeandgetuser" ));
89+ private static final HashSet <String > POST_REQUESTS_TO_DISABLE_LOGGING = new HashSet <>(Set .of (
90+ "login" ,
91+ "oauthlogin" ,
92+ "createaccount" ,
93+ "createuser" ,
94+ "updateuser" ,
95+ "forgotpassword" ,
96+ "resetpassword" ,
97+ "importrole" ,
98+ "updaterolepermission" ,
99+ "updateprojectrolepermission" ,
100+ "createstoragepool" ,
101+ "addhost" ,
102+ "updatehostpassword" ,
103+ "addcluster" ,
104+ "addvmwaredc" ,
105+ "configureoutofbandmanagement" ,
106+ "uploadcustomcertificate" ,
107+ "addciscovnmcresource" ,
108+ "addnetscalerloadbalancer" ,
109+ "createtungstenfabricprovider" ,
110+ "addnsxcontroller" ,
111+ "configtungstenfabricservice" ,
112+ "createnetworkacl" ,
113+ "updatenetworkaclitem" ,
114+ "quotavalidateactivationrule" ,
115+ "quotatariffupdate" ,
116+ "listandswitchsamlaccount" ,
117+ "uploadresourceicon"
118+ ));
81119
82120 @ Inject
83121 ApiServerService apiServer ;
@@ -193,11 +231,24 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
193231
194232 utf8Fixup (req , params );
195233
234+ final Object [] commandObj = params .get (ApiConstants .COMMAND );
235+ final String command = commandObj == null ? null : (String ) commandObj [0 ];
236+
196237 // logging the request start and end in management log for easy debugging
197238 String reqStr = "" ;
198239 String cleanQueryString = StringUtils .cleanString (req .getQueryString ());
199240 if (LOGGER .isDebugEnabled ()) {
200241 reqStr = auditTrailSb .toString () + " " + cleanQueryString ;
242+ if (req .getMethod ().equalsIgnoreCase ("POST" ) && org .apache .commons .lang3 .StringUtils .isNotBlank (command )) {
243+ if (!POST_REQUESTS_TO_DISABLE_LOGGING .contains (command .toLowerCase ()) && !reqParams .containsKey (ApiConstants .USER_DATA )) {
244+ String cleanParamsString = getCleanParamsString (reqParams );
245+ if (org .apache .commons .lang3 .StringUtils .isNotBlank (cleanParamsString )) {
246+ reqStr += "\n " + cleanParamsString ;
247+ }
248+ } else {
249+ reqStr += " " + command ;
250+ }
251+ }
201252 LOGGER .debug ("===START=== " + reqStr );
202253 }
203254
@@ -213,8 +264,6 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
213264 responseType = (String )responseTypeParam [0 ];
214265 }
215266
216- final Object [] commandObj = params .get (ApiConstants .COMMAND );
217- final String command = commandObj == null ? null : (String ) commandObj [0 ];
218267 final Object [] userObj = params .get (ApiConstants .USERNAME );
219268 String username = userObj == null ? null : (String )userObj [0 ];
220269 if (LOGGER .isTraceEnabled ()) {
@@ -317,6 +366,19 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
317366 }
318367 }
319368
369+ if (apiServer .isPostRequestsAndTimestampsEnforced () && !isStateChangingCommandUsingPOST (command , req .getMethod (), params )) {
370+ String errorText = String .format ("State changing command %s needs to be sent using POST request" , command );
371+ if (command .equalsIgnoreCase ("updateConfiguration" ) && params .containsKey ("name" )) {
372+ errorText = String .format ("Changes for configuration %s needs to be sent using POST request" , params .get ("name" )[0 ]);
373+ }
374+ auditTrailSb .append (" " + HttpServletResponse .SC_BAD_REQUEST + " " + errorText );
375+ final String serializedResponse =
376+ apiServer .getSerializedApiError (new ServerApiException (ApiErrorCode .BAD_REQUEST , errorText ), params ,
377+ responseType );
378+ HttpUtils .writeHttpResponse (resp , serializedResponse , HttpServletResponse .SC_BAD_REQUEST , responseType , ApiServer .JSONcontentType .value ());
379+ return ;
380+ }
381+
320382 Long userId = null ;
321383 if (!isNew ) {
322384 userId = (Long )session .getAttribute ("userid" );
@@ -407,6 +469,15 @@ private boolean checkIfAuthenticatorIsOf2FA(String command) {
407469 return verify2FA ;
408470 }
409471
472+ private boolean isStateChangingCommandUsingPOST (String command , String method , Map <String , Object []> params ) {
473+ if (command == null || (!GET_REQUEST_COMMANDS .matcher (command .toLowerCase ()).matches () && !GET_REQUEST_COMMANDS_LIST .contains (command .toLowerCase ())
474+ && !command .equalsIgnoreCase ("updateConfiguration" ) && !method .equals ("POST" ))) {
475+ return false ;
476+ }
477+ return !command .equalsIgnoreCase ("updateConfiguration" ) || method .equals ("POST" ) || (params .containsKey ("name" )
478+ && params .get ("name" )[0 ].toString ().equalsIgnoreCase (ApiServer .EnforcePostRequestsAndTimestamps .key ()));
479+ }
480+
410481 protected boolean skip2FAcheckForAPIs (String command ) {
411482 boolean skip2FAcheck = false ;
412483
@@ -644,4 +715,45 @@ private static String getCorrectIPAddress(String ip) {
644715 }
645716 return null ;
646717 }
718+
719+ private String getCleanParamsString (Map <String , String []> reqParams ) {
720+ if (MapUtils .isEmpty (reqParams )) {
721+ return "" ;
722+ }
723+
724+ StringBuilder cleanParamsString = new StringBuilder ();
725+ for (Map .Entry <String , String []> reqParam : reqParams .entrySet ()) {
726+ if (org .apache .commons .lang3 .StringUtils .isBlank (reqParam .getKey ())) {
727+ continue ;
728+ }
729+
730+ cleanParamsString .append (reqParam .getKey ());
731+ cleanParamsString .append ("=" );
732+
733+ if (reqParam .getKey ().toLowerCase ().contains ("password" )
734+ || reqParam .getKey ().toLowerCase ().contains ("privatekey" )
735+ || reqParam .getKey ().toLowerCase ().contains ("accesskey" )
736+ || reqParam .getKey ().toLowerCase ().contains ("secretkey" )) {
737+ cleanParamsString .append ("\n " );
738+ continue ;
739+ }
740+
741+ if (reqParam .getValue () == null || reqParam .getValue ().length == 0 ) {
742+ cleanParamsString .append ("\n " );
743+ continue ;
744+ }
745+
746+ for (String param : reqParam .getValue ()) {
747+ if (org .apache .commons .lang3 .StringUtils .isBlank (param )) {
748+ continue ;
749+ }
750+ String cleanParamString = StringUtils .cleanString (param .trim ());
751+ cleanParamsString .append (cleanParamString );
752+ cleanParamsString .append (" " );
753+ }
754+ cleanParamsString .append ("\n " );
755+ }
756+
757+ return cleanParamsString .toString ();
758+ }
647759}
0 commit comments