Move to using a PKI per server
diff --git a/containers/xos/Dockerfile.devel b/containers/xos/Dockerfile.devel
index 1ff2646..17ae9c9 100644
--- a/containers/xos/Dockerfile.devel
+++ b/containers/xos/Dockerfile.devel
@@ -100,7 +100,6 @@
RUN git -C /opt/openvpn pull origin master
RUN echo "set_var EASYRSA /opt/openvpn/easyrsa3" | tee /opt/openvpn/easyrsa3/vars
RUN /opt/openvpn/easyrsa3/easyrsa --batch init-pki
-RUN /opt/openvpn/easyrsa3/easyrsa --batch --req-cn=XOS build-ca nopass
RUN /opt/openvpn/easyrsa3/easyrsa --batch gen-dh
RUN /opt/openvpn/easyrsa3/easyrsa --batch gen-crl
RUN chmod 777 /opt/openvpn/easyrsa3/pki/dh.pem
diff --git a/xos/services/vpn/admin.py b/xos/services/vpn/admin.py
index a99e365..9e7255b 100644
--- a/xos/services/vpn/admin.py
+++ b/xos/services/vpn/admin.py
@@ -1,3 +1,6 @@
+import os
+import shutil
+
from core.admin import ReadOnlyAwareAdmin, SliceInline, TenantPrivilegeInline
from core.middleware import get_request
from core.models import TenantPrivilege, User
@@ -131,7 +134,7 @@
self.fields[
'clients_can_see_each_other'].initial = self.instance.clients_can_see_each_other
self.fields['is_persistent'].initial = self.instance.is_persistent
- self.fields['protocol'].initial = self.instance.protocol
+ self.initial['protocol'] = self.instance.protocol
if (self.instance.failover_servers):
self.initial['failover_servers'] = [model.pk for model in list(serializers.deserialize('json', self.instance.failover_servers))]
@@ -160,11 +163,18 @@
if (not self.instance.ca_crt):
self.instance.ca_crt = self.generate_ca_crt()
- return super(VPNTenantForm, self).save(commit=commit)
+ result = super(VPNTenantForm, self).save(commit=commit)
+ pki_dir = "/opt/openvpn/easyrsa3/server-" + self.instance.id
+ if (not os.path.isdir(pki_dir)):
+ os.makedirs(pki_dir)
+ shutil.copy2("/opt/openvpn/easyrsa3/", pki_dir)
+ Popen(pki_dir + "/easyrsa --batch --req-cn=XOS build-ca nopass", shell=True, stdout=PIPE).communicate()
+ self.instance.ca_crt = self.generate_ca_crt(self.instance.id)
+ return result
- def generate_ca_crt(self):
+ def generate_ca_crt(self, server_id):
"""str: Generates the ca cert by reading from the ca file"""
- with open("/opt/openvpn/easyrsa3/pki/ca.crt") as crt:
+ with open("/opt/openvpn/easyrsa3/server-" + server_id + "/ca.crt") as crt:
return crt.readlines()
class Meta:
@@ -200,7 +210,7 @@
# If anything deleated was a TenantPrivilege then revoke the certificate
if type(obj) is TenantPrivilege:
certificate = self.certificate_name(obj)
- Popen("/opt/openvpn/easyrsa3/easyrsa --batch revoke " + certificate, shell=True, stdout=PIPE).communicate()
+ Popen("/opt/openvpn/easyrsa3/server-" + obj.tenant.id + "/easyrsa --batch revoke " + certificate, shell=True, stdout=PIPE).communicate()
# TODO(jermowery): determine if this is necessary.
# if type(obj) is VPNTenant:
# if the tenant was deleted revoke all certs assoicated
@@ -210,7 +220,7 @@
# If there were any new TenantPrivlege objects then create certs
if type(obj) is TenantPrivilege:
certificate = self.certificate_name(obj)
- Popen("/opt/openvpn/easyrsa3/easyrsa --batch build-client-full " + certificate + " nopass", shell=True, stdout=PIPE).communicate()
+ Popen("/opt/openvpn/easyrsa3/server-" + obj.tenant.id + "/easyrsa --batch build-client-full " + certificate + " nopass", shell=True, stdout=PIPE).communicate()
# Associate the admin forms with the models.
diff --git a/xos/services/vpn/models.py b/xos/services/vpn/models.py
index 94e62b6..72a6407 100644
--- a/xos/services/vpn/models.py
+++ b/xos/services/vpn/models.py
@@ -1,6 +1,6 @@
from core.models import Service, TenantWithContainer
from django.db import transaction
-from xos.exceptions import XOSConfigurationError, XOSValidationError
+from xos.exceptions import XOSValidationError
VPN_KIND = "vpn"
@@ -28,7 +28,7 @@
def get_next_available_port(self, protocol):
if protocol != "udp" and protocol != "tcp":
- raise XOSConfigurationError("Port protocol must be udp or tcp")
+ raise XOSValidationError("Port protocol must be udp or tcp")
if not self.exposed_ports[protocol]:
raise XOSValidationError("No availble ports for protocol: " + protocol)
tenants = [tenant for tenant in VPNTenant.get_tenant_objects.all() if tenant.protocol == protocol]
@@ -198,10 +198,10 @@
script += (line.rstrip() + r"\n")
script += ("\" > ca.crt\n")
script += ("printf \"%b\" \"")
- for line in self.generate_client_cert(client_name):
+ for line in self.get_client_cert(client_name):
script += (line.rstrip() + r"\n")
script += ("\" > " + client_name + ".crt\n")
- for line in self.generate_client_key(client_name):
+ for line in self.get_client_key(client_name):
script += (line.rstrip() + r"\n")
script += ("\" > " + client_name + ".key\n")
# make sure openvpn is installed
@@ -211,11 +211,11 @@
# close the script
return script
- def generate_client_cert(self, client_name):
- return open("/opt/openvpn/easyrsa3/pki/issued/" + client_name + ".crt").readlines()
+ def get_client_cert(self, client_name):
+ return open("/opt/openvpn/easyrsa3/server-" + self.id + "/issued/" + client_name + ".crt").readlines()
- def generate_client_key(self, client_name):
- return open("/opt/openvpn/easyrsa3/pki/private/" + client_name + ".key").readlines()
+ def get_client_key(self, client_name):
+ return open("/opt/openvpn/easyrsa3/server-" + self.id + "/private/" + client_name + ".key").readlines()
def generate_client_conf(self, client_name):
"""str: Generates the client configuration to use to connect to this VPN server.