1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-11 05:17:41 +03:00

Feature #1209: Single VNC proxy for all connections

Taking advantage of latest developments contributed to websockify, we can now make use of multiples targets validated by a system of file-based tokens.

The stopvnc actions dissapear as we only have to close the connection. The proxy is launched at the start of Sunstone and shutdown and the end. The tokens which allow to set up the proxied connections expire after 4 seconds.

Necessary modifications have been made in Sunstone and SelfService. novnc install script has been modified to fetch the websockify proxy files from the original project. Also, only the strictly necessary files for novnc+websockify to run are now installed.
(cherry picked from commit 89631ebdb4c8a5368924cc04ddea0ebf955d75c5)
This commit is contained in:
Hector Sanjuan 2012-07-19 18:15:44 +02:00 committed by Ruben S. Montero
parent b26aa52c2b
commit 7b184d6c43
14 changed files with 209 additions and 252 deletions

View File

@ -1,7 +1,7 @@
#!/bin/bash
NOVNC_TMP=/tmp/one/novnc-$(date "+%Y%m%d%H%M%S")
PROXY_PATH=noVNC/utils/websockify
PROXY_PATH=websockify/websockify
if [ -z "$ONE_LOCATION" ]; then
ONE_SHARE=/usr/share/one
@ -28,25 +28,40 @@ fi
echo "Extracting files to temporary folder..."
tar=`ls -rt $NOVNC_TMP|tail -n1`
tar -C $ONE_SHARE -mxzf $NOVNC_TMP/$tar
tar -mxzf $NOVNC_TMP/$tar
if [ $? -ne 0 ]; then
echo "Error untaring noVNC"
exit 1
fi
echo "Moving files to OpenNebula $ONE_SHARE folder..."
rm -rf $ONE_SHARE/noVNC
dir=`ls -rt $ONE_SHARE|tail -n1`
mv $ONE_SHARE/$dir $ONE_SHARE/noVNC
echo "Installing Sunstone client libraries in $ONE_PUBLIC_SUNSTONE..."
rm -rf $ONE_PUBLIC_SUNSTONE/vendor/noVNC/
mkdir -p $ONE_PUBLIC_SUNSTONE/vendor/noVNC
cp -r $ONE_SHARE/noVNC/include/ $ONE_PUBLIC_SUNSTONE/vendor/noVNC/
cp -r $NOVNC_TMP/*noVNC*/include/ $ONE_PUBLIC_SUNSTONE/vendor/noVNC/
echo "Installing SelfService client libraries in $ONE_PUBLIC_SELFSERVICE..."
rm -rf $ONE_PUBLIC_SELFSERVICE/vendor/noVNC/
mkdir -p $ONE_PUBLIC_SELFSERVICE/vendor/noVNC
cp -r $ONE_SHARE/noVNC/include/ $ONE_PUBLIC_SELFSERVICE/vendor/noVNC/
cp -r $NOVNC_TMP/*noVNC*/include/ $ONE_PUBLIC_SELFSERVICE/vendor/noVNC/
cd $ONE_SHARE
rm -rf $NOVNC_TMP
echo "Downloading Websockify VNC proxy files"
rm -rf $ONE_SHARE/websockify
mkdir -p $ONE_SHARE/websockify
cd $ONE_SHARE/websockify
curl -O -# -L https://raw.github.com/kanaka/websockify/master/websockify
if [ $? -ne 0 ]; then
echo "\nError downloading websockify"
exit 1
fi
curl -O -# -L https://raw.github.com/kanaka/websockify/master/websocket.py
if [ $? -ne 0 ]; then
echo "\nError downloading websocket.py"
exit 1
fi
echo "Backing up and updating $SUNSTONE_CONF with new VNC proxy path..."
sed -i.bck "s%^\(:vnc_proxy_path:\).*$%\1 $ONE_SHARE/$PROXY_PATH%" $SUNSTONE_CONF

View File

@ -88,8 +88,7 @@
# VNC Configuration
# vnc_enable: yes | no. Allow users to launch vnc sessions.
# base_port: base_port + vnc_port of the VM is the port where the
# proxy will listen for VNC session connections to that VM.
# vnc_proxy_port: port where the VNC proxy will listen
# vnc_proxy_path: path to the websockets proxy (set by install_novnc.sh)
# support_wss: no | yes | only. If yes or only provide path to cert and key.
# "yes" means the proxy will accept both ws and wss connections.
@ -97,7 +96,7 @@
# vnc_proxy_key: Key for wss connections. Only necessary if not included in cert.
:vnc_enable: no
:vnc_proxy_base_port: 33876
:vnc_proxy_port: 33876
:vnc_proxy_path:
:vnc_proxy_support_wss: no
:vnc_proxy_cert:

View File

@ -558,7 +558,7 @@ class OCCIServer < CloudServer
# VNC Methods
############################################################################
def startvnc(id,config)
def startvnc(id,vnc)
vm = VirtualMachineOCCI.new(VirtualMachine.build_xml(id), @client)
rc = vm.info
@ -568,18 +568,6 @@ class OCCIServer < CloudServer
return [404, error]
end
vnc_proxy = OpenNebulaVNC.new(config, logger, {:json_errors => false})
return vnc_proxy.start(vm)
end
def stopvnc(pipe)
begin
OpenNebulaVNC.stop(pipe)
rescue Exception => e
logger.error {e.message}
return [500, "Error stopping VNC. Please check server logs."]
end
return [200,nil]
return vnc.proxy(vm)
end
end

View File

@ -124,6 +124,26 @@ end
set :cloud_auth, cloud_auth
#start VNC proxy
configure do
set :run, false
if settings.config[:vnc_enable]
opts = {
:json_errors => false,
:token_folder_name => 'selfservice_vnc_tokens'
}
set :vnc, OpenNebulaVNC.new(settings.config,
settings.logger,
opts)
settings.vnc.start()
Kernel.at_exit do
settings.vnc.stop
end
end
end
##############################################################################
# Helpers
##############################################################################
@ -359,6 +379,9 @@ get '/ui/config' do
config << " <LANG>#{session[:lang]}</LANG>"
config << " <WSS>#{wss}</WSS>"
config << " <VNC>#{vnc}</VNC>"
if vnc == "yes"
config << " <VNC_PROXY_PORT>#{settings.vnc.proxy_port}</VNC_PROXY_PORT>"
end
config << "</UI_CONFIGURARION>"
return [200, config]
@ -416,53 +439,8 @@ post '/ui/upload' do
end
post '/ui/startvnc/:id' do
if !settings.config[:vnc_enable]
return [403, "VNC sessions are disabled"]
end
vm_id = params[:id]
vnc_hash = session['vnc']
if !vnc_hash
session['vnc'] = {}
elsif vnc_hash[vm_id]
#return existing information
info = vnc_hash[vm_id].clone
info.delete(:pipe)
return [200, info.to_json]
end
rc = @occi_server.startvnc(vm_id, settings.config)
if rc[0] == 200
info = rc[1]
session['vnc'][vm_id] = info.clone
info.delete(:pipe)
rc = [200, info.to_json]
end
return rc
@occi_server.startvnc(vm_id, settings.vnc)
end
post '/ui/stopvnc/:id' do
if !settings.config[:vnc_enable]
return [403, "VNC sessions are disabled"]
end
vm_id = params[:id]
vnc_hash = session['vnc']
if !vnc_hash || !vnc_hash[vm_id]
return [403, "It seems there is no VNC proxy running for this machine"]
end
rc = @occi_server.stopvnc(vnc_hash[vm_id][:pipe])
if rc[0] == 200
session['vnc'].delete(vm_id)
end
return rc
end
Sinatra::Application.run!

View File

@ -477,10 +477,6 @@ var OCCI = {
},
"startvnc" : function(params){
OCCI.VM.vnc(params,"startvnc");
},
"stopvnc" : function(params){
OCCI.VM.vnc(params,"stopvnc");
}
/*
"monitor" : function(params){

View File

@ -286,13 +286,6 @@ var vm_actions = {
callback: vncCallback,
error: onError,
notify: true
},
"VM.stopvnc" : {
type: "single",
call: OCCI.VM.stopvnc,
error: onError,
notify: true
}
/*
@ -1035,17 +1028,13 @@ function setupVNC(){
});
dialog.bind( "dialogclose", function(event, ui) {
var id = $vnc_dialog.attr('vm_id');
rfb.disconnect();
Sunstone.runAction("VM.stopvnc",id);
});
$('.vnc').live("click",function(){
//Which VM is it?
var id = $(this).attr('vm_id');
//Set attribute to dialog
$vnc_dialog.attr('vm_id',id);
//Request proxy server start
var id = $(this).attr('vm_id');
//Ask server for connection params
Sunstone.runAction("VM.startvnc",id);
return false;
});
@ -1058,19 +1047,14 @@ function vncCallback(request,response){
'local_cursor': true,
'shared': true,
'updateState': updateVNCState});
//fetch things from clicked element host - port - password
vnc_port = response["port"];
//Hopefully this is returning sunstone server address, where
//the proxy is running
vnc_host = window.location.hostname;
vnc_pw = response["password"];
setTimeout(function(){
rfb.connect(vnc_host, vnc_port, vnc_pw);
$vnc_dialog.dialog('open');
},4000);
var proxy_host = window.location.hostname;
var proxy_port = config_response["VNC_PROXY_PORT"];
var pw = response["password"];
var token = response["token"];
var path = '?token='+token;
rfb.connect(proxy_host, proxy_port, pw, path);
$vnc_dialog.dialog('open');
}
function vncIcon(vm){

View File

@ -14,6 +14,7 @@
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
var config_response = {}
var config_tab_content =
'<form>\
<table id="config_table" style="width:100%">\
@ -68,10 +69,12 @@ Sunstone.addActions(config_actions);
Sunstone.addMainTab('config_tab',config_tab);
function updateConfig(request, response){
var config = response;
config_response = response;
//These two variables defined in compute.js
vnc_enable = config['VNC'] == 'true' || config['VNC'] == 'yes' ? true : false;
use_wss = config['WSS'] == 'true' || config['WSS'] == 'yes'? true : false;
vnc_enable = config_response['VNC'] == 'true' ||
config_response['VNC'] == 'yes' ? true : false;
use_wss = config_response['WSS'] == 'true' ||
config_response['WSS'] == 'yes'? true : false;
};
$(document).ready(function(){

View File

@ -14,16 +14,27 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'json'
require 'OpenNebula'
#
# This class provides support for launching and stopping a websockify proxy
#
require 'json'
require 'OpenNebula'
TOKEN_EXPIRE_SECONDS = 4
class OpenNebulaVNC
def initialize(config, logger, opt={:json_errors => true})
@proxy_path = config[:vnc_proxy_path]
@proxy_base_port = config[:vnc_proxy_base_port].to_i
attr_reader :proxy_port
def initialize(config, logger, opts={
:json_errors => true,
:token_folder_name => 'sunstone_vnc_tokens'})
@pipe = nil
@token_folder = File.join(VAR_LOCATION, opts[:token_folder_name])
@proxy_path = config[:vnc_proxy_path]
@proxy_port = config[:vnc_proxy_port] ||
config[:vnc_proxy_base_port] #deprecated
@wss = config[:vnc_proxy_support_wss]
@ -34,34 +45,25 @@ class OpenNebulaVNC
else
@enable_wss = false
end
@options = opt
@options = opts
@logger = logger
begin
Dir.mkdir(@token_folder)
rescue Exception => e
@logger.error "Cannot create token folder"
@logger.error e.message
end
end
# Start a VNC proxy
def start(vm_resource)
# Check configurations and VM attributes
def start
if @proxy_path == nil || @proxy_path.empty?
return error(403,"VNC proxy not configured")
@logger.info "VNC proxy not configured"
return
end
if vm_resource['LCM_STATE'] != "3"
return error(403,"VM is not running")
end
if vm_resource['TEMPLATE/GRAPHICS/TYPE'] != "vnc"
return error(403,"VM has no VNC configured")
end
# Proxy data
host = vm_resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
vnc_port = vm_resource['TEMPLATE/GRAPHICS/PORT']
proxy_port = @proxy_base_port + vnc_port.to_i
proxy_options = ""
proxy_options = "--target-config=#{@token_folder} "
if @enable_wss
proxy_options << " --cert #{@cert}"
@ -69,25 +71,86 @@ class OpenNebulaVNC
proxy_options << " --ssl-only" if @wss == "only"
end
cmd ="#{@proxy_path} #{proxy_options} #{proxy_port} #{host}:#{vnc_port}"
cmd ="python #{@proxy_path} #{proxy_options} #{@proxy_port}"
begin
@logger.info { "Starting vnc proxy: #{cmd}" }
pipe = IO.popen(cmd)
@logger.info { "Starting VNC proxy: #{cmd}" }
@pipe = IO.popen(cmd,'r')
rescue Exception => e
return [500, OpenNebula::Error.new(e.message).to_json]
@logger.error e.message
return
end
vnc_pw = vm_resource['TEMPLATE/GRAPHICS/PASSWD']
info = {:pipe => pipe, :port => proxy_port, :password => vnc_pw}
return [200, info]
end
# Stop a VNC proxy handle exceptions outside
def self.stop(pipe)
Process.kill('KILL',pipe.pid)
pipe.close
def proxy(vm_resource)
# Check configurations and VM attributes
if !@pipe
return error(400, "VNC Proxy is not running")
end
if vm_resource['LCM_STATE'] != "3"
return error(400,"VM is not running")
end
if vm_resource['TEMPLATE/GRAPHICS/TYPE'] != "vnc"
return error(400,"VM has no VNC configured")
end
# Proxy data
host = vm_resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
vnc_port = vm_resource['TEMPLATE/GRAPHICS/PORT']
vnc_pw = vm_resource['TEMPLATE/GRAPHICS/PASSWD']
# Generate token random_str: host:port
random_str = rand(36**20).to_s(36) #random string a-z0-9 length 20
token = "#{random_str}: #{host}:#{vnc_port}"
token_file = 'one-'+vm_resource['ID']
# Create token file
begin
f = File.open(File.join(@token_folder, token_file), 'w')
f.write(token)
f.close
rescue Exception => e
@logger.error e.message
return error(500, "Cannot create VNC proxy token")
end
info = {
:password => vnc_pw,
:token => random_str,
}
# Delete token soon after
Thread.new do
sleep TOKEN_EXPIRE_SECONDS
delete_token(token_file)
end
return [200, info.to_json]
end
# Delete proxy token file
def delete_token(filename)
begin
File.delete(File.join(@token_folder, filename))
rescue => e
@logger.error "Error deleting token file for VM #{vm_id}"
@logger.error e.message
end
end
def stop
if !@pipe then return end
@logger.info "Killing VNC proxy"
Process.kill('TERM',@pipe.pid)
@pipe.close
begin
Dir.rmdir(@token_folder)
rescue => e
@logger.error "Error deleting token folder"
@logger.error e.message
end
end
private
@ -100,5 +163,4 @@ class OpenNebulaVNC
end
end
end

View File

@ -62,16 +62,14 @@
# UI Settings
################################################################################
# :vnc_proxy_
# base_port: base_port + vnc_port of the VM is the port where the
# proxy will listen for VNC session connections to that VM.
# port: port where the vnc proxy will listen
# path: path to the websockets proxy (set by install_novnc.sh)
# support_wss: no | yes | only. For yes and only, provide path to
# cert and key. "yes" means both ws and wss connections will
# be supported.
# cert: Certificate to encrypt wss connections.
# key: Key for wss connections. Only if not included in cert.
#
:vnc_proxy_base_port: 29876
# cert and key. "yes" means both ws and wss connections will be
# supported.
# vnc_proxy_cert: Certificate to encrypt wss connections.
# vnc_proxy_key: Key for wss connections. Only necessary if not included in cert.
:vnc_proxy_port: 29876
:vnc_proxy_path:
:vnc_proxy_support_wss: no
:vnc_proxy_cert:

View File

@ -228,34 +228,18 @@ class SunstoneServer < CloudServer
########################################################################
# VNC
########################################################################
def startvnc(id, config)
def startvnc(id, vnc)
resource = retrieve_resource("vm", id)
if OpenNebula.is_error?(resource)
return [404, resource.to_json]
end
vnc_proxy = OpenNebulaVNC.new(config, logger)
return vnc_proxy.start(resource)
return vnc.proxy(resource)
end
############################################################################
########################################################################
#
############################################################################
def stopvnc(pipe)
begin
OpenNebulaVNC.stop(pipe)
rescue Exception => e
logger.error {e.message}
error = Error.new("Error stopping VNC. Please check server logs.")
return [500, error.to_json]
end
return [200, nil]
end
############################################################################
#
############################################################################
########################################################################
def get_pool_monitoring(resource, meters)
#pool_element
pool = case resource

View File

@ -678,9 +678,6 @@ var OpenNebula = {
"startvnc" : function(params){
OpenNebula.VM.vnc(params,"startvnc");
},
"stopvnc" : function(params){
OpenNebula.VM.vnc(params,"stopvnc");
},
"monitor" : function(params){
OpenNebula.Action.monitor(params,OpenNebula.VM.resource,false);
},

View File

@ -14,7 +14,7 @@
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
var config_response;
var config_response = {};
var config_tab_content =
'<form>\
<table id="config_table" style="width:100%">\

View File

@ -411,13 +411,6 @@ var vm_actions = {
notify: true
},
"VM.stopvnc" : {
type: "single",
call: OpenNebula.VM.stopvnc,
error: onError,
notify: true
},
"VM.monitor" : {
type: "monitor",
call : OpenNebula.VM.monitor,
@ -1538,17 +1531,13 @@ function setupVNC(){
});
dialog.bind( "dialogclose", function(event, ui) {
var id = $vnc_dialog.attr('vm_id');
rfb.disconnect();
Sunstone.runAction("VM.stopvnc",id);
});
$('.vnc').live("click",function(){
//Which VM is it?
var id = $(this).attr('vm_id');
//Set attribute to dialog
$vnc_dialog.attr('vm_id',id);
//Request proxy server start
//Ask server for connection params
Sunstone.runAction("VM.startvnc",id);
return false;
});
@ -1561,19 +1550,14 @@ function vncCallback(request,response){
'local_cursor': true,
'shared': true,
'updateState': updateVNCState});
//fetch things from clicked element host - port - password
vnc_port = response["port"];
//Hopefully this is returning sunstone server address, where
//the proxy is running
vnc_host = window.location.hostname;
vnc_pw = response["password"];
setTimeout(function(){
rfb.connect(vnc_host, vnc_port, vnc_pw);
$vnc_dialog.dialog('open');
},4000);
var proxy_host = window.location.hostname;
var proxy_port = config_response['system_config']['vnc_proxy_port'];
var pw = response["password"];
var token = response["token"];
var path = '?token='+token;
rfb.connect(proxy_host, proxy_port, pw, path);
$vnc_dialog.dialog('open');
}
function vncIcon(vm){

View File

@ -97,6 +97,17 @@ end
set :cloud_auth, cloud_auth
#start VNC proxy
configure do
set :run, false
set :vnc, OpenNebulaVNC.new(conf, settings.logger)
settings.vnc.start()
Kernel.at_exit do
settings.vnc.stop
end
end
##############################################################################
# Helpers
##############################################################################
@ -256,10 +267,10 @@ get '/config' do
:user_config => {
:lang => session[:lang],
:wss => session[:wss],
:marketplace_url => settings.config[:marketplace_url]
},
:system_config => {
:marketplace_url => settings.config[:marketplace_url]
:marketplace_url => settings.config[:marketplace_url],
:vnc_proxy_port => settings.vnc.proxy_port
}
}
@ -357,55 +368,11 @@ post '/:pool' do
end
##############################################################################
# Stop the VNC Session of a target VM
##############################################################################
post '/vm/:id/stopvnc' do
vm_id = params[:id]
vnc_hash = session['vnc']
if !vnc_hash || !vnc_hash[vm_id]
msg = "It seems there is no VNC proxy running for this machine"
return [403, OpenNebula::Error.new(msg).to_json]
end
rc = @SunstoneServer.stopvnc(vnc_hash[vm_id][:pipe])
if rc[0] == 200
session['vnc'].delete(vm_id)
end
rc
end
##############################################################################
# Start a VNC Session for a target VM
# Start VNC Session for a target VM
##############################################################################
post '/vm/:id/startvnc' do
vm_id = params[:id]
vnc_hash = session['vnc']
if !vnc_hash
session['vnc']= {}
elsif vnc_hash[vm_id]
#return existing information
info = vnc_hash[vm_id].clone
info.delete(:pipe)
return [200, info.to_json]
end
rc = @SunstoneServer.startvnc(vm_id,settings.config)
if rc[0] == 200
info = rc[1]
session['vnc'][vm_id] = info.clone
info.delete(:pipe)
[200, info.to_json]
else
rc
end
@SunstoneServer.startvnc(vm_id, settings.vnc)
end
##############################################################################
@ -416,3 +383,5 @@ post '/:resource/:id/action' do
params[:id],
request.body.read)
end
Sinatra::Application.run!