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:
parent
17ac1484bd
commit
589e19142c
@ -1077,7 +1077,8 @@ ETC_CLIENT_FILES="src/cli/etc/group.default"
|
|||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
SUNSTONE_FILES="src/sunstone/config.ru \
|
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"
|
SUNSTONE_BIN_FILES="src/sunstone/bin/sunstone-server"
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
NOVNC_TMP=/tmp/one/novnc-$(date "+%Y%m%d%H%M%S")
|
NOVNC_TMP=/tmp/one/novnc-$(date "+%Y%m%d%H%M%S")
|
||||||
|
PROXY_PATH=noVNC/utils/websockify
|
||||||
|
|
||||||
if [ -z "$ONE_LOCATION" ]; then
|
if [ -z "$ONE_LOCATION" ]; then
|
||||||
ONE_SHARE=/usr/share/one
|
ONE_SHARE=/usr/share/one
|
||||||
@ -34,7 +35,7 @@ mv $ONE_SHARE/$dir $ONE_SHARE/noVNC
|
|||||||
mkdir -p $ONE_PUBLIC_SUNSTONE/vendor/noVNC
|
mkdir -p $ONE_PUBLIC_SUNSTONE/vendor/noVNC
|
||||||
mv $ONE_SHARE/noVNC/include/ $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
|
#Update file permissions
|
||||||
chmod +x $ONE_SHARE/noVNC/utils/launch.sh
|
chmod +x $ONE_SHARE/noVNC/utils/launch.sh
|
||||||
|
90
src/sunstone/OpenNebulaVNC.rb
Normal file
90
src/sunstone/OpenNebulaVNC.rb
Normal 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
|
@ -16,8 +16,18 @@
|
|||||||
:core_auth: cipher
|
:core_auth: cipher
|
||||||
|
|
||||||
# VNC Configuration
|
# 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
|
:vnc_proxy_base_port: 29876
|
||||||
:novnc_path:
|
:vnc_proxy_path:
|
||||||
|
:vnc_proxy_support_wss: "no"
|
||||||
|
:vnc_proxy_cert:
|
||||||
|
:vnc_proxy_key:
|
||||||
|
|
||||||
|
|
||||||
# Default language setting
|
# Default language setting
|
||||||
:lang: en_US
|
:lang: en_US
|
||||||
|
@ -18,6 +18,7 @@ require 'OpenNebulaJSON'
|
|||||||
include OpenNebulaJSON
|
include OpenNebulaJSON
|
||||||
|
|
||||||
require 'acct/watch_client'
|
require 'acct/watch_client'
|
||||||
|
require 'OpenNebulaVNC'
|
||||||
|
|
||||||
class SunstoneServer
|
class SunstoneServer
|
||||||
# FLAG that will filter the elements retrieved from the Pools
|
# FLAG that will filter the elements retrieved from the Pools
|
||||||
@ -147,35 +148,35 @@ class SunstoneServer
|
|||||||
end
|
end
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
#
|
# Unused
|
||||||
############################################################################
|
############################################################################
|
||||||
def get_configuration(user_id)
|
# def get_configuration(user_id)
|
||||||
if user_id != "0"
|
# if user_id != "0"
|
||||||
return [401, ""]
|
# return [401, ""]
|
||||||
end
|
# end
|
||||||
|
|
||||||
one_config = VAR_LOCATION + "/config"
|
# one_config = VAR_LOCATION + "/config"
|
||||||
config = Hash.new
|
# config = Hash.new
|
||||||
|
|
||||||
begin
|
# begin
|
||||||
cfg = File.read(one_config)
|
# cfg = File.read(one_config)
|
||||||
rescue Exception => e
|
# rescue Exception => e
|
||||||
error = Error.new("Error reading config: #{e.inspect}")
|
# error = Error.new("Error reading config: #{e.inspect}")
|
||||||
return [500, error.to_json]
|
# return [500, error.to_json]
|
||||||
end
|
# end
|
||||||
|
|
||||||
cfg.lines do |line|
|
# cfg.lines do |line|
|
||||||
m=line.match(/^([^=]+)=(.*)$/)
|
# m=line.match(/^([^=]+)=(.*)$/)
|
||||||
|
|
||||||
if m
|
# if m
|
||||||
name=m[1].strip.upcase
|
# name=m[1].strip.upcase
|
||||||
value=m[2].strip
|
# value=m[2].strip
|
||||||
config[name]=value
|
# config[name]=value
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
return [200, config.to_json]
|
# return [200, config.to_json]
|
||||||
end
|
# end
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
#
|
#
|
||||||
@ -211,50 +212,16 @@ class SunstoneServer
|
|||||||
return [404, resource.to_json]
|
return [404, resource.to_json]
|
||||||
end
|
end
|
||||||
|
|
||||||
if resource['LCM_STATE'] != "3"
|
vnc_proxy = OpenNebulaVNC.new(config)
|
||||||
error = OpenNebula::Error.new("VM is not running")
|
return vnc_proxy.start(resource)
|
||||||
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]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
#
|
#
|
||||||
############################################################################
|
############################################################################
|
||||||
def stopvnc(id,pipe)
|
def stopvnc(pipe)
|
||||||
resource = retrieve_resource("vm", id)
|
|
||||||
if OpenNebula.is_error?(resource)
|
|
||||||
return [404, resource.to_json]
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Process.kill('KILL',pipe.pid)
|
OpenNebulaVNC.stop(pipe)
|
||||||
pipe.close
|
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
error = Error.new(e.message)
|
error = Error.new(e.message)
|
||||||
return [500, error.to_json]
|
return [500, error.to_json]
|
||||||
|
@ -33,6 +33,12 @@ var config_tab_content =
|
|||||||
</select>\
|
</select>\
|
||||||
</td>\
|
</td>\
|
||||||
</tr>\
|
</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>\
|
</table>\
|
||||||
\
|
\
|
||||||
</div>\
|
</div>\
|
||||||
@ -48,6 +54,34 @@ var config_tab = {
|
|||||||
|
|
||||||
Sunstone.addMainTab('config_tab',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(){
|
$(document).ready(function(){
|
||||||
if (lang)
|
if (lang)
|
||||||
$('table#config_table #lang_sel option[value="'+lang+'"]').attr('selected','selected');
|
$('table#config_table #lang_sel option[value="'+lang+'"]').attr('selected','selected');
|
||||||
@ -55,4 +89,10 @@ $(document).ready(function(){
|
|||||||
setLang($(this).val());
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
@ -1227,7 +1227,7 @@ function setupVNC(){
|
|||||||
|
|
||||||
function vncCallback(request,response){
|
function vncCallback(request,response){
|
||||||
rfb = new RFB({'target': $D('VNC_canvas'),
|
rfb = new RFB({'target': $D('VNC_canvas'),
|
||||||
'encrypt': false,
|
'encrypt': $('#config_table #wss_checkbox').is(':checked'),
|
||||||
'true_color': true,
|
'true_color': true,
|
||||||
'local_cursor': true,
|
'local_cursor': true,
|
||||||
'shared': true,
|
'shared': true,
|
||||||
|
@ -39,6 +39,7 @@ SUNSTONE_ROOT_DIR = File.dirname(__FILE__)
|
|||||||
|
|
||||||
$: << RUBY_LIB_LOCATION
|
$: << RUBY_LIB_LOCATION
|
||||||
$: << RUBY_LIB_LOCATION+'/cloud'
|
$: << RUBY_LIB_LOCATION+'/cloud'
|
||||||
|
$: << SUNSTONE_ROOT_DIR
|
||||||
$: << SUNSTONE_ROOT_DIR+'/models'
|
$: << SUNSTONE_ROOT_DIR+'/models'
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -115,12 +116,27 @@ helpers do
|
|||||||
session[:ip] = request.ip
|
session[:ip] = request.ip
|
||||||
session[:remember] = params[:remember]
|
session[:remember] = params[:remember]
|
||||||
|
|
||||||
|
#User IU options initialization
|
||||||
|
#Load options either from user settings or default config.
|
||||||
|
# - LANG
|
||||||
|
# - WSS CONECTION
|
||||||
|
|
||||||
if user['TEMPLATE/LANG']
|
if user['TEMPLATE/LANG']
|
||||||
session[:lang] = user['TEMPLATE/LANG']
|
session[:lang] = user['TEMPLATE/LANG']
|
||||||
else
|
else
|
||||||
session[:lang] = settings.config[:lang]
|
session[:lang] = settings.config[:lang]
|
||||||
end
|
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]
|
if params[:remember]
|
||||||
env['rack.session.options'][:expire_after] = 30*60*60*24
|
env['rack.session.options'][:expire_after] = 30*60*60*24
|
||||||
end
|
end
|
||||||
@ -212,8 +228,16 @@ end
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# Config and Logs
|
# Config and Logs
|
||||||
##############################################################################
|
##############################################################################
|
||||||
get '/config' do
|
#get '/config' do
|
||||||
@SunstoneServer.get_configuration(session[:user_id])
|
# @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
|
end
|
||||||
|
|
||||||
post '/config' do
|
post '/config' do
|
||||||
@ -226,6 +250,7 @@ post '/config' do
|
|||||||
body.each do | key,value |
|
body.each do | key,value |
|
||||||
case key
|
case key
|
||||||
when "lang" then session[:lang]=value
|
when "lang" then session[:lang]=value
|
||||||
|
when "wss" then session[:wss]=value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -301,7 +326,8 @@ post '/vm/:id/stopvnc' do
|
|||||||
return [403, OpenNebula::Error.new(msg).to_json]
|
return [403, OpenNebula::Error.new(msg).to_json]
|
||||||
end
|
end
|
||||||
|
|
||||||
rc = @SunstoneServer.stopvnc(vm_id, vnc_hash[vm_id][:pipe])
|
rc = @SunstoneServer.stopvnc(vnc_hash[vm_id][:pipe])
|
||||||
|
|
||||||
if rc[0] == 200
|
if rc[0] == 200
|
||||||
session['vnc'].delete(vm_id)
|
session['vnc'].delete(vm_id)
|
||||||
end
|
end
|
||||||
@ -327,7 +353,8 @@ post '/vm/:id/startvnc' do
|
|||||||
return [200, info.to_json]
|
return [200, info.to_json]
|
||||||
end
|
end
|
||||||
|
|
||||||
rc = @SunstoneServer.startvnc(vm_id, settings.config)
|
rc = @SunstoneServer.startvnc(vm_id,settings.config)
|
||||||
|
|
||||||
if rc[0] == 200
|
if rc[0] == 200
|
||||||
info = rc[1]
|
info = rc[1]
|
||||||
session['vnc'][vm_id] = info.clone
|
session['vnc'][vm_id] = info.clone
|
||||||
|
Loading…
Reference in New Issue
Block a user