Add support for Xen Secure-Console Intergration in console proxy

This commit is contained in:
Kelven Yang 2012-04-09 15:38:58 -07:00
parent 12ec170cc0
commit d76207a09c
10 changed files with 402 additions and 49 deletions

View File

@ -151,31 +151,36 @@ public class ConsoleProxy {
}
}
public static boolean authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket) {
public static boolean authenticateConsoleAccess(ConsoleProxyClientParam param) {
if(standaloneStart)
return true;
if(authMethod != null) {
Object result;
try {
result = authMethod.invoke(ConsoleProxy.context, host, port, vmId, sid, ticket);
result = authMethod.invoke(ConsoleProxy.context,
param.getClientHostAddress(),
String.valueOf(param.getClientHostPort()),
param.getClientTag(),
param.getClientHostPassword(),
param.getTicket());
} catch (IllegalAccessException e) {
s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + vmId, e);
s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e);
return false;
} catch (InvocationTargetException e) {
s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + vmId, e);
s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + param.getClientTag(), e);
return false;
}
if(result != null && result instanceof Boolean) {
return ((Boolean)result).booleanValue();
} else {
s_logger.error("Invalid authentication return object " + result + " for vm: " + vmId + ", decline the access");
s_logger.error("Invalid authentication return object " + result + " for vm: " + param.getClientTag() + ", decline the access");
return false;
}
} else {
s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + vmId);
s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + param.getClientTag());
return true;
}
}
@ -343,29 +348,29 @@ public class ConsoleProxy {
start(conf);
}
public static ConsoleProxyClient getVncViewer(String host, int port, String sid, String tag, String ticket) throws Exception {
public static ConsoleProxyClient getVncViewer(ConsoleProxyClientParam param) throws Exception {
ConsoleProxyClient viewer = null;
boolean reportLoadChange = false;
String clientKey = param.getClientMapKey();
synchronized (connectionMap) {
viewer = connectionMap.get(host + ":" + port);
viewer = connectionMap.get(clientKey);
if (viewer == null) {
viewer = new ConsoleProxyVncClient();
viewer.initClient(host, port, sid, tag, ticket);
connectionMap.put(host + ":" + port, viewer);
viewer.initClient(param);
connectionMap.put(clientKey, viewer);
s_logger.info("Added viewer object " + viewer);
reportLoadChange = true;
} else if (!viewer.isFrontEndAlive()) {
s_logger.info("The rfb thread died, reinitializing the viewer " +
viewer);
viewer.initClient(host, port, sid, tag, ticket);
s_logger.info("The rfb thread died, reinitializing the viewer " + viewer);
viewer.initClient(param);
reportLoadChange = true;
} else if (!sid.equals(viewer.getClientHostPassword())) {
} else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword()
+ ", sid in request: " + sid);
viewer.initClient(host, port, sid, tag, ticket);
+ ", sid in request: " + param.getClientHostPassword());
viewer.initClient(param);
reportLoadChange = true;
}
@ -382,32 +387,32 @@ public class ConsoleProxy {
return viewer;
}
public static ConsoleProxyClient getAjaxVncViewer(String host, int port, String sid, String tag,
String ticket, String ajaxSession) throws Exception {
public static ConsoleProxyClient getAjaxVncViewer(ConsoleProxyClientParam param, String ajaxSession) throws Exception {
boolean reportLoadChange = false;
String clientKey = param.getClientMapKey();
synchronized (connectionMap) {
ConsoleProxyClient viewer = connectionMap.get(host + ":" + port);
ConsoleProxyClient viewer = connectionMap.get(clientKey);
if (viewer == null) {
viewer = new ConsoleProxyVncClient();
viewer.initClient(host, port, sid, tag, ticket);
viewer.initClient(param);
connectionMap.put(host + ":" + port, viewer);
connectionMap.put(clientKey, viewer);
s_logger.info("Added viewer object " + viewer);
reportLoadChange = true;
} else if (!viewer.isFrontEndAlive()) {
s_logger.info("The rfb thread died, reinitializing the viewer " +
viewer);
viewer.initClient(host, port, sid, tag, ticket);
viewer.initClient(param);
reportLoadChange = true;
} else if (!sid.equals(viewer.getClientHostPassword())) {
} else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: "
+ viewer.getClientHostPassword() + ", sid in request: " + sid);
viewer.initClient(host, port, sid, tag, ticket);
+ viewer.getClientHostPassword() + ", sid in request: " + param.getClientHostPassword());
viewer.initClient(param);
reportLoadChange = true;
} else {
if(ajaxSession == null || ajaxSession.isEmpty())
authenticationExternally(host, String.valueOf(port), tag, sid, ticket);
authenticationExternally(param);
}
if(reportLoadChange) {
@ -436,11 +441,11 @@ public class ConsoleProxy {
return new ConsoleProxyClientStatsCollector(connectionMap);
}
public static void authenticationExternally(String host, String port, String tag, String sid, String ticket) throws AuthenticationException {
if(!authenticateConsoleAccess(host, port, tag, sid, ticket)) {
s_logger.warn("External authenticator failed authencation request for vm " + tag + " with sid " + sid);
public static void authenticationExternally(ConsoleProxyClientParam param) throws AuthenticationException {
if(!authenticateConsoleAccess(param)) {
s_logger.warn("External authenticator failed authencation request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword());
throw new AuthenticationException("External authenticator failed request for vm " + tag + " with sid " + sid);
throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword());
}
}

View File

@ -110,7 +110,14 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
ConsoleProxyClient viewer = null;
try {
viewer = ConsoleProxy.getAjaxVncViewer(host, port, sid, tag, ticket, ajaxSessionIdStr);
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(host);
param.setClientHostPort(port);
param.setClientHostPassword(sid);
param.setClientTag(tag);
param.setTicket(ticket);
viewer = ConsoleProxy.getAjaxVncViewer(param, ajaxSessionIdStr);
} catch(Exception e) {
s_logger.warn("Failed to create viewer due to " + e.getMessage(), e);

View File

@ -85,7 +85,13 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
throw new IllegalArgumentException(e);
}
ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket);
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(host);
param.setClientHostPort(port);
param.setClientHostPassword(sid);
param.setClientTag(tag);
param.setTicket(ticket);
ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(param);
byte[] img = viewer.getAjaxImageCache().getImage(key);
if(img != null) {
Headers hds = t.getResponseHeaders();

View File

@ -61,7 +61,6 @@ public interface ConsoleProxyClient {
//
// Setup/house-keeping
//
void initClient(String clientHostAddress, int clientHostPort,
String clientHostPassword, String clientTag, String ticket);
void initClient(ConsoleProxyClientParam param);
void closeClient();
}

View File

@ -124,8 +124,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
}
@Override
public abstract void initClient(String clientHostAddress, int clientHostPort,
String clientHostPassword, String clientTag, String ticket);
public abstract void initClient(ConsoleProxyClientParam param);
@Override
public abstract void closeClient();

View File

@ -0,0 +1,75 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by 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.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.consoleproxy;
/**
*
* @author Kelven Yang
* Data object to store parameter info needed by client to connect to its host
*/
public class ConsoleProxyClientParam {
private String clientHostAddress;
private int clientHostPort;
private String clientHostPassword;
private String clientTag;
private String ticket;
public ConsoleProxyClientParam() {
clientHostPort = 0;
}
public String getClientHostAddress() {
return clientHostAddress;
}
public void setClientHostAddress(String clientHostAddress) {
this.clientHostAddress = clientHostAddress;
}
public int getClientHostPort() {
return clientHostPort;
}
public void setClientHostPort(int clientHostPort) {
this.clientHostPort = clientHostPort;
}
public String getClientHostPassword() {
return clientHostPassword;
}
public void setClientHostPassword(String clientHostPassword) {
this.clientHostPassword = clientHostPassword;
}
public String getClientTag() {
return clientTag;
}
public void setClientTag(String clientTag) {
this.clientTag = clientTag;
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public String getClientMapKey() {
return clientHostAddress + ":" + clientHostPort;
}
}

View File

@ -121,7 +121,13 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler {
throw new IllegalArgumentException(e);
}
ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket);
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(host);
param.setClientHostPort(port);
param.setClientHostPassword(sid);
param.setClientTag(tag);
param.setTicket(ticket);
ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(param);
if (!viewer.isHostConnected()) {
// use generated image instead of static

View File

@ -52,13 +52,12 @@ public class ConsoleProxyVncClient extends ConsoleProxyClientBase {
}
@Override
public void initClient(final String clientHostAddress, final int clientHostPort,
final String clientHostPassword, final String clientTag, final String ticket) {
this.host = clientHostAddress;
this.port = clientHostPort;
this.passwordParam = clientHostPassword;
this.tag = clientTag;
this.ticket = ticket;
public void initClient(ConsoleProxyClientParam param) {
this.host = param.getClientHostAddress();
this.port = param.getClientHostPort();
this.passwordParam = param.getClientHostPassword();
this.tag = param.getClientTag();
this.ticket = param.getTicket();
client = new VncClient(this);
worker = new Thread(new Runnable() {
@ -66,7 +65,7 @@ public class ConsoleProxyVncClient extends ConsoleProxyClientBase {
long startTick = System.currentTimeMillis();
while(System.currentTimeMillis() - startTick < 7000) {
try {
client.connectTo(clientHostAddress, clientHostPort, clientHostPassword);
client.connectTo(host, port, passwordParam);
} catch (UnknownHostException e) {
s_logger.error("Unexpected exception: ", e);
} catch (IOException e) {

View File

@ -0,0 +1,245 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by 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.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.consoleproxy.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
//
// This file is originally from XenConsole with modifications
//
/**
* Send an HTTP CONNECT or PUT request to a XenAPI host with a Session ID,
* return the connected socket and the Task ID. Used for tunnelling VNC
* connections and import/export operations.
*/
public final class RawHTTP {
private static final Logger s_logger = Logger.getLogger(RawHTTP.class);
private static final Pattern END_PATTERN = Pattern.compile("^\r\n$");
private static final Pattern HEADER_PATTERN = Pattern
.compile("^([A-Z_a-z0-9-]+):\\s*(.*)\r\n$");
private static final Pattern HTTP_PATTERN = Pattern
.compile("^HTTP/\\d+\\.\\d+ (\\d*) (.*)\r\n$");
/**
* @uml.property name="command"
*/
private final String command;
/**
* @uml.property name="host"
*/
private final String host;
/**
* @uml.property name="port"
*/
private final int port;
/**
* @uml.property name="path"
*/
private final String path;
/**
* @uml.property name="session"
*/
private final String session;
/**
* @uml.property name="useSSL"
*/
private final boolean useSSL;
/**
* @uml.property name="responseHeaders"
* @uml.associationEnd qualifier="group:java.lang.String java.lang.String"
*/
private final Map<String, String> responseHeaders = new HashMap<String, String>();
/**
* @uml.property name="ic"
*/
private InputStream ic;
/**
* @uml.property name="oc"
*/
private OutputStream oc;
/**
* @uml.property name="s"
*/
private Socket s;
public InputStream getInputStream() {
return ic;
}
public OutputStream getOutputStream() {
return oc;
}
public Socket getSocket() {
return s;
}
public RawHTTP(String command, String host, int port, String path,
String session, boolean useSSL) {
this.command = command;
this.host = host;
this.port = port;
this.path = path;
this.session = session;
this.useSSL = useSSL;
}
private static final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
private Socket _getSocket() throws IOException {
if (useSSL) {
SSLContext context = getClientSSLContext();
if(context == null)
throw new IOException("Unable to setup SSL context");
SSLSocket ssl = null;
try {
context.init(null, trustAllCerts, new SecureRandom());
SocketFactory factory = context.getSocketFactory();
ssl = (SSLSocket) factory.createSocket(host, port);
/* ssl.setSSLParameters(context.getDefaultSSLParameters()); */
} catch (IOException e) {
s_logger.error("IOException: " + e.getMessage(), e);
throw e;
} catch (KeyManagementException e) {
s_logger.error("KeyManagementException: " + e.getMessage(), e);
}
return ssl;
} else {
return new Socket(host, port);
}
}
public Socket connect() throws IOException {
String[] headers = makeHeaders();
s = _getSocket();
try {
oc = s.getOutputStream();
for (String header : headers) {
oc.write(header.getBytes());
oc.write("\r\n".getBytes());
}
oc.flush();
ic = s.getInputStream();
while (true) {
String line = readline(ic);
Matcher m = END_PATTERN.matcher(line);
if (m.matches()) {
return s;
}
m = HEADER_PATTERN.matcher(line);
if (m.matches()) {
responseHeaders.put(m.group(1), m.group(2));
continue;
}
m = HTTP_PATTERN.matcher(line);
if (m.matches()) {
String status_code = m.group(1);
String reason_phrase = m.group(2);
if (!"200".equals(status_code)) {
throw new IOException("HTTP status " + status_code
+ " " + reason_phrase);
}
} else {
throw new IOException("Unknown HTTP line " + line);
}
}
} catch (IOException exn) {
s.close();
throw exn;
} catch (RuntimeException exn) {
s.close();
throw exn;
}
}
public Map<String, String> getResponseHeaders() {
return responseHeaders;
}
private String[] makeHeaders() {
String[] headers = { String.format("%s %s HTTP/1.0", command, path),
String.format("Host: %s", host),
String.format("Cookie: session_id=%s", session), "" };
return headers;
}
private static String readline(InputStream ic) throws IOException {
String result = "";
while (true) {
try {
int c = ic.read();
if (c == -1) {
return result;
}
result = result + (char) c;
if (c == 0x0a /* LF */) {
return result;
}
} catch (IOException e) {
ic.close();
throw e;
}
}
}
private SSLContext getClientSSLContext() {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL", "SunJSSE");
} catch (NoSuchAlgorithmException e) {
s_logger.error("Unexpected exception ", e);
} catch (NoSuchProviderException e) {
s_logger.error("Unexpected exception ", e);
}
return sslContext;
}
}

View File

@ -30,6 +30,7 @@ import javax.crypto.spec.DESKeySpec;
import com.cloud.consoleproxy.ConsoleProxyClientListener;
import com.cloud.consoleproxy.util.Logger;
import com.cloud.consoleproxy.util.RawHTTP;
import com.cloud.consoleproxy.vnc.packet.client.KeyboardEventPacket;
import com.cloud.consoleproxy.vnc.packet.client.MouseEventPacket;
@ -124,10 +125,21 @@ public class VncClient {
clientListener.onClientClose();
}
public void connectTo(String host, int port, String path,
String session, boolean useSSL) throws UnknownHostException, IOException {
RawHTTP tunnel = new RawHTTP("CONNECT", host, port, path, session, useSSL);
this.socket = tunnel.connect();
doConnect("");
}
public void connectTo(String host, int port, String password) throws UnknownHostException, IOException {
// Connect to server
s_logger.info("Connecting to VNC server " + host + ":" + port + "...");
this.socket = new Socket(host, port);
doConnect(password);
}
private void doConnect(String password) throws IOException {
is = new DataInputStream(socket.getInputStream());
os = new DataOutputStream(socket.getOutputStream());