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.

This commit is contained in:
nit 2010-10-04 14:27:40 +05:30
parent 23e9508b52
commit 38fd80e522
2 changed files with 97 additions and 30 deletions

52
server/src/com/cloud/api/ApiServer.java Normal file → Executable file
View File

@ -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<String, Object> validatedParams = cmdObj.validateParams(paramMap, decode);
List<Pair<String, Object>> 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<Pair<String, Object>> 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<String,Object> 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<String,Object> 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<String, Object[]> requestParameters, String userId) {
try {
String apiKey = null;

75
server/src/com/cloud/api/ApiServlet.java Normal file → Executable file
View File

@ -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<String, Object[]> params = new HashMap<String, Object[]>();
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<Pair<String, Object>> sessionParams = _apiServer.loginUser(username[0], pwd, domainId, domain, params);
if (sessionParams != null) {
for (Pair<String, Object> 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);
}