diff --git a/src/fireedge/src/server/routes/api/oneflow/template/functions.js b/src/fireedge/src/server/routes/api/oneflow/template/functions.js index 5b826e01a0..ec9d6e69d3 100644 --- a/src/fireedge/src/server/routes/api/oneflow/template/functions.js +++ b/src/fireedge/src/server/routes/api/oneflow/template/functions.js @@ -17,7 +17,7 @@ const { Validator } = require('jsonschema') const { role, service, action } = require('server/routes/api/oneflow/schemas') const { - oneFlowConnection, + oneFlowConection, returnSchemaError, } = require('server/routes/api/oneflow/utils') const { defaults, httpCodes } = require('server/utils/constants') @@ -91,13 +91,13 @@ const serviceTemplate = ( if (params && params.id) { config.path = '/service_template/{0}' config.request = params.id - oneFlowConnection( + oneFlowConection( config, (data) => success(next, res, data), (data) => error(next, res, data) ) } else { - oneFlowConnection( + oneFlowConection( config, (data) => success(next, res, data), (data) => error(next, res, data) @@ -130,7 +130,7 @@ const serviceTemplateDelete = ( password, request: params.id, } - oneFlowConnection( + oneFlowConection( config, (data) => success(next, res, data), (data) => error(next, res, data) @@ -173,7 +173,7 @@ const serviceTemplateCreate = ( password, post: template, } - oneFlowConnection( + oneFlowConection( config, (data) => success(next, res, data), (data) => error(next, res, data) @@ -224,7 +224,7 @@ const serviceTemplateUpdate = ( request: params.id, post: template, } - oneFlowConnection( + oneFlowConection( config, (data) => success(next, res, data), (data) => error(next, res, data) @@ -275,7 +275,7 @@ const serviceTemplateAction = ( request: params.id, post: template, } - oneFlowConnection( + oneFlowConection( config, (data) => success(next, res, data), (data) => error(next, res, data) diff --git a/src/fireedge/src/server/routes/api/vcenter/functions.js b/src/fireedge/src/server/routes/api/vcenter/functions.js index b7081eaad9..4e3f461e23 100644 --- a/src/fireedge/src/server/routes/api/vcenter/functions.js +++ b/src/fireedge/src/server/routes/api/vcenter/functions.js @@ -13,43 +13,32 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -const btoa = require('btoa') -const https = require('https') -// eslint-disable-next-line node/no-deprecated-api -const { parse } = require('url') - -const { request: axios } = require('axios') - const { defaults, httpCodes } = require('server/utils/constants') const { httpResponse, executeCommand, executeCommandAsync, publish, - getSunstoneAuth, } = require('server/utils/server') const { consoleParseToString, consoleParseToJSON, } = require('server/utils/opennebula') -const { createTokenServerAdmin } = require('server/routes/api/auth/utils') -const { Actions: ActionHost } = require('server/utils/constants/commands/host') -const { Actions: ActionVM } = require('server/utils/constants/commands/vm') const { resourceFromData, resources, params: commandParams, } = require('server/routes/api/vcenter/command-flags') const { getSunstoneConfig } = require('server/utils/yml') + const { - httpMethod, defaultEmptyFunction, defaultCommandVcenter, defaultRegexpStartJSON, defaultRegexpEndJSON, defaultRegexpSplitLine, } = defaults -const { POST } = httpMethod + const { ok, internalServerError, badRequest, accepted } = httpCodes const { LIST, IMPORT } = resourceFromData const appConfig = getSunstoneConfig() @@ -61,7 +50,6 @@ const regexExclude = [ /^\u001b\[.*?m\u001b\[.*?m# vCenter.*/i, ] const regexHeader = /^IMID,.*/i -const regexGetVcenterId = /-(?.*)_/s const validObjects = Object.values(resources) @@ -385,194 +373,11 @@ const importVobject = ( httpReturn(accepted) } -/** - * Axios request. - * - * @param {object} params - Axios params - * @param {Function} callback - Success Axios callback - * @param {Function} error - Error Axios callback - */ -const request = ( - params = {}, - callback = defaultEmptyFunction, - error = defaultEmptyFunction -) => { - const defaultsProperties = { - method: POST, - httpsAgent: new https.Agent({ - rejectUnauthorized: false, - }), - validateStatus: (status) => status, - } - axios({ - ...defaultsProperties, - ...params, - }) - .then((response) => { - if (response && response.statusText) { - if (response.status >= 200 && response.status < 400) { - if (response.data) { - return response.data - } - } - throw Error(response.data) - } else if (response.data) { - throw Error(response.data) - } - }) - .then((data) => { - callback(data) - }) - .catch((e) => { - error(e) - }) -} - -/** - * Get system config. - * - * @param {object} res - http response - * @param {Function} next - express stepper - * @param {object} params - params of http request - * @param {object} userData - user of http request - * @param {function(string, string): Function} oneConnection - One Connection - */ -const getToken = ( - res = {}, - next = defaultEmptyFunction, - params = {}, - userData = {}, - oneConnection = defaultEmptyFunction -) => { - const { username, key, iv } = getSunstoneAuth() - const { id } = params - const responser = (code = badRequest, data = '') => { - res.locals.httpCode = httpResponse(code, data, '') - next() - } - - if (!(username && key && iv) || !Number.isInteger(parseInt(id, 10))) { - responser() - - return - } - - const tokenWithServerAdmin = createTokenServerAdmin({ - serverAdmin: username, - username, - key, - iv, - }) - if (!tokenWithServerAdmin.token) { - responser() - - return - } - - const connect = oneConnection( - `${username}:${username}`, - tokenWithServerAdmin.token - ) - - connect(ActionVM.VM_INFO, [parseInt(id, 10), true], (err, vminfo) => { - if ( - !( - vminfo && - vminfo.VM && - vminfo.VM.DEPLOY_ID && - vminfo.VM.HISTORY_RECORDS && - vminfo.VM.HISTORY_RECORDS.HISTORY - ) || - err - ) { - responser(internalServerError) - } - - const history = vminfo.VM.HISTORY_RECORDS.HISTORY - const arrayHistory = Array.isArray(history) ? history : [history] - - const hostID = parseInt( - arrayHistory.reduce( - (max, record) => (record.SEQ > max.SEQ ? record : max), - arrayHistory[0] - ).HID, - 10 - ) - - const vmid = vminfo.VM.DEPLOY_ID.match(regexGetVcenterId).groups.id - - connect(ActionHost.HOST_INFO, [hostID, true], (err, hostinfo) => { - if ( - !( - hostinfo && - hostinfo.HOST && - hostinfo.HOST.TEMPLATE && - hostinfo.HOST.TEMPLATE.VCENTER_HOST && - hostinfo.HOST.TEMPLATE.VCENTER_USER && - hostinfo.HOST.TEMPLATE.VCENTER_PASSWORD - ) || - err - ) { - responser(internalServerError) - - return - } - - const { VCENTER_HOST, VCENTER_USER, VCENTER_PASSWORD } = - hostinfo.HOST.TEMPLATE - - const responseInternalServer = () => { - responser(internalServerError) - } - - const genToken = (data) => { - request( - { - url: `https://${VCENTER_HOST}/api/vcenter/vm/vm-${vmid}/console/tickets`, - headers: { - 'Content-Type': 'application/json', - 'vmware-api-session-id': data, - }, - data: JSON.stringify({ type: 'WEBMKS' }), - }, - (success) => { - const { ticket } = success - const { protocol, hostname, port, path } = parse(ticket) - - const httpProtocol = protocol === 'wss:' ? 'https' : 'http' - const esxUrl = `${httpProtocol}://${hostname}:${port}` - const token = path.replace('/ticket/', '') - global.vcenterToken = { [token]: esxUrl } - responser(ok, { - ticket: token, - }) - }, - responseInternalServer - ) - } - - request( - { - url: `https://${VCENTER_HOST}/api/session`, - headers: { - Authorization: `Basic ${btoa( - `${VCENTER_USER}:${VCENTER_PASSWORD}` - )}`, - }, - }, - genToken, - responseInternalServer - ) - }) - }) -} - const functionRoutes = { list, listAll, cleartags, importHost, importVobject, - getToken, } module.exports = functionRoutes diff --git a/src/fireedge/src/server/routes/api/vcenter/index.js b/src/fireedge/src/server/routes/api/vcenter/index.js index b9d9c4e5bd..9dfa5f4034 100644 --- a/src/fireedge/src/server/routes/api/vcenter/index.js +++ b/src/fireedge/src/server/routes/api/vcenter/index.js @@ -21,14 +21,12 @@ const { listAll, cleartags, importHost, - getToken, } = require('server/routes/api/vcenter/functions') const { resources } = require('server/routes/api/vcenter/command-flags') const { TEMPLATES, DATASTORES, NETWORKS, IMAGES } = resources const { - VCENTER_TOKEN, VCENTER_CLEAR_TAGS, VCENTER_IMPORT_HOSTS, VCENTER_IMPORT_DATASTORES, @@ -40,10 +38,6 @@ const { } = Actions module.exports = [ - { - ...Commands[VCENTER_TOKEN], - action: getToken, - }, { ...Commands[VCENTER_CLEAR_TAGS], action: cleartags, diff --git a/src/fireedge/src/server/routes/api/vcenter/routes.js b/src/fireedge/src/server/routes/api/vcenter/routes.js index 99bcc968c3..0db36fc2ab 100644 --- a/src/fireedge/src/server/routes/api/vcenter/routes.js +++ b/src/fireedge/src/server/routes/api/vcenter/routes.js @@ -23,7 +23,6 @@ const basepath = '/vcenter' const { POST, GET } = httpMethod const { resource, postBody, query } = fromData -const VCENTER_TOKEN = 'vcenter.token' const VCENTER_CLEAR_TAGS = 'vcenter.cleartags' const VCENTER_IMPORT_HOSTS = 'vcenter.importhosts' const VCENTER_IMPORT_DATASTORES = 'vcenter.importdatastores' @@ -33,7 +32,6 @@ const VCENTER_IMPORT_IMAGES = 'vcenter.importimages' const VCENTER_LIST_ALL = 'vcenter.listall' const VCENTER_LIST = 'vcenter.list' const Actions = { - VCENTER_TOKEN, VCENTER_CLEAR_TAGS, VCENTER_IMPORT_HOSTS, VCENTER_IMPORT_TEMPLATES, @@ -47,16 +45,6 @@ const Actions = { module.exports = { Actions, Commands: { - [VCENTER_TOKEN]: { - path: `${basepath}/token/:id`, - httpMethod: GET, - auth: true, - params: { - id: { - from: resource, - }, - }, - }, [VCENTER_CLEAR_TAGS]: { path: `${basepath}/cleartags/:id`, httpMethod: POST, diff --git a/src/fireedge/src/server/routes/websockets/vmrc.js b/src/fireedge/src/server/routes/websockets/vmrc.js index 4c70274dea..4bc8bd043e 100644 --- a/src/fireedge/src/server/routes/websockets/vmrc.js +++ b/src/fireedge/src/server/routes/websockets/vmrc.js @@ -17,6 +17,7 @@ // eslint-disable-next-line node/no-deprecated-api const { parse } = require('url') const { createProxyMiddleware } = require('http-proxy-middleware') +const { readFileSync } = require('fs-extra') const { getFireedgeConfig } = require('server/utils/yml') const { messageTerminal } = require('server/utils/general') const { @@ -54,10 +55,14 @@ const vmrcProxy = createProxyMiddleware(endpointVmrc, { if (parseURL && parseURL.pathname) { const ticket = parseURL.pathname.split('/')[3] writeInLogger(ticket, 'path to vmrc token: %s') - if (global && global.vcenterToken && global.vcenterToken[ticket]) { - return global.vcenterToken[ticket] - } else { - writeInLogger(ticket, 'Non-existent token: %s') + try { + const esxi = readFileSync( + `${global.paths.VMRC_TOKENS || ''}/${ticket}` + ).toString() + + return esxi + } catch (error) { + writeInLogger(ticket, 'Error to read vmrc token file: %s') } } } diff --git a/src/sunstone/models/SunstoneServer.rb b/src/sunstone/models/SunstoneServer.rb index 150265d8f7..04d0a249c2 100644 --- a/src/sunstone/models/SunstoneServer.rb +++ b/src/sunstone/models/SunstoneServer.rb @@ -21,6 +21,7 @@ include OpenNebulaJSON require 'sunstone_vnc' require 'sunstone_guac' +require 'sunstone_vmrc' require 'sunstone_vm_helper' require 'OpenNebulaAddons' require 'OpenNebulaJSON/JSONUtils' @@ -336,6 +337,33 @@ class SunstoneServer < CloudServer return guac.proxy(resource, type_connection, client) end + ######################################################################## + # VMRC + ######################################################################## + def startvmrc(id, vmrc, _client=nil) + resource = retrieve_resource("vm", id) + if OpenNebula.is_error?(resource) + return [404, resource.to_json] + end + vm_pool = VirtualMachinePool.new(@client, -1) + user_pool = UserPool.new(@client) + + rc = user_pool.info + if OpenNebula.is_error?(rc) + puts rc.message + exit -1 + end + + rc = vm_pool.info + if OpenNebula.is_error?(rc) + puts rc.message + exit -1 + end + + client = _client.nil? ? @client : _client + return vmrc.proxy(resource, client) + end + ######################################################################## # Accounting & Monitoring ######################################################################## diff --git a/src/sunstone/models/sunstone_vmrc.rb b/src/sunstone/models/sunstone_vmrc.rb new file mode 100644 index 0000000000..928632821b --- /dev/null +++ b/src/sunstone/models/sunstone_vmrc.rb @@ -0,0 +1,101 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems # +# # +# 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 class provides support for launching and stopping a vmrc proxy server # +#----------------------------------------------------------------------------# + +require 'rubygems' +require 'json' +require 'opennebula' +require 'base64' +require 'openssl' +require 'vcenter_driver' +require 'fileutils' +require 'sunstone_remotes' + +if !ONE_LOCATION + VMRC_TICKETS = '/var/lib/one/sunstone_vmrc_tokens/' +else + VMRC_TICKETS = ONE_LOCATION + '/var/sunstone_vmrc_tokens/' +end + +FileUtils.mkdir_p VMRC_TICKETS + +# Class for necessary VMRC ticket creation +class SunstoneVMRC < SunstoneRemoteConnections + + attr_reader :proxy_port + + def initialize(logger, options = {}) + super + end + + def proxy(vm_resource, client = nil) + # Check configurations and VM attributes + unless allowed_console_states.include?(vm_resource['LCM_STATE']) + error_message = "Wrong state (#{vm_resource['LCM_STATE']}) to + open a VMRC session" + return error(400, error_message) + end + + unless vm_resource['USER_TEMPLATE/HYPERVISOR'] == 'vcenter' + return error(400, 'VMRC Connection is only for vcenter hipervisor') + end + + unless vm_resource['MONITORING/VCENTER_ESX_HOST'] + error_message = 'Could not determine the vCenter ESX host where + the VM is running. Wait till the VCENTER_ESX_HOST attribute is + retrieved once the host has been monitored' + return error(400, error_message) + end + + vm_id = vm_resource['ID'] + one_vm = VCenterDriver::VIHelper.one_item( + OpenNebula::VirtualMachine, + vm_id + ) + vm_ref = one_vm['DEPLOY_ID'] + + host_id = one_vm['HISTORY_RECORDS/HISTORY[last()]/HID'].to_i + + vi_client = VCenterDriver::VIClient.new_from_host(host_id, client) + + vm = VCenterDriver::VirtualMachine.new(vi_client, vm_ref, vm_id) + + parameters = vm.html_console_parameters + + data = { + :host => parameters[:host], + :port => parameters[:port], + :ticket => parameters[:ticket] + } + + file = File.open( + VMRC_TICKETS + + VCenterDriver::FileHelper.sanitize(data[:ticket]), + 'w' + ) + file.write('https://' + data[:host] + ':' + data[:port].to_s) + file.close + + info = SunstoneVMHelper.get_remote_info(vm_resource) + encode_info = Base64.encode64(info.to_json) + + [200, { :data => data, :info => encode_info }.to_json] + end + +end diff --git a/src/sunstone/public/app/opennebula/vm.js b/src/sunstone/public/app/opennebula/vm.js index b4a5d026d6..c6af49096a 100644 --- a/src/sunstone/public/app/opennebula/vm.js +++ b/src/sunstone/public/app/opennebula/vm.js @@ -629,14 +629,14 @@ define(function(require) { "vmrc" : function(params) { var callback = params.success; var callback_error = params.error; - var vm_id = params.data.id; + var id = params.data.id; var resource = RESOURCE; var request = OpenNebulaHelper.request(resource, null, params.data); $.ajax({ - url: Config.publicFireedgeEndpoint + "/fireedge/api/vcenter/token/" + vm_id, - type: "GET", - headers: {"Authorization": fireedge_token}, + url: "vm/" + id + "/startvmrc", + type: "POST", + dataType: "json", success: function(response) { return callback ? callback(request, response) : null; }, diff --git a/src/sunstone/sunstone-server.rb b/src/sunstone/sunstone-server.rb index dddd351118..de773ea1bc 100755 --- a/src/sunstone/sunstone-server.rb +++ b/src/sunstone/sunstone-server.rb @@ -281,9 +281,13 @@ $vnc = SunstoneVNC.new($conf, logger) #init Guacamole proxy $guac = SunstoneGuac.new(logger) +#init VMRC proxy +$vmrc = SunstoneVMRC.new(logger) + configure do set :run, false set :vnc, $vnc + set :vmrc, $vmrc set :erb, :trim => '-' end @@ -1206,6 +1210,15 @@ post '/vm/:id/guac/:type' do @SunstoneServer.startguac(vm_id, type_connection, $guac, user) end +############################################################################## +# Start VMRC Session for a target VM +############################################################################## +post '/vm/:id/startvmrc' do + vm_id = params[:id] + serveradmin_client = $cloud_auth.client(nil, session[:active_zone_endpoint]) + @SunstoneServer.startvmrc(vm_id, $vmrc, serveradmin_client) +end + ############################################################################## # Perform an action on a Resource ##############################################################################