1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-03-23 02:54:51 +00:00

Use JSON Web Signature and Encryption (JWS & JWE) between webvirtcloud and gstfsd

This commit is contained in:
Valentin Samir 2016-05-08 16:12:50 +02:00
parent 17cb7ace88
commit 6dc7473ab0
9 changed files with 166 additions and 30 deletions

View file

@ -13,6 +13,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']
@ -49,6 +51,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']
@ -87,6 +91,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']
@ -126,6 +132,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)
@ -150,6 +158,9 @@ 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)

View file

@ -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),
),
]

View file

@ -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

View file

@ -84,6 +84,12 @@
<input type="password" name="password" class="form-control" value="{{ compute.password }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="host_del">
@ -121,6 +127,12 @@
<input type="text" name="login" class="form-control" value="{{ compute.login }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="host_del">
@ -163,6 +175,12 @@
<input type="password" name="password" class="form-control" value="{{ compute.password }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="host_del">
@ -187,6 +205,12 @@
<input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="host_del">

View file

@ -50,6 +50,12 @@
<input type="password" name="password" class="form-control" placeholder="{% trans "Password" %}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" placeholder="Gstfsd JSON Web Key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
@ -83,6 +89,12 @@
<input type="text" name="login" class="form-control" placeholder="{% trans "Username" %}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" placeholder="Gstfsd JSON Web Key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
@ -121,6 +133,12 @@
<input type="password" name="password" class="form-control" placeholder="{% trans "Password" %}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" placeholder="Gstfsd JSON Web Key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
@ -141,6 +159,12 @@
<input type="text" name="name" class="form-control" placeholder="Label Name" maxlength="20" required pattern="[a-z0-9\.\-_]+">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Gstfsd key" %}</label>
<div class="col-sm-6">
<input type="text" name="gstfsd_key" placeholder="Gstfsd JSON Web Key" class="form-control" value="{{ compute.gstfsd_key }}" maxlength="256">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">

View file

@ -36,7 +36,8 @@ def computes(request):
'status': connection_manager.host_is_up(compute.type, compute.hostname),
'type': compute.type,
'login': compute.login,
'password': compute.password
'password': compute.password,
'gstfsd_key': compute.gstfsd_key
})
return compute_data
@ -66,7 +67,8 @@ def computes(request):
hostname=data['hostname'],
type=CONN_TCP,
login=data['login'],
password=data['password'])
password=data['password'],
gstfsd_key=data['gstfsd_key'])
new_tcp_host.save()
return HttpResponseRedirect(request.get_full_path())
else:
@ -79,7 +81,8 @@ def computes(request):
new_ssh_host = Compute(name=data['name'],
hostname=data['hostname'],
type=CONN_SSH,
login=data['login'])
login=data['login'],
gstfsd_key=data['gstfsd_key'])
new_ssh_host.save()
return HttpResponseRedirect(request.get_full_path())
else:
@ -93,7 +96,8 @@ def computes(request):
hostname=data['hostname'],
type=CONN_TLS,
login=data['login'],
password=data['password'])
password=data['password'],
gstfsd_key=data['gstfsd_key'])
new_tls_host.save()
return HttpResponseRedirect(request.get_full_path())
else:
@ -107,7 +111,8 @@ def computes(request):
hostname='localhost',
type=CONN_SOCKET,
login='',
password='')
password='',
gstfsd_key=data['gstfsd_key'])
new_socket_host.save()
return HttpResponseRedirect(request.get_full_path())
else:
@ -122,6 +127,7 @@ def computes(request):
compute_edit.hostname = data['hostname']
compute_edit.login = data['login']
compute_edit.password = data['password']
compute_edit.gstfsd_key = data['gstfsd_key']
compute_edit.save()
return HttpResponseRedirect(request.get_full_path())
else:

View file

@ -7,10 +7,12 @@ import SocketServer
import json
import guestfs
import re
import os
from jwcrypto import jws, jwk, jwe
PORT = 16510
ADDRESS = "0.0.0.0"
SECRET = None
class MyTCPServer(SocketServer.ThreadingTCPServer):
@ -19,12 +21,20 @@ class MyTCPServer(SocketServer.ThreadingTCPServer):
class MyTCPServerHandler(SocketServer.BaseRequestHandler):
def handle(self):
# recive data
data = json.loads(self.request.recv(1024).strip())
# GuestFS
gfs = guestfs.GuestFS(python_return_dict=True)
# recive data and check authentcation
try:
signed_data = jws.JWS()
signed_data.deserialize(self.request.recv(4096).strip())
signed_data.verify(SECRET, "HS512")
encrypted_data = jwe.JWE(algs=["A256KW", "A256CBC-HS512"])
encrypted_data.deserialize(signed_data.payload)
encrypted_data.decrypt(SECRET)
data = json.loads(encrypted_data.plaintext)
# GuestFS
gfs = guestfs.GuestFS(python_return_dict=True)
gfs.add_domain(data['vname'])
gfs.launch()
parts = gfs.list_partitions()
@ -51,8 +61,29 @@ class MyTCPServerHandler(SocketServer.BaseRequestHandler):
pass
gfs.shutdown()
gfs.close()
except RuntimeError, err:
# we check signature before trying to decrypt so jwe.InvalidJWEData should not be raised ever
except (jws.InvalidJWSObject, jwe.InvalidJWEData, RuntimeError, ValueError) as err:
self.request.sendall(json.dumps({'return': 'error', 'message': err.message}))
except jws.InvalidJWSSignature as err:
self.request.sendall(json.dumps({'return': 'error', 'message': (
"Fail to verify request signature. Check if you have imported "
"the key (/var/lib/gstfsd/SECRET) in WebVirtCloud"
)}))
if not os.path.isfile("/var/lib/gstfsd/SECRET"):
try:
os.mkdir("/var/lib/gstfsd")
except OSError as error:
if error.errno != 17: # File exists
raise
os.chmod("/var/lib/gstfsd", 0700)
with open("/var/lib/gstfsd/SECRET", 'w') as f:
f.write(jwk.JWK(generate='oct', size=256).export())
os.chmod("/var/lib/gstfsd/SECRET", 0600)
with open("/var/lib/gstfsd/SECRET") as f:
SECRET = jwk.JWK(**json.load(f))
server = MyTCPServer((ADDRESS, PORT), MyTCPServerHandler)
server.serve_forever()

View file

@ -2,5 +2,6 @@ Django==1.8.11
websockify==0.8.0
gunicorn==19.3.0
libvirt-python==1.3.2
jwcrypto>=0.2.1
#http://github.com/retspen/retspen.github.io/raw/master/libxml2-python-2.9.1.tar.gz
http://git.gnome.org/browse/libxml2/snapshot/libxml2-2.9.1.tar.gz#egg=libxml2-python&subdirectory=python

View file

@ -5,6 +5,7 @@ import crypt
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
@ -291,17 +292,26 @@ def instance(request, compute_id, vname):
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 +323,26 @@ def instance(request, compute_id, vname):
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")