blob: 6b76575cafe37ac1810780fb6ae3c1866f1b31b2 [file] [log] [blame]
Jeremy Mowery0427ff82016-04-02 10:32:11 -07001from subprocess import PIPE, Popen
2
Jeremy Mowery8b664f72015-12-04 11:52:16 -07003from core.models import Service, TenantWithContainer
4from django.db import transaction
Jeremy Mowery9d718b52016-04-02 10:16:21 -07005from xos.exceptions import XOSConfigurationError, XOSValidationError
Jeremy Mowery8b664f72015-12-04 11:52:16 -07006
7VPN_KIND = "vpn"
8
Jeremy Mowery82760822016-01-08 16:36:22 -07009
Jeremy Mowery8b664f72015-12-04 11:52:16 -070010class VPNService(Service):
Jeremy Mowery82760822016-01-08 16:36:22 -070011 """Defines the Service for creating VPN servers."""
Jeremy Mowery8b664f72015-12-04 11:52:16 -070012 KIND = VPN_KIND
Jeremy Mowery9d718b52016-04-02 10:16:21 -070013 OPENVPN_PREFIX = "/opt/openvpn/"
14 VARS = OPENVPN_PREFIX + "vars"
15 EASYRSA_LOC = OPENVPN_PREFIX + "easyrsa3/easyrsa"
16 EASYRSA_COMMAND = EASYRSA_LOC + " --vars=" + VARS
17
18 @classmethod
Jeremy Moweryb46eada2016-04-02 11:00:10 -070019 def execute_easyrsa_command(cls, pki_dir, command):
Jeremy Mowery9d718b52016-04-02 10:16:21 -070020 full_command = (
21 VPNService.EASYRSA_COMMAND + " --pki-dir=" +
22 pki_dir + " " + command)
Jeremy Mowery19d1ad62016-04-02 11:17:55 -070023 proc = Popen(
24 full_command, shell=True, stdout=PIPE, stderr=PIPE
Jeremy Mowery9d718b52016-04-02 10:16:21 -070025 )
Jeremy Mowery19d1ad62016-04-02 11:17:55 -070026 (stdout, stderr) = proc.communicate()
27 if (proc.returncode != 0):
Jeremy Mowery9d718b52016-04-02 10:16:21 -070028 raise XOSConfigurationError(
29 full_command + " failed with standard out:" + str(stdout) +
30 " and stderr: " + str(stderr))
Jeremy Mowery8b664f72015-12-04 11:52:16 -070031
32 class Meta:
33 proxy = True
34 # The name used to find this service, all directories are named this
35 app_label = "vpn"
36 verbose_name = "VPN Service"
37
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -070038 default_attributes = {'exposed_ports': None,
39 'exposed_ports_str': None}
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -070040
41 @property
42 def exposed_ports(self):
43 return self.get_attribute("exposed_ports",
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -070044 self.default_attributes["exposed_ports"])
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -070045
46 @exposed_ports.setter
47 def exposed_ports(self, value):
48 self.set_attribute("exposed_ports", value)
49
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -070050 @property
51 def exposed_ports_str(self):
52 return self.get_attribute("exposed_ports_str",
53 self.default_attributes["exposed_ports_str"])
54
55 @exposed_ports_str.setter
56 def exposed_ports_str(self, value):
57 self.set_attribute("exposed_ports_str", value)
58
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -070059 def get_next_available_port(self, protocol):
60 if protocol != "udp" and protocol != "tcp":
Jeremy Mowery30bdab92016-03-29 23:52:46 -070061 raise XOSValidationError("Port protocol must be udp or tcp")
Jeremy Moweryabccb8a2016-03-27 22:20:57 -070062 if not self.exposed_ports[protocol]:
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -070063 raise XOSValidationError(
64 "No availble ports for protocol: " + protocol)
65 tenants = [
66 tenant for tenant in VPNTenant.get_tenant_objects().all()
67 if tenant.protocol == protocol]
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -070068 port_numbers = self.exposed_ports[protocol]
69 for port_number in port_numbers:
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -070070 if (
71 len([
72 tenant for tenant in tenants
73 if tenant.port_number == port_number]) == 0):
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -070074 return port_number
75
Jeremy Mowery82760822016-01-08 16:36:22 -070076
Jeremy Mowery7ced7382015-12-04 23:58:38 -070077class VPNTenant(TenantWithContainer):
Jeremy Mowery82760822016-01-08 16:36:22 -070078 """Defines the Tenant for creating VPN servers."""
Jeremy Mowery8b664f72015-12-04 11:52:16 -070079
80 class Meta:
81 proxy = True
82 verbose_name = "VPN Tenant"
83
84 KIND = VPN_KIND
85
86 sync_attributes = ("nat_ip", "nat_mac",)
87
Jeremy Mowery3f6adcd2016-02-21 15:36:32 -070088 default_attributes = {'vpn_subnet': None,
Jeremy Moweryfd081292016-02-07 17:07:55 -070089 'server_network': None,
90 'clients_can_see_each_other': True,
Jeremy Mowerye02d4b62016-01-10 15:21:52 -070091 'is_persistent': True,
Jeremy Mowery290714e2016-02-25 14:36:29 -070092 'ca_crt': None,
Jeremy Mowery47c93742016-03-02 00:01:25 -070093 'port': None,
Jeremy Mowery65c60702016-03-16 11:22:02 -070094 'script_text': None,
Jeremy Moweryb52c49a2016-04-05 23:32:10 -070095 'pki_dir': None,
96 'use_ca_from': list(),
Jeremy Mowery7b54e142016-04-06 11:33:43 -070097 'failover_servers': list(),
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -070098 'protocol': None}
Jeremy Mowery8b664f72015-12-04 11:52:16 -070099
100 def __init__(self, *args, **kwargs):
101 vpn_services = VPNService.get_service_objects().all()
102 if vpn_services:
103 self._meta.get_field(
104 "provider_service").default = vpn_services[0].id
105 super(VPNTenant, self).__init__(*args, **kwargs)
106
107 def save(self, *args, **kwargs):
108 super(VPNTenant, self).save(*args, **kwargs)
109 model_policy_vpn_tenant(self.pk)
110
111 def delete(self, *args, **kwargs):
112 self.cleanup_container()
113 super(VPNTenant, self).delete(*args, **kwargs)
114
115 @property
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700116 def protocol(self):
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -0700117 return self.get_attribute(
118 "protocol", self.default_attributes["protocol"])
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700119
120 @protocol.setter
121 def protocol(self, value):
122 self.set_attribute("protocol", value)
123
124 @property
Jeremy Moweryb52c49a2016-04-05 23:32:10 -0700125 def use_ca_from(self):
126 return self.get_attribute(
127 "use_ca_from", self.default_attributes["use_ca_from"])
128
129 @use_ca_from.setter
130 def use_ca_from(self, value):
131 self.set_attribute("use_ca_from", value)
132
133 @property
134 def pki_dir(self):
135 return self.get_attribute(
136 "pki_dir", self.default_attributes["pki_dir"])
137
138 @pki_dir.setter
139 def pki_dir(self, value):
140 self.set_attribute("pki_dir", value)
141
142 @property
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700143 def addresses(self):
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -0700144 """Mapping[str, str]: The ip, mac address, and subnet of the NAT
145 network of this Tenant."""
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700146 if (not self.id) or (not self.instance):
147 return {}
148
149 addresses = {}
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700150 for ns in self.instance.ports.all():
151 if "nat" in ns.network.name.lower():
Jeremy Mowery5055c7b2016-01-08 17:25:33 -0700152 addresses["ip"] = ns.ip
153 addresses["mac"] = ns.mac
Jeremy Mowery5055c7b2016-01-08 17:25:33 -0700154 break
155
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700156 return addresses
157
158 # This getter is necessary because nat_ip is a sync_attribute
159 @property
160 def nat_ip(self):
Jeremy Mowery82760822016-01-08 16:36:22 -0700161 """str: The IP of this Tenant on the NAT network."""
Jeremy Mowery5055c7b2016-01-08 17:25:33 -0700162 return self.addresses.get("ip", None)
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700163
164 # This getter is necessary because nat_mac is a sync_attribute
165 @property
166 def nat_mac(self):
Jeremy Mowery82760822016-01-08 16:36:22 -0700167 """str: The MAC address of this Tenant on the NAT network."""
Jeremy Mowery5055c7b2016-01-08 17:25:33 -0700168 return self.addresses.get("mac", None)
Jeremy Mowerya9b673b2016-01-07 21:25:50 -0700169
170 @property
Jeremy Moweryfd081292016-02-07 17:07:55 -0700171 def server_network(self):
Jeremy Mowery82760822016-01-08 16:36:22 -0700172 """str: The IP address of the server on the VPN."""
Jeremy Mowerybd2ed3a2016-01-05 16:52:43 -0700173 return self.get_attribute(
Jeremy Moweryfd081292016-02-07 17:07:55 -0700174 'server_network',
175 self.default_attributes['server_network'])
Jeremy Mowerybd2ed3a2016-01-05 16:52:43 -0700176
Jeremy Moweryfd081292016-02-07 17:07:55 -0700177 @server_network.setter
178 def server_network(self, value):
179 self.set_attribute("server_network", value)
Jeremy Mowerybd2ed3a2016-01-05 16:52:43 -0700180
181 @property
Jeremy Moweryfd081292016-02-07 17:07:55 -0700182 def vpn_subnet(self):
Jeremy Mowery82760822016-01-08 16:36:22 -0700183 """str: The IP address of the client on the VPN."""
Jeremy Mowerybd2ed3a2016-01-05 16:52:43 -0700184 return self.get_attribute(
Jeremy Moweryfd081292016-02-07 17:07:55 -0700185 'vpn_subnet',
186 self.default_attributes['vpn_subnet'])
Jeremy Mowerybd2ed3a2016-01-05 16:52:43 -0700187
Jeremy Moweryfd081292016-02-07 17:07:55 -0700188 @vpn_subnet.setter
189 def vpn_subnet(self, value):
190 self.set_attribute("vpn_subnet", value)
Jeremy Mowerybd2ed3a2016-01-05 16:52:43 -0700191
192 @property
Jeremy Mowery4a23e7d2016-01-06 15:16:33 -0700193 def is_persistent(self):
Jeremy Mowery82760822016-01-08 16:36:22 -0700194 """bool: True if the VPN connection is persistence, false otherwise."""
Jeremy Mowery4a23e7d2016-01-06 15:16:33 -0700195 return self.get_attribute(
196 "is_persistent",
197 self.default_attributes['is_persistent'])
198
199 @is_persistent.setter
200 def is_persistent(self, value):
201 self.set_attribute("is_persistent", value)
202
203 @property
Jeremy Mowery65c60702016-03-16 11:22:02 -0700204 def failover_servers(self):
Jeremy Mowerya74c31d2016-04-04 22:30:44 -0700205 return self.get_attribute(
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -0700206 "failover_servers", self.default_attributes["failover_servers"])
Jeremy Mowery65c60702016-03-16 11:22:02 -0700207
208 @failover_servers.setter
209 def failover_servers(self, value):
210 self.set_attribute("failover_servers", value)
211
212 @property
Jeremy Moweryfd081292016-02-07 17:07:55 -0700213 def clients_can_see_each_other(self):
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -0700214 """bool: True if the client can see the subnet of the server, false
215 otherwise."""
Jeremy Mowery4a23e7d2016-01-06 15:16:33 -0700216 return self.get_attribute(
Jeremy Moweryfd081292016-02-07 17:07:55 -0700217 "clients_can_see_each_other",
218 self.default_attributes['clients_can_see_each_other'])
Jeremy Mowery4a23e7d2016-01-06 15:16:33 -0700219
Jeremy Moweryfd081292016-02-07 17:07:55 -0700220 @clients_can_see_each_other.setter
221 def clients_can_see_each_other(self, value):
222 self.set_attribute("clients_can_see_each_other", value)
Jeremy Mowery4a23e7d2016-01-06 15:16:33 -0700223
Jeremy Mowerye02d4b62016-01-10 15:21:52 -0700224 @property
Jeremy Mowery3b1caba2016-02-02 23:53:50 -0700225 def ca_crt(self):
Jeremy Mowery1eab4fa2016-02-02 17:17:20 -0700226 """str: the string for the ca certificate"""
Jeremy Mowery3b1caba2016-02-02 23:53:50 -0700227 return self.get_attribute("ca_crt", self.default_attributes['ca_crt'])
Jeremy Mowery1eab4fa2016-02-02 17:17:20 -0700228
Jeremy Mowery3b1caba2016-02-02 23:53:50 -0700229 @ca_crt.setter
230 def ca_crt(self, value):
231 self.set_attribute("ca_crt", value)
232
Jeremy Mowery290714e2016-02-25 14:36:29 -0700233 @property
234 def port_number(self):
235 """int: the integer representing the port number for this server"""
236 return self.get_attribute("port", self.default_attributes['port'])
237
238 @port_number.setter
239 def port_number(self, value):
240 self.set_attribute("port", value)
241
Jeremy Mowery47c93742016-03-02 00:01:25 -0700242 @property
243 def script_text(self):
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -0700244 return self.get_attribute(
245 "script_text", self.default_attributes['script_text'])
Jeremy Mowery47c93742016-03-02 00:01:25 -0700246
247 @script_text.setter
248 def script_text(self, value):
249 self.set_attribute("script_text", value)
250
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700251 def create_client_script(self, client_name):
Jeremy Mowerybd3dfae2016-03-01 19:15:40 -0700252 script = ""
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700253 # write the configuration portion
Jeremy Mowerybd3dfae2016-03-01 19:15:40 -0700254 script += ("printf \"%b\" \"")
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700255 script += self.generate_client_conf(client_name)
Jeremy Mowerybd3dfae2016-03-01 19:15:40 -0700256 script += ("\" > client.conf\n")
257 script += ("printf \"%b\" \"")
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700258 for line in self.ca_crt:
Jeremy Mowerybd3dfae2016-03-01 19:15:40 -0700259 script += (line.rstrip() + r"\n")
260 script += ("\" > ca.crt\n")
Jeremy Mowerycf1c5192016-03-15 20:28:04 -0700261 script += ("printf \"%b\" \"")
Jeremy Mowery30bdab92016-03-29 23:52:46 -0700262 for line in self.get_client_cert(client_name):
Jeremy Mowerycf1c5192016-03-15 20:28:04 -0700263 script += (line.rstrip() + r"\n")
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700264 script += ("\" > " + client_name + ".crt\n")
Jeremy Mowery30bdab92016-03-29 23:52:46 -0700265 for line in self.get_client_key(client_name):
Jeremy Mowerycf1c5192016-03-15 20:28:04 -0700266 script += (line.rstrip() + r"\n")
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700267 script += ("\" > " + client_name + ".key\n")
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700268 # make sure openvpn is installed
Jeremy Mowerybd3dfae2016-03-01 19:15:40 -0700269 script += ("apt-get update\n")
270 script += ("apt-get install openvpn\n")
271 script += ("openvpn client.conf &\n")
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700272 # close the script
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700273 return script
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700274
Jeremy Mowery30bdab92016-03-29 23:52:46 -0700275 def get_client_cert(self, client_name):
Jeremy Moweryb52c49a2016-04-05 23:32:10 -0700276 with open(self.pki_dir + "/issued/" + client_name + ".crt", 'r') as f:
277 return f.readlines()
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700278
Jeremy Mowery30bdab92016-03-29 23:52:46 -0700279 def get_client_key(self, client_name):
Jeremy Moweryb52c49a2016-04-05 23:32:10 -0700280 with open(self.pki_dir + "/private/" + client_name + ".key", 'r') as f:
281 return f.readlines()
Jeremy Mowerycf1c5192016-03-15 20:28:04 -0700282
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700283 def generate_client_conf(self, client_name):
Jeremy Mowery3bcea4b2016-04-01 21:54:29 -0700284 """str: Generates the client configuration to use to connect to this
285 VPN server.
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700286 """
287 conf = ("client\n" +
Jeremy Mowery6d49f1a2016-03-21 17:56:25 -0700288 "dev tun\n" +
Jeremy Mowerya74c31d2016-04-04 22:30:44 -0700289 self.get_remote_line(
290 self.nat_ip, self.port_number, self.protocol))
291 for remote in self.failover_servers:
292 conf += self.get_remote_line(
293 remote.nat_ip, remote.port_number, remote.protocol)
294
295 conf += ("resolv-retry 60\n" +
296 "nobind\n" +
297 "ca ca.crt\n" +
298 "cert " + client_name + ".crt\n" +
299 "key " + client_name + ".key\n" +
300 "comp-lzo\n" +
301 "verb 3\n")
Jeremy Mowery01e3a5b2016-02-25 14:50:58 -0700302
303 if self.is_persistent:
304 conf += "persist-tun\n"
305 conf += "persist-key\n"
306
307 return conf
308
Jeremy Mowerya74c31d2016-04-04 22:30:44 -0700309 def get_remote_line(self, host, port_number, protocol):
310 return ("remote " + str(host) + " " + str(port_number) + " " +
311 str(protocol) + "\n")
312
Jeremy Mowery290714e2016-02-25 14:36:29 -0700313
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700314def model_policy_vpn_tenant(pk):
Jeremy Mowery82760822016-01-08 16:36:22 -0700315 """Manages the contain for the VPN Tenant."""
Jeremy Mowery8b664f72015-12-04 11:52:16 -0700316 # This section of code is atomic to prevent race conditions
317 with transaction.atomic():
318 # We find all of the tenants that are waiting to update
319 tenant = VPNTenant.objects.select_for_update().filter(pk=pk)
320 if not tenant:
321 return
322 # Since this code is atomic it is safe to always use the first tenant
323 tenant = tenant[0]
324 tenant.manage_container()