Loading services/webapp/code/rosetta/core_app/utils.py +102 −3 Original line number Diff line number Diff line Loading @@ -518,10 +518,10 @@ def hash_string_to_int(string): #================================ # Tunnel setup # Tunnel (and proxy) setup #================================ def setup_tunnel(task): def setup_tunnel_and_proxy(task): # Importing here instead of on top avoids circular dependencies problems when loading booleanize in settings from .models import Task, KeyPair, TaskStatuses Loading @@ -546,8 +546,107 @@ def setup_tunnel(task): task.tcp_tunnel_port = tcp_tunnel_port task.save() # Setup the proxy now. # Some info about the various SSL switches: https://serverfault.com/questions/577616/using-https-between-apache-loadbalancer-and-backends # Check if the tunnel is active and if not create it # Esnure conf directory exists if not os.path.exists('/shared/etc_apache2_sites_enabled'): os.makedirs('/shared/etc_apache2_sites_enabled') # Set conf file name apache_conf_file = '/shared/etc_apache2_sites_enabled/{}.conf'.format(task.uuid) # Check if proxy conf exists if not os.path.exists(apache_conf_file): # Write conf file logger.debug('Writing task proxy conf to {}'.format(apache_conf_file)) websocket_protocol = 'wss' if task.container.interface_protocol == 'https' else 'ws' task_proxy_host = os.environ.get('TASK_PROXY_HOST', 'localhost') apache_conf_content = ''' #--------------------------- # Task interface proxy #--------------------------- #<Location /desktop/{0}/> #AuthType Basic #AuthName "Restricted area" #AuthUserFile /shared/reyns/etc_apache2_sites_enabled/'''+str(task.uuid)+'''.htpasswd #Require valid-user #ProxyPass http://desktop-{0}:8590/ #ProxyPassReverse http://desktop-{0}:8590/ #</Location> #<Location /sessions/{1}> #ProxyPass ws://desktop-{0}:8590/websockify #ProxyPassReverse ws://desktop-{0}:8590/websockify #</Location> Listen '''+str(task.tcp_tunnel_port)+''' <VirtualHost _default_:'''+str(task.tcp_tunnel_port)+'''> ServerName '''+task_proxy_host+''' ServerAdmin admin@rosetta SSLEngine on SSLCertificateFile /root/certificates/rosetta_platform/rosetta_platform.crt SSLCertificateKeyFile /root/certificates/rosetta_platform/rosetta_platform.key SSLCACertificateFile /root/certificates/rosetta_platform/rosetta_platform.ca-bundle SSLProxyEngine On SSLProxyVerify none SSLProxyCheckPeerCN off SSLProxyCheckPeerName off BrowserMatch "MSIE [2-6]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown # Use RewriteEngine to handle websocket connection upgrades RewriteEngine On RewriteCond %{HTTP:Connection} Upgrade [NC] RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteRule /(.*) '''+str(websocket_protocol)+'''://webapp:'''+str(task.tcp_tunnel_port)+'''/$1 [P,L] <Location "/"> # preserve Host header to avoid cross-origin problems ProxyPreserveHost on # proxy to the port ProxyPass '''+str(task.container.interface_protocol)+'''://webapp:'''+str(task.tcp_tunnel_port)+'''/ ProxyPassReverse '''+str(task.container.interface_protocol)+'''://webapp:'''+str(task.tcp_tunnel_port)+'''/ </Location> </VirtualHost> ''' with open(apache_conf_file, 'w') as f: f.write(apache_conf_content) # Now check conf exist on proxy logger.debug('Checking if conf is enabled on proxy service') out = os_shell('ssh -o StrictHostKeyChecking=no proxy "[ -e /etc/apache2/sites-enabled/{}.conf ]"'.format(task.uuid), capture=True) if out.exit_code == 1: logger.debug('Conf not enabled on proxy service, linkig it and reloading Apache conf') # Link on proxy since conf does not exist out = os_shell('ssh -o StrictHostKeyChecking=no proxy "sudo ln -s /shared/etc_apache2_sites_enabled/{0}.conf /etc/apache2/sites-enabled/{0}.conf"'.format(task.uuid), capture=True) if out.exit_code != 0: logger.error(out.stderr) raise ErrorMessage('Somthing went wrong when activating the task proxy conf') # Reload apache conf on Proxy out = os_shell('ssh -o StrictHostKeyChecking=no proxy "sudo apache2ctl graceful"', capture=True) if out.exit_code != 0: logger.error(out.stderr) raise ErrorMessage('Somthing went wrong when loading the task proxy conf') # Check if the tunnel is (still) active and if not create it logger.debug('Checking if task "{}" has a running tunnel'.format(task)) out = os_shell('ps -ef | grep ":{}:{}:{}" | grep -v grep'.format(task.tcp_tunnel_port, task.interface_ip, task.interface_port), capture=True) Loading services/webapp/code/rosetta/core_app/views.py +18 −9 Original line number Diff line number Diff line Loading @@ -10,7 +10,7 @@ from django.contrib.auth.models import User from django.shortcuts import redirect from django.db.models import Q from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, ComputingSysConf, ComputingUserConf, Text from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param, get_tunnel_host, random_username, setup_tunnel, finalize_user_creation from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param, get_tunnel_host, random_username, setup_tunnel_and_proxy, finalize_user_creation from .decorators import public_view, private_view from .exceptions import ErrorMessage Loading Loading @@ -341,7 +341,7 @@ def tasks(request): elif action=='connect': # First ensure that the tunnel is setu up setup_tunnel(task) setup_tunnel_and_proxy(task) # Then, redirect to the task through the tunnel tunnel_host = get_tunnel_host() Loading Loading @@ -972,16 +972,25 @@ def direct_connection_handler(request, uuid): raise ErrorMessage('You do not have access to this task.') # First ensure that the tunnel is setu up setup_tunnel(task) setup_tunnel_and_proxy(task) # Set task proxy host task_proxy_host = settings.TASK_PROXY_HOST # Then, redirect to the task through the tunnel tunnel_host = get_tunnel_host() if task.requires_proxy: if task.requires_proxy_auth and task.auth_token: user = request.user.email password = task.auth_token return redirect('{}://{}:{}@{}:{}'.format(task.container.interface_protocol, user, password, tunnel_host, task.tcp_tunnel_port)) redirect_string = 'https://{}:{}@{}:{}'.format(user, password, task_proxy_host, task.tcp_tunnel_port) else: return redirect('{}://{}:{}'.format(task.container.interface_protocol, tunnel_host,task.tcp_tunnel_port)) redirect_string = 'https://{}:{}'.format(task_proxy_host, task.tcp_tunnel_port) else: redirect_string = '{}://{}:{}'.format(task.container.interface_protocol, tunnel_host,task.tcp_tunnel_port) logger.debug('Task direct connect redirect: "{}"'.format(redirect_string)) return redirect(redirect_string) Loading @@ -999,7 +1008,7 @@ def sharable_link_handler(request, short_uuid): raise ErrorMessage('You do not have access to this task.') # First ensure that the tunnel is setu up setup_tunnel(task) setup_tunnel_and_proxy(task) # Then, redirect to the task through the tunnel tunnel_host = get_tunnel_host() Loading services/webapp/code/rosetta/settings.py +2 −0 Original line number Diff line number Diff line Loading @@ -230,6 +230,8 @@ LOCAL_USER_DATA_DIR = os.environ.get('LOCAL_USER_DATA_DIR', '/data') # Invitation code if any INVITATION_CODE = os.environ.get('INVITATION_CODE', None) # Task proxy host (WARNING: direct use of the env var in utils.py) TASK_PROXY_HOST = os.environ.get('TASK_PROXY_HOST', 'localhost') #=============================== # Auth Loading Loading
services/webapp/code/rosetta/core_app/utils.py +102 −3 Original line number Diff line number Diff line Loading @@ -518,10 +518,10 @@ def hash_string_to_int(string): #================================ # Tunnel setup # Tunnel (and proxy) setup #================================ def setup_tunnel(task): def setup_tunnel_and_proxy(task): # Importing here instead of on top avoids circular dependencies problems when loading booleanize in settings from .models import Task, KeyPair, TaskStatuses Loading @@ -546,8 +546,107 @@ def setup_tunnel(task): task.tcp_tunnel_port = tcp_tunnel_port task.save() # Setup the proxy now. # Some info about the various SSL switches: https://serverfault.com/questions/577616/using-https-between-apache-loadbalancer-and-backends # Check if the tunnel is active and if not create it # Esnure conf directory exists if not os.path.exists('/shared/etc_apache2_sites_enabled'): os.makedirs('/shared/etc_apache2_sites_enabled') # Set conf file name apache_conf_file = '/shared/etc_apache2_sites_enabled/{}.conf'.format(task.uuid) # Check if proxy conf exists if not os.path.exists(apache_conf_file): # Write conf file logger.debug('Writing task proxy conf to {}'.format(apache_conf_file)) websocket_protocol = 'wss' if task.container.interface_protocol == 'https' else 'ws' task_proxy_host = os.environ.get('TASK_PROXY_HOST', 'localhost') apache_conf_content = ''' #--------------------------- # Task interface proxy #--------------------------- #<Location /desktop/{0}/> #AuthType Basic #AuthName "Restricted area" #AuthUserFile /shared/reyns/etc_apache2_sites_enabled/'''+str(task.uuid)+'''.htpasswd #Require valid-user #ProxyPass http://desktop-{0}:8590/ #ProxyPassReverse http://desktop-{0}:8590/ #</Location> #<Location /sessions/{1}> #ProxyPass ws://desktop-{0}:8590/websockify #ProxyPassReverse ws://desktop-{0}:8590/websockify #</Location> Listen '''+str(task.tcp_tunnel_port)+''' <VirtualHost _default_:'''+str(task.tcp_tunnel_port)+'''> ServerName '''+task_proxy_host+''' ServerAdmin admin@rosetta SSLEngine on SSLCertificateFile /root/certificates/rosetta_platform/rosetta_platform.crt SSLCertificateKeyFile /root/certificates/rosetta_platform/rosetta_platform.key SSLCACertificateFile /root/certificates/rosetta_platform/rosetta_platform.ca-bundle SSLProxyEngine On SSLProxyVerify none SSLProxyCheckPeerCN off SSLProxyCheckPeerName off BrowserMatch "MSIE [2-6]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown # Use RewriteEngine to handle websocket connection upgrades RewriteEngine On RewriteCond %{HTTP:Connection} Upgrade [NC] RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteRule /(.*) '''+str(websocket_protocol)+'''://webapp:'''+str(task.tcp_tunnel_port)+'''/$1 [P,L] <Location "/"> # preserve Host header to avoid cross-origin problems ProxyPreserveHost on # proxy to the port ProxyPass '''+str(task.container.interface_protocol)+'''://webapp:'''+str(task.tcp_tunnel_port)+'''/ ProxyPassReverse '''+str(task.container.interface_protocol)+'''://webapp:'''+str(task.tcp_tunnel_port)+'''/ </Location> </VirtualHost> ''' with open(apache_conf_file, 'w') as f: f.write(apache_conf_content) # Now check conf exist on proxy logger.debug('Checking if conf is enabled on proxy service') out = os_shell('ssh -o StrictHostKeyChecking=no proxy "[ -e /etc/apache2/sites-enabled/{}.conf ]"'.format(task.uuid), capture=True) if out.exit_code == 1: logger.debug('Conf not enabled on proxy service, linkig it and reloading Apache conf') # Link on proxy since conf does not exist out = os_shell('ssh -o StrictHostKeyChecking=no proxy "sudo ln -s /shared/etc_apache2_sites_enabled/{0}.conf /etc/apache2/sites-enabled/{0}.conf"'.format(task.uuid), capture=True) if out.exit_code != 0: logger.error(out.stderr) raise ErrorMessage('Somthing went wrong when activating the task proxy conf') # Reload apache conf on Proxy out = os_shell('ssh -o StrictHostKeyChecking=no proxy "sudo apache2ctl graceful"', capture=True) if out.exit_code != 0: logger.error(out.stderr) raise ErrorMessage('Somthing went wrong when loading the task proxy conf') # Check if the tunnel is (still) active and if not create it logger.debug('Checking if task "{}" has a running tunnel'.format(task)) out = os_shell('ps -ef | grep ":{}:{}:{}" | grep -v grep'.format(task.tcp_tunnel_port, task.interface_ip, task.interface_port), capture=True) Loading
services/webapp/code/rosetta/core_app/views.py +18 −9 Original line number Diff line number Diff line Loading @@ -10,7 +10,7 @@ from django.contrib.auth.models import User from django.shortcuts import redirect from django.db.models import Q from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, ComputingSysConf, ComputingUserConf, Text from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param, get_tunnel_host, random_username, setup_tunnel, finalize_user_creation from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param, get_tunnel_host, random_username, setup_tunnel_and_proxy, finalize_user_creation from .decorators import public_view, private_view from .exceptions import ErrorMessage Loading Loading @@ -341,7 +341,7 @@ def tasks(request): elif action=='connect': # First ensure that the tunnel is setu up setup_tunnel(task) setup_tunnel_and_proxy(task) # Then, redirect to the task through the tunnel tunnel_host = get_tunnel_host() Loading Loading @@ -972,16 +972,25 @@ def direct_connection_handler(request, uuid): raise ErrorMessage('You do not have access to this task.') # First ensure that the tunnel is setu up setup_tunnel(task) setup_tunnel_and_proxy(task) # Set task proxy host task_proxy_host = settings.TASK_PROXY_HOST # Then, redirect to the task through the tunnel tunnel_host = get_tunnel_host() if task.requires_proxy: if task.requires_proxy_auth and task.auth_token: user = request.user.email password = task.auth_token return redirect('{}://{}:{}@{}:{}'.format(task.container.interface_protocol, user, password, tunnel_host, task.tcp_tunnel_port)) redirect_string = 'https://{}:{}@{}:{}'.format(user, password, task_proxy_host, task.tcp_tunnel_port) else: return redirect('{}://{}:{}'.format(task.container.interface_protocol, tunnel_host,task.tcp_tunnel_port)) redirect_string = 'https://{}:{}'.format(task_proxy_host, task.tcp_tunnel_port) else: redirect_string = '{}://{}:{}'.format(task.container.interface_protocol, tunnel_host,task.tcp_tunnel_port) logger.debug('Task direct connect redirect: "{}"'.format(redirect_string)) return redirect(redirect_string) Loading @@ -999,7 +1008,7 @@ def sharable_link_handler(request, short_uuid): raise ErrorMessage('You do not have access to this task.') # First ensure that the tunnel is setu up setup_tunnel(task) setup_tunnel_and_proxy(task) # Then, redirect to the task through the tunnel tunnel_host = get_tunnel_host() Loading
services/webapp/code/rosetta/settings.py +2 −0 Original line number Diff line number Diff line Loading @@ -230,6 +230,8 @@ LOCAL_USER_DATA_DIR = os.environ.get('LOCAL_USER_DATA_DIR', '/data') # Invitation code if any INVITATION_CODE = os.environ.get('INVITATION_CODE', None) # Task proxy host (WARNING: direct use of the env var in utils.py) TASK_PROXY_HOST = os.environ.get('TASK_PROXY_HOST', 'localhost') #=============================== # Auth Loading