1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-24 21:34:41 +03:00

Several fixes:

* Added tunnel & guacaomle "fake" authid for next security
* Fixed proxy detection & use
This commit is contained in:
Adolfo Gómez García 2021-06-28 13:05:48 +02:00
parent 0da916b57c
commit 2ee4a7bcaa
8 changed files with 120 additions and 39 deletions

View File

@ -62,12 +62,16 @@ class Tunnel(Handler):
if ( if (
not isTrustedSource(self._request.ip) not isTrustedSource(self._request.ip)
or len(self._args) != 2 or len(self._args) not in (2, 3)
or len(self._args[0]) != 48 or len(self._args[0]) != 48
): ):
# Invalid requests # Invalid requests
raise AccessDenied() raise AccessDenied()
# If args is 3, the last one is the authId
authId = self._args[2][:48]
# TODO: Check auth Id
# Try to get ticket from DB # Try to get ticket from DB
try: try:
user, userService, host, port, extra = models.TicketStore.get_for_tunnel( user, userService, host, port, extra = models.TicketStore.get_for_tunnel(

View File

@ -48,15 +48,16 @@ logger = logging.getLogger(__name__)
class IPAuth(auths.Authenticator): class IPAuth(auths.Authenticator):
#acceptProxy = gui.CheckBoxField( acceptProxy = gui.CheckBoxField(
# label=_('Accept proxy'), label=_('Accept proxy'),
# order=3, defvalue=gui.FALSE,
# tooltip=_( order=3,
# 'If checked, requests via proxy will get FORWARDED ip address' tooltip=_(
# ' (take care with this bein checked, can take internal IP addresses from internet)' 'If checked, requests via proxy will get FORWARDED ip address'
# ), ' (take care with this bein checked, can take internal IP addresses from internet)'
# tab=gui.ADVANCED_TAB ),
#) tab=gui.ADVANCED_TAB
)
typeName = _('IP Authenticator') typeName = _('IP Authenticator')
typeType = 'IPAuth' typeType = 'IPAuth'
@ -71,8 +72,7 @@ class IPAuth(auths.Authenticator):
blockUserOnLoginFailures = False blockUserOnLoginFailures = False
def getIp(self) -> str: def getIp(self) -> str:
# ip = getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip # pylint: disable=maybe-no-member ip = getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip # pylint: disable=maybe-no-member
ip = getRequest().ip # Proxy is identified by UDS
logger.debug('Client IP: %s', ip) logger.debug('Client IP: %s', ip)
return ip return ip

View File

@ -55,7 +55,9 @@ logger = logging.getLogger(__name__)
class InternalDBAuth(auths.Authenticator): class InternalDBAuth(auths.Authenticator):
typeName = _('Internal Database') typeName = _('Internal Database')
typeType = 'InternalDBAuth' typeType = 'InternalDBAuth'
typeDescription = _('Internal dabasase authenticator. Doesn\'t use external sources') typeDescription = _(
'Internal dabasase authenticator. Doesn\'t use external sources'
)
iconFile = 'auth.png' iconFile = 'auth.png'
# If we need to enter the password for this user # If we need to enter the password for this user
@ -64,15 +66,40 @@ class InternalDBAuth(auths.Authenticator):
# This is the only internal source # This is the only internal source
isExternalSource = False isExternalSource = False
differentForEachHost = gui.CheckBoxField(label=_('Different user for each host'), order=1, tooltip=_('If checked, each host will have a different user name'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB) differentForEachHost = gui.CheckBoxField(
reverseDns = gui.CheckBoxField(label=_('Reverse DNS'), order=2, tooltip=_('If checked, the host will be reversed dns'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB) label=_('Different user for each host'),
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'), tab=gui.ADVANCED_TAB) order=1,
tooltip=_('If checked, each host will have a different user name'),
defvalue="false",
rdonly=True,
tab=gui.ADVANCED_TAB,
)
reverseDns = gui.CheckBoxField(
label=_('Reverse DNS'),
order=2,
tooltip=_('If checked, the host will be reversed dns'),
defvalue="false",
rdonly=True,
tab=gui.ADVANCED_TAB,
)
acceptProxy = gui.CheckBoxField(
label=_('Accept proxy'),
order=3,
tooltip=_(
'If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'
),
tab=gui.ADVANCED_TAB,
)
def getIp(self) -> str: def getIp(self) -> str:
ip = getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip # pylint: disable=maybe-no-member ip = (
getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip
) # pylint: disable=maybe-no-member
if self.reverseDns.isTrue(): if self.reverseDns.isTrue():
try: try:
return str(dns.resolver.query(dns.reversename.from_address(ip), 'PTR')[0]) return str(
dns.resolver.query(dns.reversename.from_address(ip), 'PTR')[0]
)
except Exception: except Exception:
pass pass
return ip return ip
@ -100,7 +127,9 @@ class InternalDBAuth(auths.Authenticator):
return username return username
def authenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool: def authenticate(
self, username: str, credentials: str, groupsManager: 'auths.GroupsManager'
) -> bool:
logger.debug('Username: %s, Password: %s', username, credentials) logger.debug('Username: %s, Password: %s', username, credentials)
dbAuth = self.dbAuthenticator() dbAuth = self.dbAuthenticator()
try: try:

View File

@ -40,7 +40,12 @@ from django.utils import timezone
from uds.core.util import os_detector as OsDetector from uds.core.util import os_detector as OsDetector
from uds.core.util.config import GlobalConfig from uds.core.util.config import GlobalConfig
from uds.core.auths.auth import EXPIRY_KEY, ROOT_ID, USER_KEY, getRootUser, webLogout from uds.core.auths.auth import EXPIRY_KEY, ROOT_ID, USER_KEY, getRootUser, webLogout
from uds.core.util.request import setRequest, delCurrentRequest, cleanOldRequests, ExtendedHttpRequest from uds.core.util.request import (
setRequest,
delCurrentRequest,
cleanOldRequests,
ExtendedHttpRequest,
)
from uds.models import User from uds.models import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -124,20 +129,38 @@ class GlobalRequestMiddleware:
except Exception: except Exception:
request.ip = '' # No remote addr?? ... request.ip = '' # No remote addr?? ...
try: # X-FORWARDED-FOR: CLIENT, FAR_PROXY, PROXY, NEAR_PROXY, NGINX
proxies = request.META.get('HTTP_X_FORWARDED_FOR', '').split(",") # We will accept only 2 proxies, the last ones
request.ip_proxy = proxies[0] proxies = list(
reversed(
[
i.split('%')[0]
for i in request.META.get('HTTP_X_FORWARDED_FOR', '').split(",")
]
)
)
proxies = list(reversed(['172.27.0.8', '172.27.0.128', '172.27.0.1']))
# proxies = list(reversed(['172.27.0.12', '172.27.0.1']))
# proxies = list(reversed(['172.27.0.12']))
request.ip = ''
if not request.ip or behind_proxy: logger.debug('Detected proxies: %s', proxies)
# Request.IP will be None in case of nginx & gunicorn
# Some load balancers may include "domains with a %" on x-forwarded for,
request.ip = request.ip_proxy.split('%')[0] # Stores the ip
# will raise "list out of range", leaving ip_proxy = proxy in case of no other proxy apart of nginx # Request.IP will be None in case of nginx & gunicorn using sockets, as we do
# Also, behind_proxy must be activated to work correctly (security concerns) if not request.ip:
request.ip_proxy = proxies[1].strip() if behind_proxy else request.ip request.ip = proxies[0] # Stores the ip
except Exception: proxies = proxies[1:] # Remove from proxies list
request.ip_proxy = request.ip
logger.debug('Proxies: %s', proxies)
request.ip_proxy = proxies[0] if proxies and proxies[0] else request.ip
if behind_proxy:
request.ip = request.ip_proxy
request.ip_proxy = proxies[1] if len(proxies) > 1 else request.ip
logger.debug('Behind a proxy is active')
logger.debug('ip: %s, ip_proxy: %s', request.ip, request.ip_proxy)
@staticmethod @staticmethod
def getUser(request: ExtendedHttpRequest) -> None: def getUser(request: ExtendedHttpRequest) -> None:

View File

@ -38,4 +38,6 @@ urlpatterns = [
url(r'^guacamole/(?P<tunnelId>.+)$', guacamole, name='dispatcher.guacamole'), url(r'^guacamole/(?P<tunnelId>.+)$', guacamole, name='dispatcher.guacamole'),
# New path # New path
url(r'^uds/guacamole/(?P<tunnelId>.+)$', guacamole, name='dispatcher.guacamole'), url(r'^uds/guacamole/(?P<tunnelId>.+)$', guacamole, name='dispatcher.guacamole'),
# Authenticated path
url(r'^uds/guacamole/auth/(?P<authId>.+)/(?P<tunnelId>.+)$', guacamole, name='dispatcher.guacamole'),
] ]

View File

@ -83,3 +83,9 @@ def guacamole(request: HttpRequest, tunnelId: str) -> HttpResponse:
return HttpResponse(ERROR, content_type=CONTENT_TYPE) return HttpResponse(ERROR, content_type=CONTENT_TYPE)
return HttpResponse(response, content_type=CONTENT_TYPE) return HttpResponse(response, content_type=CONTENT_TYPE)
@auth.trustedSourceRequired
def guacamole_authenticated(request: HttpRequest, authId: str, tunnelId: str) -> HttpResponse:
authId = authId[:48]
# TODO: Check the authId validity
return guacamole(request, tunnelId)

View File

@ -131,7 +131,6 @@ class Transport(ManagedObjectModel, TaggingMixin):
return self.networks.filter(net_start__lte=ip, net_end__gte=ip).count() == 0 return self.networks.filter(net_start__lte=ip, net_end__gte=ip).count() == 0
def validForOs(self, os: str) -> bool: def validForOs(self, os: str) -> bool:
logger.debug('Checkin if os "%s" is in "%s"', os, self.allowed_oss)
if not self.allowed_oss or os in self.allowed_oss.split(','): if not self.allowed_oss or os in self.allowed_oss.split(','):
return True return True
return False return False

View File

@ -198,7 +198,11 @@ class ProxmoxClient:
) )
logger.debug( logger.debug(
'DELETE result to %s: %s -- %s -- %s', path, result.status_code, result.content, result.headers 'DELETE result to %s: %s -- %s -- %s',
path,
result.status_code,
result.content,
result.headers,
) )
return ProxmoxClient.checkError(result) return ProxmoxClient.checkError(result)
@ -390,7 +394,9 @@ class ProxmoxClient:
self, vmId: int, node: typing.Optional[str] = None, purge: bool = True self, vmId: int, node: typing.Optional[str] = None, purge: bool = True
) -> types.UPID: ) -> types.UPID:
node = node or self.getVmInfo(vmId).node node = node or self.getVmInfo(vmId).node
return types.UPID.fromDict(self._delete('nodes/{}/qemu/{}?purge=1'.format(node, vmId))) return types.UPID.fromDict(
self._delete('nodes/{}/qemu/{}?purge=1'.format(node, vmId))
)
@ensureConected @ensureConected
def getTask(self, node: str, upid: str) -> types.TaskStatus: def getTask(self, node: str, upid: str) -> types.TaskStatus:
@ -426,13 +432,19 @@ class ProxmoxClient:
return sorted(result, key=lambda x: '{}{}'.format(x.node, x.name)) return sorted(result, key=lambda x: '{}{}'.format(x.node, x.name))
@ensureConected @ensureConected
@allowCache('vmip', CACHE_INFO_DURATION, cachingArgs=[1, 2], cachingKWArgs=['vmId', 'poolId'], cachingKeyFnc=cachingKeyHelper) @allowCache(
'vmip',
CACHE_INFO_DURATION,
cachingArgs=[1, 2],
cachingKWArgs=['vmId', 'poolId'],
cachingKeyFnc=cachingKeyHelper,
)
def getVMPoolInfo(self, vmId: int, poolId: str, **kwargs) -> types.VMInfo: def getVMPoolInfo(self, vmId: int, poolId: str, **kwargs) -> types.VMInfo:
# try to locate machine in pool # try to locate machine in pool
node = None node = None
if poolId: if poolId:
try: try:
for i in self._get(f'pools/{poolId}')['data']['members']: for i in self._get(f'pools/{poolId}')['data']['members']:
try: try:
if i['vmid'] == vmId: if i['vmid'] == vmId:
node = i['node'] node = i['node']
@ -445,7 +457,13 @@ class ProxmoxClient:
return self.getVmInfo(vmId, node) return self.getVmInfo(vmId, node)
@ensureConected @ensureConected
@allowCache('vmin', CACHE_INFO_DURATION, cachingArgs=[1, 2], cachingKWArgs=['vmId', 'node'], cachingKeyFnc=cachingKeyHelper) @allowCache(
'vmin',
CACHE_INFO_DURATION,
cachingArgs=[1, 2],
cachingKWArgs=['vmId', 'node'],
cachingKeyFnc=cachingKeyHelper,
)
def getVmInfo( def getVmInfo(
self, vmId: int, node: typing.Optional[str] = None, **kwargs self, vmId: int, node: typing.Optional[str] = None, **kwargs
) -> types.VMInfo: ) -> types.VMInfo:
@ -557,7 +575,7 @@ class ProxmoxClient:
self, self,
node: typing.Union[None, str, typing.Iterable[str]] = None, node: typing.Union[None, str, typing.Iterable[str]] = None,
content: typing.Optional[str] = None, content: typing.Optional[str] = None,
**kwargs **kwargs,
) -> typing.List[types.StorageInfo]: ) -> typing.List[types.StorageInfo]:
"""We use a list for storage instead of an iterator, so we can cache it...""" """We use a list for storage instead of an iterator, so we can cache it..."""
nodeList: typing.Iterable[str] nodeList: typing.Iterable[str]