mirror of https://github.com/apache/cloudstack.git
275 lines
12 KiB
Java
275 lines
12 KiB
Java
// 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.security.NoSuchAlgorithmException;
|
|
import java.security.SecureRandom;
|
|
import java.util.Date;
|
|
|
|
import org.apache.commons.codec.binary.Base64;
|
|
import org.apache.log4j.Logger;
|
|
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
|
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
|
import org.apache.cloudstack.framework.security.keys.KeysManager;
|
|
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
|
|
|
|
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.exception.AgentUnavailableException;
|
|
import com.cloud.exception.OperationTimedoutException;
|
|
import com.cloud.host.Host;
|
|
import com.cloud.host.HostVO;
|
|
import com.cloud.host.Status;
|
|
import com.cloud.host.dao.HostDao;
|
|
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;
|
|
|
|
/**
|
|
* 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;
|
|
KeysManager _keysMgr;
|
|
|
|
public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr) {
|
|
_instanceDao = instanceDao;
|
|
_hostDao = hostDao;
|
|
_agentMgr = agentMgr;
|
|
_configDao = cfgDao;
|
|
_ksMgr = ksMgr;
|
|
_keysMgr = keysMgr;
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
|
|
@Override
|
|
public void startAgentHttpHandlerInVM(StartupProxyCommand startupCmd) {
|
|
StartConsoleProxyAgentHttpHandlerCommand cmd = null;
|
|
|
|
try {
|
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
|
|
|
byte[] randomBytes = new byte[16];
|
|
random.nextBytes(randomBytes);
|
|
String storePassword = Base64.encodeBase64String(randomBytes);
|
|
|
|
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());
|
|
|
|
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 (NoSuchAlgorithmException e) {
|
|
s_logger.error("Unexpected exception in SecureRandom Algorithm selection ", e);
|
|
} 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 = _keysMgr.getEncryptionKey();
|
|
iv = _keysMgr.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");
|
|
_keysMgr.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(Host host, StartupCommand cmd) {
|
|
// no-op
|
|
}
|
|
|
|
@Override
|
|
public void onAgentDisconnect(long agentId, Status state) {
|
|
// no-op since we do not autoscale
|
|
}
|
|
}
|