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

Feature #1069: Support secure-websocket-based VNC session in Sunstone.

This commit adds support for using wss capabilities of websockify:
* Add configuration option to Sunstone and saving/restore in user template support
* Add new options to sunstone server configuration file
* VNC session is started according to user setting
* The code related to VNC proxy launch has been outsourced to OpenNebulaVNC.rb, so it can be mantained more easily and reused by, for example, SelfService.
* Install novnc script has been corrected to point to "websockify" full path.

Note: this commit changes vnc-related sunstone-server.conf keys and breaks vnc support in former versions of the configuration file. Update if necessary.
(cherry picked from commit 00cf42e6b6)
This commit is contained in:
Hector Sanjuan 2012-01-30 12:16:14 +01:00 committed by Ruben S. Montero
parent 17ac1484bd
commit 589e19142c
8 changed files with 205 additions and 69 deletions

View File

@ -1077,7 +1077,8 @@ ETC_CLIENT_FILES="src/cli/etc/group.default"
#-----------------------------------------------------------------------------
SUNSTONE_FILES="src/sunstone/config.ru \
src/sunstone/sunstone-server.rb"
src/sunstone/sunstone-server.rb \
src/sunstone/OpenNebulaVNC.rb"
SUNSTONE_BIN_FILES="src/sunstone/bin/sunstone-server"

View File

@ -1,6 +1,7 @@
#!/bin/bash
NOVNC_TMP=/tmp/one/novnc-$(date "+%Y%m%d%H%M%S")
PROXY_PATH=noVNC/utils/websockify
if [ -z "$ONE_LOCATION" ]; then
ONE_SHARE=/usr/share/one
@ -34,7 +35,7 @@ mv $ONE_SHARE/$dir $ONE_SHARE/noVNC
mkdir -p $ONE_PUBLIC_SUNSTONE/vendor/noVNC
mv $ONE_SHARE/noVNC/include/ $ONE_PUBLIC_SUNSTONE/vendor/noVNC/
sed -i.bck "s%^\(:novnc_path: \).*$%\1$ONE_SHARE/noVNC%" $SUNSTONE_CONF
sed -i.bck "s%^\(:vnc_proxy_path: \).*$%\1$ONE_SHARE/$PROXY_PATH%" $SUNSTONE_CONF
#Update file permissions
chmod +x $ONE_SHARE/noVNC/utils/launch.sh

View File

@ -0,0 +1,90 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) #
# #
# Licensed 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. #
#--------------------------------------------------------------------------- #
#This file provides support for launching and stopping a websockify proxy
require 'json'
class OpenNebulaVNC
def initialize(config,opt={:json_errors => true})
@proxy_path = config[:vnc_proxy_path]
@proxy_base_port = config[:vnc_proxy_base_port].to_i
@wss = config[:vnc_proxy_support_wss]
$stderr.puts "wss #{@wss}"
@enable_wss = (@wss == "yes") || (@wss == "only")
@cert = @enable_wss? config[:vnc_proxy_cert] : nil
@key = @enable_wss? config[:vnc_proxy_key] : nil
@options=opt
end
def error(code, msg)
if @options[:json_errors]
return [code,OpenNebula::Error.new(msg).to_json]
else
return [code,msg]
end
end
def start(vm_resource)
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
# The VM host and its VNC port
host = vm_resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
vnc_port = vm_resource['TEMPLATE/GRAPHICS/PORT']
# The port on which the proxy will listen
proxy_port = @proxy_base_port + vnc_port.to_i
if !@proxy_path || @proxy_path.size == 0
return error(403,"VNC proxy not configured")
end
proxy_options = ""
if @enable_wss
proxy_options += " --cert #{@cert}"
proxy_options += " --key #{@key}" if @key && @key.size > 0
proxy_options += " --ssl-only" if @wss == "only"
end
proxy_cmd = "#{@proxy_path} #{proxy_options} #{proxy_port} #{host}:#{vnc_port}"
begin
$stderr.puts("Starting vnc proxy: #{proxy_cmd}")
pipe = IO.popen(proxy_cmd)
rescue Exception => e
error = Error.new(e.message)
return [500, error.to_json]
end
vnc_pw = vm_resource['TEMPLATE/GRAPHICS/PASSWD']
info = {:pipe => pipe, :port => proxy_port, :password => vnc_pw}
return [200, info]
end
#handle exceptions outside
def self.stop(pipe)
Process.kill('KILL',pipe.pid)
pipe.close
end
end

View File

@ -16,8 +16,18 @@
:core_auth: cipher
# VNC Configuration
# 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_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. Note value must be a quoted string.
# (key is only necessary if not included in cert).
:vnc_proxy_base_port: 29876
:novnc_path:
:vnc_proxy_path:
:vnc_proxy_support_wss: "no"
:vnc_proxy_cert:
:vnc_proxy_key:
# Default language setting
:lang: en_US

View File

@ -18,6 +18,7 @@ require 'OpenNebulaJSON'
include OpenNebulaJSON
require 'acct/watch_client'
require 'OpenNebulaVNC'
class SunstoneServer
# FLAG that will filter the elements retrieved from the Pools
@ -147,35 +148,35 @@ class SunstoneServer
end
############################################################################
#
# Unused
############################################################################
def get_configuration(user_id)
if user_id != "0"
return [401, ""]
end
# def get_configuration(user_id)
# if user_id != "0"
# return [401, ""]
# end
one_config = VAR_LOCATION + "/config"
config = Hash.new
# one_config = VAR_LOCATION + "/config"
# config = Hash.new
begin
cfg = File.read(one_config)
rescue Exception => e
error = Error.new("Error reading config: #{e.inspect}")
return [500, error.to_json]
end
# begin
# cfg = File.read(one_config)
# rescue Exception => e
# error = Error.new("Error reading config: #{e.inspect}")
# return [500, error.to_json]
# end
cfg.lines do |line|
m=line.match(/^([^=]+)=(.*)$/)
# cfg.lines do |line|
# m=line.match(/^([^=]+)=(.*)$/)
if m
name=m[1].strip.upcase
value=m[2].strip
config[name]=value
end
end
# if m
# name=m[1].strip.upcase
# value=m[2].strip
# config[name]=value
# end
# end
return [200, config.to_json]
end
# return [200, config.to_json]
# end
############################################################################
#
@ -211,50 +212,16 @@ class SunstoneServer
return [404, resource.to_json]
end
if resource['LCM_STATE'] != "3"
error = OpenNebula::Error.new("VM is not running")
return [403, error.to_json]
end
if resource['TEMPLATE/GRAPHICS/TYPE'] != "vnc"
error = OpenNebula::Error.new("VM has no VNC configured")
return [403, error.to_json]
end
# The VM host and its VNC port
host = resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
vnc_port = resource['TEMPLATE/GRAPHICS/PORT']
# The noVNC proxy_port
proxy_port = config[:vnc_proxy_base_port].to_i + vnc_port.to_i
begin
novnc_cmd = "#{config[:novnc_path]}/utils/wsproxy.py"
novnc_exec = "#{novnc_cmd} #{proxy_port} #{host}:#{vnc_port}"
$stderr.puts("Starting vnc proxy: #{novnc_exec}")
pipe = IO.popen(novnc_exec)
rescue Exception => e
error = Error.new(e.message)
return [500, error.to_json]
end
vnc_pw = resource['TEMPLATE/GRAPHICS/PASSWD']
info = {:pipe => pipe, :port => proxy_port, :password => vnc_pw}
return [200, info]
vnc_proxy = OpenNebulaVNC.new(config)
return vnc_proxy.start(resource)
end
############################################################################
#
############################################################################
def stopvnc(id,pipe)
resource = retrieve_resource("vm", id)
if OpenNebula.is_error?(resource)
return [404, resource.to_json]
end
def stopvnc(pipe)
begin
Process.kill('KILL',pipe.pid)
pipe.close
OpenNebulaVNC.stop(pipe)
rescue Exception => e
error = Error.new(e.message)
return [500, error.to_json]

View File

@ -33,6 +33,12 @@ var config_tab_content =
</select>\
</td>\
</tr>\
<tr>\
<td class="key_td">' + tr("Secure websockets connection") + '</td>\
<td class="value_td">\
<input id="wss_checkbox" type="checkbox" value="yes" />\
</td>\
</tr>\
</table>\
\
</div>\
@ -48,6 +54,34 @@ var config_tab = {
Sunstone.addMainTab('config_tab',config_tab);
function updateWss(){
var user_info_req = {
data : {
id: uid,
},
success: function(req,user_json) {
var template = user_json.USER.TEMPLATE;
var template_str="";
template['VNC_WSS']=
$('#config_table #wss_checkbox').is(':checked') ? "yes" : "no";
//convert json to ONE template format - simple conversion
$.each(template,function(key,value){
template_str += (key + '=' + '"' + value + '"\n');
});
var request = {
data: {
id: uid,
extra_param: template_str
},
error: onError
};
OpenNebula.User.update(request);
},
};
OpenNebula.User.show(user_info_req);
};
$(document).ready(function(){
if (lang)
$('table#config_table #lang_sel option[value="'+lang+'"]').attr('selected','selected');
@ -55,4 +89,10 @@ $(document).ready(function(){
setLang($(this).val());
});
$('table#config_table #wss_checkbox').change(updateWss);
$.get('config/wss',function(response){
if (response != "no")
$('table#config_table input#wss_checkbox').attr('checked','checked');
});
});

View File

@ -1227,7 +1227,7 @@ function setupVNC(){
function vncCallback(request,response){
rfb = new RFB({'target': $D('VNC_canvas'),
'encrypt': false,
'encrypt': $('#config_table #wss_checkbox').is(':checked'),
'true_color': true,
'local_cursor': true,
'shared': true,

View File

@ -39,6 +39,7 @@ SUNSTONE_ROOT_DIR = File.dirname(__FILE__)
$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+'/cloud'
$: << SUNSTONE_ROOT_DIR
$: << SUNSTONE_ROOT_DIR+'/models'
##############################################################################
@ -115,12 +116,27 @@ helpers do
session[:ip] = request.ip
session[:remember] = params[:remember]
#User IU options initialization
#Load options either from user settings or default config.
# - LANG
# - WSS CONECTION
if user['TEMPLATE/LANG']
session[:lang] = user['TEMPLATE/LANG']
else
session[:lang] = settings.config[:lang]
end
if user['TEMPLATE/VNC_WSS']
session[:wss] = user['TEMPLATE/VNC_WSS']
else
session[:wss] = settings.config[:vnc_proxy_support_wss]
#limit to yes,no options
session[:wss] = (session[:wss] != "no" ? "yes" : "no")
end
#end user options
if params[:remember]
env['rack.session.options'][:expire_after] = 30*60*60*24
end
@ -212,8 +228,16 @@ end
##############################################################################
# Config and Logs
##############################################################################
get '/config' do
@SunstoneServer.get_configuration(session[:user_id])
#get '/config' do
# @SunstoneServer.get_configuration(session[:user_id])
#end
get '/config/:opt' do
case params[:opt]
when "lang" then session[:lang]
when "wss" then session[:wss]
else "unknown"
end
end
post '/config' do
@ -226,6 +250,7 @@ post '/config' do
body.each do | key,value |
case key
when "lang" then session[:lang]=value
when "wss" then session[:wss]=value
end
end
end
@ -301,7 +326,8 @@ post '/vm/:id/stopvnc' do
return [403, OpenNebula::Error.new(msg).to_json]
end
rc = @SunstoneServer.stopvnc(vm_id, vnc_hash[vm_id][:pipe])
rc = @SunstoneServer.stopvnc(vnc_hash[vm_id][:pipe])
if rc[0] == 200
session['vnc'].delete(vm_id)
end
@ -327,7 +353,8 @@ post '/vm/:id/startvnc' do
return [200, info.to_json]
end
rc = @SunstoneServer.startvnc(vm_id, settings.config)
rc = @SunstoneServer.startvnc(vm_id,settings.config)
if rc[0] == 200
info = rc[1]
session['vnc'][vm_id] = info.clone