| Jeremy Mowery | b31bd9e | 2016-03-14 23:59:11 -0700 | [diff] [blame] | 1 | from core.admin import ReadOnlyAwareAdmin, SliceInline, TenantPrivilegeInline |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 2 | from core.middleware import get_request |
| Jeremy Mowery | 2cfcb8e | 2016-03-16 14:21:48 -0700 | [diff] [blame] | 3 | from core.models import TenantPrivilege, User |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 4 | from django import forms |
| 5 | from django.contrib import admin |
| Jeremy Mowery | 2cfcb8e | 2016-03-16 14:21:48 -0700 | [diff] [blame] | 6 | from django.core import serializers |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 7 | from services.vpn.models import VPN_KIND, VPNService, VPNTenant |
| Jeremy Mowery | 257d591d | 2016-03-15 20:28:04 -0700 | [diff] [blame] | 8 | from subprocess import Popen, PIPE |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 9 | from xos.exceptions import XOSValidationError |
| 10 | |
| 11 | |
| 12 | class VPNServiceForm(forms.ModelForm): |
| 13 | |
| Jeremy Mowery | 9ddca29 | 2016-03-27 20:32:04 -0700 | [diff] [blame] | 14 | exposed_ports = forms.CharField(required=True) |
| 15 | |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 16 | def save(self, commit=True): |
| Jeremy Mowery | 9ddca29 | 2016-03-27 20:32:04 -0700 | [diff] [blame] | 17 | self.instance.exposed_ports = self.cleaned_data['exposed_ports'] |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 18 | return super(VPNServiceForm, self).save(commit=commit) |
| 19 | |
| Jeremy Mowery | 9ddca29 | 2016-03-27 20:32:04 -0700 | [diff] [blame] | 20 | def clean_exposed_ports(self): |
| 21 | exposed_ports = self.cleaned_data['exposed_ports'] |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 22 | port_mapping = {"udp": [], "tcp": []} |
| 23 | parts = exposed_ports.split(",") |
| 24 | for part in parts: |
| 25 | part = part.strip() |
| 26 | if "/" in part: |
| 27 | (protocol, ports) = part.split("/", 1) |
| 28 | elif " " in part: |
| 29 | (protocol, ports) = part.split(None, 1) |
| 30 | else: |
| 31 | raise XOSValidationError('malformed port specifier %s, format example: "tcp 123, tcp 201:206, udp 333"' % part) |
| 32 | |
| 33 | protocol = protocol.strip() |
| 34 | ports = ports.strip() |
| 35 | |
| 36 | if not (protocol in ["udp", "tcp"]): |
| 37 | raise XOSValidationError('unknown protocol %s' % protocol) |
| 38 | |
| 39 | if "-" in ports: |
| 40 | port_mapping[protocol].extend(self.parse_port_range(ports, "-")) |
| 41 | elif ":" in ports: |
| 42 | port_mapping[protocol].extend(self.parse_port_range(ports, ":")) |
| 43 | else: |
| 44 | port_mapping[protocol].append(int(ports)) |
| 45 | |
| 46 | return port_mapping |
| 47 | |
| 48 | def parse_port_range(self, port_str, split_str): |
| 49 | (first, last) = port_str.split(split_str) |
| 50 | first = int(first.strip()) |
| 51 | last = int(last.strip()) |
| 52 | return list(range(first, last)) |
| 53 | |
| 54 | class Meta: |
| 55 | model = VPNService |
| 56 | |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 57 | |
| 58 | class VPNServiceAdmin(ReadOnlyAwareAdmin): |
| 59 | """Defines the admin for the VPNService.""" |
| 60 | model = VPNService |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 61 | form = VPNServiceForm |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 62 | verbose_name = "VPN Service" |
| 63 | |
| 64 | list_display = ("backend_status_icon", "name", "enabled") |
| 65 | |
| 66 | list_display_links = ('backend_status_icon', 'name', ) |
| 67 | |
| 68 | fieldsets = [(None, {'fields': ['backend_status_text', 'name', 'enabled', |
| Jeremy Mowery | 9ddca29 | 2016-03-27 20:32:04 -0700 | [diff] [blame] | 69 | 'versionNumber', 'description', "view_url", 'exposed_ports'], |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 70 | 'classes':['suit-tab suit-tab-general']})] |
| 71 | |
| 72 | readonly_fields = ('backend_status_text', ) |
| 73 | |
| 74 | inlines = [SliceInline] |
| 75 | |
| 76 | extracontext_registered_admins = True |
| 77 | |
| 78 | user_readonly_fields = ["name", "enabled", "versionNumber", "description"] |
| 79 | |
| 80 | suit_form_tabs = (('general', 'VPN Service Details'), |
| 81 | ('administration', 'Tenants'), |
| 82 | ('slices', 'Slices'),) |
| 83 | |
| 84 | suit_form_includes = (('vpnserviceadmin.html', |
| 85 | 'top', |
| 86 | 'administration'),) |
| 87 | |
| 88 | def queryset(self, request): |
| 89 | return VPNService.get_service_objects_by_user(request.user) |
| 90 | |
| 91 | |
| 92 | class VPNTenantForm(forms.ModelForm): |
| 93 | """The form used to create and edit a VPNTenant. |
| 94 | |
| 95 | Attributes: |
| 96 | creator (forms.ModelChoiceField): The XOS user that created this tenant. |
| 97 | client_conf (forms.CharField): The readonly configuration used on the client to connect to this Tenant. |
| 98 | server_address (forms.GenericIPAddressField): The ip address on the VPN of this Tenant. |
| 99 | client_address (forms.GenericIPAddressField): The ip address on the VPN of the client. |
| 100 | is_persistent (forms.BooleanField): Determines if this Tenant keeps this connection alive through failures. |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 101 | """ |
| 102 | creator = forms.ModelChoiceField(queryset=User.objects.all()) |
| 103 | server_network = forms.GenericIPAddressField( |
| 104 | protocol="IPv4", required=True) |
| 105 | vpn_subnet = forms.GenericIPAddressField(protocol="IPv4", required=True) |
| 106 | is_persistent = forms.BooleanField(required=False) |
| 107 | clients_can_see_each_other = forms.BooleanField(required=False) |
| Jeremy Mowery | 4b17dac | 2016-03-16 11:25:15 -0700 | [diff] [blame] | 108 | failover_servers = forms.ModelMultipleChoiceField(queryset=VPNTenant.get_tenant_objects(), required=False) |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 109 | protocol = forms.ChoiceField(required=True, choices=[("udp", "udp"), ("tcp", "tcp")]) |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 110 | |
| 111 | def __init__(self, *args, **kwargs): |
| 112 | super(VPNTenantForm, self).__init__(*args, **kwargs) |
| 113 | self.fields['kind'].widget.attrs['readonly'] = True |
| 114 | # self.fields['script_name'].widget.attrs['readonly'] = True |
| 115 | self.fields[ |
| 116 | 'provider_service'].queryset = VPNService.get_service_objects().all() |
| 117 | |
| 118 | self.fields['kind'].initial = VPN_KIND |
| 119 | |
| 120 | if self.instance: |
| 121 | self.fields['creator'].initial = self.instance.creator |
| 122 | self.fields['vpn_subnet'].initial = self.instance.vpn_subnet |
| 123 | self.fields[ |
| 124 | 'server_network'].initial = self.instance.server_network |
| 125 | self.fields[ |
| 126 | 'clients_can_see_each_other'].initial = self.instance.clients_can_see_each_other |
| 127 | self.fields['is_persistent'].initial = self.instance.is_persistent |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 128 | self.fields['protocol'].initial = self.instance.protocol |
| Jeremy Mowery | b4cfbb2 | 2016-03-19 22:19:08 -0700 | [diff] [blame] | 129 | if (self.instance.failover_servers): |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 130 | self.initial['failover_servers'] = [model.pk for model in list(serializers.deserialize('json', self.instance.failover_servers))] |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 131 | |
| 132 | if (not self.instance) or (not self.instance.pk): |
| 133 | self.fields['creator'].initial = get_request().user |
| 134 | self.fields['vpn_subnet'].initial = "255.255.255.0" |
| 135 | self.fields['server_network'].initial = "10.66.77.0" |
| 136 | self.fields['clients_can_see_each_other'].initial = True |
| 137 | self.fields['is_persistent'].initial = True |
| 138 | if VPNService.get_service_objects().exists(): |
| 139 | self.fields["provider_service"].initial = VPNService.get_service_objects().all()[ |
| 140 | 0] |
| 141 | |
| 142 | def save(self, commit=True): |
| 143 | self.instance.creator = self.cleaned_data.get("creator") |
| 144 | self.instance.is_persistent = self.cleaned_data.get('is_persistent') |
| 145 | self.instance.vpn_subnet = self.cleaned_data.get("vpn_subnet") |
| 146 | self.instance.server_network = self.cleaned_data.get('server_network') |
| 147 | self.instance.clients_can_see_each_other = self.cleaned_data.get( |
| 148 | 'clients_can_see_each_other') |
| Jeremy Mowery | 2cfcb8e | 2016-03-16 14:21:48 -0700 | [diff] [blame] | 149 | self.instance.failover_servers = serializers.serialize("json", self.cleaned_data.get('failover_servers')) |
| Jeremy Mowery | 324caf5 | 2016-03-16 11:22:02 -0700 | [diff] [blame] | 150 | |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 151 | self.instance.port_number = self.instance.provider_service.get_next_available_port(self.instance.protocol) |
| 152 | self.instance.protocol = self.cleaned_data.get("protocol") |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 153 | |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 154 | if (not self.instance.ca_crt): |
| 155 | self.instance.ca_crt = self.generate_ca_crt() |
| 156 | |
| 157 | return super(VPNTenantForm, self).save(commit=commit) |
| 158 | |
| 159 | def generate_ca_crt(self): |
| 160 | """str: Generates the ca cert by reading from the ca file""" |
| 161 | with open("/opt/openvpn/easyrsa3/pki/ca.crt") as crt: |
| 162 | return crt.readlines() |
| 163 | |
| 164 | class Meta: |
| 165 | model = VPNTenant |
| 166 | |
| 167 | |
| 168 | class VPNTenantAdmin(ReadOnlyAwareAdmin): |
| 169 | verbose_name = "VPN Tenant Admin" |
| 170 | list_display = ('id', 'backend_status_icon', 'instance', |
| 171 | 'server_network', 'vpn_subnet') |
| 172 | list_display_links = ('id', 'backend_status_icon', |
| 173 | 'instance', 'server_network', 'vpn_subnet') |
| 174 | fieldsets = [(None, {'fields': ['backend_status_text', 'kind', |
| 175 | 'provider_service', 'instance', 'creator', |
| 176 | 'server_network', 'vpn_subnet', 'is_persistent', |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 177 | 'clients_can_see_each_other', 'failover_servers', "protocol"], |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 178 | 'classes': ['suit-tab suit-tab-general']})] |
| 179 | readonly_fields = ('backend_status_text', 'instance') |
| 180 | form = VPNTenantForm |
| Jeremy Mowery | b31bd9e | 2016-03-14 23:59:11 -0700 | [diff] [blame] | 181 | inlines = [TenantPrivilegeInline] |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 182 | |
| Jeremy Mowery | 1f3f9f6 | 2016-03-21 17:56:25 -0700 | [diff] [blame] | 183 | suit_form_tabs = (('general', 'Details'), ('tenantprivileges', 'Privileges')) |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 184 | |
| 185 | def queryset(self, request): |
| 186 | return VPNTenant.get_tenant_objects_by_user(request.user) |
| 187 | |
| Jeremy Mowery | b31bd9e | 2016-03-14 23:59:11 -0700 | [diff] [blame] | 188 | def certificate_name(self, tenant_privilege): |
| 189 | return str(tenant_privilege.user.email) + "-" + str(tenant_privilege.tenant.id) |
| 190 | |
| 191 | def save_formset(self, request, form, formset, change): |
| 192 | super(VPNTenantAdmin, self).save_formset(request, form, formset, change) |
| 193 | for obj in formset.deleted_objects: |
| 194 | # If anything deleated was a TenantPrivilege then revoke the certificate |
| 195 | if type(obj) is TenantPrivilege: |
| 196 | certificate = self.certificate_name(obj) |
| Jeremy Mowery | 257d591d | 2016-03-15 20:28:04 -0700 | [diff] [blame] | 197 | Popen("/opt/openvpn/easyrsa3/easyrsa --batch revoke " + certificate, shell=True, stdout=PIPE).communicate() |
| Jeremy Mowery | b31bd9e | 2016-03-14 23:59:11 -0700 | [diff] [blame] | 198 | # TODO(jermowery): determine if this is necessary. |
| 199 | # if type(obj) is VPNTenant: |
| 200 | # if the tenant was deleted revoke all certs assoicated |
| 201 | # pass |
| 202 | |
| 203 | for obj in formset.new_objects: |
| 204 | # If there were any new TenantPrivlege objects then create certs |
| 205 | if type(obj) is TenantPrivilege: |
| 206 | certificate = self.certificate_name(obj) |
| Jeremy Mowery | 257d591d | 2016-03-15 20:28:04 -0700 | [diff] [blame] | 207 | Popen("/opt/openvpn/easyrsa3/easyrsa --batch build-client-full " + certificate + " nopass", shell=True, stdout=PIPE).communicate() |
| Jeremy Mowery | b31bd9e | 2016-03-14 23:59:11 -0700 | [diff] [blame] | 208 | |
| 209 | |
| Jeremy Mowery | 3c5277f | 2016-02-29 21:09:04 -0700 | [diff] [blame] | 210 | # Associate the admin forms with the models. |
| 211 | admin.site.register(VPNService, VPNServiceAdmin) |
| 212 | admin.site.register(VPNTenant, VPNTenantAdmin) |