diff --git a/services/webapp/code/rosetta/core_app/utils.py b/services/webapp/code/rosetta/core_app/utils.py index afba4a7e7d79bd2de781e196e1796578a95638ee..a8b8e791bf4dd6b4262941b5f9978765ebb7c8f4 100644 --- a/services/webapp/code/rosetta/core_app/utils.py +++ b/services/webapp/code/rosetta/core_app/utils.py @@ -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 @@ -545,9 +545,108 @@ 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 + + # 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 active and if not create it + # 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) diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py index bc01e877bf0ee67fa510c6fb45808fdc40daa7a1..2dc05f1442c8b249bf6e28804de6cc58692ec094 100644 --- a/services/webapp/code/rosetta/core_app/views.py +++ b/services/webapp/code/rosetta/core_app/views.py @@ -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 @@ -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() @@ -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_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)) + if task.requires_proxy: + if task.requires_proxy_auth and task.auth_token: + user = request.user.email + password = task.auth_token + redirect_string = 'https://{}:{}@{}:{}'.format(user, password, task_proxy_host, task.tcp_tunnel_port) + else: + redirect_string = 'https://{}:{}'.format(task_proxy_host, task.tcp_tunnel_port) else: - return redirect('{}://{}:{}'.format(task.container.interface_protocol, tunnel_host,task.tcp_tunnel_port)) + 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) @@ -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() diff --git a/services/webapp/code/rosetta/settings.py b/services/webapp/code/rosetta/settings.py index 436f9d8ed56adda20eeba27ca57f2c62a885f1a9..aaba4219f24cded117fd43e5dbd8bf5ae85c516a 100644 --- a/services/webapp/code/rosetta/settings.py +++ b/services/webapp/code/rosetta/settings.py @@ -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