From 53097c67f514d981778b8c0cd8d0f1790f5a6d2e Mon Sep 17 00:00:00 2001 From: Kelven Yang Date: Tue, 17 Aug 2010 15:15:06 -0700 Subject: [PATCH] 1) Fix build problem caused by alex's Refactoring 2) Let console proxy servlet support API key to allow session-less access --- build/build-cloud.xml | 56 ++++--- core/src/com/cloud/storage/VolumeVO.java | 2 +- .../cloud/servlet/ConsoleProxyServlet.java | 149 ++++++++++++++++-- .../com/cloud/user/AccountManagerImpl.java | 2 +- server/src/com/cloud/vm/MauriceMoss.java | 11 +- 5 files changed, 184 insertions(+), 36 deletions(-) diff --git a/build/build-cloud.xml b/build/build-cloud.xml index a40f26126a6..b11abe57254 100755 --- a/build/build-cloud.xml +++ b/build/build-cloud.xml @@ -252,25 +252,45 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/com/cloud/storage/VolumeVO.java b/core/src/com/cloud/storage/VolumeVO.java index 812b6509687..e80a02a6d1d 100755 --- a/core/src/com/cloud/storage/VolumeVO.java +++ b/core/src/com/cloud/storage/VolumeVO.java @@ -41,7 +41,7 @@ import com.google.gson.annotations.Expose; public class VolumeVO implements Volume { @Id @TableGenerator(name="volume_sq", table="sequence", pkColumnName="name", valueColumnName="value", pkColumnValue="volume_seq", allocationSize=1) - @GeneratedValue(strategy=GenerationType.SEQUENCE) + @GeneratedValue(strategy=GenerationType.TABLE) @Column(name="id") long id; diff --git a/server/src/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/com/cloud/servlet/ConsoleProxyServlet.java index 361aea1d464..18bba6c7603 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/com/cloud/servlet/ConsoleProxyServlet.java @@ -19,7 +19,15 @@ package com.cloud.servlet; import java.io.IOException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,7 +40,10 @@ import com.cloud.host.HostVO; import com.cloud.server.ManagementServer; import com.cloud.user.Account; import com.cloud.user.User; +import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.encoding.Base64; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; @@ -57,16 +68,29 @@ public class ConsoleProxyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { try { + String userId = null; + String account = null; + Account accountObj = null; + + Map params = new HashMap(); + params.putAll(req.getParameterMap()); + HttpSession session = req.getSession(false); if(session == null) { - s_logger.info("Invalid web session, reject console/thumbnail access"); - sendResponse(resp, "Access denied. You haven't logged in or your web session has timed out"); - return; + if(verifyRequest(params)) { + userId = (String)params.get(BaseCmd.Properties.USER_ID.getName())[0]; + account = (String)params.get(BaseCmd.Properties.ACCOUNT.getName())[0]; + accountObj = (Account)params.get(BaseCmd.Properties.ACCOUNT_OBJ.getName())[0]; + } else { + s_logger.info("Invalid web session or API key in request, reject console/thumbnail access"); + sendResponse(resp, "Access denied. Invalid web session or API key in request"); + return; + } + } else { + userId = (String)session.getAttribute(BaseCmd.Properties.USER_ID.getName()); + account = (String)session.getAttribute(BaseCmd.Properties.ACCOUNT.getName()); + accountObj = (Account)session.getAttribute(BaseCmd.Properties.ACCOUNT_OBJ.getName()); } - - String userId = (String)session.getAttribute(BaseCmd.Properties.USER_ID.getName()); - String account = (String)session.getAttribute(BaseCmd.Properties.ACCOUNT.getName()); - Account accountObj = (Account)session.getAttribute(BaseCmd.Properties.ACCOUNT_OBJ.getName()); // Do a sanity check here to make sure the user hasn't already been deleted if ((userId == null) || (account == null) || (accountObj == null) || !verifyUser(Long.valueOf(userId))) { @@ -92,7 +116,7 @@ public class ConsoleProxyServlet extends HttpServlet { return; } - if(!checkSessionPermision(req, vmId)) { + if(!checkSessionPermision(req, vmId, accountObj)) { sendResponse(resp, "Permission denied"); return; } @@ -106,7 +130,7 @@ public class ConsoleProxyServlet extends HttpServlet { } catch (Throwable e) { s_logger.error("Unexepected exception in ConsoleProxyServlet", e); - sendResponse(resp, ""); + sendResponse(resp, "Server Internal Error"); } } @@ -265,11 +289,8 @@ public class ConsoleProxyServlet extends HttpServlet { } } - private boolean checkSessionPermision(HttpServletRequest req, long vmId) { + private boolean checkSessionPermision(HttpServletRequest req, long vmId, Account accountObj) { - HttpSession session = req.getSession(false); - Account accountObj = (Account)session.getAttribute("accountobj"); - VMInstanceVO vm = _ms.findVMInstanceById(vmId); UserVmVO userVm; switch(vm.getType()) @@ -321,4 +342,106 @@ public class ConsoleProxyServlet extends HttpServlet { } return true; } + + // copied and modified from ApiServer.java. + // TODO need to replace the whole servlet with a API command + private boolean verifyRequest(Map requestParameters) { + try { + String apiKey = null; + String secretKey = null; + String signature = null; + String unsignedRequest = null; + + // - build a request string with sorted params, make sure it's all lowercase + // - sign the request, verify the signature is the same + List parameterNames = new ArrayList(); + + for (Object paramNameObj : requestParameters.keySet()) { + parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later + } + + Collections.sort(parameterNames); + + for (String paramName : parameterNames) { + // parameters come as name/value pairs in the form String/String[] + String paramValue = ((String[])requestParameters.get(paramName))[0]; + + if ("signature".equalsIgnoreCase(paramName)) { + signature = paramValue; + } else { + if ("apikey".equalsIgnoreCase(paramName)) { + apiKey = paramValue; + } + + if (unsignedRequest == null) { + unsignedRequest = paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20"); + } else { + unsignedRequest = unsignedRequest + "&" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20"); + } + } + } + + + // if api/secret key are passed to the parameters + if ((signature == null) || (apiKey == null)) { + if (s_logger.isDebugEnabled()) { + s_logger.info("expired session, missing signature, or missing apiKey -- ignoring request...sig: " + signature + ", apiKey: " + apiKey); + } + return false; // no signature, bad request + } + + Transaction txn = Transaction.open(Transaction.CLOUD_DB); + txn.close(); + User user = null; + // verify there is a user with this api key + Pair userAcctPair = _ms.findUserByApiKey(apiKey); + if (userAcctPair == null) { + s_logger.info("apiKey does not map to a valid user -- ignoring request, apiKey: " + apiKey); + return false; + } + + user = userAcctPair.first(); + Account account = userAcctPair.second(); + + if (!user.getState().equals(Account.ACCOUNT_STATE_ENABLED) || !account.getState().equals(Account.ACCOUNT_STATE_ENABLED)) { + s_logger.info("disabled or locked user accessing the api, userid = " + user.getId() + "; name = " + user.getUsername() + "; state: " + user.getState() + "; accountState: " + account.getState()); + return false; + } + + if (account.getType() == Account.ACCOUNT_TYPE_NORMAL) { + requestParameters.put(BaseCmd.Properties.USER_ID.getName(), new String[] { user.getId().toString() }); + requestParameters.put(BaseCmd.Properties.ACCOUNT.getName(), new String[] { account.getAccountName() }); + requestParameters.put(BaseCmd.Properties.DOMAIN_ID.getName(), new String[] { account.getDomainId().toString() }); + requestParameters.put(BaseCmd.Properties.ACCOUNT_OBJ.getName(), new Object[] { account }); + } else { + requestParameters.put(BaseCmd.Properties.USER_ID.getName(), new String[] { user.getId().toString() }); + requestParameters.put(BaseCmd.Properties.ACCOUNT.getName(), new String[] { account.getAccountName() }); + requestParameters.put(BaseCmd.Properties.ACCOUNT_OBJ.getName(), new Object[] { account }); + } + + // verify secret key exists + secretKey = user.getSecretKey(); + if (secretKey == null) { + s_logger.info("User does not have a secret key associated with the account -- ignoring request, username: " + user.getUsername()); + return false; + } + + unsignedRequest = unsignedRequest.toLowerCase(); + + Mac mac = Mac.getInstance("HmacSHA1"); + SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1"); + mac.init(keySpec); + mac.update(unsignedRequest.getBytes()); + byte[] encryptedBytes = mac.doFinal(); + String computedSignature = Base64.encodeBytes(encryptedBytes); + boolean equalSig = signature.equals(computedSignature); + if (!equalSig) { + s_logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature); + } + return equalSig; + } catch (Exception ex) { + s_logger.error("unable to verifty request signature", ex); + } + return false; + } } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index c7bab1530fa..beeee83728b 100644 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -52,7 +52,7 @@ public class AccountManagerImpl implements AccountManager { @Inject private VMTemplateDao _templateDao; @Inject private ResourceLimitDao _resourceLimitDao; @Inject private ResourceCountDao _resourceCountDao; - @Inject private final GlobalLock m_resourceCountLock = GlobalLock.getInternLock("resource.count"); + private final GlobalLock m_resourceCountLock = GlobalLock.getInternLock("resource.count"); AccountVO _systemAccount; diff --git a/server/src/com/cloud/vm/MauriceMoss.java b/server/src/com/cloud/vm/MauriceMoss.java index eafefe160d7..2b54c1b90cd 100644 --- a/server/src/com/cloud/vm/MauriceMoss.java +++ b/server/src/com/cloud/vm/MauriceMoss.java @@ -36,9 +36,14 @@ public class MauriceMoss implements VmManager { @Inject private NetworkManager _networkMgr; @Override - public VMInstanceVO allocate(VMInstanceVO vm, ServiceOfferingVO serviceOffering, List networkOfferings, List diskOffering, DataCenterVO dc, AccountVO account) { - _storageMgr.allocateTemplatedVm(vm, template, rootOffering, dataOffering, size, dc, account) - return null; + public VMInstanceVO allocate(VMInstanceVO vm, + ServiceOfferingVO serviceOffering, + NetworkOfferingVO[] networkOfferings, + DiskOfferingVO[] diskOffering, + DataCenterVO dc, + AccountVO account) { + + return null; } @Override