Implement config generator and other things

This commit is contained in:
Kumi 2018-11-28 22:35:57 +01:00
parent 0de4709c30
commit 0b751b841a
6 changed files with 153 additions and 4 deletions

View file

@ -22,13 +22,16 @@ class Network(models.Model):
class Device(models.Model): class Device(models.Model):
serial = models.CharField("Device Serial Number", max_length=12, unique=True) serial = models.CharField("Device Serial Number", max_length=12, unique=True)
name = models.CharField("Common Name", max_length=100, blank=True, null=True) name = models.CharField("Common Name", max_length=100, default="", blank=True, null=True)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE) organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
network = models.ForeignKey(Network, on_delete=models.SET_NULL, blank=True, null=True) network = models.ForeignKey(Network, on_delete=models.SET_NULL, blank=True, null=True)
curip = models.CharField("Current IP Address", max_length=15, blank=True, null=True) curip = models.CharField("Current IP Address", max_length=15, blank=True, null=True)
lasttime = models.DateTimeField("Last Received Heartbeat", blank=True, null=True) lasttime = models.DateTimeField("Last Received Heartbeat", blank=True, null=True)
secret = models.CharField("Secret", default=getRandom, max_length=128) secret = models.CharField("Secret", default=getRandom, max_length=128)
password = models.CharField("Device Password", default=getRandom, max_length=128)
vpnconfig = models.TextField("VPN Configuration", blank=True, null=True, editable=False)
reboot = models.BooleanField("Trigger Reboot", default=False) reboot = models.BooleanField("Trigger Reboot", default=False)
def __str__(self): def __str__(self):
return "%s%s" % (self.serial, " (%s)" % self.curip if self.curip else "") return "%s%s" % (self.serial, " (%s)" % self.curip if self.curip else "")

View file

@ -37,6 +37,11 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/devices">Devices</a> <a class="nav-link" href="/devices">Devices</a>
</li> </li>
{% if user.is_superuser %}
<li class="nav-item">
<a class="nav-link" href="/admin">Backend</a>
</li>
{% endif %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/accounts/logout">Logout</a> <a class="nav-link" href="/accounts/logout">Logout</a>
</li> </li>

View file

@ -14,7 +14,7 @@
<th>Network</th> <th>Network</th>
<th>Latest IP</th> <th>Latest IP</th>
<th>Secret</th> <th>Secret</th>
<th>Options</th> <th>Options {% if user.is_superuser %}<a href="/makedevice/" style="font-weight:bold;color:green;"><i class="fas fa-plus" title="Add Device"></i></a>{% endif %}</th>
</tr> </tr>
</thead> </thead>
@ -25,7 +25,7 @@
<td>{{ device.network }}</td> <td>{{ device.network }}</td>
<td><div style="display:inline" id="{{ device.id }}-ip">{% if device.curip %}{{ device.curip }} (at {{ device.lasttime }}){% endif %}</div></td> <td><div style="display:inline" id="{{ device.id }}-ip">{% if device.curip %}{{ device.curip }} (at {{ device.lasttime }}){% endif %}</div></td>
<td>{{ device.secret }}</td> <td>{{ device.secret }}</td>
<td><a href="/devices/{{ device.id }}/edit"><i class="fas fa-edit"></i></a></td> <td><a href="/devices/{{ device.id }}/edit"><i class="fas fa-edit" title="Edit Device"></i></a> <a href="#"><i style="color: green;" onclick="askreboot({{ device.id }});" class="fas fa-sync" title="Reboot Device"></i></a>{% if user.is_superuser %} <a href="/devices/{{ device.id }}/download"><i class="fas fa-download" title="Download Configuration"></i></a> <a href="#"><i style="color: darkred;" onclick="askdelete({{ device.id }});" class="fas fa-trash-alt" title="Delete Device"></i></a>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -8,5 +8,9 @@ urlpatterns = [
path('hosts', views.hosts, name='hosts'), path('hosts', views.hosts, name='hosts'),
path('devices/<int:device_id>/edit/', views.editdevice, name='editdevice'), path('devices/<int:device_id>/edit/', views.editdevice, name='editdevice'),
path('devices/<int:device_id>/ping/', views.ping, name="ping"), path('devices/<int:device_id>/ping/', views.ping, name="ping"),
path('heartbeat', views.heartbeat, name='heartbeat') path('devices/<int:device_id>/download/', views.getconfig, name="getconfig"),
path('devices/<int:device_id>/delete/', views.deletedevice, name="deletedevice"),
path('devices/<int:device_id>/reboot/', views.rebootdevice, name="rebootdevice"),
path('heartbeat', views.heartbeat, name='heartbeat'),
path('makedevice/', views.makedevice, name="makedevice")
] ]

View file

@ -4,10 +4,20 @@ from django.contrib.auth.forms import AuthenticationForm
from django.db.models import Q from django.db.models import Q
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone from django.utils import timezone
from django.core.files import File
from django.db.models.fields.files import FieldFile
from .models import Device, Organization, Network from .models import Device, Organization, Network
from distutils.dir_util import copy_tree
import glob
import sys
import subprocess
import os import os
import socket import socket
import tempfile
import crypt
import tarfile
def index(request): def index(request):
if request.user.is_authenticated: if request.user.is_authenticated:
@ -112,3 +122,122 @@ def editdevice(request, device_id):
else: else:
return redirect("/") return redirect("/")
def getconfig(request, device_id):
DEVICEDIR = "/opt/vpnmanager/device-config/"
if not request.user.is_superuser:
return redirect("/")
device = get_object_or_404(Device, id=device_id)
tempdir = tempfile.TemporaryDirectory()
copy_tree(DEVICEDIR, tempdir.name)
# Write OpenVPN config
with open(tempdir.name + "/etc/openvpn/client.conf", "w") as vpnconf:
vpnconf.write(device.vpnconfig)
# Write secret
with open(tempdir.name + "/etc/vpnsecret", "w") as secret:
secret.write('SECRET="%s"' % device.secret)
# Write password
with open(tempdir.name + "/etc/shadow", "r") as shadow:
password = crypt.crypt(device.password, crypt.mksalt(crypt.METHOD_MD5))
shadowin = shadow.read()
with open(tempdir.name + "/etc/shadow", "w") as shadowout:
shadowout.write(shadowin.replace("$PASSWORD", password))
# Write SSID
with open(tempdir.name + "/etc/config/wireless", "r") as wireless:
wirein = wireless.read()
with open(tempdir.name + "/etc/config/wireless", "w") as wireout:
wireout.write(wirein.replace("$SSID", device.serial))
# Generate .tar.gz file
with tarfile.open(tempdir.name + ".tar.gz", "w:gz") as tar:
tar.add(tempdir.name, arcname=os.path.sep)
with open(tempdir.name + ".tar.gz", "rb") as download:
response = HttpResponse(download.read(), content_type="application/tar+gzip")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(device.serial + ".tar.gz")
return response
def rebootdevice(request, device_id):
if request.user.is_authenticated:
device = None
for organization in Organization.objects.filter(users=request.user):
device = device or Device.objects.filter(id=device_id, organization=organization)
if not device:
return redirect("/")
device[0].reboot = True
device[0].save()
return redirect("/")
def deletedevice(request, device_id):
if request.user.is_superuser:
device = get_object_or_404(Device, id=device_id)
device.delete()
return redirect("/")
def makedevice(request):
CADIR = "/etc/openvpn/ca/"
CONFIGDIR = "/etc/openvpn/client-configs/"
BEFORE = os.getcwd()
device_serial = request.POST.get("serial", "")
device_name = request.POST.get("name", "")
device_organization = request.POST.get("organization", "")
if not request.user.is_superuser:
return redirect("/")
if not device_serial:
orga = Organization.objects.all()
return render(request, "manager/add.html",
{
"title": "Add Device",
"organizations": orga,
}
)
if glob.glob(CADIR + "/keys/" + device_serial + "*"):
return HttpResponse("This key already exists.")
os.chdir(CADIR)
if subprocess.call(CADIR + "/generate-key " + device_serial, shell=True):
return HttpResponse("Something went wrong trying to generate the key.")
if glob.glob(CONFIGDIR + "/files/" + device_serial + "*"):
return HttpResponse("This configuration file already exists.")
os.chdir(CONFIGDIR)
if subprocess.call(CONFIGDIR + "/make_config " + device_serial, shell=True):
return HttpResponse("Something went wrong trying to generate the config file.")
os.chdir(BEFORE)
device = Device.objects.create(
serial=device_serial,
name=device_name,
organization=Organization.objects.filter(id=device_organization)[0],
vpnconfig = open(CONFIGDIR + "/files/" + device_serial + ".ovpn").read()
)
return redirect("/")

View file

@ -29,3 +29,11 @@ function styleStatus(msg, device) {
function updateStatus(device_id) { function updateStatus(device_id) {
$.getJSON( "/devices/" + device_id + "/ping/", function(json) { styleStatus(json, device_id); }); $.getJSON( "/devices/" + device_id + "/ping/", function(json) { styleStatus(json, device_id); });
}; };
function askdelete(device_id) {
if (confirm("Are you sure you want to delete this device?")) window.location.href = "/devices/" + device_id + "/delete";
};
function askreboot(device_id) {
if (confirm("Are you sure you want to reboot this device?")) window.location.href = "/devices/" + device_id + "/reboot";
};