mirror of https://github.com/apache/cloudstack.git
Console access enhancements
This commit is contained in:
parent
d177678fd3
commit
15b740d397
|
|
@ -382,9 +382,10 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
|
|||
}
|
||||
}
|
||||
|
||||
public String authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket, Boolean isReauthentication) {
|
||||
public String authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket,
|
||||
Boolean isReauthentication, String sessionToken) {
|
||||
|
||||
ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(host, port, vmId, sid, ticket);
|
||||
ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(host, port, vmId, sid, ticket, sessionToken);
|
||||
cmd.setReauthenticating(isReauthentication);
|
||||
|
||||
ConsoleProxyAuthenticationResult result = new ConsoleProxyAuthenticationResult();
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class VirtualMachineTO {
|
|||
boolean enableDynamicallyScaleVm;
|
||||
String vncPassword;
|
||||
String vncAddr;
|
||||
String vncPort;
|
||||
Map<String, String> params;
|
||||
String uuid;
|
||||
String bootType;
|
||||
|
|
@ -283,6 +284,14 @@ public class VirtualMachineTO {
|
|||
this.vncAddr = vncAddr;
|
||||
}
|
||||
|
||||
public String getVncPort() {
|
||||
return vncPort;
|
||||
}
|
||||
|
||||
public void setVncPort(String vncPort) {
|
||||
this.vncPort = vncPort;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return params;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
// 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 org.apache.cloudstack.api.command.user.consoleproxy;
|
||||
|
||||
public class ConsoleEndpoint {
|
||||
|
||||
private boolean result;
|
||||
private String details;
|
||||
private String url;
|
||||
|
||||
public ConsoleEndpoint(boolean result, String url) {
|
||||
this.result = result;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public ConsoleEndpoint(boolean result, String url, String details) {
|
||||
this(result, url);
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public boolean isResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(boolean result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(String details) {
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// 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 org.apache.cloudstack.api.command.user.consoleproxy;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.CreateConsoleUrlResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.utils.consoleproxy.ConsoleAccessUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = CreateConsoleEndpointCmd.APINAME, description = "Create a console endpoint to connect to a VM console",
|
||||
responseObject = CreateConsoleUrlResponse.class, since = "4.18.0",
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
|
||||
public class CreateConsoleEndpointCmd extends BaseCmd {
|
||||
|
||||
public static final String APINAME = "createConsoleEndpoint";
|
||||
public static final Logger s_logger = Logger.getLogger(CreateConsoleEndpointCmd.class.getName());
|
||||
|
||||
@Inject
|
||||
private ConsoleAccessManager consoleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = UserVmResponse.class,
|
||||
required = true,
|
||||
description = "ID of the VM")
|
||||
private Long vmId;
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
String clientSecurityToken = getClientSecurityToken();
|
||||
String clientAddress = getClientAddress();
|
||||
ConsoleEndpoint endpoint = consoleManager.generateConsoleEndpoint(vmId, clientSecurityToken, clientAddress);
|
||||
if (endpoint != null) {
|
||||
CreateConsoleUrlResponse response = new CreateConsoleUrlResponse();
|
||||
response.setResult(endpoint.isResult());
|
||||
response.setDetails(endpoint.getDetails());
|
||||
response.setUrl(endpoint.getUrl());
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName("consoleendpoint");
|
||||
setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to generate console endpoint for vm " + vmId);
|
||||
}
|
||||
}
|
||||
|
||||
private String getParameterBase(String paramKey) {
|
||||
Map<String, String> params = getFullUrlParams();
|
||||
return MapUtils.isNotEmpty(params) && params.containsKey(paramKey) ? params.get(paramKey) : null;
|
||||
}
|
||||
|
||||
private String getClientAddress() {
|
||||
return getParameterBase(ConsoleAccessUtils.CLIENT_INET_ADDRESS_KEY);
|
||||
}
|
||||
|
||||
private String getClientSecurityToken() {
|
||||
return getParameterBase(ConsoleAccessUtils.CLIENT_SECURITY_HEADER_PARAM_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// 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 org.apache.cloudstack.api.response;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
|
||||
public class CreateConsoleUrlResponse extends BaseResponse {
|
||||
|
||||
@SerializedName(ApiConstants.RESULT)
|
||||
@Param(description = "true if the console endpoint is generated properly")
|
||||
private Boolean result;
|
||||
|
||||
@SerializedName(ApiConstants.DETAILS)
|
||||
@Param(description = "details in case of an error")
|
||||
private String details;
|
||||
|
||||
@SerializedName(ApiConstants.IP_ADDRESS)
|
||||
@Param(description = "the console ip address")
|
||||
private String ipAddress;
|
||||
|
||||
@SerializedName(ApiConstants.PORT)
|
||||
@Param(description = "the console port")
|
||||
private String port;
|
||||
|
||||
@SerializedName(ApiConstants.TOKEN)
|
||||
@Param(description = "the console token")
|
||||
private String token;
|
||||
|
||||
@SerializedName(ApiConstants.URL)
|
||||
@Param(description = "the console url")
|
||||
private String url;
|
||||
|
||||
public Boolean getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(Boolean result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(String details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ip) {
|
||||
this.ipAddress = ip;
|
||||
}
|
||||
|
||||
public String getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(String port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 org.apache.cloudstack.consoleproxy;
|
||||
|
||||
import com.cloud.utils.component.Manager;
|
||||
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
||||
public interface ConsoleAccessManager extends Manager, Configurable {
|
||||
|
||||
ConfigKey<String> ConsoleProxySchema = new ConfigKey<>("Advanced", String.class,
|
||||
"consoleproxy.schema", "http",
|
||||
"The http/https schema to be used by the console proxy URLs", true);
|
||||
|
||||
ConfigKey<Boolean> ConsoleProxyExtraSecurityHeaderEnabled = new ConfigKey<>("Advanced", Boolean.class,
|
||||
"consoleproxy.extra.security.header.enabled", "false",
|
||||
"Enable/disable extra security validation for console proxy using client header", true);
|
||||
|
||||
ConfigKey<String> ConsoleProxyExtraSecurityHeaderName = new ConfigKey<>("Advanced", String.class,
|
||||
"consoleproxy.extra.security.header.name", "SECURITY_TOKEN",
|
||||
"A client header for extra security validation when using the console proxy", true);
|
||||
|
||||
ConsoleEndpoint generateConsoleEndpoint(Long vmId, String clientSecurityToken, String clientAddress);
|
||||
|
||||
boolean isSessionAllowed(String sessionUuid);
|
||||
|
||||
void removeSessions(String[] sessionUuids);
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
|
|||
private String _vmId;
|
||||
private String _sid;
|
||||
private String _ticket;
|
||||
private String sessionUuid;
|
||||
|
||||
private boolean _isReauthenticating;
|
||||
|
||||
|
|
@ -33,12 +34,14 @@ public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
|
|||
_isReauthenticating = false;
|
||||
}
|
||||
|
||||
public ConsoleAccessAuthenticationCommand(String host, String port, String vmId, String sid, String ticket) {
|
||||
public ConsoleAccessAuthenticationCommand(String host, String port, String vmId, String sid, String ticket,
|
||||
String sessiontkn) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
_vmId = vmId;
|
||||
_sid = sid;
|
||||
_ticket = ticket;
|
||||
sessionUuid = sessiontkn;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
|
|
@ -68,4 +71,12 @@ public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
|
|||
public void setReauthenticating(boolean value) {
|
||||
_isReauthenticating = value;
|
||||
}
|
||||
|
||||
public String getSessionUuid() {
|
||||
return sessionUuid;
|
||||
}
|
||||
|
||||
public void setSessionUuid(String sessionUuid) {
|
||||
this.sessionUuid = sessionUuid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public class ConsoleProxyConnectionInfo {
|
|||
public String tag;
|
||||
public long createTime;
|
||||
public long lastUsedTime;
|
||||
public String sessionUuid;
|
||||
|
||||
public ConsoleProxyConnectionInfo() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package com.cloud.info;
|
|||
|
||||
public class ConsoleProxyStatus {
|
||||
private ConsoleProxyConnectionInfo[] connections;
|
||||
private String[] removedSessions;
|
||||
|
||||
public ConsoleProxyStatus() {
|
||||
}
|
||||
|
|
@ -28,4 +29,8 @@ public class ConsoleProxyStatus {
|
|||
public ConsoleProxyConnectionInfo[] getConnections() {
|
||||
return connections;
|
||||
}
|
||||
|
||||
public String[] getRemovedSessions() {
|
||||
return removedSessions;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
|
@ -42,11 +43,15 @@ import com.cloud.resource.CommandWrapper;
|
|||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.vm.UserVmManager;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.utils.ssh.SshHelper;
|
||||
|
||||
@ResourceWrapper(handles = StartCommand.class)
|
||||
public final class LibvirtStartCommandWrapper extends CommandWrapper<StartCommand, Answer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(LibvirtStartCommandWrapper.class);
|
||||
private static final int sshPort = Integer.parseInt(LibvirtComputingResource.DEFAULTDOMRSSHPORT);
|
||||
private static final File pemFile = new File(LibvirtComputingResource.SSHPRVKEYPATH);
|
||||
private static final String vncConfFileLocation = "/root/vncport";
|
||||
|
||||
@Override
|
||||
public Answer execute(final StartCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||
|
|
@ -107,6 +112,17 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper<StartComman
|
|||
}
|
||||
}
|
||||
|
||||
if (vmSpec.getType() == VirtualMachine.Type.ConsoleProxy && vmSpec.getVncPort() != null) {
|
||||
String novncPort = vmSpec.getVncPort();
|
||||
try {
|
||||
String addCmd = "echo " + novncPort + " > " + vncConfFileLocation;
|
||||
SshHelper.sshExecute(controlIp, sshPort, "root",
|
||||
pemFile, null, addCmd, 20000, 20000, 600000);
|
||||
} catch (Exception e) {
|
||||
s_logger.error("Could not set the noVNC port " + novncPort + " to the CPVM", e);
|
||||
}
|
||||
}
|
||||
|
||||
final VirtualRoutingResource virtRouterResource = libvirtComputingResource.getVirtRouterResource();
|
||||
// check if the router is up?
|
||||
for (int count = 0; count < 60; count++) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.apache.cloudstack.api.BaseAsyncCreateCmd;
|
|||
import org.apache.cloudstack.api.BaseAsyncCustomIdCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.BaseCustomIdCmd;
|
||||
import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJob;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||
|
|
@ -158,6 +159,12 @@ public class ApiDispatcher {
|
|||
((BaseAsyncCustomIdCmd)cmd).checkUuid();
|
||||
} else if (cmd instanceof BaseCustomIdCmd) {
|
||||
((BaseCustomIdCmd)cmd).checkUuid();
|
||||
} else if (cmd instanceof CreateConsoleEndpointCmd) {
|
||||
Map<String, String> fullUrlParams = ((CreateConsoleEndpointCmd) cmd).getFullUrlParams();
|
||||
s_logger.info("Console URL full params:");
|
||||
for (String key : fullUrlParams.keySet()) {
|
||||
s_logger.info(key + " : " + fullUrlParams.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
cmd.execute();
|
||||
|
|
|
|||
|
|
@ -41,8 +41,11 @@ import org.apache.cloudstack.api.ServerApiException;
|
|||
import org.apache.cloudstack.api.auth.APIAuthenticationManager;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticator;
|
||||
import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd;
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.managed.context.ManagedContext;
|
||||
import org.apache.cloudstack.utils.consoleproxy.ConsoleAccessUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
|
||||
|
|
@ -187,8 +190,8 @@ public class ApiServlet extends HttpServlet {
|
|||
}
|
||||
|
||||
final Object[] commandObj = params.get(ApiConstants.COMMAND);
|
||||
final String command = commandObj == null ? null : (String) commandObj[0];
|
||||
if (commandObj != null) {
|
||||
final String command = (String) commandObj[0];
|
||||
|
||||
APIAuthenticator apiAuthenticator = authManager.getAPIAuthenticator(command);
|
||||
if (apiAuthenticator != null) {
|
||||
|
|
@ -283,7 +286,6 @@ public class ApiServlet extends HttpServlet {
|
|||
|
||||
// Do a sanity check here to make sure the user hasn't already been deleted
|
||||
if ((userId != null) && (account != null) && (accountObj != null) && apiServer.verifyUser(userId)) {
|
||||
final String[] command = (String[])params.get(ApiConstants.COMMAND);
|
||||
if (command == null) {
|
||||
s_logger.info("missing command, ignoring request...");
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_BAD_REQUEST + " " + "no command specified");
|
||||
|
|
@ -318,6 +320,16 @@ public class ApiServlet extends HttpServlet {
|
|||
// Add the HTTP method (GET/POST/PUT/DELETE) as well into the params map.
|
||||
params.put("httpmethod", new String[]{req.getMethod()});
|
||||
setProjectContext(params);
|
||||
if (org.apache.commons.lang3.StringUtils.isNotBlank(command) &&
|
||||
command.equalsIgnoreCase(CreateConsoleEndpointCmd.APINAME)) {
|
||||
InetAddress addr = getClientAddress(req);
|
||||
String clientAddress = addr != null ? addr.getHostAddress() : null;
|
||||
params.put(ConsoleAccessUtils.CLIENT_INET_ADDRESS_KEY, new String[]{clientAddress});
|
||||
if (ConsoleAccessManager.ConsoleProxyExtraSecurityHeaderEnabled.value()) {
|
||||
String clientSecurityToken = req.getHeader(ConsoleAccessManager.ConsoleProxyExtraSecurityHeaderName.value());
|
||||
params.put(ConsoleAccessUtils.CLIENT_SECURITY_HEADER_PARAM_KEY, new String[]{clientSecurityToken});
|
||||
}
|
||||
}
|
||||
final String response = apiServer.handleRequest(params, responseType, auditTrailSb);
|
||||
HttpUtils.writeHttpResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType, ApiServer.JSONcontentType.value());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.util.Map;
|
|||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
|
@ -67,6 +68,8 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol
|
|||
protected ConsoleProxyDao _cpDao;
|
||||
@Inject
|
||||
protected KeystoreManager _ksMgr;
|
||||
@Inject
|
||||
protected ConsoleAccessManager consoleAccessManager;
|
||||
|
||||
@Inject
|
||||
ConfigurationDao _configDao;
|
||||
|
|
@ -77,8 +80,9 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol
|
|||
|
||||
public class AgentBasedAgentHook extends AgentHookBase {
|
||||
|
||||
public AgentBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr) {
|
||||
super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, keysMgr);
|
||||
public AgentBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr,
|
||||
AgentManager agentMgr, KeysManager keysMgr, ConsoleAccessManager consoleAccessManager) {
|
||||
super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, keysMgr, consoleAccessManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -121,7 +125,8 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol
|
|||
|
||||
_consoleProxyUrlDomain = configs.get("consoleproxy.url.domain");
|
||||
|
||||
_listener = new ConsoleProxyListener(new AgentBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr, _agentMgr, _keysMgr));
|
||||
_listener = new ConsoleProxyListener(new AgentBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr,
|
||||
_agentMgr, _keysMgr, consoleAccessManager));
|
||||
_agentMgr.registerForHostEvents(_listener, true, true, false);
|
||||
|
||||
if (s_logger.isInfoEnabled()) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.framework.security.keys.KeysManager;
|
||||
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
|
||||
|
|
@ -68,14 +69,17 @@ public abstract class AgentHookBase implements AgentHook {
|
|||
AgentManager _agentMgr;
|
||||
KeystoreManager _ksMgr;
|
||||
KeysManager _keysMgr;
|
||||
ConsoleAccessManager consoleAccessManager;
|
||||
|
||||
public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr) {
|
||||
public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr,
|
||||
AgentManager agentMgr, KeysManager keysMgr, ConsoleAccessManager consoleAccessMgr) {
|
||||
_instanceDao = instanceDao;
|
||||
_hostDao = hostDao;
|
||||
_agentMgr = agentMgr;
|
||||
_configDao = cfgDao;
|
||||
_ksMgr = ksMgr;
|
||||
_keysMgr = keysMgr;
|
||||
consoleAccessManager = consoleAccessMgr;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -83,6 +87,8 @@ public abstract class AgentHookBase implements AgentHook {
|
|||
Long vmId = null;
|
||||
|
||||
String ticketInUrl = cmd.getTicket();
|
||||
String sessionUuid = cmd.getSessionUuid();
|
||||
|
||||
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);
|
||||
|
|
@ -93,16 +99,20 @@ public abstract class AgentHookBase implements AgentHook {
|
|||
}
|
||||
|
||||
if (!cmd.isReauthenticating()) {
|
||||
String ticket = ConsoleProxyServlet.genAccessTicket(cmd.getHost(), cmd.getPort(), cmd.getSid(), cmd.getVmId());
|
||||
String ticket = ConsoleAccessManagerImpl.genAccessTicket(cmd.getHost(), cmd.getPort(), cmd.getSid(), cmd.getVmId(), sessionUuid);
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Console authentication. Ticket in 1 minute boundary for " + cmd.getHost() + ":" + cmd.getPort() + "-" + cmd.getVmId() + " is " + ticket);
|
||||
}
|
||||
|
||||
if (!consoleAccessManager.isSessionAllowed(sessionUuid)) {
|
||||
s_logger.error("Invalid session, only one session allowed per token");
|
||||
return new ConsoleAccessAuthenticationAnswer(cmd, false);
|
||||
}
|
||||
|
||||
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));
|
||||
String minuteEarlyTicket = ConsoleAccessManagerImpl.genAccessTicket(cmd.getHost(), cmd.getPort(), cmd.getSid(), cmd.getVmId(), new Date(now.getTime() - 60 * 1000), sessionUuid);
|
||||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Console authentication. Ticket in 2-minute boundary for " + cmd.getHost() + ":" + cmd.getPort() + "-" + cmd.getVmId() + " is " +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,451 @@
|
|||
// 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 com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.GetVmVncTicketAnswer;
|
||||
import com.cloud.agent.api.GetVmVncTicketCommand;
|
||||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.resource.ResourceState;
|
||||
import com.cloud.server.ManagementServer;
|
||||
import com.cloud.servlet.ConsoleProxyClientParam;
|
||||
import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor;
|
||||
import com.cloud.storage.GuestOSVO;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.Ternary;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.UserVmDetailVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineManager;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.security.keys.KeysManager;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAccessManager {
|
||||
|
||||
@Inject
|
||||
private AccountManager _accountMgr;
|
||||
@Inject
|
||||
private VirtualMachineManager _vmMgr;
|
||||
@Inject
|
||||
private ManagementServer _ms;
|
||||
@Inject
|
||||
private EntityManager _entityMgr;
|
||||
@Inject
|
||||
private UserVmDetailsDao _userVmDetailsDao;
|
||||
@Inject
|
||||
private KeysManager _keysMgr;
|
||||
@Inject
|
||||
private AgentManager agentManager;
|
||||
|
||||
private static KeysManager s_keysMgr;
|
||||
private final Gson _gson = new GsonBuilder().create();
|
||||
|
||||
public static final Logger s_logger = Logger.getLogger(ConsoleAccessManagerImpl.class.getName());
|
||||
|
||||
private static Set<String> allowedSessions;
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
s_keysMgr = _keysMgr;
|
||||
allowedSessions = new HashSet<>();
|
||||
return super.configure(name, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String clientSecurityToken, String clientAddress) {
|
||||
try {
|
||||
if (_accountMgr == null || _vmMgr == null || _ms == null) {
|
||||
return new ConsoleEndpoint(false, null,"Console service is not ready");
|
||||
}
|
||||
|
||||
if (_keysMgr.getHashKey() == null) {
|
||||
String msg = "Console access denied. Ticket service is not ready yet";
|
||||
s_logger.debug(msg);
|
||||
return new ConsoleEndpoint(false, null, msg);
|
||||
}
|
||||
|
||||
Account account = CallContext.current().getCallingAccount();
|
||||
|
||||
// Do a sanity check here to make sure the user hasn't already been deleted
|
||||
if (account == null) {
|
||||
s_logger.debug("Invalid user/account, reject console access");
|
||||
return new ConsoleEndpoint(false, null,"Access denied. Invalid or inconsistent account is found");
|
||||
}
|
||||
|
||||
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmId);
|
||||
if (vm == null) {
|
||||
s_logger.info("Invalid console servlet command parameter: " + vmId);
|
||||
return new ConsoleEndpoint(false, null, "Cannot find VM with ID " + vmId);
|
||||
}
|
||||
|
||||
if (!checkSessionPermision(vm, account)) {
|
||||
return new ConsoleEndpoint(false, null, "Permission denied");
|
||||
}
|
||||
|
||||
String sessionToken = UUID.randomUUID().toString();
|
||||
return generateAccessEndpoint(vmId, sessionToken, clientSecurityToken, clientAddress);
|
||||
} catch (Throwable e) {
|
||||
s_logger.error("Unexepected exception in ConsoleProxyServlet", e);
|
||||
return new ConsoleEndpoint(false, null, "Server Internal Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSessionAllowed(String sessionUuid) {
|
||||
return allowedSessions.contains(sessionUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSessions(String[] sessionUuids) {
|
||||
for (String r : sessionUuids) {
|
||||
allowedSessions.remove(r);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSessionPermision(VirtualMachine vm, Account account) {
|
||||
if (_accountMgr.isRootAdmin(account.getId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (vm.getType()) {
|
||||
case User:
|
||||
try {
|
||||
_accountMgr.checkAccess(account, null, true, vm);
|
||||
} catch (PermissionDeniedException ex) {
|
||||
if (_accountMgr.isNormalUser(account.getId())) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("VM access is denied. VM owner account " + vm.getAccountId() + " does not match the account id in session " +
|
||||
account.getId() + " and caller is a normal user");
|
||||
}
|
||||
} else if (_accountMgr.isDomainAdmin(account.getId())
|
||||
|| account.getType() == Account.Type.READ_ONLY_ADMIN) {
|
||||
if(s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("VM access is denied. VM owner account " + vm.getAccountId()
|
||||
+ " does not match the account id in session " + account.getId() + " and the domain-admin caller does not manage the target domain");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case DomainRouter:
|
||||
case ConsoleProxy:
|
||||
case SecondaryStorageVm:
|
||||
return false;
|
||||
|
||||
default:
|
||||
s_logger.warn("Unrecoginized virtual machine type, deny access by default. type: " + vm.getType());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ConsoleEndpoint generateAccessEndpoint(Long vmId, String sessionToken, String clientSecurityToken, String clientAddress) {
|
||||
VirtualMachine vm = _vmMgr.findById(vmId);
|
||||
String msg;
|
||||
if (vm == null) {
|
||||
msg = "VM " + vmId + " does not exist, sending blank response for console access request";
|
||||
s_logger.warn(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
if (vm.getHostId() == null) {
|
||||
msg = "VM " + vmId + " lost host info, sending blank response for console access request";
|
||||
s_logger.warn(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
HostVO host = _ms.getHostBy(vm.getHostId());
|
||||
if (host == null) {
|
||||
msg = "VM " + vmId + "'s host does not exist, sending blank response for console access request";
|
||||
s_logger.warn(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
if (Hypervisor.HypervisorType.LXC.equals(vm.getHypervisorType())) {
|
||||
throw new CloudRuntimeException("Console access is not supported for LXC");
|
||||
}
|
||||
|
||||
String rootUrl = _ms.getConsoleAccessUrlRoot(vmId);
|
||||
if (rootUrl == null) {
|
||||
throw new CloudRuntimeException("Console access will be ready in a few minutes. Please try it again later.");
|
||||
}
|
||||
|
||||
ConsoleEndpoint consoleEndpoint = composeConsoleAccessEndpoint(rootUrl, vm, host, clientAddress, sessionToken, clientSecurityToken);
|
||||
s_logger.debug("The console URL is: " + consoleEndpoint.getUrl());
|
||||
return consoleEndpoint;
|
||||
}
|
||||
|
||||
private ConsoleEndpoint composeConsoleAccessEndpoint(String rootUrl, VirtualMachine vm, HostVO hostVo, String addr,
|
||||
String sessionUuid, String clientSecurityToken) {
|
||||
StringBuffer sb = new StringBuffer(rootUrl);
|
||||
String host = hostVo.getPrivateIpAddress();
|
||||
|
||||
Pair<String, Integer> portInfo = null;
|
||||
if (hostVo.getHypervisorType() == Hypervisor.HypervisorType.KVM &&
|
||||
(hostVo.getResourceState().equals(ResourceState.ErrorInMaintenance) ||
|
||||
hostVo.getResourceState().equals(ResourceState.ErrorInPrepareForMaintenance))) {
|
||||
UserVmDetailVO detailAddress = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KVM_VNC_ADDRESS);
|
||||
UserVmDetailVO detailPort = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KVM_VNC_PORT);
|
||||
if (detailAddress != null && detailPort != null) {
|
||||
portInfo = new Pair<>(detailAddress.getValue(), Integer.valueOf(detailPort.getValue()));
|
||||
} else {
|
||||
s_logger.warn("KVM Host in ErrorInMaintenance/ErrorInPrepareForMaintenance but " +
|
||||
"no VNC Address/Port was available. Falling back to default one from MS.");
|
||||
}
|
||||
}
|
||||
|
||||
if (portInfo == null) {
|
||||
portInfo = _ms.getVncPort(vm);
|
||||
}
|
||||
|
||||
if (s_logger.isDebugEnabled())
|
||||
s_logger.debug("Port info " + portInfo.first());
|
||||
|
||||
Ternary<String, String, String> parsedHostInfo = parseHostInfo(portInfo.first());
|
||||
|
||||
int port = -1;
|
||||
if (portInfo.second() == -9) {
|
||||
//for hyperv
|
||||
port = Integer.parseInt(_ms.findDetail(hostVo.getId(), "rdp.server.port").getValue());
|
||||
} else {
|
||||
port = portInfo.second();
|
||||
}
|
||||
|
||||
String sid = vm.getVncPassword();
|
||||
UserVmDetailVO details = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KEYBOARD);
|
||||
|
||||
String tag = vm.getUuid();
|
||||
|
||||
String ticket = genAccessTicket(parsedHostInfo.first(), String.valueOf(port), sid, tag, sessionUuid);
|
||||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword());
|
||||
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
|
||||
param.setClientHostAddress(parsedHostInfo.first());
|
||||
param.setClientHostPort(port);
|
||||
param.setClientHostPassword(sid);
|
||||
param.setClientTag(tag);
|
||||
param.setTicket(ticket);
|
||||
param.setSessionUuid(sessionUuid);
|
||||
param.setSourceIP(addr);
|
||||
|
||||
if (StringUtils.isNotBlank(clientSecurityToken)) {
|
||||
param.setClientSecurityHeader(ConsoleAccessManager.ConsoleProxyExtraSecurityHeaderName.value());
|
||||
param.setClientSecurityToken(clientSecurityToken);
|
||||
s_logger.debug("Added security token " + clientSecurityToken + " for header " + ConsoleAccessManager.ConsoleProxyExtraSecurityHeaderName.value());
|
||||
}
|
||||
|
||||
if (requiresVncOverWebSocketConnection(vm, hostVo)) {
|
||||
setWebsocketUrl(vm, param);
|
||||
}
|
||||
|
||||
if (details != null) {
|
||||
param.setLocale(details.getValue());
|
||||
}
|
||||
|
||||
if (portInfo.second() == -9) {
|
||||
//For Hyperv Clinet Host Address will send Instance id
|
||||
param.setHypervHost(host);
|
||||
param.setUsername(_ms.findDetail(hostVo.getId(), "username").getValue());
|
||||
param.setPassword(_ms.findDetail(hostVo.getId(), "password").getValue());
|
||||
}
|
||||
if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) {
|
||||
param.setClientTunnelUrl(parsedHostInfo.second());
|
||||
param.setClientTunnelSession(parsedHostInfo.third());
|
||||
}
|
||||
|
||||
String token = encryptor.encryptObject(ConsoleProxyClientParam.class, param);
|
||||
if (param.getHypervHost() != null || !ConsoleProxyManager.NoVncConsoleDefault.value()) {
|
||||
sb.append("/ajax?token=" + token);
|
||||
} else {
|
||||
sb.append("/resource/noVNC/vnc.html")
|
||||
.append("?autoconnect=true")
|
||||
.append("&port=" + ConsoleProxyManager.NoVncConsolePort.value())
|
||||
.append("&token=" + token);
|
||||
}
|
||||
|
||||
// for console access, we need guest OS type to help implement keyboard
|
||||
long guestOs = vm.getGuestOSId();
|
||||
GuestOSVO guestOsVo = _ms.getGuestOs(guestOs);
|
||||
if (guestOsVo.getCategoryId() == 6)
|
||||
sb.append("&guest=windows");
|
||||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Compose console url: " + sb);
|
||||
}
|
||||
s_logger.debug("Adding allowed session: " + sessionUuid);
|
||||
allowedSessions.add(sessionUuid);
|
||||
String url = !sb.toString().startsWith("http") ? ConsoleAccessManager.ConsoleProxySchema.value() + ":" + sb : sb.toString();
|
||||
return new ConsoleEndpoint(true, url);
|
||||
}
|
||||
|
||||
static public Ternary<String, String, String> parseHostInfo(String hostInfo) {
|
||||
String host = null;
|
||||
String tunnelUrl = null;
|
||||
String tunnelSession = null;
|
||||
|
||||
s_logger.info("Parse host info returned from executing GetVNCPortCommand. host info: " + hostInfo);
|
||||
|
||||
if (hostInfo != null) {
|
||||
if (hostInfo.startsWith("consoleurl")) {
|
||||
String tokens[] = hostInfo.split("&");
|
||||
|
||||
if (hostInfo.length() > 19 && hostInfo.indexOf('/', 19) > 19) {
|
||||
host = hostInfo.substring(19, hostInfo.indexOf('/', 19)).trim();
|
||||
tunnelUrl = tokens[0].substring("consoleurl=".length());
|
||||
tunnelSession = tokens[1].split("=")[1];
|
||||
} else {
|
||||
host = "";
|
||||
}
|
||||
} else if (hostInfo.startsWith("instanceId")) {
|
||||
host = hostInfo.substring(hostInfo.indexOf('=') + 1);
|
||||
} else {
|
||||
host = hostInfo;
|
||||
}
|
||||
} else {
|
||||
host = hostInfo;
|
||||
}
|
||||
|
||||
return new Ternary<String, String, String>(host, tunnelUrl, tunnelSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since VMware 7.0 VNC servers are deprecated, it uses a ticket to create a VNC over websocket connection
|
||||
* Check: https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-70-release-notes.html
|
||||
*/
|
||||
private boolean requiresVncOverWebSocketConnection(VirtualMachine vm, HostVO hostVo) {
|
||||
return vm.getHypervisorType() == Hypervisor.HypervisorType.VMware && hostVo.getHypervisorVersion().compareTo("7.0") >= 0;
|
||||
}
|
||||
|
||||
public static String genAccessTicket(String host, String port, String sid, String tag, String sessionUuid) {
|
||||
return genAccessTicket(host, port, sid, tag, new Date(), sessionUuid);
|
||||
}
|
||||
|
||||
public static String genAccessTicket(String host, String port, String sid, String tag, Date normalizedHashTime, String sessionUuid) {
|
||||
String params = "host=" + host + "&port=" + port + "&sid=" + sid + "&tag=" + tag + "&session=" + sessionUuid;
|
||||
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
|
||||
long ts = normalizedHashTime.getTime();
|
||||
ts = ts / 60000; // round up to 1 minute
|
||||
String secretKey = s_keysMgr.getHashKey();
|
||||
|
||||
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
|
||||
mac.init(keySpec);
|
||||
mac.update(params.getBytes());
|
||||
mac.update(String.valueOf(ts).getBytes());
|
||||
|
||||
byte[] encryptedBytes = mac.doFinal();
|
||||
|
||||
return Base64.encodeBase64String(encryptedBytes);
|
||||
} catch (Exception e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getEncryptorPassword() {
|
||||
String key = _keysMgr.getEncryptionKey();
|
||||
String iv = _keysMgr.getEncryptionIV();
|
||||
|
||||
ConsoleProxyPasswordBasedEncryptor.KeyIVPair keyIvPair = new ConsoleProxyPasswordBasedEncryptor.KeyIVPair(key, iv);
|
||||
return _gson.toJson(keyIvPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL to establish a VNC over websocket connection
|
||||
*/
|
||||
private void setWebsocketUrl(VirtualMachine vm, ConsoleProxyClientParam param) {
|
||||
String ticket = acquireVncTicketForVmwareVm(vm);
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
s_logger.error("Could not obtain VNC ticket for VM " + vm.getInstanceName());
|
||||
return;
|
||||
}
|
||||
String wsUrl = composeWebsocketUrlForVmwareVm(ticket, param);
|
||||
param.setWebsocketUrl(wsUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format expected: wss://<ESXi_HOST_IP>:443/ticket/<TICKET_ID>
|
||||
*/
|
||||
private String composeWebsocketUrlForVmwareVm(String ticket, ConsoleProxyClientParam param) {
|
||||
param.setClientHostPort(443);
|
||||
return String.format("wss://%s:%s/ticket/%s", param.getClientHostAddress(), param.getClientHostPort(), ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a ticket to be used for console proxy as described in 'Removal of VNC Server from ESXi' on:
|
||||
* https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-70-release-notes.html
|
||||
*/
|
||||
private String acquireVncTicketForVmwareVm(VirtualMachine vm) {
|
||||
try {
|
||||
s_logger.info("Acquiring VNC ticket for VM = " + vm.getHostName());
|
||||
GetVmVncTicketCommand cmd = new GetVmVncTicketCommand(vm.getInstanceName());
|
||||
Answer answer = agentManager.send(vm.getHostId(), cmd);
|
||||
GetVmVncTicketAnswer ans = (GetVmVncTicketAnswer) answer;
|
||||
if (!ans.getResult()) {
|
||||
s_logger.info("VNC ticket could not be acquired correctly: " + ans.getDetails());
|
||||
}
|
||||
return ans.getTicket();
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
s_logger.error("Error acquiring ticket", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return ConsoleAccessManagerImpl.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey[] { ConsoleProxySchema, ConsoleProxyExtraSecurityHeaderName,
|
||||
ConsoleProxyExtraSecurityHeaderEnabled };
|
||||
}
|
||||
}
|
||||
|
|
@ -23,39 +23,40 @@ import org.apache.cloudstack.framework.config.ConfigKey;
|
|||
|
||||
public interface ConsoleProxyManager extends Manager, ConsoleProxyService {
|
||||
|
||||
public static final int DEFAULT_PROXY_CAPACITY = 50;
|
||||
public static final int DEFAULT_STANDBY_CAPACITY = 10;
|
||||
public static final int DEFAULT_PROXY_VM_RAMSIZE = 1024; // 1G
|
||||
public static final int DEFAULT_PROXY_VM_CPUMHZ = 500; // 500 MHz
|
||||
int DEFAULT_PROXY_CAPACITY = 50;
|
||||
int DEFAULT_STANDBY_CAPACITY = 10;
|
||||
int DEFAULT_PROXY_VM_RAMSIZE = 1024; // 1G
|
||||
int DEFAULT_PROXY_VM_CPUMHZ = 500; // 500 MHz
|
||||
|
||||
public static final int DEFAULT_PROXY_CMD_PORT = 8001;
|
||||
public static final int DEFAULT_PROXY_VNC_PORT = 0;
|
||||
public static final int DEFAULT_PROXY_URL_PORT = 80;
|
||||
public static final int DEFAULT_PROXY_SESSION_TIMEOUT = 300000; // 5 minutes
|
||||
int DEFAULT_PROXY_CMD_PORT = 8001;
|
||||
int DEFAULT_PROXY_VNC_PORT = 0;
|
||||
int DEFAULT_PROXY_URL_PORT = 80;
|
||||
int DEFAULT_PROXY_SESSION_TIMEOUT = 300000; // 5 minutes
|
||||
|
||||
public static final int DEFAULT_NOVNC_PORT = 8080;
|
||||
String ALERT_SUBJECT = "proxy-alert";
|
||||
String CERTIFICATE_NAME = "CPVMCertificate";
|
||||
|
||||
public static final String ALERT_SUBJECT = "proxy-alert";
|
||||
public static final String CERTIFICATE_NAME = "CPVMCertificate";
|
||||
|
||||
public static final ConfigKey<Boolean> NoVncConsoleDefault = new ConfigKey<Boolean>("Advanced", Boolean.class, "novnc.console.default", "true",
|
||||
ConfigKey<Boolean> NoVncConsoleDefault = new ConfigKey<Boolean>("Advanced", Boolean.class, "novnc.console.default", "true",
|
||||
"If true, noVNC console will be default console for virtual machines", true);
|
||||
|
||||
public static final ConfigKey<Boolean> NoVncConsoleSourceIpCheckEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "novnc.console.sourceip.check.enabled", "false",
|
||||
ConfigKey<Boolean> NoVncConsoleSourceIpCheckEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "novnc.console.sourceip.check.enabled", "false",
|
||||
"If true, The source IP to access novnc console must be same as the IP in request to management server for console URL. Needs to reconnect CPVM to management server when this changes (via restart CPVM, or management server, or cloud service in CPVM)", false);
|
||||
|
||||
public void setManagementState(ConsoleProxyManagementState state);
|
||||
ConfigKey<Integer> NoVncConsolePort = new ConfigKey<>("Advanced", Integer.class, "novnc.console.port",
|
||||
"8080", "The listen port for noVNC console", true);
|
||||
|
||||
public ConsoleProxyManagementState getManagementState();
|
||||
void setManagementState(ConsoleProxyManagementState state);
|
||||
|
||||
public void resumeLastManagementState();
|
||||
ConsoleProxyManagementState getManagementState();
|
||||
|
||||
public ConsoleProxyVO startProxy(long proxyVmId, boolean ignoreRestartSetting);
|
||||
void resumeLastManagementState();
|
||||
|
||||
public boolean stopProxy(long proxyVmId);
|
||||
ConsoleProxyVO startProxy(long proxyVmId, boolean ignoreRestartSetting);
|
||||
|
||||
public boolean rebootProxy(long proxyVmId);
|
||||
boolean stopProxy(long proxyVmId);
|
||||
|
||||
public boolean destroyProxy(long proxyVmId);
|
||||
boolean rebootProxy(long proxyVmId);
|
||||
|
||||
boolean destroyProxy(long proxyVmId);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import javax.inject.Inject;
|
|||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
|
@ -256,11 +257,13 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
|
|||
private KeystoreDao _ksDao;
|
||||
@Inject
|
||||
private KeystoreManager _ksMgr;
|
||||
@Inject
|
||||
private ConsoleAccessManager consoleAccessManager;
|
||||
|
||||
public class VmBasedAgentHook extends AgentHookBase {
|
||||
|
||||
public VmBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr) {
|
||||
super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, keysMgr);
|
||||
public VmBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr, ConsoleAccessManager consoleAccessManager) {
|
||||
super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, keysMgr, consoleAccessManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1148,7 +1151,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
|
|||
value = agentMgrConfigs.get("port");
|
||||
managementPort = NumbersUtil.parseInt(value, 8250);
|
||||
|
||||
consoleProxyListener = new ConsoleProxyListener(new VmBasedAgentHook(vmInstanceDao, hostDao, configurationDao, _ksMgr, agentManager, keysManager));
|
||||
consoleProxyListener = new ConsoleProxyListener(new VmBasedAgentHook(vmInstanceDao, hostDao, configurationDao,
|
||||
_ksMgr, agentManager, keysManager, consoleAccessManager));
|
||||
agentManager.registerForHostEvents(consoleProxyListener, true, true, false);
|
||||
|
||||
virtualMachineManager.registerGuru(VirtualMachine.Type.ConsoleProxy, this);
|
||||
|
|
@ -1582,7 +1586,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
|
|||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] { NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled };
|
||||
return new ConfigKey<?>[] { NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled, NoVncConsolePort };
|
||||
}
|
||||
|
||||
protected ConsoleProxyStatus parseJsonToConsoleProxyStatus(String json) throws JsonParseException {
|
||||
|
|
@ -1606,6 +1610,9 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
|
|||
if (status.getConnections() != null) {
|
||||
count = status.getConnections().length;
|
||||
}
|
||||
if (status.getRemovedSessions() != null) {
|
||||
consoleAccessManager.removeSessions(status.getRemovedSessions());
|
||||
}
|
||||
|
||||
details = statusInfo.getBytes(Charset.forName("US-ASCII"));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.UUID;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.consoleproxy.ConsoleProxyManager;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.backup.Backup;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
|
|
@ -277,6 +278,15 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
|
|||
to.setConfigDriveLocation(vmProfile.getConfigDriveLocation());
|
||||
to.setState(vm.getState());
|
||||
|
||||
if (vmInstance.getType() == VirtualMachine.Type.ConsoleProxy) {
|
||||
try {
|
||||
String vncPort = String.valueOf(ConsoleProxyManager.NoVncConsolePort.value());
|
||||
to.setVncPort(vncPort);
|
||||
} catch (Exception e) {
|
||||
s_logger.error("Could not parse the noVNC port set on " + ConsoleProxyManager.NoVncConsolePort.key(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScalePolicyCmd
|
|||
import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
|
||||
import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd;
|
||||
import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.ListEventTypesCmd;
|
||||
|
|
@ -3503,6 +3504,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||
cmdList.add(IssueOutOfBandManagementPowerActionCmd.class);
|
||||
cmdList.add(ChangeOutOfBandManagementPasswordCmd.class);
|
||||
cmdList.add(GetUserKeysCmd.class);
|
||||
cmdList.add(CreateConsoleEndpointCmd.class);
|
||||
return cmdList;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ public class ConsoleProxyClientParam {
|
|||
private String sourceIP;
|
||||
private String websocketUrl;
|
||||
|
||||
private String sessionUuid;
|
||||
private String clientSecurityHeader;
|
||||
private String clientSecurityToken;
|
||||
|
||||
public ConsoleProxyClientParam() {
|
||||
clientHostPort = 0;
|
||||
}
|
||||
|
|
@ -159,4 +163,28 @@ public class ConsoleProxyClientParam {
|
|||
public void setWebsocketUrl(String websocketUrl) {
|
||||
this.websocketUrl = websocketUrl;
|
||||
}
|
||||
|
||||
public String getSessionUuid() {
|
||||
return sessionUuid;
|
||||
}
|
||||
|
||||
public String getClientSecurityHeader() {
|
||||
return clientSecurityHeader;
|
||||
}
|
||||
|
||||
public void setClientSecurityHeader(String clientSecurityHeader) {
|
||||
this.clientSecurityHeader = clientSecurityHeader;
|
||||
}
|
||||
|
||||
public void setSessionUuid(String sessionUuid) {
|
||||
this.sessionUuid = sessionUuid;
|
||||
}
|
||||
|
||||
public String getClientSecurityToken() {
|
||||
return clientSecurityToken;
|
||||
}
|
||||
|
||||
public void setClientSecurityToken(String clientSecurityToken) {
|
||||
this.clientSecurityToken = clientSecurityToken;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@
|
|||
package com.cloud.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
|
@ -37,13 +35,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.GetVmVncTicketAnswer;
|
||||
import com.cloud.agent.api.GetVmVncTicketCommand;
|
||||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import org.apache.cloudstack.framework.security.keys.KeysManager;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.log4j.Logger;
|
||||
|
|
@ -51,31 +42,22 @@ import org.springframework.stereotype.Component;
|
|||
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
|
||||
|
||||
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.cloud.api.ApiServlet;
|
||||
import com.cloud.consoleproxy.ConsoleProxyManager;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.resource.ResourceState;
|
||||
import com.cloud.server.ManagementServer;
|
||||
import com.cloud.storage.GuestOSVO;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.ConstantTimeComparator;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.Ternary;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.vm.UserVmDetailVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineManager;
|
||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
|
||||
/**
|
||||
* Thumbnail access : /console?cmd=thumbnail&vm=xxx&w=xxx&h=xxx
|
||||
|
|
@ -98,11 +80,7 @@ public class ConsoleProxyServlet extends HttpServlet {
|
|||
@Inject
|
||||
EntityManager _entityMgr;
|
||||
@Inject
|
||||
UserVmDetailsDao _userVmDetailsDao;
|
||||
@Inject
|
||||
KeysManager _keysMgr;
|
||||
@Inject
|
||||
AgentManager agentManager;
|
||||
|
||||
static KeysManager s_keysMgr;
|
||||
|
||||
|
|
@ -198,8 +176,6 @@ public class ConsoleProxyServlet extends HttpServlet {
|
|||
|
||||
if (cmd.equalsIgnoreCase("thumbnail")) {
|
||||
handleThumbnailRequest(req, resp, vmId);
|
||||
} else if (cmd.equalsIgnoreCase("access")) {
|
||||
handleAccessRequest(req, resp, vmId);
|
||||
} else {
|
||||
handleAuthRequest(req, resp, vmId);
|
||||
}
|
||||
|
|
@ -260,61 +236,6 @@ public class ConsoleProxyServlet extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
private void handleAccessRequest(HttpServletRequest req, HttpServletResponse resp, long vmId) {
|
||||
VirtualMachine vm = _vmMgr.findById(vmId);
|
||||
if (vm == null) {
|
||||
s_logger.warn("VM " + vmId + " does not exist, sending blank response for console access request");
|
||||
sendResponse(resp, "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (vm.getHostId() == null) {
|
||||
s_logger.warn("VM " + vmId + " lost host info, sending blank response for console access request");
|
||||
sendResponse(resp, "");
|
||||
return;
|
||||
}
|
||||
|
||||
HostVO host = _ms.getHostBy(vm.getHostId());
|
||||
if (host == null) {
|
||||
s_logger.warn("VM " + vmId + "'s host does not exist, sending blank response for console access request");
|
||||
sendResponse(resp, "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Hypervisor.HypervisorType.LXC.equals(vm.getHypervisorType())){
|
||||
sendResponse(resp, "<html><body><p>Console access is not supported for LXC</p></body></html>");
|
||||
return;
|
||||
}
|
||||
|
||||
String rootUrl = _ms.getConsoleAccessUrlRoot(vmId);
|
||||
if (rootUrl == null) {
|
||||
sendResponse(resp, "<html><body><p>Console access will be ready in a few minutes. Please try it again later.</p></body></html>");
|
||||
return;
|
||||
}
|
||||
|
||||
String vmName = vm.getHostName();
|
||||
if (vm.getType() == VirtualMachine.Type.User) {
|
||||
UserVm userVm = _entityMgr.findById(UserVm.class, vmId);
|
||||
String displayName = userVm.getDisplayName();
|
||||
if (displayName != null && !displayName.isEmpty() && !displayName.equals(vmName)) {
|
||||
vmName += "(" + displayName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
InetAddress remoteAddress = null;
|
||||
try {
|
||||
remoteAddress = ApiServlet.getClientAddress(req);
|
||||
} catch (UnknownHostException e) {
|
||||
s_logger.warn("UnknownHostException when trying to lookup remote IP-Address. This should never happen. Blocking request.", e);
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("<html><title>").append(escapeHTML(vmName)).append("</title><frameset><frame src=\"").append(composeConsoleAccessUrl(rootUrl, vm, host, remoteAddress));
|
||||
sb.append("\"></frame></frameset></html>");
|
||||
s_logger.debug("the console url is :: " + sb.toString());
|
||||
sendResponse(resp, sb.toString());
|
||||
}
|
||||
|
||||
private void handleAuthRequest(HttpServletRequest req, HttpServletResponse resp, long vmId) {
|
||||
|
||||
// TODO authentication channel between console proxy VM and management server needs to be secured,
|
||||
|
|
@ -436,145 +357,6 @@ public class ConsoleProxyServlet extends HttpServlet {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL to establish a VNC over websocket connection
|
||||
*/
|
||||
private void setWebsocketUrl(VirtualMachine vm, ConsoleProxyClientParam param) {
|
||||
String ticket = acquireVncTicketForVmwareVm(vm);
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
s_logger.error("Could not obtain VNC ticket for VM " + vm.getInstanceName());
|
||||
return;
|
||||
}
|
||||
String wsUrl = composeWebsocketUrlForVmwareVm(ticket, param);
|
||||
param.setWebsocketUrl(wsUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format expected: wss://<ESXi_HOST_IP>:443/ticket/<TICKET_ID>
|
||||
*/
|
||||
private String composeWebsocketUrlForVmwareVm(String ticket, ConsoleProxyClientParam param) {
|
||||
param.setClientHostPort(443);
|
||||
return String.format("wss://%s:%s/ticket/%s", param.getClientHostAddress(), param.getClientHostPort(), ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a ticket to be used for console proxy as described in 'Removal of VNC Server from ESXi' on:
|
||||
* https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-70-release-notes.html
|
||||
*/
|
||||
private String acquireVncTicketForVmwareVm(VirtualMachine vm) {
|
||||
try {
|
||||
s_logger.info("Acquiring VNC ticket for VM = " + vm.getHostName());
|
||||
GetVmVncTicketCommand cmd = new GetVmVncTicketCommand(vm.getInstanceName());
|
||||
Answer answer = agentManager.send(vm.getHostId(), cmd);
|
||||
GetVmVncTicketAnswer ans = (GetVmVncTicketAnswer) answer;
|
||||
if (!ans.getResult()) {
|
||||
s_logger.info("VNC ticket could not be acquired correctly: " + ans.getDetails());
|
||||
}
|
||||
return ans.getTicket();
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
s_logger.error("Error acquiring ticket", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo, InetAddress addr) {
|
||||
StringBuffer sb = new StringBuffer(rootUrl);
|
||||
String host = hostVo.getPrivateIpAddress();
|
||||
|
||||
Pair<String, Integer> portInfo = null;
|
||||
if (hostVo.getHypervisorType() == Hypervisor.HypervisorType.KVM &&
|
||||
(hostVo.getResourceState().equals(ResourceState.ErrorInMaintenance) ||
|
||||
hostVo.getResourceState().equals(ResourceState.ErrorInPrepareForMaintenance))) {
|
||||
UserVmDetailVO detailAddress = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KVM_VNC_ADDRESS);
|
||||
UserVmDetailVO detailPort = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KVM_VNC_PORT);
|
||||
if (detailAddress != null && detailPort != null) {
|
||||
portInfo = new Pair<>(detailAddress.getValue(), Integer.valueOf(detailPort.getValue()));
|
||||
} else {
|
||||
s_logger.warn("KVM Host in ErrorInMaintenance/ErrorInPrepareForMaintenance but " +
|
||||
"no VNC Address/Port was available. Falling back to default one from MS.");
|
||||
}
|
||||
}
|
||||
|
||||
if (portInfo == null) {
|
||||
portInfo = _ms.getVncPort(vm);
|
||||
}
|
||||
|
||||
if (s_logger.isDebugEnabled())
|
||||
s_logger.debug("Port info " + portInfo.first());
|
||||
|
||||
Ternary<String, String, String> parsedHostInfo = parseHostInfo(portInfo.first());
|
||||
|
||||
int port = -1;
|
||||
if (portInfo.second() == -9) {
|
||||
//for hyperv
|
||||
port = Integer.parseInt(_ms.findDetail(hostVo.getId(), "rdp.server.port").getValue());
|
||||
} else {
|
||||
port = portInfo.second();
|
||||
}
|
||||
|
||||
String sid = vm.getVncPassword();
|
||||
UserVmDetailVO details = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KEYBOARD);
|
||||
|
||||
String tag = vm.getUuid();
|
||||
|
||||
String ticket = genAccessTicket(parsedHostInfo.first(), String.valueOf(port), sid, tag);
|
||||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword());
|
||||
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
|
||||
param.setClientHostAddress(parsedHostInfo.first());
|
||||
param.setClientHostPort(port);
|
||||
param.setClientHostPassword(sid);
|
||||
param.setClientTag(tag);
|
||||
param.setTicket(ticket);
|
||||
param.setSourceIP(addr != null ? addr.getHostAddress(): null);
|
||||
|
||||
if (requiresVncOverWebSocketConnection(vm, hostVo)) {
|
||||
setWebsocketUrl(vm, param);
|
||||
}
|
||||
|
||||
if (details != null) {
|
||||
param.setLocale(details.getValue());
|
||||
}
|
||||
|
||||
if (portInfo.second() == -9) {
|
||||
//For Hyperv Clinet Host Address will send Instance id
|
||||
param.setHypervHost(host);
|
||||
param.setUsername(_ms.findDetail(hostVo.getId(), "username").getValue());
|
||||
param.setPassword(_ms.findDetail(hostVo.getId(), "password").getValue());
|
||||
}
|
||||
if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) {
|
||||
param.setClientTunnelUrl(parsedHostInfo.second());
|
||||
param.setClientTunnelSession(parsedHostInfo.third());
|
||||
}
|
||||
|
||||
if (param.getHypervHost() != null || !ConsoleProxyManager.NoVncConsoleDefault.value()) {
|
||||
sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
|
||||
} else {
|
||||
sb.append("/resource/noVNC/vnc.html")
|
||||
.append("?autoconnect=true")
|
||||
.append("&port=" + ConsoleProxyManager.DEFAULT_NOVNC_PORT)
|
||||
.append("&token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
|
||||
}
|
||||
|
||||
// for console access, we need guest OS type to help implement keyboard
|
||||
long guestOs = vm.getGuestOSId();
|
||||
GuestOSVO guestOsVo = _ms.getGuestOs(guestOs);
|
||||
if (guestOsVo.getCategoryId() == 6)
|
||||
sb.append("&guest=windows");
|
||||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Compose console url: " + sb.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Since VMware 7.0 VNC servers are deprecated, it uses a ticket to create a VNC over websocket connection
|
||||
* Check: https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-70-release-notes.html
|
||||
*/
|
||||
private boolean requiresVncOverWebSocketConnection(VirtualMachine vm, HostVO hostVo) {
|
||||
return vm.getHypervisorType() == Hypervisor.HypervisorType.VMware && hostVo.getHypervisorVersion().compareTo("7.0") >= 0;
|
||||
}
|
||||
|
||||
public static String genAccessTicket(String host, String port, String sid, String tag) {
|
||||
return genAccessTicket(host, port, sid, tag, new Date());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@
|
|||
value="#{consoleProxyAllocatorsRegistry.registered}" />
|
||||
</bean>
|
||||
|
||||
<bean id="consoleAccessManagerImpl" class="com.cloud.consoleproxy.ConsoleAccessManagerImpl" />
|
||||
|
||||
<bean id="securityGroupManagerImpl2" class="com.cloud.network.security.SecurityGroupManagerImpl2" />
|
||||
|
||||
<bean id="ipv6AddressManagerImpl" class="com.cloud.network.Ipv6AddressManagerImpl" />
|
||||
|
|
|
|||
|
|
@ -165,7 +165,8 @@ public class ConsoleProxy {
|
|||
}
|
||||
}
|
||||
|
||||
public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(ConsoleProxyClientParam param, boolean reauthentication) {
|
||||
public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(ConsoleProxyClientParam param,
|
||||
boolean reauthentication, Session session) {
|
||||
|
||||
ConsoleProxyAuthenticationResult authResult = new ConsoleProxyAuthenticationResult();
|
||||
authResult.setSuccess(true);
|
||||
|
|
@ -173,6 +174,20 @@ public class ConsoleProxy {
|
|||
authResult.setHost(param.getClientHostAddress());
|
||||
authResult.setPort(param.getClientHostPort());
|
||||
|
||||
if (session != null && param.getClientSecurityToken() != null) {
|
||||
String clientSecurityHeader = param.getClientSecurityHeader();
|
||||
String headerValue = session.getUpgradeRequest().getHeader(clientSecurityHeader);
|
||||
if (!param.getClientSecurityToken().equals(headerValue)) {
|
||||
s_logger.error("Security token found but not matching the expected value for this session");
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug(String.format("Expected value for header %s was %s but found %s",
|
||||
clientSecurityHeader, param.getClientSecurityToken(), headerValue));
|
||||
}
|
||||
authResult.setSuccess(false);
|
||||
return authResult;
|
||||
}
|
||||
}
|
||||
|
||||
String websocketUrl = param.getWebsocketUrl();
|
||||
if (StringUtils.isNotBlank(websocketUrl)) {
|
||||
return authResult;
|
||||
|
|
@ -187,7 +202,7 @@ public class ConsoleProxy {
|
|||
try {
|
||||
result =
|
||||
authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), param.getClientTag(),
|
||||
param.getClientHostPassword(), param.getTicket(), new Boolean(reauthentication));
|
||||
param.getClientHostPassword(), param.getTicket(), reauthentication, param.getSessionUuid());
|
||||
} catch (IllegalAccessException e) {
|
||||
s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e);
|
||||
authResult.setSuccess(false);
|
||||
|
|
@ -259,7 +274,8 @@ public class ConsoleProxy {
|
|||
try {
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
Class<?> contextClazz = loader.loadClass("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource");
|
||||
authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class, Boolean.class);
|
||||
authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class,
|
||||
String.class, String.class, String.class, Boolean.class, String.class);
|
||||
reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class);
|
||||
ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class);
|
||||
} catch (SecurityException e) {
|
||||
|
|
@ -449,7 +465,7 @@ public class ConsoleProxy {
|
|||
synchronized (connectionMap) {
|
||||
ConsoleProxyClient viewer = connectionMap.get(clientKey);
|
||||
if (viewer == null || viewer.getClass() == ConsoleProxyNoVncClient.class) {
|
||||
authenticationExternally(param);
|
||||
authenticationExternally(param, null);
|
||||
viewer = getClient(param);
|
||||
viewer.initClient(param);
|
||||
|
||||
|
|
@ -470,7 +486,7 @@ public class ConsoleProxy {
|
|||
|
||||
if (!viewer.isFrontEndAlive()) {
|
||||
|
||||
authenticationExternally(param);
|
||||
authenticationExternally(param, null);
|
||||
viewer.initClient(param);
|
||||
reportLoadChange = true;
|
||||
}
|
||||
|
|
@ -512,8 +528,8 @@ public class ConsoleProxy {
|
|||
}
|
||||
}
|
||||
|
||||
public static void authenticationExternally(ConsoleProxyClientParam param) throws AuthenticationException {
|
||||
ConsoleProxyAuthenticationResult authResult = authenticateConsoleAccess(param, false);
|
||||
public static void authenticationExternally(ConsoleProxyClientParam param, Session session) throws AuthenticationException {
|
||||
ConsoleProxyAuthenticationResult authResult = authenticateConsoleAccess(param, false, session);
|
||||
|
||||
if (authResult == null || !authResult.isSuccess()) {
|
||||
s_logger.warn("External authenticator failed authencation request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword());
|
||||
|
|
@ -523,7 +539,7 @@ public class ConsoleProxy {
|
|||
}
|
||||
|
||||
public static ConsoleProxyAuthenticationResult reAuthenticationExternally(ConsoleProxyClientParam param) {
|
||||
return authenticateConsoleAccess(param, true);
|
||||
return authenticateConsoleAccess(param, true, null);
|
||||
}
|
||||
|
||||
public static String getEncryptorPassword() {
|
||||
|
|
@ -552,7 +568,7 @@ public class ConsoleProxy {
|
|||
synchronized (connectionMap) {
|
||||
ConsoleProxyClient viewer = connectionMap.get(clientKey);
|
||||
if (viewer == null || viewer.getClass() != ConsoleProxyNoVncClient.class) {
|
||||
authenticationExternally(param);
|
||||
authenticationExternally(param, session);
|
||||
viewer = new ConsoleProxyNoVncClient(session);
|
||||
viewer.initClient(param);
|
||||
|
||||
|
|
@ -564,7 +580,7 @@ public class ConsoleProxy {
|
|||
throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid");
|
||||
|
||||
try {
|
||||
authenticationExternally(param);
|
||||
authenticationExternally(param, session);
|
||||
} catch (Exception e) {
|
||||
s_logger.error("Authencation failed for param: " + param);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -78,4 +78,6 @@ public interface ConsoleProxyClient {
|
|||
void initClient(ConsoleProxyClientParam param);
|
||||
|
||||
void closeClient();
|
||||
|
||||
String getSessionUuid();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
|
|||
protected boolean framebufferResized = false;
|
||||
protected int resizedFramebufferWidth;
|
||||
protected int resizedFramebufferHeight;
|
||||
protected String sessionUuid;
|
||||
|
||||
public ConsoleProxyClientBase() {
|
||||
tracker = new TileTracker();
|
||||
|
|
@ -422,4 +423,9 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
|
|||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword());
|
||||
this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSessionUuid() {
|
||||
return sessionUuid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ public class ConsoleProxyClientParam {
|
|||
|
||||
private String sourceIP;
|
||||
|
||||
private String sessionUuid;
|
||||
private String clientSecurityHeader;
|
||||
private String clientSecurityToken;
|
||||
|
||||
public ConsoleProxyClientParam() {
|
||||
clientHostPort = 0;
|
||||
}
|
||||
|
|
@ -162,4 +166,28 @@ public class ConsoleProxyClientParam {
|
|||
public void setWebsocketUrl(String websocketUrl) {
|
||||
this.websocketUrl = websocketUrl;
|
||||
}
|
||||
|
||||
public String getSessionUuid() {
|
||||
return sessionUuid;
|
||||
}
|
||||
|
||||
public void setSessionUuid(String sessionUuid) {
|
||||
this.sessionUuid = sessionUuid;
|
||||
}
|
||||
|
||||
public String getClientSecurityHeader() {
|
||||
return clientSecurityHeader;
|
||||
}
|
||||
|
||||
public void setClientSecurityHeader(String clientSecurityHeader) {
|
||||
this.clientSecurityHeader = clientSecurityHeader;
|
||||
}
|
||||
|
||||
public String getClientSecurityToken() {
|
||||
return clientSecurityToken;
|
||||
}
|
||||
|
||||
public void setClientSecurityToken(String clientSecurityToken) {
|
||||
this.clientSecurityToken = clientSecurityToken;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.io.OutputStreamWriter;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
|
@ -31,10 +32,16 @@ import com.google.gson.GsonBuilder;
|
|||
public class ConsoleProxyClientStatsCollector {
|
||||
|
||||
ArrayList<ConsoleProxyConnection> connections;
|
||||
ArrayList<String> removedSessions;
|
||||
|
||||
public ConsoleProxyClientStatsCollector() {
|
||||
}
|
||||
|
||||
public void setRemovedSessions(List<String> removed) {
|
||||
removedSessions = new ArrayList<>();
|
||||
removedSessions.addAll(removed);
|
||||
}
|
||||
|
||||
public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
|
||||
setConnections(connMap);
|
||||
}
|
||||
|
|
@ -67,6 +74,7 @@ public class ConsoleProxyClientStatsCollector {
|
|||
conn.tag = client.getClientTag();
|
||||
conn.createTime = client.getClientCreateTime();
|
||||
conn.lastUsedTime = client.getClientLastFrontEndActivityTime();
|
||||
conn.sessionUuid = client.getSessionUuid();
|
||||
conns.add(conn);
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +89,7 @@ public class ConsoleProxyClientStatsCollector {
|
|||
public String tag;
|
||||
public long createTime;
|
||||
public long lastUsedTime;
|
||||
public String sessionUuid;
|
||||
|
||||
public ConsoleProxyConnection() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -67,9 +69,12 @@ public class ConsoleProxyGCThread extends Thread {
|
|||
|
||||
boolean bReportLoad = false;
|
||||
long lastReportTick = System.currentTimeMillis();
|
||||
List<String> removedSessions = new ArrayList<>();
|
||||
|
||||
while (true) {
|
||||
cleanupLogging();
|
||||
bReportLoad = false;
|
||||
removedSessions.clear();
|
||||
|
||||
if (s_logger.isDebugEnabled())
|
||||
s_logger.debug("connMap=" + connMap);
|
||||
|
|
@ -89,6 +94,7 @@ public class ConsoleProxyGCThread extends Thread {
|
|||
}
|
||||
|
||||
synchronized (connMap) {
|
||||
removedSessions.add(client.getSessionUuid());
|
||||
connMap.remove(key);
|
||||
bReportLoad = true;
|
||||
}
|
||||
|
|
@ -100,7 +106,9 @@ public class ConsoleProxyGCThread extends Thread {
|
|||
|
||||
if (bReportLoad || System.currentTimeMillis() - lastReportTick > 5000) {
|
||||
// report load changes
|
||||
String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport();
|
||||
ConsoleProxyClientStatsCollector collector = new ConsoleProxyClientStatsCollector(connMap);
|
||||
collector.setRemovedSessions(removedSessions);
|
||||
String loadInfo = collector.getStatsReport();
|
||||
ConsoleProxy.reportLoadInfo(loadInfo);
|
||||
lastReportTick = System.currentTimeMillis();
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,15 @@ public class ConsoleProxyHttpHandlerHelper {
|
|||
if (param.getWebsocketUrl() != null) {
|
||||
map.put("websocketUrl", param.getWebsocketUrl());
|
||||
}
|
||||
if (param.getSessionUuid() != null) {
|
||||
map.put("sessionUuid", param.getSessionUuid());
|
||||
}
|
||||
if (param.getClientSecurityHeader() != null) {
|
||||
map.put("clientSecurityHeader", param.getClientSecurityHeader());
|
||||
}
|
||||
if (param.getClientSecurityToken() != null) {
|
||||
map.put("clientSecurityToken", param.getClientSecurityToken());
|
||||
}
|
||||
} else {
|
||||
s_logger.error("Unable to decode token");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
|
|||
String password = queryMap.get("password");
|
||||
String sourceIP = queryMap.get("sourceIP");
|
||||
String websocketUrl = queryMap.get("websocketUrl");
|
||||
String sessionUuid = queryMap.get("sessionUuid");
|
||||
String clientSecurityToken = queryMap.get("clientSecurityToken");
|
||||
String clientSecurityHeader = queryMap.get("clientSecurityHeader");
|
||||
|
||||
if (tag == null)
|
||||
tag = "";
|
||||
|
|
@ -133,6 +136,9 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
|
|||
param.setUsername(username);
|
||||
param.setPassword(password);
|
||||
param.setWebsocketUrl(websocketUrl);
|
||||
param.setSessionUuid(sessionUuid);
|
||||
param.setClientSecurityHeader(clientSecurityHeader);
|
||||
param.setClientSecurityToken(clientSecurityToken);
|
||||
viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session);
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Failed to create viewer due to " + e.getMessage(), e);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import com.cloud.consoleproxy.util.Logger;
|
||||
|
|
@ -32,17 +34,30 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|||
public class ConsoleProxyNoVNCServer {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(ConsoleProxyNoVNCServer.class);
|
||||
private static final int wsPort = 8080;
|
||||
private static int wsPort = 8080;
|
||||
private static final String vncConfFileLocation = "/root/vncport";
|
||||
|
||||
private Server server;
|
||||
|
||||
private void init() {
|
||||
try {
|
||||
String portStr = Files.readString(Path.of(vncConfFileLocation)).trim();
|
||||
wsPort = Integer.parseInt(portStr);
|
||||
s_logger.info("Setting port to: " + wsPort);
|
||||
} catch (Exception e) {
|
||||
s_logger.error("Error loading properties from " + vncConfFileLocation, e);
|
||||
}
|
||||
}
|
||||
|
||||
public ConsoleProxyNoVNCServer() {
|
||||
init();
|
||||
this.server = new Server(wsPort);
|
||||
ConsoleProxyNoVNCHandler handler = new ConsoleProxyNoVNCHandler();
|
||||
this.server.setHandler(handler);
|
||||
}
|
||||
|
||||
public ConsoleProxyNoVNCServer(byte[] ksBits, String ksPassword) {
|
||||
init();
|
||||
this.server = new Server();
|
||||
ConsoleProxyNoVNCHandler handler = new ConsoleProxyNoVNCHandler();
|
||||
this.server.setHandler(handler);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
|||
private boolean connectionAlive;
|
||||
|
||||
private ConsoleProxyClientParam clientParam;
|
||||
private String sessionUuid;
|
||||
|
||||
public ConsoleProxyNoVncClient(Session session) {
|
||||
this.session = session;
|
||||
|
|
@ -89,6 +90,7 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
|||
setClientParam(param);
|
||||
client = new NoVncClient();
|
||||
connectionAlive = true;
|
||||
this.sessionUuid = param.getSessionUuid();
|
||||
|
||||
updateFrontEndActivityTime();
|
||||
Thread worker = new Thread(new Runnable() {
|
||||
|
|
@ -192,6 +194,11 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
|||
ConsoleProxy.removeViewer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSessionUuid() {
|
||||
return sessionUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientId() {
|
||||
return this.clientId;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ setup_console_proxy() {
|
|||
setup_sshd $ETH0_IP "eth0"
|
||||
fi
|
||||
|
||||
vncport=`cat /root/vncport`
|
||||
log_it "vncport read: ${vncport}"
|
||||
sed -i 's/8080/${vncport}/' /etc/iptables/rules.v4
|
||||
log_it "vnc port ${vncport} rule applied"
|
||||
|
||||
disable_rpfilter
|
||||
enable_fwding 0
|
||||
enable_irqbalance 0
|
||||
|
|
|
|||
|
|
@ -201,7 +201,8 @@ known_categories = {
|
|||
'UnmanagedInstance': 'Virtual Machine',
|
||||
'Rolling': 'Rolling Maintenance',
|
||||
'importVsphereStoragePolicies' : 'vSphere storage policies',
|
||||
'listVsphereStoragePolicies' : 'vSphere storage policies'
|
||||
'listVsphereStoragePolicies' : 'vSphere storage policies',
|
||||
'ConsoleEndpoint': 'Console Endpoint'
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@
|
|||
|
||||
<template>
|
||||
<a
|
||||
v-if="['vm', 'systemvm', 'router', 'ilbvm'].includes($route.meta.name) && 'updateVirtualMachine' in $store.getters.apis"
|
||||
:href="server + '/console?cmd=access&vm=' + resource.id"
|
||||
target="_blank">
|
||||
v-if="['vm', 'systemvm', 'router', 'ilbvm'].includes($route.meta.name) && 'updateVirtualMachine' in $store.getters.apis && 'createConsoleEndpoint' in $store.getters.apis"
|
||||
@click="consoleUrl">
|
||||
<a-button style="margin-left: 5px" shape="circle" type="dashed" :size="size" :disabled="['Stopped', 'Error', 'Destroyed'].includes(resource.state)" >
|
||||
<a-icon type="code" />
|
||||
</a-button>
|
||||
|
|
@ -29,6 +28,7 @@
|
|||
<script>
|
||||
import Vue from 'vue'
|
||||
import { SERVER_MANAGER } from '@/store/mutation-types'
|
||||
import { api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'Console',
|
||||
|
|
@ -42,6 +42,19 @@ export default {
|
|||
default: 'small'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
url: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
consoleUrl () {
|
||||
api('createConsoleEndpoint', { virtualmachineid: this.resource.id }).then(json => {
|
||||
this.url = (json && json.createconsoleendpointresponse) ? json.createconsoleendpointresponse.consoleendpoint.url : '#/exception/404'
|
||||
window.open(this.url, '_blank')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
server () {
|
||||
if (!this.$config.multipleServer) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// 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 org.apache.cloudstack.utils.consoleproxy;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
public class ConsoleAccessUtils {
|
||||
|
||||
public static final Logger s_logger = Logger.getLogger(ConsoleAccessUtils.class.getName());
|
||||
|
||||
public static String CLIENT_SECURITY_HEADER_PARAM_KEY = "client-security-token";
|
||||
public static String CLIENT_INET_ADDRESS_KEY = "client-inet-address";
|
||||
}
|
||||
Loading…
Reference in New Issue