| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package com.cloud.consoleproxy; |
| |
| import java.util.Date; |
| import java.util.Random; |
| import java.util.UUID; |
| |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.agent.AgentManager; |
| import com.cloud.agent.api.AgentControlAnswer; |
| import com.cloud.agent.api.Answer; |
| import com.cloud.agent.api.ConsoleAccessAuthenticationAnswer; |
| import com.cloud.agent.api.ConsoleAccessAuthenticationCommand; |
| import com.cloud.agent.api.ConsoleProxyLoadReportCommand; |
| import com.cloud.agent.api.GetVncPortAnswer; |
| import com.cloud.agent.api.GetVncPortCommand; |
| import com.cloud.agent.api.StartupCommand; |
| import com.cloud.agent.api.StartupProxyCommand; |
| import com.cloud.agent.api.proxy.StartConsoleProxyAgentHttpHandlerCommand; |
| import com.cloud.configuration.Config; |
| import com.cloud.configuration.dao.ConfigurationDao; |
| import com.cloud.exception.AgentUnavailableException; |
| import com.cloud.exception.OperationTimedoutException; |
| import com.cloud.host.HostVO; |
| import com.cloud.host.Status; |
| import com.cloud.host.dao.HostDao; |
| import com.cloud.keystore.KeystoreManager; |
| import com.cloud.server.ManagementServer; |
| import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor; |
| import com.cloud.servlet.ConsoleProxyServlet; |
| import com.cloud.utils.Ternary; |
| import com.cloud.vm.VirtualMachine; |
| import com.cloud.vm.dao.VMInstanceDao; |
| import com.google.gson.Gson; |
| import com.google.gson.GsonBuilder; |
| |
| /** |
| * Utility class to manage interactions with agent-based console access |
| * Extracted from ConsoleProxyManagerImpl so that other console proxy managers |
| * can reuse |
| */ |
| public abstract class AgentHookBase implements AgentHook { |
| private static final Logger s_logger = Logger.getLogger(AgentHookBase.class); |
| |
| VMInstanceDao _instanceDao; |
| HostDao _hostDao; |
| ConfigurationDao _configDao; |
| AgentManager _agentMgr; |
| KeystoreManager _ksMgr; |
| ManagementServer _ms; |
| final Random _random = new Random(System.currentTimeMillis()); |
| private String _hashKey; |
| |
| |
| public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, |
| AgentManager agentMgr, ManagementServer ms) { |
| this._instanceDao = instanceDao; |
| this._hostDao = hostDao; |
| this._agentMgr = agentMgr; |
| this._configDao = cfgDao; |
| this._ksMgr = ksMgr; |
| this._ms = ms; |
| } |
| |
| public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd) { |
| Long vmId = null; |
| |
| String ticketInUrl = cmd.getTicket(); |
| if (ticketInUrl == null) { |
| s_logger.error("Access ticket could not be found, you could be running an old version of console proxy. vmId: " |
| + cmd.getVmId()); |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Console authentication. Ticket in url for " + cmd.getHost() + ":" + cmd.getPort() + "-" |
| + cmd.getVmId() + " is " + ticketInUrl); |
| } |
| |
| if (!cmd.isReauthenticating()) { |
| String ticket = |
| ConsoleProxyServlet.genAccessTicket(cmd.getHost(), cmd.getPort(), cmd.getSid(), cmd.getVmId()); |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Console authentication. Ticket in 1 minute boundary for " + cmd.getHost() + ":" |
| + cmd.getPort() + "-" + cmd.getVmId() + " is " + ticket); |
| } |
| |
| if (!ticket.equals(ticketInUrl)) { |
| Date now = new Date(); |
| // considering of minute round-up |
| String minuteEarlyTicket = |
| ConsoleProxyServlet.genAccessTicket(cmd.getHost(), cmd.getPort(), cmd.getSid(), cmd.getVmId(), |
| new Date(now.getTime() - 60 * 1000)); |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Console authentication. Ticket in 2-minute boundary for " + cmd.getHost() + ":" |
| + cmd.getPort() + "-" + cmd.getVmId() + " is " + minuteEarlyTicket); |
| } |
| |
| if (!minuteEarlyTicket.equals(ticketInUrl)) { |
| s_logger.error("Access ticket expired or has been modified. vmId: " + cmd.getVmId() |
| + "ticket in URL: " + ticketInUrl + ", tickets to check against: " + ticket + "," |
| + minuteEarlyTicket); |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| } |
| } |
| |
| if (cmd.getVmId() != null && cmd.getVmId().isEmpty()) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Invalid vm id sent from proxy(happens when proxy session has terminated)"); |
| } |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| |
| VirtualMachine vm = _instanceDao.findByUuid(cmd.getVmId()); |
| if (vm == null) { |
| vm = _instanceDao.findById(Long.parseLong(cmd.getVmId())); |
| } |
| if (vm == null) { |
| s_logger.error("Invalid vm id " + cmd.getVmId() + " sent from console access authentication"); |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| |
| if (vm.getHostId() == null) { |
| s_logger.warn("VM " + vmId + " lost host info, failed authentication request"); |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| |
| HostVO host = _hostDao.findById(vm.getHostId()); |
| if (host == null) { |
| s_logger.warn("VM " + vmId + "'s host does not exist, fail authentication request"); |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| |
| String sid = cmd.getSid(); |
| if (sid == null || !sid.equals(vm.getVncPassword())) { |
| s_logger.warn("sid " + sid + " in url does not match stored sid " + vm.getVncPassword()); |
| return new ConsoleAccessAuthenticationAnswer(cmd, false); |
| } |
| |
| if (cmd.isReauthenticating()) { |
| ConsoleAccessAuthenticationAnswer authenticationAnswer = new ConsoleAccessAuthenticationAnswer(cmd, true); |
| authenticationAnswer.setReauthenticating(true); |
| |
| s_logger.info("Re-authentication request, ask host " + vm.getHostId() + " for new console info"); |
| GetVncPortAnswer answer = |
| (GetVncPortAnswer) _agentMgr.easySend(vm.getHostId(), |
| new GetVncPortCommand(vm.getId(), vm.getInstanceName())); |
| |
| if (answer != null && answer.getResult()) { |
| Ternary<String, String, String> parsedHostInfo = ConsoleProxyServlet.parseHostInfo(answer.getAddress()); |
| |
| if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) { |
| |
| s_logger.info("Re-authentication result. vm: " + vm.getId() + ", tunnel url: " |
| + parsedHostInfo.second() + ", tunnel session: " + parsedHostInfo.third()); |
| |
| authenticationAnswer.setTunnelUrl(parsedHostInfo.second()); |
| authenticationAnswer.setTunnelSession(parsedHostInfo.third()); |
| } else { |
| s_logger.info("Re-authentication result. vm: " + vm.getId() + ", host address: " |
| + parsedHostInfo.first() + ", port: " + answer.getPort()); |
| |
| authenticationAnswer.setHost(parsedHostInfo.first()); |
| authenticationAnswer.setPort(answer.getPort()); |
| } |
| } else { |
| s_logger.warn("Re-authentication request failed"); |
| |
| authenticationAnswer.setSuccess(false); |
| } |
| |
| return authenticationAnswer; |
| } |
| |
| return new ConsoleAccessAuthenticationAnswer(cmd, true); |
| } |
| |
| public void startAgentHttpHandlerInVM(StartupProxyCommand startupCmd) { |
| StartConsoleProxyAgentHttpHandlerCommand cmd = null; |
| if (_configDao.isPremium()) { |
| String storePassword = String.valueOf(_random.nextLong()); |
| byte[] ksBits = |
| _ksMgr.getKeystoreBits(ConsoleProxyManager.CERTIFICATE_NAME, ConsoleProxyManager.CERTIFICATE_NAME, |
| storePassword); |
| |
| assert (ksBits != null); |
| if (ksBits == null) { |
| s_logger.error("Could not find and construct a valid SSL certificate"); |
| } |
| cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword); |
| cmd.setEncryptorPassword(getEncryptorPassword()); |
| } else { |
| cmd = new StartConsoleProxyAgentHttpHandlerCommand(); |
| cmd.setEncryptorPassword(getEncryptorPassword()); |
| } |
| |
| try { |
| |
| HostVO consoleProxyHost = findConsoleProxyHost(startupCmd); |
| |
| assert (consoleProxyHost != null); |
| |
| Answer answer = _agentMgr.send(consoleProxyHost.getId(), cmd); |
| if (answer == null || !answer.getResult()) { |
| s_logger.error("Console proxy agent reported that it failed to execute http handling startup command"); |
| } else { |
| s_logger.info("Successfully sent out command to start HTTP handling in console proxy agent"); |
| } |
| } catch (AgentUnavailableException e) { |
| s_logger.error("Unable to send http handling startup command to the console proxy resource for proxy:" |
| + startupCmd.getProxyVmId(), e); |
| } catch (OperationTimedoutException e) { |
| s_logger.error( |
| "Unable to send http handling startup command(time out) to the console proxy resource for proxy:" |
| + startupCmd.getProxyVmId(), e); |
| } catch (OutOfMemoryError e) { |
| s_logger.error("Unrecoverable OutOfMemory Error, exit and let it be re-launched"); |
| System.exit(1); |
| } catch (Exception e) { |
| s_logger.error( |
| "Unexpected exception when sending http handling startup command(time out) to the console proxy resource for proxy:" |
| + startupCmd.getProxyVmId(), e); |
| } |
| } |
| |
| private String getEncryptorPassword() { |
| String key; |
| String iv; |
| ConsoleProxyPasswordBasedEncryptor.KeyIVPair keyIvPair = null; |
| |
| // if we failed after reset, something is definitely wrong |
| for(int i = 0; i < 2; i++) { |
| key = _ms.getEncryptionKey(); |
| iv = _ms.getEncryptionIV(); |
| |
| keyIvPair = new ConsoleProxyPasswordBasedEncryptor.KeyIVPair(key, iv); |
| |
| if(keyIvPair.getIvBytes() == null || keyIvPair.getIvBytes().length != 16 || |
| keyIvPair.getKeyBytes() == null || keyIvPair.getKeyBytes().length != 16) { |
| |
| s_logger.warn("Console access AES KeyIV sanity check failed, reset and regenerate"); |
| _ms.resetEncryptionKeyIV(); |
| } else { |
| break; |
| } |
| } |
| |
| Gson gson = new GsonBuilder().create(); |
| return gson.toJson(keyIvPair); |
| } |
| |
| |
| protected abstract HostVO findConsoleProxyHost(StartupProxyCommand cmd); |
| |
| @Override |
| public void onLoadReport(ConsoleProxyLoadReportCommand cmd) { |
| // no-op since we do not auto-scale |
| } |
| |
| @Override |
| public void onAgentConnect(HostVO host, StartupCommand cmd) { |
| // no-op |
| } |
| |
| @Override |
| public void onAgentDisconnect(long agentId, Status state) { |
| // no-op since we do not autoscale |
| } |
| } |