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 \
|
||||
src/sunstone/sunstone-server.rb"
|
||||
src/sunstone/sunstone-server.rb \
|
||||
src/sunstone/OpenNebulaVNC.rb"
|
||||
|
||||
SUNSTONE_BIN_FILES="src/sunstone/bin/sunstone-server"
|
||||
|
||||
|
@ -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
|
||||
|
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
|
||||
|
||||
# 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
|
||||
|
@ -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]
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user