diff --git a/containers/MetaDesktop/files/entrypoint.sh b/containers/MetaDesktop/files/entrypoint.sh index efe8414b605e6a4c743c2b0164cadf67c723ce05..f818af5cfcf39cb3b58fdd7b5dc7120e4de5490d 100644 --- a/containers/MetaDesktop/files/entrypoint.sh +++ b/containers/MetaDesktop/files/entrypoint.sh @@ -49,6 +49,22 @@ while read env_var; do fi done +#--------------------- +# Password +#--------------------- + +if [ "x$AUTH_PASS" != "x" ]; then + echo "[INFO] Setting up VNC password..." + mkdir -p /metauser/.vnc + /opt/tigervnc/usr/bin/vncpasswd -f <<< $AUTH_PASS > /metauser/.vnc/passwd + chmod 600 /metauser/.vnc/passwd + export VNC_AUTH=True +else + echo "[INFO] Not setting up any VNC password" + +fi + + #--------------------- # Entrypoint command #--------------------- diff --git a/containers/MetaDesktop/files/run_vnc.sh b/containers/MetaDesktop/files/run_vnc.sh index 628f8e88542e75a9918b98db99d8cfdcd6cac281..d42a22269cfdff10ccc212539cde7c283460e8f6 100755 --- a/containers/MetaDesktop/files/run_vnc.sh +++ b/containers/MetaDesktop/files/run_vnc.sh @@ -1,7 +1,13 @@ #!/bin/bash # Exec TigerVNC server -/opt/tigervnc/usr/bin/vncserver :0 -SecurityTypes None -xstartup /opt/tigervnc/xstartup + +if [ "x$VNC_AUTH" == "xTrue" ]; then + /opt/tigervnc/usr/bin/vncserver :0 -SecurityTypes vncauth,tlsvnc -xstartup /opt/tigervnc/xstartup +else + /opt/tigervnc/usr/bin/vncserver :0 -SecurityTypes None -xstartup /opt/tigervnc/xstartup +fi + # Check it is running. If it is not, exit while true diff --git a/containers/MetaDesktop/push.sh b/containers/MetaDesktop/push.sh new file mode 100755 index 0000000000000000000000000000000000000000..22c2868f450fcc1eda88859723d1521a0b52ccef --- /dev/null +++ b/containers/MetaDesktop/push.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker tag rosetta/metadesktop localhost:5000/rosetta/metadesktop +docker push localhost:5000/rosetta/metadesktop diff --git a/services/webapp/code/rosetta/base_app/admin.py b/services/webapp/code/rosetta/base_app/admin.py index 3b91d61fdf2e3927d0fe68c029915c1fd991d051..21bc697ddbe20cd00e6d9cd1f841ab3d916db4ed 100644 --- a/services/webapp/code/rosetta/base_app/admin.py +++ b/services/webapp/code/rosetta/base_app/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Profile, LoginToken, Task, Container, Computing, ComputingSysConf, ComputingUserConf +from .models import Profile, LoginToken, Task, Container, Computing, ComputingSysConf, ComputingUserConf, Keys admin.site.register(Profile) admin.site.register(LoginToken) @@ -8,4 +8,5 @@ admin.site.register(Task) admin.site.register(Container) admin.site.register(Computing) admin.site.register(ComputingSysConf) -admin.site.register(ComputingUserConf) \ No newline at end of file +admin.site.register(ComputingUserConf) +admin.site.register(Keys) diff --git a/services/webapp/code/rosetta/base_app/decorators.py b/services/webapp/code/rosetta/base_app/decorators.py index b8ef2d0f1744032f04017ec06450ae58b896b155..1a7e71018843d9c043609f185cd8648e50c59873 100644 --- a/services/webapp/code/rosetta/base_app/decorators.py +++ b/services/webapp/code/rosetta/base_app/decorators.py @@ -2,7 +2,7 @@ import inspect from django.conf import settings from django.shortcuts import render -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from .utils import format_exception, log_user_activity from .exceptions import ErrorMessage, ConsistencyException diff --git a/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py b/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py index 1b91182bc7b37f03deeb9a5553e71203c7152f54..68d997f3cbfe050bc372505e3b3ae242b7b714e2 100644 --- a/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py +++ b/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand from django.contrib.auth.models import User -from ...models import Profile, Container, Computing, ComputingSysConf, ComputingUserConf +from ...models import Profile, Container, Computing, ComputingSysConf, ComputingUserConf, Keys class Command(BaseCommand): help = 'Adds the admin superuser with \'a\' password.' @@ -31,6 +31,13 @@ class Command(BaseCommand): testuser.save() print('Creating testuser profile') Profile.objects.create(user=testuser, authtoken='129aac94-284a-4476-953c-ffa4349b4a50') + + # Create default keys + print('Creating testuser defualt keys') + Keys.objects.create(user = testuser, + default = True, + private_key_file = '/rosetta/.ssh/id_rsa', + public_key_file = '/rosetta/.ssh/id_rsa.pub') # Public containers @@ -46,7 +53,8 @@ class Command(BaseCommand): image = 'rosetta/metadesktop', type = 'docker', registry = 'docker_local', - service_ports = '8590') + service_ports = '8590', + require_pass = True) # MetaDesktop Singularity Container.objects.create(user = None, @@ -54,7 +62,8 @@ class Command(BaseCommand): image = 'rosetta/metadesktop', type = 'singularity', registry = 'docker_local', - service_ports = '8590') + service_ports = '8590', + require_pass = True) # Astrocook Container.objects.create(user = None, @@ -87,37 +96,57 @@ class Command(BaseCommand): else: print('Creating demo computing resources containers...') - # Local computing resource + #============================== + # Local remote computing + #============================== Computing.objects.create(user = None, name = 'Local', - type = 'local') - - # Demo remote computing resource + type = 'local', + require_sys_conf = False, + require_user_conf = False, + require_user_keys = False) + + + #============================== + # Demo remote computing + #============================== demo_remote_computing = Computing.objects.create(user = None, - name = 'Demo remote', - type = 'remote', - requires_sys_conf = True, - requires_user_conf = False) - - # Create demo remote sys computing conf + name = 'Demo remote', + type = 'remote', + require_sys_conf = True, + require_user_conf = False, + require_user_keys = False) ComputingSysConf.objects.create(computing = demo_remote_computing, data = {'host': 'slurmclusterworker-one'}) - # Create demo remote user computing conf - ComputingUserConf.objects.create(user = testuser, - computing = demo_remote_computing, - data = {'user': 'testuser', - 'id_rsa': '/rosetta/.ssh/id_rsa', - 'id_rsa.pub': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2n4wiLiRmE1sla5+w0IW3wwPW/mqhhkm7IyCBS+rGTgnts7xsWcxobvamNdD6KSLNnjFZbBb7Yaf/BvWrwQgdqIFVU3gRWHYzoU6js+lKtBjd0e2DAVGivWCKEkSGLx7zhx7uH/Jt8kyZ4NaZq0p5+SFHBzePdR/1rURd8G8+G3OaCPKqP+JQT4RMUQHC5SNRJLcK1piYdmhDiYEyuQG4FlStKCWLCXeUY2EVirNMeQIfOgbUHJsVjH07zm1y8y7lTWDMWVZOnkG6Ap5kB+n4l1eWbslOKgDv29JTFOMU+bvGvYZh70lmLK7Hg4CMpXVgvw5VF9v97YiiigLwvC7wasBHaASwH7wUqakXYhdGFxJ23xVMSLnvJn4S++4L8t8bifRIVqhT6tZCPOU4fdOvJKCRjKrf7gcW/E33ovZFgoOCJ2vBLIh9N9ME0v7tG15JpRtgIBsCXwLcl3tVyCZJ/eyYMbc3QJGsbcPGb2CYRjDbevPCQlNavcMdlyrNIke7VimM5aW8OBJKVh5wCNRpd9XylrKo1cZHYxu/c5Lr6VUZjLpxDlSz+IuTn4VE7vmgHNPnXdlxRKjLHG/FZrZTSCWFEBcRoSa/hysLSFwwDjKd9nelOZRNBvJ+NY48vA8ixVnk4WAMlR/5qhjTRam66BVysHeRcbjJ2IGjwTJC5Q== rosetta@rosetta.platform'}) + #============================== + # Demo remote (auth) computing + #============================== + demo_remote_auth_computing = Computing.objects.create(user = None, + name = 'Demo remote (auth)', + type = 'remote', + require_sys_conf = True, + require_user_conf = True, + require_user_keys = True) + + ComputingSysConf.objects.create(computing = demo_remote_auth_computing, + data = {'host': 'slurmclusterworker-one'}) + ComputingUserConf.objects.create(user = testuser, + computing = demo_remote_auth_computing, + data = {'user': 'testuser'}) + - # Demo slurm computing resource + #============================== + # Demo Slurm computing + #============================== demo_slurm_computing = Computing.objects.create(user = None, - name = 'Demo Slurm', - type = 'slurm', - requires_sys_conf = True, - requires_user_conf = True) + name = 'Demo Slurm', + type = 'slurm', + require_sys_conf = True, + require_user_conf = True, + require_user_keys = True) # Create demo slurm sys computing conf ComputingSysConf.objects.create(computing = demo_slurm_computing, @@ -126,10 +155,6 @@ class Command(BaseCommand): # Create demo slurm user computing conf ComputingUserConf.objects.create(user = testuser, computing = demo_slurm_computing, - data = {'user': 'testuser', - 'id_rsa': '/rosetta/.ssh/id_rsa', - 'id_rsa.pub': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2n4wiLiRmE1sla5+w0IW3wwPW/mqhhkm7IyCBS+rGTgnts7xsWcxobvamNdD6KSLNnjFZbBb7Yaf/BvWrwQgdqIFVU3gRWHYzoU6js+lKtBjd0e2DAVGivWCKEkSGLx7zhx7uH/Jt8kyZ4NaZq0p5+SFHBzePdR/1rURd8G8+G3OaCPKqP+JQT4RMUQHC5SNRJLcK1piYdmhDiYEyuQG4FlStKCWLCXeUY2EVirNMeQIfOgbUHJsVjH07zm1y8y7lTWDMWVZOnkG6Ap5kB+n4l1eWbslOKgDv29JTFOMU+bvGvYZh70lmLK7Hg4CMpXVgvw5VF9v97YiiigLwvC7wasBHaASwH7wUqakXYhdGFxJ23xVMSLnvJn4S++4L8t8bifRIVqhT6tZCPOU4fdOvJKCRjKrf7gcW/E33ovZFgoOCJ2vBLIh9N9ME0v7tG15JpRtgIBsCXwLcl3tVyCZJ/eyYMbc3QJGsbcPGb2CYRjDbevPCQlNavcMdlyrNIke7VimM5aW8OBJKVh5wCNRpd9XylrKo1cZHYxu/c5Lr6VUZjLpxDlSz+IuTn4VE7vmgHNPnXdlxRKjLHG/FZrZTSCWFEBcRoSa/hysLSFwwDjKd9nelOZRNBvJ+NY48vA8ixVnk4WAMlR/5qhjTRam66BVysHeRcbjJ2IGjwTJC5Q== rosetta@rosetta.platform'}) - - + data = {'user': 'testuser'}) diff --git a/services/webapp/code/rosetta/base_app/models.py b/services/webapp/code/rosetta/base_app/models.py index f7388993aa9425954cbb74c184bc47e1d71abc9e..67ee3da0f940c4ad58f0e30460384067e8e2696b 100644 --- a/services/webapp/code/rosetta/base_app/models.py +++ b/services/webapp/code/rosetta/base_app/models.py @@ -75,6 +75,8 @@ class Container(models.Model): registry = models.CharField('Container registry', max_length=255, blank=False, null=False) service_ports = models.CharField('Container service ports', max_length=36, blank=True, null=True) #private = models.BooleanField('Container is private and needs auth to be pulled from the registry') + require_user = models.BooleanField(default=False) + require_pass = models.BooleanField(default=False) def __str__(self): return str('Container of type "{}" with image "{}" with service ports "{}" from registry "{}" of user "{}"'.format(self.type, self.image, self.service_ports, self.registry, self.user)) @@ -103,11 +105,17 @@ class Computing(models.Model): name = models.CharField('Computing Name', max_length=255, blank=False, null=False) type = models.CharField('Computing Type', max_length=255, blank=False, null=False) - requires_sys_conf = models.BooleanField(default=False) - requires_user_conf = models.BooleanField(default=False) + require_sys_conf = models.BooleanField(default=False) + require_user_conf = models.BooleanField(default=False) + require_user_keys = models.BooleanField(default=False) + def __str__(self): - return str('Computing Resource "{}" of user "{}"'.format(self.name, self.user)) + if self.user: + return str('Computing Resource "{}" of user "{}"'.format(self.name, self.user)) + else: + return str('Computing Resource "{}"'.format(self.name)) + @property def id(self): @@ -201,6 +209,10 @@ class ComputingSysConf(models.Model): def id(self): return str(self.uuid).split('-')[0] + def __str__(self): + return str('Computing sys conf for {} with id "{}"'.format(self.computing, self.id)) + + class ComputingUserConf(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -210,7 +222,7 @@ class ComputingUserConf(models.Model): @property def id(self): - return str(self.uuid).split('-')[0] + return str('Computing sys conf for {} with id "{}" of user "{}"'.format(self.computing, self.id, self.user)) #========================= @@ -234,7 +246,7 @@ class Task(models.Model): # Auth auth_user = models.CharField('Task auth user', max_length=36, blank=True, null=True) - auth_password = models.CharField('Task auth password', max_length=36, blank=True, null=True) + auth_pass = models.CharField('Task auth pass', max_length=36, blank=True, null=True) access_method = models.CharField('Task access method', max_length=36, blank=True, null=True) def save(self, *args, **kwargs): @@ -279,5 +291,23 @@ class Task(models.Model): return str(self.uuid).split('-')[0] +#========================= +# Keys +#========================= +class Keys(models.Model): + + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, null=False) + + private_key_file = models.CharField('Private key file', max_length=4096, blank=False, null=False) + public_key_file = models.CharField('Public key file', max_length=4096, blank=False, null=False) + default = models.BooleanField('Default keys?', default=False) + + def __str__(self): + return str('Keys with id "{}" of user "{}"'.format(self.id, self.user)) + + @property + def id(self): + return str(self.uuid).split('-')[0] diff --git a/services/webapp/code/rosetta/base_app/tasks.py b/services/webapp/code/rosetta/base_app/tasks.py index b2102b9d74e8261f74b598c87bdc995c886fc667..cdf306e193cf3635998f5d675371b37c334a2659 100644 --- a/services/webapp/code/rosetta/base_app/tasks.py +++ b/services/webapp/code/rosetta/base_app/tasks.py @@ -1,4 +1,4 @@ -from .models import TaskStatuses +from .models import TaskStatuses, Keys from .utils import os_shell from .exceptions import ErrorMessage, ConsistencyException @@ -23,6 +23,10 @@ def start_task(task): # Init run command #--cap-add=NET_ADMIN --cap-add=NET_RAW run_command = 'sudo docker run --network=rosetta_default --name rosetta-task-{}'.format( task.id) + # Pass if any + if task.auth_pass: + run_command += ' -eAUTH_PASS={} '.format(task.auth_pass) + # Data volume run_command += ' -v {}/task-{}:/data'.format(TASK_DATA_DIR, task.id) @@ -67,16 +71,28 @@ def start_task(task): host = task.computing.get_conf_param('host') # Get id_rsa - id_rsa_file = task.computing.get_conf_param('id_rsa') - if not id_rsa_file: - raise Exception('This computing requires an id_rsa file but cannot find any') + #id_rsa_file = task.computing.get_conf_param('id_rsa') + #if not id_rsa_file: + # raise Exception('This computing requires an id_rsa file but cannot find any') + + # Get user keys + if task.computing.require_user_keys: + user_keys = Keys.objects.get(user=task.user, default=True) + else: + raise NotImplementedError('Remote tasks not requiring keys are not yet supported') # 1) Run the container on the host (non blocking) if task.container.type == 'singularity': - - run_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {} '.format(id_rsa_file, host) - run_command += '"export SINGULARITY_NOHTTPS=true && ' + + # Set pass if any + if task.auth_pass: + authstring = ' export SINGULARITYENV_AUTH_PASS={} && '.format(task.auth_pass) + else: + authstring = '' + + run_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {} '.format(user_keys.private_key_file, host) + run_command += '"export SINGULARITY_NOHTTPS=true && {} '.format(authstring) run_command += 'exec nohup singularity run --pid --writable-tmpfs --containall --cleanenv ' # Set registry @@ -139,14 +155,17 @@ def stop_task(task): elif task.computing.type == 'remote': + # Get user keys + if task.computing.require_user_keys: + user_keys = Keys.objects.get(user=task.user, default=True) + else: + raise NotImplementedError('Remote tasks not requiring keys are not yet supported') + # Get computing host host = task.computing.get_conf_param('host') - # Get id_rsa - id_rsa_file = task.computing.get_conf_param('id_rsa') - # Stop the task remotely - stop_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {} "kill -9 {}"'.format(id_rsa_file, host, task.pid) + stop_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {} "kill -9 {}"'.format(user_keys.private_key_file, host, task.pid) logger.debug(stop_command) out = os_shell(stop_command, capture=True) if out.exit_code != 0: diff --git a/services/webapp/code/rosetta/base_app/templates/components/task.html b/services/webapp/code/rosetta/base_app/templates/components/task.html index a075868e6c061734945b3491bec641db37819edc..2eb422af13c4456c9bd06973dba0a8da5b9bdc75 100644 --- a/services/webapp/code/rosetta/base_app/templates/components/task.html +++ b/services/webapp/code/rosetta/base_app/templates/components/task.html @@ -26,6 +26,12 @@ <td>{{ task.created }}</td> </tr> + + <tr> + <td><b>Running on</b></td> + <td>{{ task.computing }}</td> + </tr> + <!-- <tr> <td><b>Task pid</b></td> <td>{{ task.pid}}</td> @@ -45,7 +51,15 @@ <td><b>Task tunnel port</b></td> <td>{{ task.tunnel_port }}</td> </tr> + + {% if task.auth_pass %} + <tr> + <td><b>Task Auth pass</b></td> + <td>{{ task.auth_pass }}</td> + </tr> + {% endif %} + <tr> <td><b>Operations</b></td> diff --git a/services/webapp/code/rosetta/base_app/templates/computings.html b/services/webapp/code/rosetta/base_app/templates/computings.html index 26982d46d0aca41620460ea4734b8f62ccb74674..b6dbb3fa2f11fde34c1026379c51130a244cfe05 100644 --- a/services/webapp/code/rosetta/base_app/templates/computings.html +++ b/services/webapp/code/rosetta/base_app/templates/computings.html @@ -18,19 +18,14 @@ <hr/> {% if data.computing %} - {% include "components/computing.html" with computing=data.computin %} + {% include "components/computing.html" with computing=data.computing %} {% else %} - {% for computing in data.platform_computings %} + {% for computing in data.computings %} {% include "components/computing.html" with computing=computing %} <br /> {% endfor %} - - - {% for computing in data.user_computings %} - {% include "components/computing.html" with computing=computing %} - <br /> - {% endfor %} + <br /> <a href="/add_computing">Add new...</a> diff --git a/services/webapp/code/rosetta/base_app/templates/create_task.html b/services/webapp/code/rosetta/base_app/templates/create_task.html index d3119ff5650dfc4ab96d0ba4e5ac9b47b89b029b..ccc1e7a1b391c459b1db56c507d1a6b4aa498d0b 100644 --- a/services/webapp/code/rosetta/base_app/templates/create_task.html +++ b/services/webapp/code/rosetta/base_app/templates/create_task.html @@ -74,23 +74,28 @@ <form action="/create_task/" method="POST"> {% csrf_token %} <input type="hidden" name="step" value="two" /> - <input type="hidden" name="task_uuid" value="{{ data.task_uuid }}" /> + <input type="hidden" name="task_uuid" value="{{ data.task.uuid }}" /> <table class="dashboard" style="max-width:700px"> - + + {% if data.task.container.require_user %} <tr> <td><b>Task user</b></td> <td> <input type="text" name="auth_user" value="" placeholder="" size="23" /> </td> </tr> - + {% endif %} + + {% if data.task.container.require_pass %} <tr> - <td><b>Task password</b></td> + <td valign="top"><b>Set task password</b></td> <td> - <input type="password" name="auth_password" value="" placeholder="" size="23" /> + <input type="password" name="auth_password" value="" placeholder="" size="23" /><br> + <font size=-1>Use a non-sensitive password as it will be stored in plain text. 6 chars min.</font> </td> </tr> + {% endif %} <tr> <td><b>Access method</b></td><td> diff --git a/services/webapp/code/rosetta/base_app/views.py b/services/webapp/code/rosetta/base_app/views.py index 16b78ee7de5a6d57c9943ac7daeca040740098b4..043ad2a8ee87d046c2542930bbab5b5348dabf58 100644 --- a/services/webapp/code/rosetta/base_app/views.py +++ b/services/webapp/code/rosetta/base_app/views.py @@ -6,7 +6,7 @@ from django.contrib.auth import authenticate, login, logout from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.models import User from django.shortcuts import redirect -from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing +from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, Keys from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param from .decorators import public_view, private_view from .tasks import start_task, stop_task @@ -435,7 +435,7 @@ def create_task(request): # Set step and task uuid data['step'] = 'two' - data['task_uuid'] = task.uuid + data['task'] = task elif step == 'two': @@ -444,10 +444,13 @@ def create_task(request): task = _task_cache[task_uuid] # Add auth - task.task_auth_user = request.POST.get('auth_user', None) - task.task_auth_password = request.POST.get('auth_password', None) - task.task_access_method = request.POST.get('access_method', None) + task.auth_user = request.POST.get('auth_user', None) + task.auth_pass = request.POST.get('auth_password', None) + task.access_method = request.POST.get('access_method', None) + # Cheks + if len(task.auth_pass) < 6: + raise ErrorMessage('Task password must be at least 6 chars') # Add auth and/or computing parameters to the task if any # TODO... (i..e num cores) @@ -524,7 +527,11 @@ def task_log(request): host = task.computing.get_conf_param('host') # Get id_rsa - id_rsa_file = task.computing.get_conf_param('id_rsa') + if task.computing.require_user_keys: + user_keys = Keys.objects.get(user=task.user, default=True) + id_rsa_file = user_keys.private_key_file + else: + raise NotImplementedError('temote with no keys not yet') # View the Singularity container log view_log_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {} "cat /tmp/{}.log"'.format(id_rsa_file, host, task.uuid) @@ -682,10 +689,10 @@ def computings(request): data={} data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) - data['title'] = 'Add computing' + data['title'] = 'Computing resources' data['name'] = request.POST.get('name',None) - data['computings'] = list(Computing.objects.filter(user=None)) + Computing.objects.filter(user=request.user) + data['computings'] = list(Computing.objects.filter(user=None)) + list(Computing.objects.filter(user=request.user)) # Attach user conf in any for computing in data['computings']: