From 38fd80e522c8013df023fc5a9e1912b2f951fbac Mon Sep 17 00:00:00 2001 From: nit Date: Mon, 4 Oct 2010 14:27:40 +0530 Subject: [PATCH] bug 5871: Introducing audit trail for all the interactions with the cloud stack - User 'X' initiated an action 'Y' on resource 'Z'. The audit will contain http api request along with the contextual parameters (userId, accountId, sessionId). For the response part only log success/failure for all sync api's with the exception of queryAsyncJob where reason code and reason will also be logged. For async api's I will also log the async job id. --- server/src/com/cloud/api/ApiServer.java | 52 +++++++++++++--- server/src/com/cloud/api/ApiServlet.java | 75 +++++++++++++++++------- 2 files changed, 97 insertions(+), 30 deletions(-) mode change 100644 => 100755 server/src/com/cloud/api/ApiServer.java mode change 100644 => 100755 server/src/com/cloud/api/ApiServlet.java diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java old mode 100644 new mode 100755 index 5c29f8dada8..02902039a82 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -46,6 +46,7 @@ import java.util.concurrent.TimeUnit; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletResponse; import org.apache.http.ConnectionClosedException; import org.apache.http.HttpException; @@ -73,6 +74,7 @@ import org.apache.http.protocol.ResponseContent; import org.apache.http.protocol.ResponseDate; import org.apache.http.protocol.ResponseServer; import org.apache.log4j.Logger; +import org.apache.log4j.NDC; import com.cloud.configuration.ConfigurationVO; import com.cloud.configuration.dao.ConfigurationDao; @@ -235,9 +237,8 @@ public class ApiServer implements HttpRequestHandler { try { // always trust commands from API port, user context will always be UID_SYSTEM/ACCOUNT_ID_SYSTEM UserContext.registerContext(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, null, true); - - String responseText = handleRequest(parameterMap, true, responseType); - sb.append(" 200 " + ((responseText == null) ? 0 : responseText.length())); + NDC.push("userId="+User.UID_SYSTEM+ " accountId="+Account.ACCOUNT_ID_SYSTEM+ " sessionId="+null ); + String responseText = handleRequest(parameterMap, true, responseType, sb); writeResponse(response, responseText, false, responseType); } catch (ServerApiException se) { try { @@ -260,12 +261,12 @@ public class ApiServer implements HttpRequestHandler { } } finally { s_accessLogger.info(sb.toString()); - + NDC.remove(); UserContext.unregisterContext(); } } - public String handleRequest(Map params, boolean decode, String responseType) throws ServerApiException { + public String handleRequest(Map params, boolean decode, String responseType, StringBuffer auditTrailSb) throws ServerApiException { String response = null; try { String[] command = (String[])params.get("command"); @@ -300,10 +301,13 @@ public class ApiServer implements HttpRequestHandler { Map validatedParams = cmdObj.validateParams(paramMap, decode); List> resultValues = cmdObj.execute(validatedParams); + buildAuditTrail(auditTrailSb, command[0], resultValues); response = cmdObj.buildResponse(resultValues, responseType); } else { - s_logger.warn("unknown API command: " + ((command == null) ? "null" : command[0])); - response = buildErrorResponse("unknown API command: " + ((command == null) ? "null" : command[0]), responseType); + String errorString = " unknown API command: " + ((command == null) ? "null" : command[0]); + s_logger.warn(errorString); + auditTrailSb.append(" " +errorString); + response = buildErrorResponse(errorString, responseType); } } } catch (Exception ex) { @@ -316,7 +320,39 @@ public class ApiServer implements HttpRequestHandler { } return response; } - + + private void buildAuditTrail(StringBuffer auditTrailSb, String command, List> resultValues){ + + if (resultValues == null) return; + auditTrailSb.append(" " + HttpServletResponse.SC_OK); + if (command.equals("queryAsyncJobResult")){ //For this command we need to also log job status and job resultcode + for (Pair pair : resultValues){ + String key = pair.first(); + if (key.equals(BaseCmd.Properties.JOB_STATUS.getName())){ + auditTrailSb.append(" "); + auditTrailSb.append(key); + auditTrailSb.append("="); + auditTrailSb.append(pair.second()); + }else if (key.equals(BaseCmd.Properties.JOB_RESULT_CODE.getName())){ + auditTrailSb.append(" "); + auditTrailSb.append(key); + auditTrailSb.append("="); + auditTrailSb.append(pair.second()); + } + } + }else { + for (Pair pair : resultValues){ + if (pair.first().equals(BaseCmd.Properties.JOB_ID.getName())){ // Its an async job so report the jobid + auditTrailSb.append(" "); + auditTrailSb.append(pair.first()); + auditTrailSb.append("="); + auditTrailSb.append(pair.second()); + } + } + } + + } + public boolean verifyRequest(Map requestParameters, String userId) { try { String apiKey = null; diff --git a/server/src/com/cloud/api/ApiServlet.java b/server/src/com/cloud/api/ApiServlet.java old mode 100644 new mode 100755 index d761286096b..7c6b3de73fe --- a/server/src/com/cloud/api/ApiServlet.java +++ b/server/src/com/cloud/api/ApiServlet.java @@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; +import org.apache.log4j.NDC; import com.cloud.maid.StackMaid; import com.cloud.user.Account; @@ -40,10 +41,11 @@ import com.cloud.utils.exception.CloudRuntimeException; @SuppressWarnings("serial") public class ApiServlet extends HttpServlet { - public static final Logger s_logger = Logger.getLogger(ApiServlet.class.getName()); - - private ApiServer _apiServer = null; + public static final Logger s_logger = Logger.getLogger(ApiServlet.class.getName()); + private static final Logger s_accessLogger = Logger.getLogger("apiserver." + ApiServer.class.getName()); + private ApiServer _apiServer = null; + public ApiServlet() { super(); _apiServer = ApiServer.getInstance(); @@ -53,7 +55,7 @@ public class ApiServlet extends HttpServlet { } protected void doGet(HttpServletRequest req, HttpServletResponse resp) { - try { + try { processRequest(req, resp); } finally { StackMaid.current().exitCleanup(); @@ -69,11 +71,14 @@ public class ApiServlet extends HttpServlet { } @SuppressWarnings("unchecked") - private void processRequest(HttpServletRequest req, HttpServletResponse resp) { + private void processRequest(HttpServletRequest req, HttpServletResponse resp) { + StringBuffer auditTrailSb = new StringBuffer(); + auditTrailSb.append(req.getRemoteAddr()); + auditTrailSb.append(" -- " + req.getMethod() + " " ); try { Map params = new HashMap(); params.putAll(req.getParameterMap()); - HttpSession session = req.getSession(false); + HttpSession session = req.getSession(false); // get the response format since we'll need it in a couple of places String responseType = BaseCmd.RESPONSE_TYPE_XML; @@ -87,22 +92,30 @@ public class ApiServlet extends HttpServlet { String command = (String)commandObj[0]; if ("logout".equalsIgnoreCase(command)) { // if this is just a logout, invalidate the session and return - if (session != null) { - String userIdStr = (String)session.getAttribute("userId"); + if (session != null) { + String userIdStr = (String)session.getAttribute(BaseCmd.Properties.USER_ID.getName()); + Account account = (Account)session.getAttribute(BaseCmd.Properties.ACCOUNT_OBJ.getName()); + NDC.push("userId="+userIdStr+ + " accountId="+ account==null ? null:account.getId()+ + " sessionId="+session.getId() ); if (userIdStr != null) { _apiServer.logoutUser(Long.parseLong(userIdStr)); } session.invalidate(); - } + } + auditTrailSb.append("command=logout"); + auditTrailSb.append(" " +HttpServletResponse.SC_OK); writeResponse(resp, getLogoutSuccessResponse(responseType), false, responseType); return; - } else if ("login".equalsIgnoreCase(command)) { + } else if ("login".equalsIgnoreCase(command)) { + auditTrailSb.append("command=login"); // if this is a login, authenticate the user and return if (session != null) session.invalidate(); session = req.getSession(true); String[] username = (String[])params.get("username"); String[] password = (String[])params.get("password"); - String[] domainIdArr = (String[])params.get("domainid"); + String[] domainIdArr = (String[])params.get("domainid"); + if (domainIdArr == null) { domainIdArr = (String[])params.get("domainId"); } @@ -111,16 +124,19 @@ public class ApiServlet extends HttpServlet { if ((domainIdArr != null) && (domainIdArr.length > 0)) { try{ domainId = new Long(Long.parseLong(domainIdArr[0])); + auditTrailSb.append(" domainid=" +domainId);// building the params for POST call } catch(NumberFormatException e) { s_logger.warn("Invalid domain id entered by user"); + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one"); resp.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid domain id entered, please enter a valid one"); } } String domain = null; if (domainName != null) { domain = domainName[0]; + auditTrailSb.append(" domain=" +domain); if (domain != null) { // ensure domain starts with '/' and ends with '/' if (!domain.endsWith("/")) { @@ -133,25 +149,31 @@ public class ApiServlet extends HttpServlet { } if (username != null) { + auditTrailSb.append(" username=" +username[0]); String pwd = ((password == null) ? null : password[0]); List> sessionParams = _apiServer.loginUser(username[0], pwd, domainId, domain, params); if (sessionParams != null) { for (Pair sessionParam : sessionParams) { session.setAttribute(sessionParam.first(), sessionParam.second()); - } - String loginResponse = getLoginSuccessResponse(session, responseType); + } + NDC.push("userId="+session.getAttribute(BaseCmd.Properties.USER_ID.getName())+ + " accountId="+ ((Account)session.getAttribute(BaseCmd.Properties.ACCOUNT_OBJ.getName())).getId()+ + " sessionId="+session.getId() ); + String loginResponse = getLoginSuccessResponse(session, responseType); + auditTrailSb.append(" " +HttpServletResponse.SC_OK); writeResponse(resp, loginResponse, false, responseType); return; } else { // TODO: fall through to API key, or just fail here w/ auth error? (HTTP 401) - session.invalidate(); + session.invalidate(); + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "failed to authenticated user, check username/password are correct"); resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "failed to authenticated user, check username/password are correct"); return; } } } } - + auditTrailSb.append(req.getQueryString()); boolean isNew = ((session == null) ? true : session.isNew()); Object accountObj = null; @@ -168,6 +190,7 @@ public class ApiServlet extends HttpServlet { String[] sessionKeyParam = (String[])params.get(BaseCmd.Properties.SESSION_KEY.getName()); if ((sessionKeyParam == null) || (sessionKey == null) || !sessionKey.equals(sessionKeyParam[0])) { session.invalidate(); + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials"); resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials"); } @@ -175,7 +198,8 @@ public class ApiServlet extends HttpServlet { if ((userId != null) && (account != null) && (accountObj != null) && _apiServer.verifyUser(Long.valueOf(userId))) { String[] command = (String[])params.get("command"); if (command == null) { - s_logger.info("missing command, ignoring request..."); + s_logger.info("missing command, ignoring request..."); + auditTrailSb.append(" " + HttpServletResponse.SC_BAD_REQUEST + " " + "no command specified"); resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no command specified"); return; } @@ -187,6 +211,7 @@ public class ApiServlet extends HttpServlet { account = null; accountObj = null; session.invalidate(); + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials"); resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials"); return; } @@ -212,29 +237,35 @@ public class ApiServlet extends HttpServlet { } // update user context info here so that we can take information if the request is authenticated - // via api key mechenism + // via api key mechanism updateUserContext(params, session != null ? session.getId() : null); try { - String response = _apiServer.handleRequest(params, false, responseType); + String response = _apiServer.handleRequest(params, false, responseType, auditTrailSb); writeResponse(resp, response != null ? response : "", false, responseType); - } catch (ServerApiException se) { + } catch (ServerApiException se) { + auditTrailSb.append(" " +se.getErrorCode() + " " + se.getDescription()); resp.sendError(se.getErrorCode(), se.getDescription()); } } else { if (session != null) { session.invalidate(); } + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials and/or request signature"); resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials and/or request signature"); } } catch (IOException ioex) { if (s_logger.isTraceEnabled()) { s_logger.trace("exception processing request: " + ioex); - } + } + auditTrailSb.append(" exception processing request" ); } catch (Exception ex) { - s_logger.error("unknown exception writing api response", ex); + s_logger.error("unknown exception writing api response", ex); + auditTrailSb.append(" unknown exception writing api response"); } finally { + s_accessLogger.info(auditTrailSb.toString()); // cleanup user context to prevent from being peeked in other request context UserContext.unregisterContext(); + NDC.remove(); } } @@ -249,7 +280,7 @@ public class ApiServlet extends HttpServlet { if(accountObj != null) accountId = accountObj.getId(); - + NDC.push("userId="+userId+ " accountId="+accountId+ " sessionId="+sessionId ); UserContext.updateContext(userId, accountId, sessionId); }