diff --git a/computes/forms.py b/computes/forms.py index a626106c..8feaa0e8 100644 --- a/computes/forms.py +++ b/computes/forms.py @@ -1,4 +1,5 @@ import re +import json from django import forms from django.utils.translation import ugettext_lazy as _ from computes.models import Compute @@ -13,6 +14,8 @@ class ComputeAddTcpForm(forms.Form): max_length=100) password = forms.CharField(error_messages={'required': _('No password has been entered')}, max_length=100) + gstfsd_key = forms.CharField(max_length=256, required=False) + def clean_name(self): name = self.cleaned_data['name'] @@ -41,6 +44,20 @@ def clean_hostname(self): return hostname raise forms.ValidationError(_('This host is already connected')) + def clean_gstfsd_key(self): + gstfsd_key = self.cleaned_data['gstfsd_key'] + try: + data = json.loads(gstfsd_key) + if not isinstance(data, dict): + raise forms.ValidationError(_('Gstfsd key must be a json object')) + if not 'k' in data: + raise forms.ValidationError(_('Gstfsd key must have a "k" field')) + if not 'kty' in data: + raise forms.ValidationError(_('Gstfsd key must have a "kty" field')) + except ValueError: + raise forms.ValidationError(_('Gstfsd key must be a valid json')) + return gstfsd_key + class ComputeAddSshForm(forms.Form): name = forms.CharField(error_messages={'required': _('No hostname has been entered')}, @@ -49,6 +66,8 @@ class ComputeAddSshForm(forms.Form): max_length=100) login = forms.CharField(error_messages={'required': _('No login has been entered')}, max_length=20) + gstfsd_key = forms.CharField(max_length=256, required=False) + def clean_name(self): name = self.cleaned_data['name'] @@ -77,6 +96,20 @@ def clean_hostname(self): return hostname raise forms.ValidationError(_('This host is already connected')) + def clean_gstfsd_key(self): + gstfsd_key = self.cleaned_data['gstfsd_key'] + try: + data = json.loads(gstfsd_key) + if not isinstance(data, dict): + raise forms.ValidationError(_('Gstfsd key must be a json object')) + if not 'k' in data: + raise forms.ValidationError(_('Gstfsd key must have a "k" field')) + if not 'kty' in data: + raise forms.ValidationError(_('Gstfsd key must have a "kty" field')) + except ValueError: + raise forms.ValidationError(_('Gstfsd key must be a valid json')) + return gstfsd_key + class ComputeAddTlsForm(forms.Form): name = forms.CharField(error_messages={'required': _('No hostname has been entered')}, @@ -87,6 +120,8 @@ class ComputeAddTlsForm(forms.Form): max_length=100) password = forms.CharField(error_messages={'required': _('No password has been entered')}, max_length=100) + gstfsd_key = forms.CharField(max_length=256, required=False) + def clean_name(self): name = self.cleaned_data['name'] @@ -115,6 +150,20 @@ def clean_hostname(self): return hostname raise forms.ValidationError(_('This host is already connected')) + def clean_gstfsd_key(self): + gstfsd_key = self.cleaned_data['gstfsd_key'] + try: + data = json.loads(gstfsd_key) + if not isinstance(data, dict): + raise forms.ValidationError(_('Gstfsd key must be a json object')) + if not 'k' in data: + raise forms.ValidationError(_('Gstfsd key must have a "k" field')) + if not 'kty' in data: + raise forms.ValidationError(_('Gstfsd key must have a "kty" field')) + except ValueError: + raise forms.ValidationError(_('Gstfsd key must be a valid json')) + return gstfsd_key + class ComputeEditHostForm(forms.Form): host_id = forms.CharField() @@ -126,6 +175,8 @@ class ComputeEditHostForm(forms.Form): max_length=100) password = forms.CharField(max_length=100) + gstfsd_key = forms.CharField(max_length=256, required=False) + def clean_name(self): name = self.cleaned_data['name'] have_symbol = re.match('[^a-zA-Z0-9._-]+', name) @@ -145,11 +196,28 @@ def clean_hostname(self): raise forms.ValidationError(_('Wrong IP address')) return hostname + def clean_gstfsd_key(self): + gstfsd_key = self.cleaned_data['gstfsd_key'] + try: + data = json.loads(gstfsd_key) + if not isinstance(data, dict): + raise forms.ValidationError(_('Gstfsd key must be a json object')) + if not 'k' in data: + raise forms.ValidationError(_('Gstfsd key must have a "k" field')) + if not 'kty' in data: + raise forms.ValidationError(_('Gstfsd key must have a "kty" field')) + except ValueError: + raise forms.ValidationError(_('Gstfsd key must be a valid json')) + return gstfsd_key + class ComputeAddSocketForm(forms.Form): name = forms.CharField(error_messages={'required': _('No hostname has been entered')}, max_length=20) + gstfsd_key = forms.CharField(max_length=256, required=False) + + def clean_name(self): name = self.cleaned_data['name'] have_symbol = re.match('[^a-zA-Z0-9._-]+', name) @@ -162,3 +230,17 @@ def clean_name(self): except Compute.DoesNotExist: return name raise forms.ValidationError(_('This host is already connected')) + + def clean_gstfsd_key(self): + gstfsd_key = self.cleaned_data['gstfsd_key'] + try: + data = json.loads(gstfsd_key) + if not isinstance(data, dict): + raise forms.ValidationError(_('Gstfsd key must be a json object')) + if not 'k' in data: + raise forms.ValidationError(_('Gstfsd key must have a "k" field')) + if not 'kty' in data: + raise forms.ValidationError(_('Gstfsd key must have a "kty" field')) + except ValueError: + raise forms.ValidationError(_('Gstfsd key must be a valid json')) + return gstfsd_key diff --git a/computes/migrations/0002_compute_gstfsd_key.py b/computes/migrations/0002_compute_gstfsd_key.py new file mode 100644 index 00000000..f1c4debe --- /dev/null +++ b/computes/migrations/0002_compute_gstfsd_key.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('computes', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='compute', + name='gstfsd_key', + field=models.CharField(max_length=256, null=True, blank=True), + ), + ] diff --git a/computes/models.py b/computes/models.py index 6ee7de8a..553f0370 100644 --- a/computes/models.py +++ b/computes/models.py @@ -7,6 +7,7 @@ class Compute(models.Model): login = models.CharField(max_length=20) password = models.CharField(max_length=14, blank=True, null=True) type = models.IntegerField() + gstfsd_key = models.CharField(max_length=256, blank=True, null=True) def __unicode__(self): return self.hostname diff --git a/computes/templates/computes.html b/computes/templates/computes.html index 7c1c28f5..70fec700 100644 --- a/computes/templates/computes.html +++ b/computes/templates/computes.html @@ -84,6 +84,12 @@ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+

{% trans "You need shut down your instance and enter a new root password." %}

-
{% csrf_token %} + {% csrf_token %}
- +
{% ifequal status 5 %} @@ -257,6 +257,7 @@

{{ disk.size|filesizeformat }} {% trans "Disk" %}

{% endifequal %}
+

{% trans "An empty password disable the root password." %}

diff --git a/instances/views.py b/instances/views.py index 8d9afd9e..a119fb51 100644 --- a/instances/views.py +++ b/instances/views.py @@ -5,6 +5,7 @@ from string import letters, digits from random import choice from bisect import insort +from jwcrypto import jws, jwk, jwe from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse from django.shortcuts import render, get_object_or_404 @@ -287,21 +288,34 @@ def show_clone_disk(disks): if 'rootpasswd' in request.POST: passwd = request.POST.get('passwd', '') - passwd_hash = crypt.crypt(passwd, '$6$kgPoiREy') + if passwd: + passwd_hash = crypt.crypt(passwd, '$6$%s' % ''.join([choice(letters + digits) for i in xrange(8)])) + # if password is empty, disable the root password + else: + passwd_hash = "*" data = {'action': 'password', 'passwd': passwd_hash, 'vname': vname} if conn.get_status() == 5: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((compute.hostname, 16510)) - s.send(json.dumps(data)) - result = json.loads(s.recv(1024)) - s.close() - msg = _("Reset root password") - addlogmsg(request.user.username, instance.name, msg) + if compute.gstfsd_key: + key = jwk.JWK(**json.loads(compute.gstfsd_key.strip())) + data = jwe.JWE(json.dumps(data), algs=["A256KW", "A256CBC-HS512"]) + data.add_recipient(key, header='{"alg":"A256KW","enc":"A256CBC-HS512"}') + data = jws.JWS(data.serialize()) + data.add_signature(key, alg="HS512") + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((compute.hostname, 16510)) + s.send(data.serialize()) + result = json.loads(s.recv(4096)) + s.close() + msg = _("Reset root password") + addlogmsg(request.user.username, instance.name, msg) - if result['return'] == 'success': - messages.append(msg) + if result['return'] == 'success': + messages.append(msg) + else: + error_messages.append(result.get('message', msg)) else: + msg = _("Please import the gstfsd key into this compute. It is in /var/lib/gstfsd/SECRET on %s") % compute.name error_messages.append(msg) else: msg = _("Please shutdow down your instance and then try again") @@ -313,17 +327,26 @@ def show_clone_disk(disks): data = {'action': 'publickey', 'key': publickey.keypublic, 'vname': vname} if conn.get_status() == 5: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((compute.hostname, 16510)) - s.send(json.dumps(data)) - result = json.loads(s.recv(1024)) - s.close() - msg = _("Installed new ssh public key %s" % publickey.keyname) - addlogmsg(request.user.username, instance.name, msg) + if compute.gstfsd_key: + key = jwk.JWK(**json.loads(compute.gstfsd_key.strip())) + data = jwe.JWE(json.dumps(data), algs=["A256KW", "A256CBC-HS512"]) + data.add_recipient(key, header='{"alg":"A256KW","enc":"A256CBC-HS512"}') + data = jws.JWS(data.serialize()) + data.add_signature(key, alg="HS512") + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((compute.hostname, 16510)) + s.send(data.serialize()) + result = json.loads(s.recv(4096)) + s.close() + msg = _("Installed new ssh public key %s" % publickey.keyname) + addlogmsg(request.user.username, instance.name, msg) - if result['return'] == 'success': - messages.append(msg) + if result['return'] == 'success': + messages.append(msg) + else: + error_messages.append(result.get('message', msg)) else: + msg = _("Please import the gstfsd key into this compute. It is in /var/lib/gstfsd/SECRET on %s") % compute.name error_messages.append(msg) else: msg = _("Please shutdow down your instance and then try again")