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):
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)
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)
lasttime = models.DateTimeField("Last Received Heartbeat", blank=True, null=True)
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)
def __str__(self):
return "%s%s" % (self.serial, " (%s)" % self.curip if self.curip else "")

View file

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

View file

@ -14,7 +14,7 @@
<th>Network</th>
<th>Latest IP</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>
</thead>
@ -25,7 +25,7 @@
<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>{{ 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>
{% endfor %}

View file

@ -8,5 +8,9 @@ urlpatterns = [
path('hosts', views.hosts, name='hosts'),
path('devices/<int:device_id>/edit/', views.editdevice, name='editdevice'),
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.views.decorators.csrf import csrf_exempt
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 distutils.dir_util import copy_tree
import glob
import sys
import subprocess
import os
import socket
import tempfile
import crypt
import tarfile
def index(request):
if request.user.is_authenticated:
@ -112,3 +122,122 @@ def editdevice(request, device_id):
else:
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) {
$.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";
};