Implement config generator and other things
This commit is contained in:
parent
0de4709c30
commit
0b751b841a
6 changed files with 153 additions and 4 deletions
|
@ -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 "")
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
]
|
]
|
||||||
|
|
129
manager/views.py
129
manager/views.py
|
@ -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("/")
|
||||||
|
|
|
@ -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";
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue