1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-10 01:17:59 +03:00

Added Support for SSH through HTML5

This commit is contained in:
Adolfo Gómez García 2023-03-16 15:18:20 +01:00
parent b9a01e686f
commit 6bb4c3bd5e
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
4 changed files with 295 additions and 1 deletions

View File

@ -45,6 +45,7 @@ NX = 'nx'
X11 = 'x11'
X2GO = 'x2go' # Based on NX
NICEDCV = 'nicedcv'
SSH = 'ssh'
OTHER = 'other'
GENERIC = (RDP, RGS, VNC, NX, X11, X2GO, PCOIP, NICEDCV, OTHER)
GENERIC = (RDP, RGS, VNC, NX, X11, X2GO, PCOIP, NICEDCV, SSH, OTHER)

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from .html5ssh import HTML5SSHTransport

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,259 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import logging
import typing
from django.utils.translation import ugettext_noop as _
from uds.core.ui import gui
from uds.core import transports
from uds.core.util import os_detector as OsDetector
from uds.core.managers import cryptoManager
from uds import models
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import Module
from django.http import HttpRequest # pylint: disable=ungrouped-imports
logger = logging.getLogger(__name__)
READY_CACHE_TIMEOUT = 30
class HTML5SSHTransport(transports.Transport):
"""
Provides access via SSH to service.
"""
typeName = _('HTML5 SSH')
typeType = 'HTML5SSHTransport'
typeDescription = _('SSH protocol using HTML5 client')
iconFile = 'html5ssh.png'
ownLink = True
supportedOss = OsDetector.allOss
protocol = transports.protocols.SSH
group = transports.TUNNELED_GROUP
guacamoleServer = gui.TextField(
label=_('Tunnel Server'),
order=1,
tooltip=_(
'Host of the tunnel server (use http/https & port if needed) as accesible from users'
),
defvalue='https://',
length=64,
required=True,
tab=gui.TUNNEL_TAB,
)
username = gui.TextField(
label=_('Username'),
order=20,
tooltip=_('Username for VNC connection authentication.'),
tab=gui.CREDENTIALS_TAB,
)
password = gui.PasswordField(
label=_('Password'),
order=21,
tooltip=_('Password for VNC connection authentication'),
tab=gui.CREDENTIALS_TAB,
)
sshPrivateKey = gui.TextField(
label=_('SSH Private Key'),
order=22,
tooltip=_(
'Private key for SSH authentication. If not provided, password authentication is used.'
),
tab=gui.CREDENTIALS_TAB,
)
sshPassphrase = gui.PasswordField(
label=_('SSH Private Key Passphrase'),
order=23,
tooltip=_(
'Passphrase for SSH private key if it is required. If not provided, but it is needed, user will be prompted for it.'
),
tab=gui.CREDENTIALS_TAB,
)
sshCommand = gui.TextField(
label=_('SSH Command'),
order=30,
tooltip=_(
'Command to execute on the remote server. If not provided, an interactive shell will be executed.'
),
tab=gui.PARAMETERS_TAB,
)
sshPort = gui.NumericField(
length=40,
label=_('SSH Server port'),
defvalue='22',
order=22,
tooltip=_('Port of the SSH server.'),
required=True,
tab=gui.PARAMETERS_TAB,
)
sshHostKey = gui.TextField(
label=_('SSH Host Key'),
order=41,
tooltip=_(
'Host key of the SSH server. If not provided, no verification of host identity is done.'
),
tab=gui.PARAMETERS_TAB,
)
serverKeepAlive = gui.NumericField(
length=3,
label=_('Server Keep Alive'),
defvalue='30',
order=42,
tooltip=_(
'Time in seconds between keep alive messages sent to server. If not provided, no keep alive messages are sent.'
),
required=True,
minValue=0,
tab=gui.PARAMETERS_TAB,
)
ticketValidity = gui.NumericField(
length=3,
label=_('Ticket Validity'),
defvalue='60',
order=90,
tooltip=_(
'Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.'
),
required=True,
minValue=60,
tab=gui.ADVANCED_TAB,
)
forceNewWindow = gui.ChoiceField(
order=91,
label=_('Force new HTML Window'),
tooltip=_('Select windows behavior for new connections on HTML5'),
required=True,
values=[
gui.choiceItem(
gui.FALSE,
_('Open every connection on the same window, but keeps UDS window.'),
),
gui.choiceItem(
gui.TRUE, _('Force every connection to be opened on a new window.')
),
gui.choiceItem(
'overwrite',
_('Override UDS window and replace it with the connection.'),
),
],
defvalue=gui.FALSE,
tab=gui.ADVANCED_TAB,
)
def initialize(self, values: 'Module.ValuesType'):
if not values:
return
# Strip spaces
# Remove trailing / (one or more) from url if it exists from "guacamoleServer" field
self.guacamoleServer.value = self.guacamoleServer.value.strip().rstrip('/')
if self.guacamoleServer.value[0:4] != 'http':
raise transports.Transport.ValidationException(
_('The server must be http or https')
)
def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool:
"""
Checks if the transport is available for the requested destination ip
Override this in yours transports
"""
logger.debug('Checking availability for %s', ip)
ready = self.cache.get(ip)
if not ready:
# Check again for readyness
if self.testServer(userService, ip, self.sshPort.value) is True:
self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
return True
self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
return ready == 'Y'
def getLink( # pylint: disable=too-many-locals
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest',
) -> str:
# Build params dict
params = {
'protocol': 'ssh',
'hostname': ip,
'port': str(self.sshPort.num()),
}
# Optional numeric keep alive. If less than 2, it is not sent
if self.serverKeepAlive.num() >= 2:
params['server-alive-interval'] = str(self.serverKeepAlive.num())
# Add optional parameters (strings only)
for i in (
('username', self.username),
('password', self.password),
('private-key', self.sshPrivateKey),
('passphrase', self.sshPassphrase),
('command', self.sshCommand),
('host-key', self.sshHostKey),
):
if i[1].value.strip():
params[i[0]] = i[1].value.strip()
logger.debug('SSH Params: %s', params)
scrambler = cryptoManager().randomString(32)
ticket = models.TicketStore.create(params, validity=self.ticketValidity.num())
onw = ''
if self.forceNewWindow.value == gui.TRUE:
onw = 'o_n_w={}'
elif self.forceNewWindow.value == 'overwrite':
onw = 'o_s_w=yes'
onw = onw.format(hash(transport.name))
return str(
"{}/guacamole/#/?data={}.{}{}".format(
self.guacamoleServer.value, ticket, scrambler, onw
)
)