1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #9816: VPN tunnel support (#14276)

- Introduces a new `vpn` app with the following models:
    - Tunnel
    - TunnelTermination
    - IKEProposal
    - IKEPolicy
    - IPSecProposal
    - IPSecPolicy
    - IPSecProfile
This commit is contained in:
Jeremy Stretch
2023-11-27 16:17:15 -05:00
committed by GitHub
parent 975a647d9a
commit 6678880db5
58 changed files with 5656 additions and 10 deletions

View File

@ -0,0 +1,49 @@
# Tunnels
NetBox can model private tunnels formed among virtual termination points across your network. Typical tunnel implementations include GRE, IP-in-IP, and IPSec. A tunnel may be terminated to two or more device or virtual machine interfaces.
```mermaid
flowchart TD
Termination1[TunnelTermination]
Termination2[TunnelTermination]
Interface1[Interface]
Interface2[Interface]
Tunnel --> Termination1 & Termination2
Termination1 --> Interface1
Termination2 --> Interface2
Interface1 --> Device
Interface2 --> VirtualMachine
click Tunnel "../../models/vpn/tunnel/"
click TunnelTermination1 "../../models/vpn/tunneltermination/"
click TunnelTermination2 "../../models/vpn/tunneltermination/"
```
# IPSec & IKE
NetBox includes robust support for modeling IPSec & IKE policies. These are used to define encryption and authentication parameters for IPSec tunnels.
```mermaid
flowchart TD
subgraph IKEProposals[Proposals]
IKEProposal1[IKEProposal]
IKEProposal2[IKEProposal]
end
subgraph IPSecProposals[Proposals]
IPSecProposal1[IPSecProposal]
IPSecProposal2[IPSecProposal]
end
IKEProposals --> IKEPolicy
IPSecProposals --> IPSecPolicy
IKEPolicy & IPSecPolicy--> IPSecProfile
IPSecProfile --> Tunnel
click IKEProposal1 "../../models/vpn/ikeproposal/"
click IKEProposal2 "../../models/vpn/ikeproposal/"
click IKEPolicy "../../models/vpn/ikepolicy/"
click IPSecProposal1 "../../models/vpn/ipsecproposal/"
click IPSecProposal2 "../../models/vpn/ipsecproposal/"
click IPSecPolicy "../../models/vpn/ipsecpolicy/"
click IPSecProfile "../../models/vpn/ipsecprofile/"
click Tunnel "../../models/vpn/tunnel/"
```

View File

@ -0,0 +1,25 @@
# IKE Policies
An [Internet Key Exhcnage (IKE)](https://en.wikipedia.org/wiki/Internet_Key_Exchange) policy defines an IKE version, mode, and set of [proposals](./ikeproposal.md) to be used in IKE negotiation. These policies are referenced by [IPSec profiles](./ipsecprofile.md).
## Fields
### Name
The unique user-assigned name for the policy.
### Version
The IKE version employed (v1 or v2).
### Mode
The IKE mode employed (main or aggressive).
### Proposals
One or more [IKE proposals](./ikeproposal.md) supported for use by this policy.
### Pre-shared Key
A pre-shared secret key associated with this policy (optional).

View File

@ -0,0 +1,39 @@
# IKE Proposals
An [Internet Key Exhcnage (IKE)](https://en.wikipedia.org/wiki/Internet_Key_Exchange) proposal defines a set of parameters used to establish a secure bidirectional connection across an untrusted medium, such as the Internet. IKE proposals defined in NetBox can be referenced by [IKE policies](./ikepolicy.md), which are in turn employed by [IPSec profiles](./ipsecprofile.md).
!!! note
Some platforms refer to IKE proposals as [ISAKMP](https://en.wikipedia.org/wiki/Internet_Security_Association_and_Key_Management_Protocol), which is a framework for authentication and key exchange which employs IKE.
## Fields
### Name
The unique user-assigned name for the proposal.
### Authentication Method
The strategy employed for authenticating the IKE peer. Available options are listed below.
| Name |
|----------------|
| Pre-shared key |
| Certificate |
| RSA signature |
| DSA signature |
### Encryption Algorithm
The protocol employed for data encryption. Options include DES, 3DES, and various flavors of AES.
### Authentication Algorithm
The mechanism employed to ensure data integrity. Options include MD5 and SHA HMAC implementations.
### Group
The [Diffie-Hellman group](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) supported by the proposal. Group IDs are [managed by IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml#ikev2-parameters-8).
### SA Lifetime
The maximum lifetime for the IKE security association (SA), in seconds.

View File

@ -0,0 +1,17 @@
# IPSec Policy
An [IPSec](https://en.wikipedia.org/wiki/IPsec) policy defines a set of [proposals](./ikeproposal.md) to be used in the formation of IPSec tunnels. A perfect forward secrecy (PFS) group may optionally also be defined. These policies are referenced by [IPSec profiles](./ipsecprofile.md).
## Fields
### Name
The unique user-assigned name for the policy.
### Proposals
One or more [IPSec proposals](./ipsecproposal.md) supported for use by this policy.
### PFS Group
The [perfect forward secrecy (PFS)](https://en.wikipedia.org/wiki/Forward_secrecy) group supported by this policy (optional).

View File

@ -0,0 +1,21 @@
# IPSec Profile
An [IPSec](https://en.wikipedia.org/wiki/IPsec) profile defines an [IKE policy](./ikepolicy.md), [IPSec policy](./ipsecpolicy.md), and IPSec mode used for establishing an IPSec tunnel.
## Fields
### Name
The unique user-assigned name for the profile.
### Mode
The IPSec mode employed by the profile: Encapsulating Security Payload (ESP) or Authentication Header (AH).
### IKE Policy
The [IKE policy](./ikepolicy.md) associated with the profile.
### IPSec Policy
The [IPSec policy](./ipsecpolicy.md) associated with the profile.

View File

@ -0,0 +1,25 @@
# IPSec Proposal
An [IPSec](https://en.wikipedia.org/wiki/IPsec) proposal defines a set of parameters used in negotiating security associations for IPSec tunnels. IPSec proposals defined in NetBox can be referenced by [IPSec policies](./ipsecpolicy.md), which are in turn employed by [IPSec profiles](./ipsecprofile.md).
## Fields
### Name
The unique user-assigned name for the proposal.
### Encryption Algorithm
The protocol employed for data encryption. Options include DES, 3DES, and various flavors of AES.
### Authentication Algorithm
The mechanism employed to ensure data integrity. Options include MD5 and SHA HMAC implementations.
### SA Lifetime (Seconds)
The maximum amount of time for which the security association (SA) may be active, in seconds.
### SA Lifetime (Data)
The maximum amount of data which can be transferred within the security association (SA) before it must be rebuilt, in kilobytes.

36
docs/models/vpn/tunnel.md Normal file
View File

@ -0,0 +1,36 @@
# Tunnels
A tunnel represents a private virtual connection established among two or more endpoints across a shared infrastructure by employing protocol encapsulation. Common encapsulation techniques include [Generic Routing Encapsulation (GRE)](https://en.wikipedia.org/wiki/Generic_Routing_Encapsulation), [IP-in-IP](https://en.wikipedia.org/wiki/IP_in_IP), and [IPSec](https://en.wikipedia.org/wiki/IPsec). NetBox supports modeling both peer-to-peer and hub-and-spoke tunnel topologies.
Device and virtual machine interfaces are associated to tunnels by creating [tunnel terminations](./tunneltermination.md).
## Fields
### Name
A unique name assigned to the tunnel for identification.
### Status
The operational status of the tunnel. By default, the following statuses are available:
| Name |
|----------------|
| Planned |
| Active |
| Disabled |
!!! tip "Custom tunnel statuses"
Additional tunnel statuses may be defined by setting `Tunnel.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
### Encapsulation
The encapsulation protocol or technique employed to effect the tunnel. NetBox supports GRE, IP-in-IP, and IPSec encapsulations.
### Tunnel ID
An optional numeric identifier for the tunnel.
### IPSec Profile
For IPSec tunnels, this is the [IPSec Profile](./ipsecprofile.md) employed to negotiate security associations.

View File

@ -0,0 +1,30 @@
# Tunnel Terminations
A tunnel termination connects a device or virtual machine interface to a [tunnel](./tunnel.md). The tunnel must be created before any terminations may be added.
## Fields
### Tunnel
The [tunnel](./tunnel.md) to which this termination is made.
### Role
The functional role of the attached interface. The following options are available:
| Name | Description |
|-------|--------------------------------------------------|
| Peer | An endpoint in a point-to-point or mesh topology |
| Hub | A central point in a hub-and-spoke topology |
| Spoke | An edge point in a hub-and-spoke topology |
!!! note
Multiple hub terminations may be attached to a tunnel.
### Termination
The device or virtual machine interface terminated to the tunnel.
### Outside IP
The public or underlay IP address with which this termination is associated. This is the IP to which peers will route tunneled traffic.

View File

@ -74,6 +74,7 @@ nav:
- Circuits: 'features/circuits.md'
- Wireless: 'features/wireless.md'
- Virtualization: 'features/virtualization.md'
- VPN Tunnels: 'features/vpn-tunnels.md'
- Tenancy: 'features/tenancy.md'
- Contacts: 'features/contacts.md'
- Search: 'features/search.md'
@ -252,6 +253,14 @@ nav:
- ClusterType: 'models/virtualization/clustertype.md'
- VMInterface: 'models/virtualization/vminterface.md'
- VirtualMachine: 'models/virtualization/virtualmachine.md'
- VPN:
- IKEPolicy: 'models/vpn/ikepolicy.md'
- IKEProposal: 'models/vpn/ikeproposal.md'
- IPSecPolicy: 'models/vpn/ipsecpolicy.md'
- IPSecProfile: 'models/vpn/ipsecprofile.md'
- IPSecProposal: 'models/vpn/ipsecproposal.md'
- Tunnel: 'models/vpn/tunnel.md'
- TunnelTermination: 'models/vpn/tunneltermination.md'
- Wireless:
- WirelessLAN: 'models/wireless/wirelesslan.md'
- WirelessLANGroup: 'models/wireless/wirelesslangroup.md'

View File

@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
APPS = ('circuits', 'core', 'dcim', 'extras', 'ipam', 'tenancy', 'users', 'virtualization', 'wireless')
APPS = ('circuits', 'core', 'dcim', 'extras', 'ipam', 'tenancy', 'users', 'virtualization', 'vpn', 'wireless')
BANNER_TEXT = """### NetBox interactive shell ({node})
### Python {python} | Django {django} | NetBox {netbox}

View File

@ -566,6 +566,10 @@ class BaseInterface(models.Model):
return super().save(*args, **kwargs)
@property
def tunnel_termination(self):
return self.tunnel_terminations.first()
@property
def count_ipaddresses(self):
return self.ip_addresses.count()
@ -719,6 +723,12 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
object_id_field='interface_id',
related_query_name='+'
)
tunnel_terminations = GenericRelation(
to='vpn.TunnelTermination',
content_type_field='termination_type',
object_id_field='termination_id',
related_query_name='interface'
)
l2vpn_terminations = GenericRelation(
to='ipam.L2VPNTermination',
content_type_field='assigned_object_type',

View File

@ -584,6 +584,12 @@ class BaseInterfaceTable(NetBoxTable):
orderable=False,
verbose_name=_('L2VPN')
)
tunnel = tables.Column(
accessor=tables.A('tunnel_termination__tunnel'),
linkify=True,
orderable=False,
verbose_name=_('Tunnel')
)
untagged_vlan = tables.Column(
verbose_name=_('Untagged VLAN'),
linkify=True
@ -646,7 +652,8 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
'speed', 'speed_formatted', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable',
'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn',
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'inventory_items', 'created', 'last_updated',
'tunnel', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'inventory_items', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description')
@ -682,8 +689,8 @@ class DeviceInterfaceTable(InterfaceTable):
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag',
'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link',
'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn', 'ip_addresses', 'fhrp_groups',
'untagged_vlan', 'tagged_vlans', 'actions',
'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn', 'tunnel', 'ip_addresses',
'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
)
default_columns = (
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',

View File

@ -359,6 +359,16 @@ INTERFACE_BUTTONS = """
<i class="mdi mdi-wifi-off" aria-hidden="true"></i>
</a>
{% endif %}
{% elif record.type == 'virtual' %}
{% if perms.vpn.add_tunnel and not record.tunnel_termination %}
<a href="{% url 'vpn:tunnel_add' %}?termination1_type=dcim.device&termination1_parent={{ record.device.pk }}&termination1_interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" title="Create a tunnel" class="btn btn-success btn-sm">
<i class="mdi mdi-tunnel-outline" aria-hidden="true"></i>
</a>
{% elif perms.vpn.delete_tunneltermination and record.tunnel_termination %}
<a href="{% url 'vpn:tunneltermination_delete' pk=record.tunnel_termination.pk %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" title="Remove tunnel" class="btn btn-danger btn-sm">
<i class="mdi mdi-tunnel-outline" aria-hidden="true"></i>
</a>
{% endif %}
{% elif record.is_wired and perms.dcim.add_cable %}
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>

View File

@ -39,6 +39,7 @@ class APIRootView(APIView):
'tenancy': reverse('tenancy-api:api-root', request=request, format=format),
'users': reverse('users-api:api-root', request=request, format=format),
'virtualization': reverse('virtualization-api:api-root', request=request, format=format),
'vpn': reverse('vpn-api:api-root', request=request, format=format),
'wireless': reverse('wireless-api:api-root', request=request, format=format),
})

View File

@ -9,6 +9,7 @@ from netbox.registry import registry
from tenancy.graphql.schema import TenancyQuery
from users.graphql.schema import UsersQuery
from virtualization.graphql.schema import VirtualizationQuery
from vpn.graphql.schema import VPNQuery
from wireless.graphql.schema import WirelessQuery
@ -21,6 +22,7 @@ class Query(
IPAMQuery,
TenancyQuery,
VirtualizationQuery,
VPNQuery,
WirelessQuery,
*registry['plugins']['graphql_schemas'], # Append plugin schemas
graphene.ObjectType

View File

@ -195,17 +195,34 @@ IPAM_MENU = Menu(
),
)
OVERLAY_MENU = Menu(
label=_('Overlay'),
VPN_MENU = Menu(
label=_('VPN'),
icon_class='mdi mdi-graph-outline',
groups=(
MenuGroup(
label='L2VPNs',
label=_('Tunnels'),
items=(
get_model_item('vpn', 'tunnel', _('Tunnels')),
get_model_item('vpn', 'tunneltermination', _('Tunnel Terminations')),
),
),
MenuGroup(
label=_('L2VPNs'),
items=(
get_model_item('ipam', 'l2vpn', _('L2VPNs')),
get_model_item('ipam', 'l2vpntermination', _('Terminations')),
),
),
MenuGroup(
label=_('Security'),
items=(
get_model_item('vpn', 'ikeproposal', _('IKE Proposals')),
get_model_item('vpn', 'ikepolicy', _('IKE Policies')),
get_model_item('vpn', 'ipsecproposal', _('IPSec Proposals')),
get_model_item('vpn', 'ipsecpolicy', _('IPSec Policies')),
get_model_item('vpn', 'ipsecprofile', _('IPSec Profiles')),
),
),
),
)
@ -444,7 +461,7 @@ MENUS = [
CONNECTIONS_MENU,
WIRELESS_MENU,
IPAM_MENU,
OVERLAY_MENU,
VPN_MENU,
VIRTUALIZATION_MENU,
CIRCUITS_MENU,
POWER_MENU,

View File

@ -379,6 +379,7 @@ INSTALLED_APPS = [
'users',
'utilities',
'virtualization',
'vpn',
'wireless',
'django_rq', # Must come after extras to allow overriding management commands
'drf_spectacular',

View File

@ -33,6 +33,7 @@ _patterns = [
path('tenancy/', include('tenancy.urls')),
path('users/', include('users.urls')),
path('virtualization/', include('virtualization.urls')),
path('vpn/', include('vpn.urls')),
path('wireless/', include('wireless.urls')),
# Current user views
@ -51,6 +52,7 @@ _patterns = [
path('api/tenancy/', include('tenancy.api.urls')),
path('api/users/', include('users.api.urls')),
path('api/virtualization/', include('virtualization.api.urls')),
path('api/vpn/', include('vpn.api.urls')),
path('api/wireless/', include('wireless.api.urls')),
path('api/status/', StatusView.as_view(), name='api-status'),

View File

@ -0,0 +1,67 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "IKE Policy" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "IKE Version" %}</th>
<td>{{ object.get_version_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Mode" %}</th>
<td>{{ object.get_mode_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Pre-Shared Key" %}</th>
<td>
<span id="secret" class="font-monospace" data-secret="{{ object.preshared_key }}">{{ object.preshared_key|placeholder }}</span>
{% if object.preshared_key %}
<button type="button" class="btn btn-sm btn-primary toggle-secret float-end" data-bs-toggle="button">{% trans "Show Secret" %}</button>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "IPSec Profiles" %}</th>
<td>
<a href="{% url 'vpn:ipsecprofile_list' %}?ike_policy_id={{ object.pk }}">{{ object.ipsec_profiles.count }}</a>
</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">{% trans "Proposals" %}</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'vpn:ikeproposal_list' %}?ike_policy_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,63 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "IKE Proposal" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Authentication method" %}</th>
<td>{{ object.get_authentication_method_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Encryption algorithm" %}</th>
<td>{{ object.get_encryption_algorithm_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Authentication algorithm" %}</th>
<td>{{ object.get_authentication_algorithm_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "DH group" %}</th>
<td>{{ object.get_group_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "SA lifetime (seconds)" %}</th>
<td>{{ object.sa_lifetime|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "IKE Policies" %}</th>
<td>
<a href="{% url 'vpn:ikepolicy_list' %}?proposal_id={{ object.pk }}">{{ object.ike_policies.count }}</a>
</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,55 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "IPSec Policy" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "PFS group" %}</th>
<td>{{ object.get_pfs_group_display|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "IPSec Profiles" %}</th>
<td>
<a href="{% url 'vpn:ipsecprofile_list' %}?ipsec_policy_id={{ object.pk }}">{{ object.ipsec_profiles.count }}</a>
</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">{% trans "Proposals" %}</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'vpn:ipsecproposal_list' %}?ipsec_policy_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,112 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "IPSec Profile" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Mode" %}</th>
<td>{{ object.get_mode_display }}</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/comments.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "IKE Policy" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.ike_policy|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.ike_policy.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Version" %}</th>
<td>{{ object.ike_policy.get_version_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Mode" %}</th>
<td>{{ object.ike_policy.get_mode_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Proposals" %}</th>
<td>
<ul class="list-unstyled mb-0">
{% for proposal in object.ike_policy.proposals.all %}
<li>
<a href="{{ proposal.get_absolute_url }}">{{ proposal }}</a>
</li>
{% endfor %}
</ul>
</td>
</tr>
<tr>
<th scope="row">{% trans "Pre-Shared Key" %}</th>
<td>{% checkmark object.ike_policy.preshared_key %}</td>
</tr>
</table>
</div>
</div>
<div class="card">
<h5 class="card-header">{% trans "IPSec Policy" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.ipsec_policy|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.ipsec_policy.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Proposals" %}</th>
<td>
<ul class="list-unstyled mb-0">
{% for proposal in object.ipsec_policy.proposals.all %}
<li>
<a href="{{ proposal.get_absolute_url }}">{{ proposal }}</a>
</li>
{% endfor %}
</ul>
</td>
</tr>
<tr>
<th scope="row">{% trans "PFS Group" %}</th>
<td>{{ object.ipsec_policy.get_pfs_group_display }}</td>
</tr>
</table>
</div>
</div>
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,59 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "IPSec Proposal" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Encryption algorithm" %}</th>
<td>{{ object.get_encryption_algorithm_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Authentication algorithm" %}</th>
<td>{{ object.get_authentication_algorithm_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "SA lifetime (seconds)" %}</th>
<td>{{ object.sa_lifetime_seconds|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "SA lifetime (KB)" %}</th>
<td>{{ object.sa_lifetime_data|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "IPSec Policies" %}</th>
<td>
<a href="{% url 'vpn:ipsecpolicy_list' %}?proposal_id={{ object.pk }}">{{ object.ipsec_policies.count }}</a>
</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,85 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block extra_controls %}
{% if perms.vpn.add_tunneltermination %}
<a href="{% url 'vpn:tunneltermination_add' %}?tunnel={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">
<i class="mdi mdi-plus-thick"></i> {% trans "Add Termination" %}
</a>
{% endif %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "Tunnel" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Status" %}</th>
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Encapsulation" %}</th>
<td>{{ object.get_encapsulation_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "IPSec profile" %}</th>
<td>{{ object.ipsec_profile|linkify|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Tunnel ID" %}</th>
<td>{{ object.tunnel_id|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Tenant" %}</th>
<td>
{% if object.tenant.group %}
{{ object.tenant.group|linkify }} /
{% endif %}
{{ object.tenant|linkify|placeholder }}
</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/comments.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">{% trans "Terminations" %}</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'vpn:tunneltermination_list' %}?tunnel_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.vpn.add_tunneltermination %}
<div class="card-footer text-end noprint">
<a href="{% url 'vpn:tunneltermination_add' %}?tunnel={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add a Termination" %}
</a>
</div>
{% endif %}
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,62 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{% trans "Tunnel Termination" %}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Tunnel" %}</th>
<td>{{ object.tunnel|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Role" %}</th>
<td>{% badge object.get_role_display bg_color=object.get_role_color %}</td>
</tr>
<tr>
<th scope="row">
{% if object.termination.device %}
{% trans "Device" %}
{% elif object.termination.virtual_machine %}
{% trans "Virtual Machine" %}
{% endif %}
</th>
<td>{{ object.termination.parent_object|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Interface" %}</th>
<td>{{ object.termination|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Outside IP" %}</th>
<td>{{ object.outside_ip|linkify|placeholder }}</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">{% trans "Peer Terminations" %}</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'vpn:tunneltermination_list' %}?tunnel_id={{ object.tunnel.pk }}&id__n={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -351,6 +351,12 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
object_id_field='interface_id',
related_query_name='+'
)
tunnel_terminations = GenericRelation(
to='vpn.TunnelTermination',
content_type_field='termination_type',
object_id_field='termination_id',
related_query_name='vminterface',
)
l2vpn_terminations = GenericRelation(
to='ipam.L2VPNTermination',
content_type_field='assigned_object_type',

View File

@ -131,7 +131,8 @@ class VMInterfaceTable(BaseInterfaceTable):
model = VMInterface
fields = (
'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
'vrf', 'l2vpn', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated',
'vrf', 'l2vpn', 'tunnel', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
@ -154,7 +155,7 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
model = VMInterface
fields = (
'pk', 'id', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
'vrf', 'l2vpn', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
'vrf', 'l2vpn', 'tunnel', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
)
default_columns = ('pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'ip_addresses')
row_attrs = {

0
netbox/vpn/__init__.py Normal file
View File

3
netbox/vpn/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

View File

@ -0,0 +1,84 @@
from rest_framework import serializers
from netbox.api.serializers import WritableNestedSerializer
from vpn import models
__all__ = (
'NestedIKEPolicySerializer',
'NestedIKEProposalSerializer',
'NestedIPSecPolicySerializer',
'NestedIPSecProfileSerializer',
'NestedIPSecProposalSerializer',
'NestedTunnelSerializer',
'NestedTunnelTerminationSerializer',
)
class NestedTunnelSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunnel-detail'
)
class Meta:
model = models.Tunnel
fields = ('id', 'url', 'display', 'name')
class NestedTunnelTerminationSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunneltermination-detail'
)
class Meta:
model = models.TunnelTermination
fields = ('id', 'url', 'display')
class NestedIKEProposalSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ikeproposal-detail'
)
class Meta:
model = models.IKEProposal
fields = ('id', 'url', 'display', 'name')
class NestedIKEPolicySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ikepolicy-detail'
)
class Meta:
model = models.IKEPolicy
fields = ('id', 'url', 'display', 'name')
class NestedIPSecProposalSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ipsecproposal-detail'
)
class Meta:
model = models.IPSecProposal
fields = ('id', 'url', 'display', 'name')
class NestedIPSecPolicySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ipsecpolicy-detail'
)
class Meta:
model = models.IPSecPolicy
fields = ('id', 'url', 'display', 'name')
class NestedIPSecProfileSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ipsecprofile-detail'
)
class Meta:
model = models.IPSecProfile
fields = ('id', 'url', 'display', 'name')

View File

@ -0,0 +1,193 @@
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from ipam.api.nested_serializers import NestedIPAddressSerializer
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from tenancy.api.nested_serializers import NestedTenantSerializer
from utilities.api import get_serializer_for_model
from vpn.choices import *
from vpn.models import *
from .nested_serializers import *
__all__ = (
'IKEPolicySerializer',
'IKEProposalSerializer',
'IPSecPolicySerializer',
'IPSecProfileSerializer',
'IPSecProposalSerializer',
'TunnelSerializer',
'TunnelTerminationSerializer',
)
class TunnelSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunnel-detail'
)
status = ChoiceField(
choices=TunnelStatusChoices
)
encapsulation = ChoiceField(
choices=TunnelEncapsulationChoices
)
ipsec_profile = NestedIPSecProfileSerializer(
required=False,
allow_null=True
)
tenant = NestedTenantSerializer(
required=False,
allow_null=True
)
class Meta:
model = Tunnel
fields = (
'id', 'url', 'display', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id',
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
)
class TunnelTerminationSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunneltermination-detail'
)
tunnel = NestedTunnelSerializer()
role = ChoiceField(
choices=TunnelTerminationRoleChoices
)
termination_type = ContentTypeField(
queryset=ContentType.objects.all()
)
termination = serializers.SerializerMethodField(
read_only=True
)
outside_ip = NestedIPAddressSerializer(
required=False,
allow_null=True
)
class Meta:
model = TunnelTermination
fields = (
'id', 'url', 'display', 'tunnel', 'role', 'termination_type', 'termination_id', 'termination', 'outside_ip',
'tags', 'custom_fields', 'created', 'last_updated',
)
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_termination(self, obj):
serializer = get_serializer_for_model(obj.termination, prefix=NESTED_SERIALIZER_PREFIX)
context = {'request': self.context['request']}
return serializer(obj.termination, context=context).data
class IKEProposalSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ikeproposal-detail'
)
authentication_method = ChoiceField(
choices=AuthenticationMethodChoices
)
encryption_algorithm = ChoiceField(
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = ChoiceField(
choices=AuthenticationAlgorithmChoices
)
group = ChoiceField(
choices=DHGroupChoices
)
class Meta:
model = IKEProposal
fields = (
'id', 'url', 'display', 'name', 'description', 'authentication_method', 'encryption_algorithm',
'authentication_algorithm', 'group', 'sa_lifetime', 'tags', 'custom_fields', 'created', 'last_updated',
)
class IKEPolicySerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ikepolicy-detail'
)
version = ChoiceField(
choices=IKEVersionChoices
)
mode = ChoiceField(
choices=IKEModeChoices
)
proposals = SerializedPKRelatedField(
queryset=IKEProposal.objects.all(),
serializer=NestedIKEProposalSerializer,
required=False,
many=True
)
class Meta:
model = IKEPolicy
fields = (
'id', 'url', 'display', 'name', 'description', 'version', 'mode', 'proposals', 'preshared_key', 'tags',
'custom_fields', 'created', 'last_updated',
)
class IPSecProposalSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ipsecproposal-detail'
)
encryption_algorithm = ChoiceField(
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = ChoiceField(
choices=AuthenticationAlgorithmChoices
)
class Meta:
model = IPSecProposal
fields = (
'id', 'url', 'display', 'name', 'description', 'encryption_algorithm', 'authentication_algorithm',
'sa_lifetime_seconds', 'sa_lifetime_data', 'tags', 'custom_fields', 'created', 'last_updated',
)
class IPSecPolicySerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ipsecpolicy-detail'
)
proposals = SerializedPKRelatedField(
queryset=IPSecProposal.objects.all(),
serializer=NestedIPSecProposalSerializer,
required=False,
many=True
)
pfs_group = ChoiceField(
choices=DHGroupChoices,
required=False
)
class Meta:
model = IPSecPolicy
fields = (
'id', 'url', 'display', 'name', 'description', 'proposals', 'pfs_group', 'tags', 'custom_fields', 'created',
'last_updated',
)
class IPSecProfileSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ipsecprofile-detail'
)
mode = ChoiceField(
choices=IPSecModeChoices
)
ike_policy = NestedIKEPolicySerializer()
ipsec_policy = NestedIPSecPolicySerializer()
class Meta:
model = IPSecProfile
fields = (
'id', 'url', 'display', 'name', 'description', 'mode', 'ike_policy', 'ipsec_policy', 'comments', 'tags',
'custom_fields', 'created', 'last_updated',
)

15
netbox/vpn/api/urls.py Normal file
View File

@ -0,0 +1,15 @@
from netbox.api.routers import NetBoxRouter
from . import views
router = NetBoxRouter()
router.APIRootView = views.VPNRootView
router.register('ike-policies', views.IKEPolicyViewSet)
router.register('ike-proposals', views.IKEProposalViewSet)
router.register('ipsec-policies', views.IPSecPolicyViewSet)
router.register('ipsec-proposals', views.IPSecProposalViewSet)
router.register('ipsec-profiles', views.IPSecProfileViewSet)
router.register('tunnels', views.TunnelViewSet)
router.register('tunnel-terminations', views.TunnelTerminationViewSet)
app_name = 'vpn-api'
urlpatterns = router.urls

74
netbox/vpn/api/views.py Normal file
View File

@ -0,0 +1,74 @@
from rest_framework.routers import APIRootView
from netbox.api.viewsets import NetBoxModelViewSet
from utilities.utils import count_related
from vpn import filtersets
from vpn.models import *
from . import serializers
__all__ = (
'IKEPolicyViewSet',
'IKEProposalViewSet',
'IPSecPolicyViewSet',
'IPSecProfileViewSet',
'IPSecProposalViewSet',
'TunnelTerminationViewSet',
'TunnelViewSet',
'VPNRootView',
)
class VPNRootView(APIRootView):
"""
VPN API root view
"""
def get_view_name(self):
return 'VPN'
#
# Viewsets
#
class TunnelViewSet(NetBoxModelViewSet):
queryset = Tunnel.objects.prefetch_related('ipsec_profile', 'tenant').annotate(
terminations_count=count_related(TunnelTermination, 'tunnel')
)
serializer_class = serializers.TunnelSerializer
filterset_class = filtersets.TunnelFilterSet
class TunnelTerminationViewSet(NetBoxModelViewSet):
queryset = TunnelTermination.objects.prefetch_related('tunnel')
serializer_class = serializers.TunnelTerminationSerializer
filterset_class = filtersets.TunnelTerminationFilterSet
class IKEProposalViewSet(NetBoxModelViewSet):
queryset = IKEProposal.objects.all()
serializer_class = serializers.IKEProposalSerializer
filterset_class = filtersets.IKEProposalFilterSet
class IKEPolicyViewSet(NetBoxModelViewSet):
queryset = IKEPolicy.objects.all()
serializer_class = serializers.IKEPolicySerializer
filterset_class = filtersets.IKEPolicyFilterSet
class IPSecProposalViewSet(NetBoxModelViewSet):
queryset = IPSecProposal.objects.all()
serializer_class = serializers.IPSecProposalSerializer
filterset_class = filtersets.IPSecProposalFilterSet
class IPSecPolicyViewSet(NetBoxModelViewSet):
queryset = IPSecPolicy.objects.all()
serializer_class = serializers.IPSecPolicySerializer
filterset_class = filtersets.IPSecPolicyFilterSet
class IPSecProfileViewSet(NetBoxModelViewSet):
queryset = IPSecProfile.objects.all()
serializer_class = serializers.IPSecProfileSerializer
filterset_class = filtersets.IPSecProfileFilterSet

9
netbox/vpn/apps.py Normal file
View File

@ -0,0 +1,9 @@
from django.apps import AppConfig
class VPNConfig(AppConfig):
name = 'vpn'
verbose_name = 'VPN'
def ready(self):
from . import search

201
netbox/vpn/choices.py Normal file
View File

@ -0,0 +1,201 @@
from django.utils.translation import gettext_lazy as _
from utilities.choices import ChoiceSet
#
# Tunnels
#
class TunnelStatusChoices(ChoiceSet):
key = 'Tunnel.status'
STATUS_PLANNED = 'planned'
STATUS_ACTIVE = 'active'
STATUS_DISABLED = 'disabled'
CHOICES = [
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_DISABLED, _('Disabled'), 'red'),
]
class TunnelEncapsulationChoices(ChoiceSet):
ENCAP_GRE = 'gre'
ENCAP_IP_IP = 'ip-ip'
ENCAP_IPSEC_TRANSPORT = 'ipsec-transport'
ENCAP_IPSEC_TUNNEL = 'ipsec-tunnel'
CHOICES = [
(ENCAP_IPSEC_TRANSPORT, _('IPsec - Transport')),
(ENCAP_IPSEC_TUNNEL, _('IPsec - Tunnel')),
(ENCAP_IP_IP, _('IP-in-IP')),
(ENCAP_GRE, _('GRE')),
]
class TunnelTerminationTypeChoices(ChoiceSet):
# For TunnelCreateForm
TYPE_DEVICE = 'dcim.device'
TYPE_VIRUTALMACHINE = 'virtualization.virtualmachine'
CHOICES = (
(TYPE_DEVICE, _('Device')),
(TYPE_VIRUTALMACHINE, _('Virtual Machine')),
)
class TunnelTerminationRoleChoices(ChoiceSet):
ROLE_PEER = 'peer'
ROLE_HUB = 'hub'
ROLE_SPOKE = 'spoke'
CHOICES = [
(ROLE_PEER, _('Peer'), 'green'),
(ROLE_HUB, _('Hub'), 'blue'),
(ROLE_SPOKE, _('Spoke'), 'orange'),
]
#
# Crypto
#
class IKEVersionChoices(ChoiceSet):
VERSION_1 = 1
VERSION_2 = 2
CHOICES = (
(VERSION_1, 'IKEv1'),
(VERSION_2, 'IKEv2'),
)
class IKEModeChoices(ChoiceSet):
AGGRESSIVE = 'aggressive'
MAIN = 'main'
CHOICES = (
(AGGRESSIVE, _('Aggressive')),
(MAIN, _('Main')),
)
class AuthenticationMethodChoices(ChoiceSet):
PRESHARED_KEYS = 'preshared-keys'
CERTIFICATES = 'certificates'
RSA_SIGNATURES = 'rsa-signatures'
DSA_SIGNATURES = 'dsa-signatures'
CHOICES = (
(PRESHARED_KEYS, _('Pre-shared keys')),
(CERTIFICATES, _('Certificates')),
(RSA_SIGNATURES, _('RSA signatures')),
(DSA_SIGNATURES, _('DSA signatures')),
)
class IPSecModeChoices(ChoiceSet):
ESP = 'esp'
AH = 'ah'
CHOICES = (
(ESP, 'ESP'),
(AH, 'AH'),
)
class EncryptionAlgorithmChoices(ChoiceSet):
ENCRYPTION_AES128_CBC = 'aes-128-cbc'
ENCRYPTION_AES128_GCM = 'aes-128-gcm'
ENCRYPTION_AES192_CBC = 'aes-192-cbc'
ENCRYPTION_AES192_GCM = 'aes-192-gcm'
ENCRYPTION_AES256_CBC = 'aes-256-cbc'
ENCRYPTION_AES256_GCM = 'aes-256-gcm'
ENCRYPTION_3DES = '3des-cbc'
ENCRYPTION_DES = 'des-cbc'
CHOICES = (
(ENCRYPTION_AES128_CBC, '128-bit AES (CBC)'),
(ENCRYPTION_AES128_GCM, '128-bit AES (GCM)'),
(ENCRYPTION_AES192_CBC, '192-bit AES (CBC)'),
(ENCRYPTION_AES192_GCM, '192-bit AES (GCM)'),
(ENCRYPTION_AES256_CBC, '256-bit AES (CBC)'),
(ENCRYPTION_AES256_GCM, '256-bit AES (GCM)'),
(ENCRYPTION_3DES, '3DES'),
(ENCRYPTION_3DES, 'DES'),
)
class AuthenticationAlgorithmChoices(ChoiceSet):
AUTH_HMAC_SHA1 = 'hmac-sha1'
AUTH_HMAC_SHA256 = 'hmac-sha256'
AUTH_HMAC_SHA384 = 'hmac-sha384'
AUTH_HMAC_SHA512 = 'hmac-sha512'
AUTH_HMAC_MD5 = 'hmac-md5'
CHOICES = (
(AUTH_HMAC_SHA1, 'SHA-1 HMAC'),
(AUTH_HMAC_SHA256, 'SHA-256 HMAC'),
(AUTH_HMAC_SHA384, 'SHA-384 HMAC'),
(AUTH_HMAC_SHA512, 'SHA-512 HMAC'),
(AUTH_HMAC_MD5, 'MD5 HMAC'),
)
class DHGroupChoices(ChoiceSet):
# https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml#ikev2-parameters-8
GROUP_1 = 1 # 768-bit MODP
GROUP_2 = 2 # 1024-but MODP
# Groups 3-4 reserved
GROUP_5 = 5 # 1536-bit MODP
# Groups 6-13 unassigned
GROUP_14 = 14 # 2048-bit MODP
GROUP_15 = 15 # 3072-bit MODP
GROUP_16 = 16 # 4096-bit MODP
GROUP_17 = 17 # 6144-bit MODP
GROUP_18 = 18 # 8192-bit MODP
GROUP_19 = 19 # 256-bit random ECP
GROUP_20 = 20 # 384-bit random ECP
GROUP_21 = 21 # 521-bit random ECP (521 is not a typo)
GROUP_22 = 22 # 1024-bit MODP w/160-bit prime
GROUP_23 = 23 # 2048-bit MODP w/224-bit prime
GROUP_24 = 24 # 2048-bit MODP w/256-bit prime
GROUP_25 = 25 # 192-bit ECP
GROUP_26 = 26 # 224-bit ECP
GROUP_27 = 27 # brainpoolP224r1
GROUP_28 = 28 # brainpoolP256r1
GROUP_29 = 29 # brainpoolP384r1
GROUP_30 = 30 # brainpoolP512r1
GROUP_31 = 31 # Curve25519
GROUP_32 = 32 # Curve448
GROUP_33 = 33 # GOST3410_2012_256
GROUP_34 = 34 # GOST3410_2012_512
CHOICES = (
# Strings are formatted in this manner to optimize translations
(GROUP_1, _('Group {n}').format(n=1)),
(GROUP_2, _('Group {n}').format(n=2)),
(GROUP_5, _('Group {n}').format(n=5)),
(GROUP_14, _('Group {n}').format(n=14)),
(GROUP_16, _('Group {n}').format(n=16)),
(GROUP_17, _('Group {n}').format(n=17)),
(GROUP_18, _('Group {n}').format(n=18)),
(GROUP_19, _('Group {n}').format(n=19)),
(GROUP_20, _('Group {n}').format(n=20)),
(GROUP_21, _('Group {n}').format(n=21)),
(GROUP_22, _('Group {n}').format(n=22)),
(GROUP_23, _('Group {n}').format(n=23)),
(GROUP_24, _('Group {n}').format(n=24)),
(GROUP_25, _('Group {n}').format(n=25)),
(GROUP_26, _('Group {n}').format(n=26)),
(GROUP_27, _('Group {n}').format(n=27)),
(GROUP_28, _('Group {n}').format(n=28)),
(GROUP_29, _('Group {n}').format(n=29)),
(GROUP_30, _('Group {n}').format(n=30)),
(GROUP_31, _('Group {n}').format(n=31)),
(GROUP_32, _('Group {n}').format(n=32)),
(GROUP_33, _('Group {n}').format(n=33)),
(GROUP_34, _('Group {n}').format(n=34)),
)

241
netbox/vpn/filtersets.py Normal file
View File

@ -0,0 +1,241 @@
import django_filters
from django.db.models import Q
from django.utils.translation import gettext as _
from dcim.models import Interface
from ipam.models import IPAddress
from netbox.filtersets import NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
from virtualization.models import VMInterface
from .choices import *
from .models import *
__all__ = (
'IKEPolicyFilterSet',
'IKEProposalFilterSet',
'IPSecPolicyFilterSet',
'IPSecProfileFilterSet',
'IPSecProposalFilterSet',
'TunnelFilterSet',
'TunnelTerminationFilterSet',
)
class TunnelFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
status = django_filters.MultipleChoiceFilter(
choices=TunnelStatusChoices
)
encapsulation = django_filters.MultipleChoiceFilter(
choices=TunnelEncapsulationChoices
)
ipsec_profile_id = django_filters.ModelMultipleChoiceFilter(
queryset=IPSecProfile.objects.all(),
label=_('IPSec profile (ID)'),
)
ipsec_profile = django_filters.ModelMultipleChoiceFilter(
field_name='ipsec_profile__name',
queryset=IPSecProfile.objects.all(),
to_field_name='name',
label=_('IPSec profile (name)'),
)
class Meta:
model = Tunnel
fields = ['id', 'name', 'tunnel_id']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
)
class TunnelTerminationFilterSet(NetBoxModelFilterSet):
tunnel_id = django_filters.ModelMultipleChoiceFilter(
field_name='tunnel',
queryset=Tunnel.objects.all(),
label=_('Tunnel (ID)'),
)
tunnel = django_filters.ModelMultipleChoiceFilter(
field_name='tunnel__name',
queryset=Tunnel.objects.all(),
to_field_name='name',
label=_('Tunnel (name)'),
)
role = django_filters.MultipleChoiceFilter(
choices=TunnelTerminationRoleChoices
)
termination_type = ContentTypeFilter()
interface = django_filters.ModelMultipleChoiceFilter(
field_name='interface__name',
queryset=Interface.objects.all(),
to_field_name='name',
label=_('Interface (name)'),
)
interface_id = django_filters.ModelMultipleChoiceFilter(
field_name='interface',
queryset=Interface.objects.all(),
label=_('Interface (ID)'),
)
vminterface = django_filters.ModelMultipleChoiceFilter(
field_name='vminterface__name',
queryset=VMInterface.objects.all(),
to_field_name='name',
label=_('VM interface (name)'),
)
vminterface_id = django_filters.ModelMultipleChoiceFilter(
field_name='vminterface',
queryset=VMInterface.objects.all(),
label=_('VM interface (ID)'),
)
outside_ip_id = django_filters.ModelMultipleChoiceFilter(
field_name='outside_ip',
queryset=IPAddress.objects.all(),
label=_('Outside IP (ID)'),
)
class Meta:
model = TunnelTermination
fields = ['id']
class IKEProposalFilterSet(NetBoxModelFilterSet):
authentication_method = django_filters.MultipleChoiceFilter(
choices=AuthenticationMethodChoices
)
encryption_algorithm = django_filters.MultipleChoiceFilter(
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = django_filters.MultipleChoiceFilter(
choices=AuthenticationAlgorithmChoices
)
group = django_filters.MultipleChoiceFilter(
choices=DHGroupChoices
)
class Meta:
model = IKEProposal
fields = ['id', 'name', 'sa_lifetime']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value)
)
class IKEPolicyFilterSet(NetBoxModelFilterSet):
version = django_filters.MultipleChoiceFilter(
choices=IKEVersionChoices
)
mode = django_filters.MultipleChoiceFilter(
choices=IKEModeChoices
)
proposal_id = MultiValueNumberFilter(
field_name='proposals__id'
)
proposal = MultiValueCharFilter(
field_name='proposals__name'
)
class Meta:
model = IKEPolicy
fields = ['id', 'name', 'preshared_key']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value)
)
class IPSecProposalFilterSet(NetBoxModelFilterSet):
encryption_algorithm = django_filters.MultipleChoiceFilter(
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = django_filters.MultipleChoiceFilter(
choices=AuthenticationAlgorithmChoices
)
class Meta:
model = IPSecProposal
fields = ['id', 'name', 'sa_lifetime_seconds', 'sa_lifetime_data']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value)
)
class IPSecPolicyFilterSet(NetBoxModelFilterSet):
pfs_group = django_filters.MultipleChoiceFilter(
choices=DHGroupChoices
)
proposal_id = MultiValueNumberFilter(
field_name='proposals__id'
)
proposal = MultiValueCharFilter(
field_name='proposals__name'
)
class Meta:
model = IPSecPolicy
fields = ['id', 'name']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value)
)
class IPSecProfileFilterSet(NetBoxModelFilterSet):
mode = django_filters.MultipleChoiceFilter(
choices=IPSecModeChoices
)
ike_policy_id = django_filters.ModelMultipleChoiceFilter(
queryset=IKEPolicy.objects.all(),
label=_('IKE policy (ID)'),
)
ike_policy = django_filters.ModelMultipleChoiceFilter(
field_name='ike_policy__name',
queryset=IKEPolicy.objects.all(),
to_field_name='name',
label=_('IKE policy (name)'),
)
ipsec_policy_id = django_filters.ModelMultipleChoiceFilter(
queryset=IPSecPolicy.objects.all(),
label=_('IPSec policy (ID)'),
)
ipsec_policy = django_filters.ModelMultipleChoiceFilter(
field_name='ipsec_policy__name',
queryset=IPSecPolicy.objects.all(),
to_field_name='name',
label=_('IPSec policy (name)'),
)
class Meta:
model = IPSecProfile
fields = ['id', 'name']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
)

View File

@ -0,0 +1,4 @@
from .bulk_edit import *
from .bulk_import import *
from .filtersets import *
from .model_forms import *

View File

@ -0,0 +1,243 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant
from utilities.forms import add_blank_choice
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from vpn.choices import *
from vpn.models import *
__all__ = (
'IKEPolicyBulkEditForm',
'IKEProposalBulkEditForm',
'IPSecPolicyBulkEditForm',
'IPSecProfileBulkEditForm',
'IPSecProposalBulkEditForm',
'TunnelBulkEditForm',
'TunnelTerminationBulkEditForm',
)
class TunnelBulkEditForm(NetBoxModelBulkEditForm):
status = forms.ChoiceField(
label=_('Status'),
choices=add_blank_choice(TunnelStatusChoices),
required=False
)
encapsulation = forms.ChoiceField(
label=_('Encapsulation'),
choices=add_blank_choice(TunnelEncapsulationChoices),
required=False
)
ipsec_profile = DynamicModelMultipleChoiceField(
queryset=IPSecProfile.objects.all(),
label=_('IPSec profile'),
required=False
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
tunnel_id = forms.IntegerField(
label=_('Tunnel ID'),
required=False
)
comments = CommentField()
model = Tunnel
fieldsets = (
(_('Tunnel'), ('status', 'encapsulation', 'tunnel_id', 'description')),
(_('Security'), ('ipsec_profile',)),
(_('Tenancy'), ('tenant',)),
)
nullable_fields = (
'ipsec_profile', 'tunnel_id', 'tenant', 'description', 'comments',
)
class TunnelTerminationBulkEditForm(NetBoxModelBulkEditForm):
role = forms.ChoiceField(
label=_('Role'),
choices=add_blank_choice(TunnelTerminationRoleChoices),
required=False
)
model = TunnelTermination
class IKEProposalBulkEditForm(NetBoxModelBulkEditForm):
authentication_method = forms.ChoiceField(
label=_('Authentication method'),
choices=add_blank_choice(AuthenticationMethodChoices),
required=False
)
encryption_algorithm = forms.ChoiceField(
label=_('Encryption algorithm'),
choices=add_blank_choice(EncryptionAlgorithmChoices),
required=False
)
authentication_algorithm = forms.ChoiceField(
label=_('Authentication algorithm'),
choices=add_blank_choice(AuthenticationAlgorithmChoices),
required=False
)
group = forms.ChoiceField(
label=_('Group'),
choices=add_blank_choice(DHGroupChoices),
required=False
)
sa_lifetime = forms.IntegerField(
label=_('SA lifetime'),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = IKEProposal
fieldsets = (
(None, (
'authentication_method', 'encryption_algorithm', 'authentication_algorithm', 'group', 'sa_lifetime',
'description',
)),
)
nullable_fields = (
'sa_lifetime', 'description', 'comments',
)
class IKEPolicyBulkEditForm(NetBoxModelBulkEditForm):
version = forms.ChoiceField(
label=_('Version'),
choices=add_blank_choice(IKEVersionChoices),
required=False
)
mode = forms.ChoiceField(
label=_('Mode'),
choices=add_blank_choice(IKEModeChoices),
required=False
)
preshared_key = forms.CharField(
label=_('Pre-shared key'),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = IKEPolicy
fieldsets = (
(None, (
'version', 'mode', 'preshared_key', 'description',
)),
)
nullable_fields = (
'preshared_key', 'description', 'comments',
)
class IPSecProposalBulkEditForm(NetBoxModelBulkEditForm):
encryption_algorithm = forms.ChoiceField(
label=_('Encryption algorithm'),
choices=add_blank_choice(EncryptionAlgorithmChoices),
required=False
)
authentication_algorithm = forms.ChoiceField(
label=_('Authentication algorithm'),
choices=add_blank_choice(AuthenticationAlgorithmChoices),
required=False
)
sa_lifetime_seconds = forms.IntegerField(
label=_('SA lifetime (seconds)'),
required=False
)
sa_lifetime_data = forms.IntegerField(
label=_('SA lifetime (KB)'),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = IPSecProposal
fieldsets = (
(None, (
'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds', 'sa_lifetime_data',
'description',
)),
)
nullable_fields = (
'sa_lifetime_seconds', 'sa_lifetime_data', 'description', 'comments',
)
class IPSecPolicyBulkEditForm(NetBoxModelBulkEditForm):
pfs_group = forms.ChoiceField(
label=_('PFS group'),
choices=add_blank_choice(DHGroupChoices),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = IPSecPolicy
fieldsets = (
(None, ('pfs_group', 'description',)),
)
nullable_fields = (
'pfs_group', 'description', 'comments',
)
class IPSecProfileBulkEditForm(NetBoxModelBulkEditForm):
mode = forms.ChoiceField(
label=_('Mode'),
choices=add_blank_choice(IPSecModeChoices),
required=False
)
ike_policy = DynamicModelChoiceField(
label=_('IKE policy'),
queryset=IKEPolicy.objects.all(),
required=False
)
ipsec_policy = DynamicModelChoiceField(
label=_('IPSec policy'),
queryset=IPSecPolicy.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = IPSecProfile
fieldsets = (
(_('Profile'), (
'mode', 'ike_policy', 'ipsec_policy', 'description',
)),
)
nullable_fields = (
'description', 'comments',
)

View File

@ -0,0 +1,230 @@
from django.utils.translation import gettext_lazy as _
from dcim.models import Device, Interface
from ipam.models import IPAddress
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField
from virtualization.models import VirtualMachine, VMInterface
from vpn.choices import *
from vpn.models import *
__all__ = (
'IKEPolicyImportForm',
'IKEProposalImportForm',
'IPSecPolicyImportForm',
'IPSecProfileImportForm',
'IPSecProposalImportForm',
'TunnelImportForm',
'TunnelTerminationImportForm',
)
class TunnelImportForm(NetBoxModelImportForm):
status = CSVChoiceField(
label=_('Status'),
choices=TunnelStatusChoices,
help_text=_('Operational status')
)
encapsulation = CSVChoiceField(
label=_('Encapsulation'),
choices=TunnelEncapsulationChoices,
help_text=_('Tunnel encapsulation')
)
ipsec_profile = CSVModelChoiceField(
label=_('IPSec profile'),
queryset=IPSecProfile.objects.all(),
required=False,
to_field_name='name'
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned tenant')
)
class Meta:
model = Tunnel
fields = (
'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id', 'description', 'comments',
'tags',
)
class TunnelTerminationImportForm(NetBoxModelImportForm):
tunnel = CSVModelChoiceField(
label=_('Tunnel'),
queryset=Tunnel.objects.all(),
to_field_name='name'
)
role = CSVChoiceField(
label=_('Role'),
choices=TunnelTerminationRoleChoices,
help_text=_('Operational role')
)
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent device of assigned interface')
)
virtual_machine = CSVModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent VM of assigned interface')
)
termination = CSVModelChoiceField(
label=_('Termination'),
queryset=Interface.objects.none(), # Can also refer to VMInterface
required=False,
to_field_name='name',
help_text=_('Device or virtual machine interface')
)
outside_ip = CSVModelChoiceField(
label=_('Outside IP'),
queryset=IPAddress.objects.all(),
required=False,
to_field_name='name'
)
class Meta:
model = TunnelTermination
fields = (
'tunnel', 'role', 'outside_ip', 'tags',
)
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit termination queryset by assigned device/VM
if data.get('device'):
self.fields['termination'].queryset = Interface.objects.filter(
**{f"device__{self.fields['device'].to_field_name}": data['device']}
)
elif data.get('virtual_machine'):
self.fields['termination'].queryset = VMInterface.objects.filter(
**{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
)
def save(self, *args, **kwargs):
# Assign termination object
if self.cleaned_data.get('termination'):
self.instance.termination = self.cleaned_data['termination']
return super().save(*args, **kwargs)
class IKEProposalImportForm(NetBoxModelImportForm):
authentication_method = CSVChoiceField(
label=_('Authentication method'),
choices=AuthenticationMethodChoices
)
encryption_algorithm = CSVChoiceField(
label=_('Encryption algorithm'),
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = CSVChoiceField(
label=_('Authentication algorithm'),
choices=AuthenticationAlgorithmChoices
)
group = CSVChoiceField(
label=_('Group'),
choices=DHGroupChoices
)
class Meta:
model = IKEProposal
fields = (
'name', 'description', 'authentication_method', 'encryption_algorithm', 'authentication_algorithm',
'group', 'sa_lifetime', 'tags',
)
class IKEPolicyImportForm(NetBoxModelImportForm):
version = CSVChoiceField(
label=_('Version'),
choices=IKEVersionChoices
)
mode = CSVChoiceField(
label=_('Mode'),
choices=IKEModeChoices
)
proposals = CSVModelMultipleChoiceField(
queryset=IKEProposal.objects.all(),
to_field_name='name',
help_text=_('IKE proposal(s)'),
)
class Meta:
model = IKEPolicy
fields = (
'name', 'description', 'version', 'mode', 'proposals', 'preshared_key', 'tags',
)
class IPSecProposalImportForm(NetBoxModelImportForm):
encryption_algorithm = CSVChoiceField(
label=_('Encryption algorithm'),
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = CSVChoiceField(
label=_('Authentication algorithm'),
choices=AuthenticationAlgorithmChoices
)
class Meta:
model = IPSecProposal
fields = (
'name', 'description', 'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds',
'sa_lifetime_data', 'tags',
)
class IPSecPolicyImportForm(NetBoxModelImportForm):
pfs_group = CSVChoiceField(
label=_('Diffie-Hellman group for Perfect Forward Secrecy'),
choices=DHGroupChoices
)
proposals = CSVModelMultipleChoiceField(
queryset=IPSecProposal.objects.all(),
to_field_name='name',
help_text=_('IPSec proposal(s)'),
)
class Meta:
model = IPSecPolicy
fields = (
'name', 'description', 'proposals', 'pfs_group', 'tags',
)
class IPSecProfileImportForm(NetBoxModelImportForm):
mode = CSVChoiceField(
label=_('Mode'),
choices=IPSecModeChoices,
help_text=_('IPSec protocol')
)
ike_policy = CSVModelChoiceField(
label=_('IKE policy'),
queryset=IKEPolicy.objects.all(),
to_field_name='name'
)
ipsec_policy = CSVModelChoiceField(
label=_('IPSec policy'),
queryset=IPSecPolicy.objects.all(),
to_field_name='name'
)
class Meta:
model = IPSecProfile
fields = (
'name', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags',
)

View File

@ -0,0 +1,182 @@
from django import forms
from django.utils.translation import gettext as _
from netbox.forms import NetBoxModelFilterSetForm
from tenancy.forms import TenancyFilterForm
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
from vpn.choices import *
from vpn.models import *
__all__ = (
'IKEPolicyFilterForm',
'IKEProposalFilterForm',
'IPSecPolicyFilterForm',
'IPSecProfileFilterForm',
'IPSecProposalFilterForm',
'TunnelFilterForm',
'TunnelTerminationFilterForm',
)
class TunnelFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = Tunnel
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Tunnel'), ('status', 'encapsulation', 'tunnel_id')),
(_('Security'), ('ipsec_profile_id',)),
(_('Tenancy'), ('tenant_group_id', 'tenant_id')),
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=TunnelStatusChoices,
required=False
)
encapsulation = forms.MultipleChoiceField(
label=_('Encapsulation'),
choices=TunnelEncapsulationChoices,
required=False
)
ipsec_profile_id = DynamicModelMultipleChoiceField(
queryset=IPSecProfile.objects.all(),
required=False,
label=_('IPSec profile')
)
tunnel_id = forms.IntegerField(
required=False,
label=_('Tunnel ID')
)
tag = TagFilterField(model)
class TunnelTerminationFilterForm(NetBoxModelFilterSetForm):
model = TunnelTermination
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Termination'), ('tunnel_id', 'role')),
)
tunnel_id = DynamicModelMultipleChoiceField(
queryset=Tunnel.objects.all(),
required=False,
label=_('Tunnel')
)
role = forms.MultipleChoiceField(
label=_('Role'),
choices=TunnelTerminationRoleChoices,
required=False
)
tag = TagFilterField(model)
class IKEProposalFilterForm(NetBoxModelFilterSetForm):
model = IKEProposal
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Parameters'), ('authentication_method', 'encryption_algorithm', 'authentication_algorithm', 'group')),
)
authentication_method = forms.MultipleChoiceField(
label=_('Authentication method'),
choices=AuthenticationMethodChoices,
required=False
)
encryption_algorithm = forms.MultipleChoiceField(
label=_('Encryption algorithm'),
choices=EncryptionAlgorithmChoices,
required=False
)
authentication_algorithm = forms.MultipleChoiceField(
label=_('Authentication algorithm'),
choices=AuthenticationAlgorithmChoices,
required=False
)
group = forms.MultipleChoiceField(
label=_('Group'),
choices=DHGroupChoices,
required=False
)
tag = TagFilterField(model)
class IKEPolicyFilterForm(NetBoxModelFilterSetForm):
model = IKEPolicy
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Parameters'), ('version', 'mode', 'proposal_id')),
)
version = forms.MultipleChoiceField(
label=_('IKE version'),
choices=IKEVersionChoices,
required=False
)
mode = forms.MultipleChoiceField(
label=_('Mode'),
choices=IKEModeChoices,
required=False
)
proposal_id = DynamicModelMultipleChoiceField(
queryset=IKEProposal.objects.all(),
required=False,
label=_('Proposal')
)
tag = TagFilterField(model)
class IPSecProposalFilterForm(NetBoxModelFilterSetForm):
model = IPSecProposal
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Parameters'), ('encryption_algorithm', 'authentication_algorithm')),
)
encryption_algorithm = forms.MultipleChoiceField(
label=_('Encryption algorithm'),
choices=EncryptionAlgorithmChoices,
required=False
)
authentication_algorithm = forms.MultipleChoiceField(
label=_('Authentication algorithm'),
choices=AuthenticationAlgorithmChoices,
required=False
)
tag = TagFilterField(model)
class IPSecPolicyFilterForm(NetBoxModelFilterSetForm):
model = IPSecPolicy
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Parameters'), ('proposal_id', 'pfs_group')),
)
proposal_id = DynamicModelMultipleChoiceField(
queryset=IKEProposal.objects.all(),
required=False,
label=_('Proposal')
)
pfs_group = forms.MultipleChoiceField(
label=_('Mode'),
choices=DHGroupChoices,
required=False
)
tag = TagFilterField(model)
class IPSecProfileFilterForm(NetBoxModelFilterSetForm):
model = IPSecProfile
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Profile'), ('mode', 'ike_policy_id', 'ipsec_policy_id')),
)
mode = forms.MultipleChoiceField(
label=_('Mode'),
choices=IPSecModeChoices,
required=False
)
ike_policy_id = DynamicModelMultipleChoiceField(
queryset=IKEPolicy.objects.all(),
required=False,
label=_('IKE policy')
)
ipsec_policy_id = DynamicModelMultipleChoiceField(
queryset=IPSecPolicy.objects.all(),
required=False,
label=_('IPSec policy')
)
tag = TagFilterField(model)

View File

@ -0,0 +1,357 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from dcim.models import Device, Interface
from ipam.models import IPAddress
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.utils import add_blank_choice
from utilities.forms.widgets import HTMXSelect
from virtualization.models import VirtualMachine, VMInterface
from vpn.choices import *
from vpn.models import *
__all__ = (
'IKEPolicyForm',
'IKEProposalForm',
'IPSecPolicyForm',
'IPSecProfileForm',
'IPSecProposalForm',
'TunnelCreateForm',
'TunnelForm',
'TunnelTerminationForm',
)
class TunnelForm(TenancyForm, NetBoxModelForm):
ipsec_profile = DynamicModelChoiceField(
queryset=IPSecProfile.objects.all(),
label=_('IPSec Profile'),
required=False
)
comments = CommentField()
fieldsets = (
(_('Tunnel'), ('name', 'status', 'encapsulation', 'description', 'tunnel_id', 'tags')),
(_('Security'), ('ipsec_profile',)),
(_('Tenancy'), ('tenant_group', 'tenant')),
)
class Meta:
model = Tunnel
fields = [
'name', 'status', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tenant_group', 'tenant',
'comments', 'tags',
]
class TunnelCreateForm(TunnelForm):
# First termination
termination1_role = forms.ChoiceField(
choices=add_blank_choice(TunnelTerminationRoleChoices),
required=False,
label=_('Role')
)
termination1_type = forms.ChoiceField(
choices=TunnelTerminationTypeChoices,
required=False,
widget=HTMXSelect(),
label=_('Type')
)
termination1_parent = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
selector=True,
label=_('Device')
)
termination1_termination = DynamicModelChoiceField(
queryset=Interface.objects.all(),
required=False,
label=_('Interface'),
query_params={
'device_id': '$termination1_parent',
}
)
termination1_outside_ip = DynamicModelChoiceField(
queryset=IPAddress.objects.all(),
label=_('Outside IP'),
required=False,
query_params={
'device_id': '$termination1_parent',
}
)
# Second termination
termination2_role = forms.ChoiceField(
choices=add_blank_choice(TunnelTerminationRoleChoices),
required=False,
label=_('Role')
)
termination2_type = forms.ChoiceField(
choices=TunnelTerminationTypeChoices,
required=False,
widget=HTMXSelect(),
label=_('Type')
)
termination2_parent = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
selector=True,
label=_('Device')
)
termination2_termination = DynamicModelChoiceField(
queryset=Interface.objects.all(),
required=False,
label=_('Interface'),
query_params={
'device_id': '$termination2_parent',
}
)
termination2_outside_ip = DynamicModelChoiceField(
queryset=IPAddress.objects.all(),
required=False,
label=_('Outside IP'),
query_params={
'device_id': '$termination2_parent',
}
)
fieldsets = (
(_('Tunnel'), ('name', 'status', 'encapsulation', 'description', 'tunnel_id', 'tags')),
(_('Security'), ('ipsec_profile',)),
(_('Tenancy'), ('tenant_group', 'tenant')),
(_('First Termination'), (
'termination1_role', 'termination1_type', 'termination1_parent', 'termination1_termination',
'termination1_outside_ip',
)),
(_('Second Termination'), (
'termination2_role', 'termination2_type', 'termination2_parent', 'termination2_termination',
'termination2_outside_ip',
)),
)
def __init__(self, *args, initial=None, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
if initial and initial.get('termination1_type') == TunnelTerminationTypeChoices.TYPE_VIRUTALMACHINE:
self.fields['termination1_parent'].label = _('Virtual Machine')
self.fields['termination1_parent'].queryset = VirtualMachine.objects.all()
self.fields['termination1_termination'].queryset = VMInterface.objects.all()
self.fields['termination1_termination'].widget.add_query_params({
'virtual_machine_id': '$termination1_parent',
})
self.fields['termination1_outside_ip'].widget.add_query_params({
'virtual_machine_id': '$termination1_parent',
})
if initial and initial.get('termination2_type') == TunnelTerminationTypeChoices.TYPE_VIRUTALMACHINE:
self.fields['termination2_parent'].label = _('Virtual Machine')
self.fields['termination2_parent'].queryset = VirtualMachine.objects.all()
self.fields['termination2_termination'].queryset = VMInterface.objects.all()
self.fields['termination2_termination'].widget.add_query_params({
'virtual_machine_id': '$termination2_parent',
})
self.fields['termination2_outside_ip'].widget.add_query_params({
'virtual_machine_id': '$termination2_parent',
})
def clean(self):
super().clean()
# Validate attributes for each termination (if any)
for term in ('termination1', 'termination2'):
required_parameters = (
f'{term}_role', f'{term}_parent', f'{term}_termination',
)
parameters = (
*required_parameters,
f'{term}_outside_ip',
)
if any([self.cleaned_data[param] for param in parameters]):
for param in required_parameters:
if not self.cleaned_data[param]:
raise forms.ValidationError({
param: _("This parameter is required when defining a termination.")
})
def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs)
# Create first termination
if self.cleaned_data['termination1_termination']:
TunnelTermination.objects.create(
tunnel=instance,
role=self.cleaned_data['termination1_role'],
termination=self.cleaned_data['termination1_termination'],
outside_ip=self.cleaned_data['termination1_outside_ip'],
)
# Create second termination, if defined
if self.cleaned_data['termination2_termination']:
TunnelTermination.objects.create(
tunnel=instance,
role=self.cleaned_data['termination2_role'],
termination=self.cleaned_data['termination2_termination'],
outside_ip=self.cleaned_data.get('termination1_outside_ip'),
)
return instance
class TunnelTerminationForm(NetBoxModelForm):
tunnel = DynamicModelChoiceField(
queryset=Tunnel.objects.all()
)
type = forms.ChoiceField(
choices=TunnelTerminationTypeChoices,
widget=HTMXSelect(),
label=_('Type')
)
parent = DynamicModelChoiceField(
queryset=Device.objects.all(),
selector=True,
label=_('Device')
)
termination = DynamicModelChoiceField(
queryset=Interface.objects.all(),
label=_('Interface'),
query_params={
'device_id': '$parent',
}
)
outside_ip = DynamicModelChoiceField(
queryset=IPAddress.objects.all(),
label=_('Outside IP'),
required=False,
query_params={
'device_id': '$parent',
}
)
fieldsets = (
(None, ('tunnel', 'role', 'type', 'parent', 'termination', 'outside_ip', 'tags')),
)
class Meta:
model = TunnelTermination
fields = [
'tunnel', 'role', 'termination', 'outside_ip', 'tags',
]
def __init__(self, *args, initial=None, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
if initial and initial.get('type') == TunnelTerminationTypeChoices.TYPE_VIRUTALMACHINE:
self.fields['parent'].label = _('Virtual Machine')
self.fields['parent'].queryset = VirtualMachine.objects.all()
self.fields['termination'].queryset = VMInterface.objects.all()
self.fields['termination'].widget.add_query_params({
'virtual_machine_id': '$parent',
})
self.fields['outside_ip'].widget.add_query_params({
'virtual_machine_id': '$parent',
})
if self.instance.pk:
self.fields['parent'].initial = self.instance.termination.parent_object
self.fields['termination'].initial = self.instance.termination
def clean(self):
super().clean()
# Set the terminated object
self.instance.termination = self.cleaned_data.get('termination')
class IKEProposalForm(NetBoxModelForm):
fieldsets = (
(_('Proposal'), ('name', 'description', 'tags')),
(_('Parameters'), (
'authentication_method', 'encryption_algorithm', 'authentication_algorithm', 'group', 'sa_lifetime',
)),
)
class Meta:
model = IKEProposal
fields = [
'name', 'description', 'authentication_method', 'encryption_algorithm', 'authentication_algorithm', 'group',
'sa_lifetime', 'tags',
]
class IKEPolicyForm(NetBoxModelForm):
proposals = DynamicModelMultipleChoiceField(
queryset=IKEProposal.objects.all(),
label=_('Proposals')
)
fieldsets = (
(_('Policy'), ('name', 'description', 'tags')),
(_('Parameters'), ('version', 'mode', 'proposals', 'preshared_key')),
)
class Meta:
model = IKEPolicy
fields = [
'name', 'description', 'version', 'mode', 'proposals', 'preshared_key', 'tags',
]
class IPSecProposalForm(NetBoxModelForm):
fieldsets = (
(_('Proposal'), ('name', 'description', 'tags')),
(_('Parameters'), (
'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds', 'sa_lifetime_data',
)),
)
class Meta:
model = IPSecProposal
fields = [
'name', 'description', 'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds',
'sa_lifetime_data', 'tags',
]
class IPSecPolicyForm(NetBoxModelForm):
proposals = DynamicModelMultipleChoiceField(
queryset=IPSecProposal.objects.all(),
label=_('Proposals')
)
fieldsets = (
(_('Policy'), ('name', 'description', 'tags')),
(_('Parameters'), ('proposals', 'pfs_group')),
)
class Meta:
model = IPSecPolicy
fields = [
'name', 'description', 'proposals', 'pfs_group', 'tags',
]
class IPSecProfileForm(NetBoxModelForm):
ike_policy = DynamicModelChoiceField(
queryset=IKEPolicy.objects.all(),
label=_('IKE policy')
)
ipsec_policy = DynamicModelChoiceField(
queryset=IPSecPolicy.objects.all(),
label=_('IPSec policy')
)
comments = CommentField()
fieldsets = (
(_('Profile'), ('name', 'description', 'tags')),
(_('Parameters'), ('mode', 'ike_policy', 'ipsec_policy')),
)
class Meta:
model = IPSecProfile
fields = [
'name', 'description', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags',
]

View File

View File

@ -0,0 +1,51 @@
import graphene
from netbox.graphql.fields import ObjectField, ObjectListField
from utilities.graphql_optimizer import gql_query_optimizer
from vpn import models
from .types import *
class VPNQuery(graphene.ObjectType):
ike_policy = ObjectField(IKEPolicyType)
ike_policy_list = ObjectListField(IKEPolicyType)
def resolve_ike_policy_list(root, info, **kwargs):
return gql_query_optimizer(models.IKEPolicy.objects.all(), info)
ike_proposal = ObjectField(IKEProposalType)
ike_proposal_list = ObjectListField(IKEProposalType)
def resolve_ike_proposal_list(root, info, **kwargs):
return gql_query_optimizer(models.IKEProposal.objects.all(), info)
ipsec_policy = ObjectField(IPSecPolicyType)
ipsec_policy_list = ObjectListField(IPSecPolicyType)
def resolve_ipsec_policy_list(root, info, **kwargs):
return gql_query_optimizer(models.IPSecPolicy.objects.all(), info)
ipsec_profile = ObjectField(IPSecProfileType)
ipsec_profile_list = ObjectListField(IPSecProfileType)
def resolve_ipsec_profile_list(root, info, **kwargs):
return gql_query_optimizer(models.IPSecProfile.objects.all(), info)
ipsec_proposal = ObjectField(IPSecProposalType)
ipsec_proposal_list = ObjectListField(IPSecProposalType)
def resolve_ipsec_proposal_list(root, info, **kwargs):
return gql_query_optimizer(models.IPSecProposal.objects.all(), info)
tunnel = ObjectField(TunnelType)
tunnel_list = ObjectListField(TunnelType)
def resolve_tunnel_list(root, info, **kwargs):
return gql_query_optimizer(models.Tunnel.objects.all(), info)
tunnel_termination = ObjectField(TunnelTerminationType)
tunnel_termination_list = ObjectListField(TunnelTerminationType)
def resolve_tunnel_termination_list(root, info, **kwargs):
return gql_query_optimizer(models.TunnelTermination.objects.all(), info)

View File

@ -0,0 +1,69 @@
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
from vpn import filtersets, models
__all__ = (
'IKEPolicyType',
'IKEProposalType',
'IPSecPolicyType',
'IPSecProfileType',
'IPSecProposalType',
'TunnelTerminationType',
'TunnelType',
)
class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
class Meta:
model = models.TunnelTermination
fields = '__all__'
filterset_class = filtersets.TunnelTerminationFilterSet
class TunnelType(NetBoxObjectType):
class Meta:
model = models.Tunnel
fields = '__all__'
filterset_class = filtersets.TunnelFilterSet
class IKEProposalType(OrganizationalObjectType):
class Meta:
model = models.IKEProposal
fields = '__all__'
filterset_class = filtersets.IKEProposalFilterSet
class IKEPolicyType(OrganizationalObjectType):
class Meta:
model = models.IKEPolicy
fields = '__all__'
filterset_class = filtersets.IKEPolicyFilterSet
class IPSecProposalType(OrganizationalObjectType):
class Meta:
model = models.IPSecProposal
fields = '__all__'
filterset_class = filtersets.IPSecProposalFilterSet
class IPSecPolicyType(OrganizationalObjectType):
class Meta:
model = models.IPSecPolicy
fields = '__all__'
filterset_class = filtersets.IPSecPolicyFilterSet
class IPSecProfileType(OrganizationalObjectType):
class Meta:
model = models.IPSecProfile
fields = '__all__'
filterset_class = filtersets.IPSecProfileFilterSet

View File

@ -0,0 +1,186 @@
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
import utilities.json
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('extras', '0099_cachedvalue_ordering'),
('ipam', '0067_ipaddress_index_host'),
('tenancy', '0012_contactassignment_custom_fields'),
]
operations = [
migrations.CreateModel(
name='IKEPolicy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('name', models.CharField(max_length=100, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('version', models.PositiveSmallIntegerField(default=2)),
('mode', models.CharField()),
('preshared_key', models.TextField(blank=True)),
],
options={
'verbose_name': 'IKE policy',
'verbose_name_plural': 'IKE policies',
'ordering': ('name',),
},
),
migrations.CreateModel(
name='IPSecPolicy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('name', models.CharField(max_length=100, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('pfs_group', models.PositiveSmallIntegerField(blank=True, null=True)),
],
options={
'verbose_name': 'IPSec policy',
'verbose_name_plural': 'IPSec policies',
'ordering': ('name',),
},
),
migrations.CreateModel(
name='IPSecProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('name', models.CharField(max_length=100, unique=True)),
('mode', models.CharField()),
('ike_policy', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ipsec_profiles', to='vpn.ikepolicy')),
('ipsec_policy', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ipsec_profiles', to='vpn.ipsecpolicy')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'verbose_name': 'IPSec profile',
'verbose_name_plural': 'IPSec profiles',
'ordering': ('name',),
},
),
migrations.CreateModel(
name='Tunnel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('name', models.CharField(max_length=100, unique=True)),
('status', models.CharField(default='active', max_length=50)),
('encapsulation', models.CharField(max_length=50)),
('tunnel_id', models.PositiveBigIntegerField(blank=True, null=True)),
('ipsec_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnels', to='vpn.ipsecprofile')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnels', to='tenancy.tenant')),
],
options={
'verbose_name': 'tunnel',
'verbose_name_plural': 'tunnels',
'ordering': ('name',),
},
),
migrations.CreateModel(
name='TunnelTermination',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('role', models.CharField(default='peer', max_length=50)),
('termination_id', models.PositiveBigIntegerField(blank=True, null=True)),
('termination_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
('outside_ip', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnel_termination', to='ipam.ipaddress')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
('tunnel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='vpn.tunnel')),
],
options={
'verbose_name': 'tunnel termination',
'verbose_name_plural': 'tunnel terminations',
'ordering': ('tunnel', 'role', 'pk'),
},
),
migrations.CreateModel(
name='IPSecProposal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('name', models.CharField(max_length=100, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('encryption_algorithm', models.CharField()),
('authentication_algorithm', models.CharField()),
('sa_lifetime_seconds', models.PositiveIntegerField(blank=True, null=True)),
('sa_lifetime_data', models.PositiveIntegerField(blank=True, null=True)),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'verbose_name': 'IPSec proposal',
'verbose_name_plural': 'IPSec proposals',
'ordering': ('name',),
},
),
migrations.AddField(
model_name='ipsecpolicy',
name='proposals',
field=models.ManyToManyField(related_name='ipsec_policies', to='vpn.ipsecproposal'),
),
migrations.AddField(
model_name='ipsecpolicy',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.CreateModel(
name='IKEProposal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('name', models.CharField(max_length=100, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('authentication_method', models.CharField()),
('encryption_algorithm', models.CharField()),
('authentication_algorithm', models.CharField()),
('group', models.PositiveSmallIntegerField()),
('sa_lifetime', models.PositiveIntegerField(blank=True, null=True)),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'verbose_name': 'IKE proposal',
'verbose_name_plural': 'IKE proposals',
'ordering': ('name',),
},
),
migrations.AddField(
model_name='ikepolicy',
name='proposals',
field=models.ManyToManyField(related_name='ike_policies', to='vpn.ikeproposal'),
),
migrations.AddField(
model_name='ikepolicy',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AddConstraint(
model_name='tunneltermination',
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='vpn_tunneltermination_termination', violation_error_message='An object may be terminated to only one tunnel at a time.'),
),
]

View File

View File

@ -0,0 +1,2 @@
from .crypto import *
from .tunnels import *

254
netbox/vpn/models/crypto.py Normal file
View File

@ -0,0 +1,254 @@
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from netbox.models import NetBoxModel, PrimaryModel
from vpn.choices import *
__all__ = (
'IKEPolicy',
'IKEProposal',
'IPSecPolicy',
'IPSecProfile',
'IPSecProposal',
)
#
# IKE
#
class IKEProposal(NetBoxModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True
)
authentication_method = models.CharField(
verbose_name=('authentication method'),
choices=AuthenticationMethodChoices
)
encryption_algorithm = models.CharField(
verbose_name=_('encryption algorithm'),
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = models.CharField(
verbose_name=_('authentication algorithm'),
choices=AuthenticationAlgorithmChoices
)
group = models.PositiveSmallIntegerField(
verbose_name=_('group'),
choices=DHGroupChoices,
help_text=_('Diffie-Hellman group ID')
)
sa_lifetime = models.PositiveIntegerField(
verbose_name=_('SA lifetime'),
blank=True,
null=True,
help_text=_('Security association lifetime (in seconds)')
)
clone_fields = (
'authentication_method', 'encryption_algorithm', 'authentication_algorithm', 'group', 'sa_lifetime',
)
class Meta:
ordering = ('name',)
verbose_name = _('IKE proposal')
verbose_name_plural = _('IKE proposals')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('vpn:ikeproposal', args=[self.pk])
class IKEPolicy(NetBoxModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True
)
version = models.PositiveSmallIntegerField(
verbose_name=_('version'),
choices=IKEVersionChoices,
default=IKEVersionChoices.VERSION_2
)
mode = models.CharField(
verbose_name=_('mode'),
choices=IKEModeChoices
)
proposals = models.ManyToManyField(
to='vpn.IKEProposal',
related_name='ike_policies',
verbose_name=_('proposals')
)
preshared_key = models.TextField(
verbose_name=_('pre-shared key'),
blank=True
)
clone_fields = (
'version', 'mode', 'proposals',
)
prerequisite_models = (
'vpn.IKEProposal',
)
class Meta:
ordering = ('name',)
verbose_name = _('IKE policy')
verbose_name_plural = _('IKE policies')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('vpn:ikepolicy', args=[self.pk])
#
# IPSec
#
class IPSecProposal(NetBoxModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True
)
encryption_algorithm = models.CharField(
verbose_name=_('encryption'),
choices=EncryptionAlgorithmChoices
)
authentication_algorithm = models.CharField(
verbose_name=_('authentication'),
choices=AuthenticationAlgorithmChoices
)
sa_lifetime_seconds = models.PositiveIntegerField(
verbose_name=_('SA lifetime (seconds)'),
blank=True,
null=True,
help_text=_('Security association lifetime (seconds)')
)
sa_lifetime_data = models.PositiveIntegerField(
verbose_name=_('SA lifetime (KB)'),
blank=True,
null=True,
help_text=_('Security association lifetime (in kilobytes)')
)
clone_fields = (
'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds', 'sa_lifetime_data',
)
class Meta:
ordering = ('name',)
verbose_name = _('IPSec proposal')
verbose_name_plural = _('IPSec proposals')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('vpn:ipsecproposal', args=[self.pk])
class IPSecPolicy(NetBoxModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True
)
proposals = models.ManyToManyField(
to='vpn.IPSecProposal',
related_name='ipsec_policies',
verbose_name=_('proposals')
)
pfs_group = models.PositiveSmallIntegerField(
verbose_name=_('PFS group'),
choices=DHGroupChoices,
blank=True,
null=True,
help_text=_('Diffie-Hellman group for Perfect Forward Secrecy')
)
clone_fields = (
'proposals', 'pfs_group',
)
prerequisite_models = (
'vpn.IPSecProposal',
)
class Meta:
ordering = ('name',)
verbose_name = _('IPSec policy')
verbose_name_plural = _('IPSec policies')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('vpn:ipsecpolicy', args=[self.pk])
class IPSecProfile(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
mode = models.CharField(
verbose_name=_('mode'),
choices=IPSecModeChoices
)
ike_policy = models.ForeignKey(
to='vpn.IKEPolicy',
on_delete=models.PROTECT,
related_name='ipsec_profiles'
)
ipsec_policy = models.ForeignKey(
to='vpn.IPSecPolicy',
on_delete=models.PROTECT,
related_name='ipsec_profiles'
)
clone_fields = (
'mode', 'ike_policy', 'ipsec_policy',
)
prerequisite_models = (
'vpn.IKEPolicy',
'vpn.IPSecPolicy',
)
class Meta:
ordering = ('name',)
verbose_name = _('IPSec profile')
verbose_name_plural = _('IPSec profiles')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('vpn:ipsecprofile', args=[self.pk])

View File

@ -0,0 +1,146 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from netbox.models import ChangeLoggedModel, PrimaryModel
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, TagsMixin
from vpn.choices import *
__all__ = (
'Tunnel',
'TunnelTermination',
)
class Tunnel(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=TunnelStatusChoices,
default=TunnelStatusChoices.STATUS_ACTIVE
)
encapsulation = models.CharField(
verbose_name=_('encapsulation'),
max_length=50,
choices=TunnelEncapsulationChoices
)
ipsec_profile = models.ForeignKey(
to='vpn.IPSecProfile',
on_delete=models.PROTECT,
related_name='tunnels',
blank=True,
null=True
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='tunnels',
blank=True,
null=True
)
tunnel_id = models.PositiveBigIntegerField(
verbose_name=_('tunnel ID'),
blank=True,
null=True
)
clone_fields = (
'status', 'encapsulation', 'ipsec_profile', 'tenant',
)
class Meta:
ordering = ('name',)
verbose_name = _('tunnel')
verbose_name_plural = _('tunnels')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('vpn:tunnel', args=[self.pk])
def get_status_color(self):
return TunnelStatusChoices.colors.get(self.status)
class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLoggedModel):
tunnel = models.ForeignKey(
to='vpn.Tunnel',
on_delete=models.CASCADE,
related_name='terminations'
)
role = models.CharField(
verbose_name=_('role'),
max_length=50,
choices=TunnelTerminationRoleChoices,
default=TunnelTerminationRoleChoices.ROLE_PEER
)
termination_type = models.ForeignKey(
to='contenttypes.ContentType',
on_delete=models.PROTECT,
related_name='+'
)
termination_id = models.PositiveBigIntegerField(
blank=True,
null=True
)
termination = GenericForeignKey(
ct_field='termination_type',
fk_field='termination_id'
)
outside_ip = models.OneToOneField(
to='ipam.IPAddress',
on_delete=models.PROTECT,
related_name='tunnel_termination',
blank=True,
null=True
)
prerequisite_models = (
'vpn.Tunnel',
)
class Meta:
ordering = ('tunnel', 'role', 'pk')
constraints = (
models.UniqueConstraint(
fields=('termination_type', 'termination_id'),
name='%(app_label)s_%(class)s_termination',
violation_error_message=_("An object may be terminated to only one tunnel at a time.")
),
)
verbose_name = _('tunnel termination')
verbose_name_plural = _('tunnel terminations')
def __str__(self):
return f'{self.tunnel}: Termination {self.pk}'
def get_absolute_url(self):
return reverse('vpn:tunneltermination', args=[self.pk])
def get_role_color(self):
return TunnelTerminationRoleChoices.colors.get(self.role)
def clean(self):
super().clean()
# Check that the selected termination object is not already attached to a Tunnel
if getattr(self.termination, 'tunnel_termination', None) and self.termination.tunnel_termination.pk != self.pk:
raise ValidationError({
'termination': _("{name} is already attached to a tunnel ({tunnel}).").format(
name=self.termination.name,
tunnel=self.termination.tunnel_termination.tunnel
)
})
def to_objectchange(self, action):
objectchange = super().to_objectchange(action)
objectchange.related_object = self.tunnel
return objectchange

65
netbox/vpn/search.py Normal file
View File

@ -0,0 +1,65 @@
from netbox.search import SearchIndex, register_search
from . import models
@register_search
class TunnelIndex(SearchIndex):
model = models.Tunnel
fields = (
('name', 100),
('tunnel_id', 300),
('description', 500),
('comments', 5000),
)
display_attrs = ('status', 'encapsulation', 'tenant', 'description')
@register_search
class IKEProposalIndex(SearchIndex):
model = models.IKEProposal
fields = (
('name', 100),
('description', 500),
)
display_attrs = ('description',)
@register_search
class IKEPolicyIndex(SearchIndex):
model = models.IKEPolicy
fields = (
('name', 100),
('description', 500),
)
display_attrs = ('description',)
@register_search
class IPSecProposalIndex(SearchIndex):
model = models.IPSecProposal
fields = (
('name', 100),
('description', 500),
)
display_attrs = ('description',)
@register_search
class IPSecPolicyIndex(SearchIndex):
model = models.IPSecPolicy
fields = (
('name', 100),
('description', 500),
)
display_attrs = ('description',)
@register_search
class IPSecProfileIndex(SearchIndex):
model = models.IPSecProfile
fields = (
('name', 100),
('description', 500),
('comments', 5000),
)
display_attrs = ('description',)

254
netbox/vpn/tables.py Normal file
View File

@ -0,0 +1,254 @@
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from django_tables2.utils import Accessor
from tenancy.tables import TenancyColumnsMixin
from netbox.tables import NetBoxTable, columns
from vpn.models import *
__all__ = (
'IKEPolicyTable',
'IKEProposalTable',
'IPSecPolicyTable',
'IPSecProposalTable',
'IPSecProfileTable',
'TunnelTable',
'TunnelTerminationTable',
)
class TunnelTable(TenancyColumnsMixin, NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
status = columns.ChoiceFieldColumn(
verbose_name=_('Status')
)
ipsec_profile = tables.Column(
verbose_name=_('IPSec profile'),
linkify=True
)
terminations_count = columns.LinkedCountColumn(
accessor=Accessor('count_terminations'),
viewname='vpn:tunneltermination_list',
url_params={'tunnel_id': 'pk'},
verbose_name=_('Terminations')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
tags = columns.TagColumn(
url_name='vpn:tunnel_list'
)
class Meta(NetBoxTable.Meta):
model = Tunnel
fields = (
'pk', 'id', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tenant_group', 'tunnel_id',
'termination_count', 'description', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'terminations_count')
class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable):
tunnel = tables.Column(
verbose_name=_('Tunnel'),
linkify=True
)
role = columns.ChoiceFieldColumn(
verbose_name=_('Role')
)
termination_parent = tables.Column(
accessor='termination__parent_object',
linkify=True,
orderable=False,
verbose_name=_('Host')
)
termination = tables.Column(
verbose_name=_('Termination'),
linkify=True
)
ip_addresses = tables.ManyToManyColumn(
accessor=tables.A('termination__ip_addresses'),
orderable=False,
linkify_item=True,
verbose_name=_('IP Addresses')
)
outside_ip = tables.Column(
verbose_name=_('Outside IP'),
linkify=True
)
tags = columns.TagColumn(
url_name='vpn:tunneltermination_list'
)
class Meta(NetBoxTable.Meta):
model = TunnelTermination
fields = (
'pk', 'id', 'tunnel', 'role', 'termination_parent', 'termination', 'ip_addresses', 'outside_ip', 'tags',
'created', 'last_updated',
)
default_columns = (
'pk', 'id', 'tunnel', 'role', 'termination_parent', 'termination', 'ip_addresses', 'outside_ip',
)
class IKEProposalTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
authentication_method = tables.Column(
verbose_name=_('Authentication Method')
)
encryption_algorithm = tables.Column(
verbose_name=_('Encryption Algorithm')
)
authentication_algorithm = tables.Column(
verbose_name=_('Authentication Algorithm')
)
group = tables.Column(
verbose_name=_('Group')
)
sa_lifetime = tables.Column(
verbose_name=_('SA Lifetime')
)
tags = columns.TagColumn(
url_name='vpn:ikeproposal_list'
)
class Meta(NetBoxTable.Meta):
model = IKEProposal
fields = (
'pk', 'id', 'name', 'authentication_method', 'encryption_algorithm', 'authentication_algorithm',
'group', 'sa_lifetime', 'description', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'authentication_method', 'encryption_algorithm', 'authentication_algorithm', 'group',
'sa_lifetime', 'description',
)
class IKEPolicyTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
version = tables.Column(
verbose_name=_('Version')
)
mode = tables.Column(
verbose_name=_('Mode')
)
proposals = tables.ManyToManyColumn(
linkify_item=True,
verbose_name=_('Proposals')
)
preshared_key = tables.Column(
verbose_name=_('Pre-shared Key')
)
tags = columns.TagColumn(
url_name='vpn:ikepolicy_list'
)
class Meta(NetBoxTable.Meta):
model = IKEPolicy
fields = (
'pk', 'id', 'name', 'version', 'mode', 'proposals', 'preshared_key', 'description', 'tags', 'created',
'last_updated',
)
default_columns = (
'pk', 'name', 'version', 'mode', 'proposals', 'description',
)
class IPSecProposalTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
encryption_algorithm = tables.Column(
verbose_name=_('Encryption Algorithm')
)
authentication_algorithm = tables.Column(
verbose_name=_('Authentication Algorithm')
)
sa_lifetime_seconds = tables.Column(
verbose_name=_('SA Lifetime (Seconds)')
)
sa_lifetime_data = tables.Column(
verbose_name=_('SA Lifetime (KB)')
)
tags = columns.TagColumn(
url_name='vpn:ipsecproposal_list'
)
class Meta(NetBoxTable.Meta):
model = IPSecProposal
fields = (
'pk', 'id', 'name', 'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds',
'sa_lifetime_data', 'description', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'encryption_algorithm', 'authentication_algorithm', 'sa_lifetime_seconds',
'sa_lifetime_data', 'description',
)
class IPSecPolicyTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
proposals = tables.ManyToManyColumn(
linkify_item=True,
verbose_name=_('Proposals')
)
pfs_group = tables.Column(
verbose_name=_('PFS Group')
)
tags = columns.TagColumn(
url_name='vpn:ipsecpolicy_list'
)
class Meta(NetBoxTable.Meta):
model = IPSecPolicy
fields = (
'pk', 'id', 'name', 'proposals', 'pfs_group', 'description', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'proposals', 'pfs_group', 'description',
)
class IPSecProfileTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
mode = tables.Column(
verbose_name=_('Mode')
)
ike_policy = tables.Column(
linkify=True,
verbose_name=_('IKE Policy')
)
ipsec_policy = tables.Column(
linkify=True,
verbose_name=_('IPSec Policy')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
tags = columns.TagColumn(
url_name='vpn:ipsecprofile_list'
)
class Meta(NetBoxTable.Meta):
model = IPSecProfile
fields = (
'pk', 'id', 'name', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'mode', 'ike_policy', 'ipsec_policy', 'description')

View File

View File

@ -0,0 +1,473 @@
from django.urls import reverse
from dcim.choices import InterfaceTypeChoices
from dcim.models import Interface
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
from vpn.choices import *
from vpn.models import *
class AppTest(APITestCase):
def test_root(self):
url = reverse('vpn-api:api-root')
response = self.client.get('{}?format=api'.format(url), **self.header)
self.assertEqual(response.status_code, 200)
class TunnelTest(APIViewTestCases.APIViewTestCase):
model = Tunnel
brief_fields = ['display', 'id', 'name', 'url']
bulk_update_data = {
'status': TunnelStatusChoices.STATUS_PLANNED,
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
'description': 'New description',
}
@classmethod
def setUpTestData(cls):
tunnels = (
Tunnel(
name='Tunnel 1',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
Tunnel(
name='Tunnel 2',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
Tunnel(
name='Tunnel 3',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
)
Tunnel.objects.bulk_create(tunnels)
cls.create_data = [
{
'name': 'Tunnel 4',
'status': TunnelStatusChoices.STATUS_DISABLED,
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
},
{
'name': 'Tunnel 5',
'status': TunnelStatusChoices.STATUS_DISABLED,
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
},
{
'name': 'Tunnel 6',
'status': TunnelStatusChoices.STATUS_DISABLED,
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
},
]
class TunnelTerminationTest(APIViewTestCases.APIViewTestCase):
model = TunnelTermination
brief_fields = ['display', 'id', 'url']
bulk_update_data = {
'role': TunnelTerminationRoleChoices.ROLE_PEER,
}
@classmethod
def setUpTestData(cls):
device = create_test_device('Device 1')
interfaces = (
Interface(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 4', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 5', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 6', type=InterfaceTypeChoices.TYPE_VIRTUAL),
)
Interface.objects.bulk_create(interfaces)
tunnel = Tunnel.objects.create(
name='Tunnel 1',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
)
tunnel_terminations = (
TunnelTermination(
tunnel=tunnel,
role=TunnelTerminationRoleChoices.ROLE_HUB,
termination=interfaces[0]
),
TunnelTermination(
tunnel=tunnel,
role=TunnelTerminationRoleChoices.ROLE_HUB,
termination=interfaces[1]
),
TunnelTermination(
tunnel=tunnel,
role=TunnelTerminationRoleChoices.ROLE_HUB,
termination=interfaces[2]
),
)
TunnelTermination.objects.bulk_create(tunnel_terminations)
cls.create_data = [
{
'tunnel': tunnel.pk,
'role': TunnelTerminationRoleChoices.ROLE_PEER,
'termination_type': 'dcim.interface',
'termination_id': interfaces[3].pk,
},
{
'tunnel': tunnel.pk,
'role': TunnelTerminationRoleChoices.ROLE_PEER,
'termination_type': 'dcim.interface',
'termination_id': interfaces[4].pk,
},
{
'tunnel': tunnel.pk,
'role': TunnelTerminationRoleChoices.ROLE_PEER,
'termination_type': 'dcim.interface',
'termination_id': interfaces[5].pk,
},
]
class IKEProposalTest(APIViewTestCases.APIViewTestCase):
model = IKEProposal
brief_fields = ['display', 'id', 'name', 'url']
bulk_update_data = {
'authentication_method': AuthenticationMethodChoices.CERTIFICATES,
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_MD5,
'group': DHGroupChoices.GROUP_19,
'description': 'New description',
}
@classmethod
def setUpTestData(cls):
ike_proposals = (
IKEProposal(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 2',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 3',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
)
IKEProposal.objects.bulk_create(ike_proposals)
cls.create_data = [
{
'name': 'IKE Proposal 4',
'authentication_method': AuthenticationMethodChoices.CERTIFICATES,
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'group': DHGroupChoices.GROUP_19,
},
{
'name': 'IKE Proposal 5',
'authentication_method': AuthenticationMethodChoices.CERTIFICATES,
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'group': DHGroupChoices.GROUP_19,
},
{
'name': 'IKE Proposal 6',
'authentication_method': AuthenticationMethodChoices.CERTIFICATES,
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'group': DHGroupChoices.GROUP_19,
},
]
class IKEPolicyTest(APIViewTestCases.APIViewTestCase):
model = IKEPolicy
brief_fields = ['display', 'id', 'name', 'url']
bulk_update_data = {
'version': IKEVersionChoices.VERSION_1,
'mode': IKEModeChoices.AGGRESSIVE,
'description': 'New description',
'preshared_key': 'New key',
}
@classmethod
def setUpTestData(cls):
ike_proposals = (
IKEProposal(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 2',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
)
IKEProposal.objects.bulk_create(ike_proposals)
ike_policies = (
IKEPolicy(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 2',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 3',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
)
IKEPolicy.objects.bulk_create(ike_policies)
for ike_policy in ike_policies:
ike_policy.proposals.set(ike_proposals)
cls.create_data = [
{
'name': 'IKE Policy 4',
'version': IKEVersionChoices.VERSION_1,
'mode': IKEModeChoices.MAIN,
'proposals': [ike_proposals[0].pk, ike_proposals[1].pk],
},
{
'name': 'IKE Policy 5',
'version': IKEVersionChoices.VERSION_1,
'mode': IKEModeChoices.MAIN,
'proposals': [ike_proposals[0].pk, ike_proposals[1].pk],
},
{
'name': 'IKE Policy 6',
'version': IKEVersionChoices.VERSION_1,
'mode': IKEModeChoices.MAIN,
'proposals': [ike_proposals[0].pk, ike_proposals[1].pk],
},
]
class IPSecProposalTest(APIViewTestCases.APIViewTestCase):
model = IPSecProposal
brief_fields = ['display', 'id', 'name', 'url']
bulk_update_data = {
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_MD5,
'description': 'New description',
}
@classmethod
def setUpTestData(cls):
ipsec_proposals = (
IPSecProposal(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
IPSecProposal(
name='IPSec Proposal 2',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
IPSecProposal(
name='IPSec Proposal 3',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
)
IPSecProposal.objects.bulk_create(ipsec_proposals)
cls.create_data = [
{
'name': 'IPSec Proposal 4',
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
},
{
'name': 'IPSec Proposal 5',
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
},
{
'name': 'IPSec Proposal 6',
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
},
]
class IPSecPolicyTest(APIViewTestCases.APIViewTestCase):
model = IPSecPolicy
brief_fields = ['display', 'id', 'name', 'url']
bulk_update_data = {
'pfs_group': DHGroupChoices.GROUP_5,
'description': 'New description',
}
@classmethod
def setUpTestData(cls):
ipsec_proposals = (
IPSecProposal(
name='IPSec Policy 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
IPSecProposal(
name='IPSec Proposal 2',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
)
IPSecProposal.objects.bulk_create(ipsec_proposals)
ipsec_policies = (
IPSecPolicy(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 2',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 3',
pfs_group=DHGroupChoices.GROUP_14
),
)
IPSecPolicy.objects.bulk_create(ipsec_policies)
for ipsec_policy in ipsec_policies:
ipsec_policy.proposals.set(ipsec_proposals)
cls.create_data = [
{
'name': 'IPSec Policy 4',
'pfs_group': DHGroupChoices.GROUP_16,
'proposals': [ipsec_proposals[0].pk, ipsec_proposals[1].pk],
},
{
'name': 'IPSec Policy 5',
'pfs_group': DHGroupChoices.GROUP_16,
'proposals': [ipsec_proposals[0].pk, ipsec_proposals[1].pk],
},
{
'name': 'IPSec Policy 6',
'pfs_group': DHGroupChoices.GROUP_16,
'proposals': [ipsec_proposals[0].pk, ipsec_proposals[1].pk],
},
]
class IPSecProfileTest(APIViewTestCases.APIViewTestCase):
model = IPSecProfile
brief_fields = ['display', 'id', 'name', 'url']
@classmethod
def setUpTestData(cls):
ike_proposal = IKEProposal.objects.create(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
)
ipsec_proposal = IPSecProposal.objects.create(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
)
ike_policies = (
IKEPolicy(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 2',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
)
IKEPolicy.objects.bulk_create(ike_policies)
for ike_policy in ike_policies:
ike_policy.proposals.add(ike_proposal)
ipsec_policies = (
IPSecPolicy(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 2',
pfs_group=DHGroupChoices.GROUP_14
),
)
IPSecPolicy.objects.bulk_create(ipsec_policies)
for ipsec_policy in ipsec_policies:
ipsec_policy.proposals.add(ipsec_proposal)
ipsec_profiles = (
IPSecProfile(
name='IPSec Profile 1',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
IPSecProfile(
name='IPSec Profile 2',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
IPSecProfile(
name='IPSec Profile 3',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
)
IPSecProfile.objects.bulk_create(ipsec_profiles)
cls.create_data = [
{
'name': 'IPSec Profile 4',
'mode': IPSecModeChoices.AH,
'ike_policy': ike_policies[1].pk,
'ipsec_policy': ipsec_policies[1].pk,
},
]
cls.bulk_update_data = {
'mode': IPSecModeChoices.AH,
'ike_policy': ike_policies[1].pk,
'ipsec_policy': ipsec_policies[1].pk,
'description': 'New description',
}

View File

@ -0,0 +1,592 @@
from django.test import TestCase
from dcim.choices import InterfaceTypeChoices
from dcim.models import Interface
from ipam.models import IPAddress
from virtualization.models import VMInterface
from vpn.choices import *
from vpn.filtersets import *
from vpn.models import *
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Tunnel.objects.all()
filterset = TunnelFilterSet
@classmethod
def setUpTestData(cls):
ike_proposal = IKEProposal.objects.create(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
)
ike_policy = IKEPolicy.objects.create(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
)
ike_policy.proposals.add(ike_proposal)
ipsec_proposal = IPSecProposal.objects.create(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
)
ipsec_policy = IPSecPolicy.objects.create(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_14
)
ipsec_policy.proposals.add(ipsec_proposal)
ipsec_profiles = (
IPSecProfile(
name='IPSec Profile 1',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policy,
ipsec_policy=ipsec_policy
),
IPSecProfile(
name='IPSec Profile 2',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policy,
ipsec_policy=ipsec_policy
),
)
IPSecProfile.objects.bulk_create(ipsec_profiles)
tunnels = (
Tunnel(
name='Tunnel 1',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_GRE,
ipsec_profile=ipsec_profiles[0],
tunnel_id=100
),
Tunnel(
name='Tunnel 2',
status=TunnelStatusChoices.STATUS_PLANNED,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP,
ipsec_profile=ipsec_profiles[0],
tunnel_id=200
),
Tunnel(
name='Tunnel 3',
status=TunnelStatusChoices.STATUS_DISABLED,
encapsulation=TunnelEncapsulationChoices.ENCAP_IPSEC_TUNNEL,
ipsec_profile=None,
tunnel_id=300
),
)
Tunnel.objects.bulk_create(tunnels)
def test_name(self):
params = {'name': ['Tunnel 1', 'Tunnel 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_status(self):
params = {'status': [TunnelStatusChoices.STATUS_ACTIVE, TunnelStatusChoices.STATUS_PLANNED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_encapsulation(self):
params = {'encapsulation': [TunnelEncapsulationChoices.ENCAP_GRE, TunnelEncapsulationChoices.ENCAP_IP_IP]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_ipsec_profile(self):
ipsec_profiles = IPSecProfile.objects.all()[:2]
params = {'ipsec_profile_id': [ipsec_profiles[0].pk, ipsec_profiles[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'ipsec_profile': [ipsec_profiles[0].name, ipsec_profiles[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_tunnel_id(self):
params = {'tunnel_id': [100, 200]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class TunnelTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = TunnelTermination.objects.all()
filterset = TunnelTerminationFilterSet
@classmethod
def setUpTestData(cls):
device = create_test_device('Device 1')
interfaces = (
Interface(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL),
)
Interface.objects.bulk_create(interfaces)
virtual_machine = create_test_virtualmachine('Virtual Machine 1')
vm_interfaces = (
VMInterface(virtual_machine=virtual_machine, name='Interface 1'),
VMInterface(virtual_machine=virtual_machine, name='Interface 2'),
VMInterface(virtual_machine=virtual_machine, name='Interface 3'),
)
VMInterface.objects.bulk_create(vm_interfaces)
ip_addresses = (
IPAddress(address='192.168.0.1/32'),
IPAddress(address='192.168.0.2/32'),
IPAddress(address='192.168.0.3/32'),
IPAddress(address='192.168.0.4/32'),
IPAddress(address='192.168.0.5/32'),
IPAddress(address='192.168.0.6/32'),
)
IPAddress.objects.bulk_create(ip_addresses)
tunnels = (
Tunnel(
name='Tunnel 1',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
Tunnel(
name='Tunnel 2',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
Tunnel(
name='Tunnel 3',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
)
Tunnel.objects.bulk_create(tunnels)
tunnel_terminations = (
# Tunnel 1
TunnelTermination(
tunnel=tunnels[0],
role=TunnelTerminationRoleChoices.ROLE_HUB,
termination=interfaces[0],
outside_ip=ip_addresses[0]
),
TunnelTermination(
tunnel=tunnels[0],
role=TunnelTerminationRoleChoices.ROLE_SPOKE,
termination=vm_interfaces[0],
outside_ip=ip_addresses[1]
),
# Tunnel 2
TunnelTermination(
tunnel=tunnels[1],
role=TunnelTerminationRoleChoices.ROLE_HUB,
termination=interfaces[1],
outside_ip=ip_addresses[2]
),
TunnelTermination(
tunnel=tunnels[1],
role=TunnelTerminationRoleChoices.ROLE_SPOKE,
termination=vm_interfaces[1],
outside_ip=ip_addresses[3]
),
# Tunnel 3
TunnelTermination(
tunnel=tunnels[2],
role=TunnelTerminationRoleChoices.ROLE_PEER,
termination=interfaces[2],
outside_ip=ip_addresses[4]
),
TunnelTermination(
tunnel=tunnels[2],
role=TunnelTerminationRoleChoices.ROLE_PEER,
termination=vm_interfaces[2],
outside_ip=ip_addresses[5]
),
)
TunnelTermination.objects.bulk_create(tunnel_terminations)
def test_tunnel(self):
tunnels = Tunnel.objects.all()[:2]
params = {'tunnel_id': [tunnels[0].pk, tunnels[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'tunnel': [tunnels[0].name, tunnels[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_role(self):
params = {'role': [TunnelTerminationRoleChoices.ROLE_HUB, TunnelTerminationRoleChoices.ROLE_SPOKE]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_termination_type(self):
params = {'termination_type': 'dcim.interface'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
params = {'termination_type': 'virtualization.vminterface'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_interface(self):
interfaces = Interface.objects.all()[:2]
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'interface': [interfaces[0].name, interfaces[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_vminterface(self):
vm_interfaces = VMInterface.objects.all()[:2]
params = {'vminterface_id': [vm_interfaces[0].pk, vm_interfaces[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'vminterface': [vm_interfaces[0].name, vm_interfaces[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_outside_ip(self):
ip_addresses = IPAddress.objects.all()[:2]
params = {'outside_ip_id': [ip_addresses[0].pk, ip_addresses[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IKEProposalTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IKEProposal.objects.all()
filterset = IKEProposalFilterSet
@classmethod
def setUpTestData(cls):
ike_proposals = (
IKEProposal(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_1,
sa_lifetime=1000
),
IKEProposal(
name='IKE Proposal 2',
authentication_method=AuthenticationMethodChoices.CERTIFICATES,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
group=DHGroupChoices.GROUP_2,
sa_lifetime=2000
),
IKEProposal(
name='IKE Proposal 3',
authentication_method=AuthenticationMethodChoices.RSA_SIGNATURES,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA512,
group=DHGroupChoices.GROUP_5,
sa_lifetime=3000
),
)
IKEProposal.objects.bulk_create(ike_proposals)
def test_name(self):
params = {'name': ['IKE Proposal 1', 'IKE Proposal 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_authentication_method(self):
params = {'authentication_method': [
AuthenticationMethodChoices.PRESHARED_KEYS, AuthenticationMethodChoices.CERTIFICATES
]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_encryption_algorithm(self):
params = {'encryption_algorithm': [
EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC, EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC
]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_authentication_algorithm(self):
params = {'authentication_algorithm': [
AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1, AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256
]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_group(self):
params = {'group': [DHGroupChoices.GROUP_1, DHGroupChoices.GROUP_2]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_sa_lifetime(self):
params = {'sa_lifetime': [1000, 2000]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IKEPolicyTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IKEPolicy.objects.all()
filterset = IKEPolicyFilterSet
@classmethod
def setUpTestData(cls):
ike_proposals = (
IKEProposal(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 2',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 3',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
)
IKEProposal.objects.bulk_create(ike_proposals)
ike_policies = (
IKEPolicy(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 2',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 3',
version=IKEVersionChoices.VERSION_2,
mode=IKEModeChoices.AGGRESSIVE,
),
)
IKEPolicy.objects.bulk_create(ike_policies)
ike_policies[0].proposals.add(ike_proposals[0])
ike_policies[1].proposals.add(ike_proposals[1])
ike_policies[2].proposals.add(ike_proposals[2])
def test_name(self):
params = {'name': ['IKE Policy 1', 'IKE Policy 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_version(self):
params = {'version': [IKEVersionChoices.VERSION_1]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_mode(self):
params = {'mode': [IKEModeChoices.MAIN]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_proposal(self):
proposals = IKEProposal.objects.all()[:2]
params = {'proposal_id': [proposals[0].pk, proposals[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'proposal': [proposals[0].name, proposals[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IPSecProposalTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IPSecProposal.objects.all()
filterset = IPSecProposalFilterSet
@classmethod
def setUpTestData(cls):
ipsec_proposals = (
IPSecProposal(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
sa_lifetime_seconds=1000,
sa_lifetime_data=1000
),
IPSecProposal(
name='IPSec Proposal 2',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
sa_lifetime_seconds=2000,
sa_lifetime_data=2000
),
IPSecProposal(
name='IPSec Proposal 3',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES256_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA512,
sa_lifetime_seconds=3000,
sa_lifetime_data=3000
),
)
IPSecProposal.objects.bulk_create(ipsec_proposals)
def test_name(self):
params = {'name': ['IPSec Proposal 1', 'IPSec Proposal 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_encryption_algorithm(self):
params = {'encryption_algorithm': [
EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC, EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC
]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_authentication_algorithm(self):
params = {'authentication_algorithm': [
AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1, AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256
]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_sa_lifetime_seconds(self):
params = {'sa_lifetime_seconds': [1000, 2000]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_sa_lifetime_data(self):
params = {'sa_lifetime_data': [1000, 2000]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IPSecPolicyTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IPSecPolicy.objects.all()
filterset = IPSecPolicyFilterSet
@classmethod
def setUpTestData(cls):
ipsec_proposals = (
IPSecProposal(
name='IPSec Policy 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
IPSecProposal(
name='IPSec Proposal 2',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
IPSecProposal(
name='IPSec Proposal 3',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
)
IPSecProposal.objects.bulk_create(ipsec_proposals)
ipsec_policies = (
IPSecPolicy(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_1
),
IPSecPolicy(
name='IPSec Policy 2',
pfs_group=DHGroupChoices.GROUP_2
),
IPSecPolicy(
name='IPSec Policy 3',
pfs_group=DHGroupChoices.GROUP_5
),
)
IPSecPolicy.objects.bulk_create(ipsec_policies)
ipsec_policies[0].proposals.add(ipsec_proposals[0])
ipsec_policies[1].proposals.add(ipsec_proposals[1])
ipsec_policies[2].proposals.add(ipsec_proposals[2])
def test_name(self):
params = {'name': ['IPSec Policy 1', 'IPSec Policy 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_pfs_group(self):
params = {'pfs_group': [DHGroupChoices.GROUP_1, DHGroupChoices.GROUP_2]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_proposal(self):
proposals = IPSecProposal.objects.all()[:2]
params = {'proposal_id': [proposals[0].pk, proposals[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'proposal': [proposals[0].name, proposals[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IPSecProfileTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IPSecProfile.objects.all()
filterset = IPSecProfileFilterSet
@classmethod
def setUpTestData(cls):
ike_proposal = IKEProposal.objects.create(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
)
ipsec_proposal = IPSecProposal.objects.create(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
)
ike_policies = (
IKEPolicy(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 2',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 3',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
)
IKEPolicy.objects.bulk_create(ike_policies)
for ike_policy in ike_policies:
ike_policy.proposals.add(ike_proposal)
ipsec_policies = (
IPSecPolicy(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 2',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 3',
pfs_group=DHGroupChoices.GROUP_14
),
)
IPSecPolicy.objects.bulk_create(ipsec_policies)
for ipsec_policy in ipsec_policies:
ipsec_policy.proposals.add(ipsec_proposal)
ipsec_profiles = (
IPSecProfile(
name='IPSec Profile 1',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
IPSecProfile(
name='IPSec Profile 2',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[1],
ipsec_policy=ipsec_policies[1]
),
IPSecProfile(
name='IPSec Profile 3',
mode=IPSecModeChoices.AH,
ike_policy=ike_policies[2],
ipsec_policy=ipsec_policies[2]
),
)
IPSecProfile.objects.bulk_create(ipsec_profiles)
def test_name(self):
params = {'name': ['IPSec Profile 1', 'IPSec Profile 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_mode(self):
params = {'mode': [IPSecModeChoices.ESP]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_ike_policy(self):
ike_policies = IKEPolicy.objects.all()[:2]
params = {'ike_policy_id': [ike_policies[0].pk, ike_policies[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'ike_policy': [ike_policies[0].name, ike_policies[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_ipsec_policy(self):
ipsec_policies = IPSecPolicy.objects.all()[:2]
params = {'ipsec_policy_id': [ipsec_policies[0].pk, ipsec_policies[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'ipsec_policy': [ipsec_policies[0].name, ipsec_policies[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -0,0 +1,508 @@
from dcim.choices import InterfaceTypeChoices
from dcim.models import Interface
from vpn.choices import *
from vpn.models import *
from utilities.testing import ViewTestCases, create_tags, create_test_device
class TunnelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = Tunnel
@classmethod
def setUpTestData(cls):
tunnels = (
Tunnel(
name='Tunnel 1',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
Tunnel(
name='Tunnel 2',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
Tunnel(
name='Tunnel 3',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
),
)
Tunnel.objects.bulk_create(tunnels)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'name': 'Tunnel X',
'description': 'New tunnel',
'status': TunnelStatusChoices.STATUS_PLANNED,
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"name,status,encapsulation",
"Tunnel 4,planned,gre",
"Tunnel 5,planned,gre",
"Tunnel 6,planned,gre",
)
cls.csv_update_data = (
"id,status,encapsulation",
f"{tunnels[0].pk},active,ip-ip",
f"{tunnels[1].pk},active,ip-ip",
f"{tunnels[2].pk},active,ip-ip",
)
cls.bulk_edit_data = {
'description': 'New description',
'status': TunnelStatusChoices.STATUS_DISABLED,
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
}
class TunnelTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = TunnelTermination
# TODO: Workaround for conflict between form field and GFK
validation_excluded_fields = ('termination',)
@classmethod
def setUpTestData(cls):
device = create_test_device('Device 1')
interfaces = (
Interface(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 4', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 5', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 6', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 7', type=InterfaceTypeChoices.TYPE_VIRTUAL),
)
Interface.objects.bulk_create(interfaces)
tunnel = Tunnel.objects.create(
name='Tunnel 1',
status=TunnelStatusChoices.STATUS_ACTIVE,
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
)
tunnel_terminations = (
TunnelTermination(
tunnel=tunnel,
role=TunnelTerminationRoleChoices.ROLE_HUB,
termination=interfaces[0]
),
TunnelTermination(
tunnel=tunnel,
role=TunnelTerminationRoleChoices.ROLE_SPOKE,
termination=interfaces[1]
),
TunnelTermination(
tunnel=tunnel,
role=TunnelTerminationRoleChoices.ROLE_SPOKE,
termination=interfaces[2]
),
)
TunnelTermination.objects.bulk_create(tunnel_terminations)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'tunnel': tunnel.pk,
'role': TunnelTerminationRoleChoices.ROLE_PEER,
'type': TunnelTerminationTypeChoices.TYPE_DEVICE,
'parent': device.pk,
'termination': interfaces[6].pk,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"tunnel,role,device,termination",
"Tunnel 1,peer,Device 1,Interface 4",
"Tunnel 1,peer,Device 1,Interface 5",
"Tunnel 1,peer,Device 1,Interface 6",
)
cls.csv_update_data = (
"id,role",
f"{tunnel_terminations[0].pk},peer",
f"{tunnel_terminations[1].pk},peer",
f"{tunnel_terminations[2].pk},peer",
)
cls.bulk_edit_data = {
'role': TunnelTerminationRoleChoices.ROLE_PEER,
}
class IKEProposalTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = IKEProposal
@classmethod
def setUpTestData(cls):
ike_proposals = (
IKEProposal(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 2',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 3',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
)
IKEProposal.objects.bulk_create(ike_proposals)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'name': 'IKE Proposal X',
'authentication_method': AuthenticationMethodChoices.CERTIFICATES,
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'group': DHGroupChoices.GROUP_19,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"name,authentication_method,encryption_algorithm,authentication_algorithm,group",
"IKE Proposal 4,preshared-keys,aes-128-cbc,hmac-sha1,14",
"IKE Proposal 5,preshared-keys,aes-128-cbc,hmac-sha1,14",
"IKE Proposal 6,preshared-keys,aes-128-cbc,hmac-sha1,14",
)
cls.csv_update_data = (
"id,description",
f"{ike_proposals[0].pk},New description",
f"{ike_proposals[1].pk},New description",
f"{ike_proposals[2].pk},New description",
)
cls.bulk_edit_data = {
'description': 'New description',
'authentication_method': AuthenticationMethodChoices.CERTIFICATES,
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'group': DHGroupChoices.GROUP_19
}
class IKEPolicyTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = IKEPolicy
@classmethod
def setUpTestData(cls):
ike_proposals = (
IKEProposal(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
IKEProposal(
name='IKE Proposal 2',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
),
)
IKEProposal.objects.bulk_create(ike_proposals)
ike_policies = (
IKEPolicy(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 2',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 3',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
)
IKEPolicy.objects.bulk_create(ike_policies)
for ike_policy in ike_policies:
ike_policy.proposals.set(ike_proposals)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'name': 'IKE Policy X',
'version': IKEVersionChoices.VERSION_2,
'mode': IKEModeChoices.AGGRESSIVE,
'proposals': [p.pk for p in ike_proposals],
'tags': [t.pk for t in tags],
}
ike_proposal_names = ','.join([p.name for p in ike_proposals])
cls.csv_data = (
"name,version,mode,proposals",
f"IKE Proposal 4,2,aggressive,\"{ike_proposal_names}\"",
f"IKE Proposal 5,2,aggressive,\"{ike_proposal_names}\"",
f"IKE Proposal 6,2,aggressive,\"{ike_proposal_names}\"",
)
cls.csv_update_data = (
"id,description",
f"{ike_policies[0].pk},New description",
f"{ike_policies[1].pk},New description",
f"{ike_policies[2].pk},New description",
)
cls.bulk_edit_data = {
'description': 'New description',
'version': IKEVersionChoices.VERSION_2,
'mode': IKEModeChoices.AGGRESSIVE,
}
class IPSecProposalTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = IPSecProposal
@classmethod
def setUpTestData(cls):
ipsec_proposals = (
IPSecProposal(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
),
IPSecProposal(
name='IPSec Proposal 2',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
),
IPSecProposal(
name='IPSec Proposal 3',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
),
)
IPSecProposal.objects.bulk_create(ipsec_proposals)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'name': 'IPSec Proposal X',
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'sa_lifetime_seconds': 3600,
'sa_lifetime_data': 1000000,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"name,encryption_algorithm,authentication_algorithm,sa_lifetime_seconds,sa_lifetime_data",
"IKE Proposal 4,aes-128-cbc,hmac-sha1,3600,1000000",
"IKE Proposal 5,aes-128-cbc,hmac-sha1,3600,1000000",
"IKE Proposal 6,aes-128-cbc,hmac-sha1,3600,1000000",
)
cls.csv_update_data = (
"id,description",
f"{ipsec_proposals[0].pk},New description",
f"{ipsec_proposals[1].pk},New description",
f"{ipsec_proposals[2].pk},New description",
)
cls.bulk_edit_data = {
'description': 'New description',
'encryption_algorithm': EncryptionAlgorithmChoices.ENCRYPTION_AES192_CBC,
'authentication_algorithm': AuthenticationAlgorithmChoices.AUTH_HMAC_SHA256,
'sa_lifetime_seconds': 3600,
'sa_lifetime_data': 1000000,
}
class IPSecPolicyTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = IPSecPolicy
@classmethod
def setUpTestData(cls):
ipsec_proposals = (
IPSecProposal(
name='IPSec Policy 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
IPSecProposal(
name='IPSec Proposal 2',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
),
)
IPSecProposal.objects.bulk_create(ipsec_proposals)
ipsec_policies = (
IPSecPolicy(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 2',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 3',
pfs_group=DHGroupChoices.GROUP_14
),
)
IPSecPolicy.objects.bulk_create(ipsec_policies)
for ipsec_policy in ipsec_policies:
ipsec_policy.proposals.set(ipsec_proposals)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'name': 'IPSec Policy X',
'pfs_group': DHGroupChoices.GROUP_5,
'proposals': [p.pk for p in ipsec_proposals],
'tags': [t.pk for t in tags],
}
ipsec_proposal_names = ','.join([p.name for p in ipsec_proposals])
cls.csv_data = (
"name,pfs_group,proposals",
f"IKE Proposal 4,19,\"{ipsec_proposal_names}\"",
f"IKE Proposal 5,19,\"{ipsec_proposal_names}\"",
f"IKE Proposal 6,19,\"{ipsec_proposal_names}\"",
)
cls.csv_update_data = (
"id,description",
f"{ipsec_policies[0].pk},New description",
f"{ipsec_policies[1].pk},New description",
f"{ipsec_policies[2].pk},New description",
)
cls.bulk_edit_data = {
'description': 'New description',
'pfs_group': DHGroupChoices.GROUP_5,
}
class IPSecProfileTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = IPSecProfile
@classmethod
def setUpTestData(cls):
ike_proposal = IKEProposal.objects.create(
name='IKE Proposal 1',
authentication_method=AuthenticationMethodChoices.PRESHARED_KEYS,
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1,
group=DHGroupChoices.GROUP_14
)
ipsec_proposal = IPSecProposal.objects.create(
name='IPSec Proposal 1',
encryption_algorithm=EncryptionAlgorithmChoices.ENCRYPTION_AES128_CBC,
authentication_algorithm=AuthenticationAlgorithmChoices.AUTH_HMAC_SHA1
)
ike_policies = (
IKEPolicy(
name='IKE Policy 1',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
IKEPolicy(
name='IKE Policy 2',
version=IKEVersionChoices.VERSION_1,
mode=IKEModeChoices.MAIN,
),
)
IKEPolicy.objects.bulk_create(ike_policies)
for ike_policy in ike_policies:
ike_policy.proposals.add(ike_proposal)
ipsec_policies = (
IPSecPolicy(
name='IPSec Policy 1',
pfs_group=DHGroupChoices.GROUP_14
),
IPSecPolicy(
name='IPSec Policy 2',
pfs_group=DHGroupChoices.GROUP_14
),
)
IPSecPolicy.objects.bulk_create(ipsec_policies)
for ipsec_policy in ipsec_policies:
ipsec_policy.proposals.add(ipsec_proposal)
ipsec_profiles = (
IPSecProfile(
name='IPSec Profile 1',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
IPSecProfile(
name='IPSec Profile 2',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
IPSecProfile(
name='IPSec Profile 3',
mode=IPSecModeChoices.ESP,
ike_policy=ike_policies[0],
ipsec_policy=ipsec_policies[0]
),
)
IPSecProfile.objects.bulk_create(ipsec_profiles)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'name': 'IPSec Profile X',
'mode': IPSecModeChoices.AH,
'ike_policy': ike_policies[1].pk,
'ipsec_policy': ipsec_policies[1].pk,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"name,mode,ike_policy,ipsec_policy",
f"IKE Proposal 4,ah,IKE Policy 2,IPSec Policy 2",
f"IKE Proposal 5,ah,IKE Policy 2,IPSec Policy 2",
f"IKE Proposal 6,ah,IKE Policy 2,IPSec Policy 2",
)
cls.csv_update_data = (
"id,description",
f"{ipsec_profiles[0].pk},New description",
f"{ipsec_profiles[1].pk},New description",
f"{ipsec_profiles[2].pk},New description",
)
cls.bulk_edit_data = {
'description': 'New description',
'mode': IPSecModeChoices.AH,
'ike_policy': ike_policies[1].pk,
'ipsec_policy': ipsec_policies[1].pk,
}

65
netbox/vpn/urls.py Normal file
View File

@ -0,0 +1,65 @@
from django.urls import include, path
from utilities.urls import get_model_urls
from . import views
app_name = 'vpn'
urlpatterns = [
# Tunnels
path('tunnels/', views.TunnelListView.as_view(), name='tunnel_list'),
path('tunnels/add/', views.TunnelEditView.as_view(), name='tunnel_add'),
path('tunnels/import/', views.TunnelBulkImportView.as_view(), name='tunnel_import'),
path('tunnels/edit/', views.TunnelBulkEditView.as_view(), name='tunnel_bulk_edit'),
path('tunnels/delete/', views.TunnelBulkDeleteView.as_view(), name='tunnel_bulk_delete'),
path('tunnels/<int:pk>/', include(get_model_urls('vpn', 'tunnel'))),
# Tunnel terminations
path('tunnel-terminations/', views.TunnelTerminationListView.as_view(), name='tunneltermination_list'),
path('tunnel-terminations/add/', views.TunnelTerminationEditView.as_view(), name='tunneltermination_add'),
path('tunnel-terminations/import/', views.TunnelTerminationBulkImportView.as_view(), name='tunneltermination_import'),
path('tunnel-terminations/edit/', views.TunnelTerminationBulkEditView.as_view(), name='tunneltermination_bulk_edit'),
path('tunnel-terminations/delete/', views.TunnelTerminationBulkDeleteView.as_view(), name='tunneltermination_bulk_delete'),
path('tunnel-terminations/<int:pk>/', include(get_model_urls('vpn', 'tunneltermination'))),
# IKE proposals
path('ike-proposals/', views.IKEProposalListView.as_view(), name='ikeproposal_list'),
path('ike-proposals/add/', views.IKEProposalEditView.as_view(), name='ikeproposal_add'),
path('ike-proposals/import/', views.IKEProposalBulkImportView.as_view(), name='ikeproposal_import'),
path('ike-proposals/edit/', views.IKEProposalBulkEditView.as_view(), name='ikeproposal_bulk_edit'),
path('ike-proposals/delete/', views.IKEProposalBulkDeleteView.as_view(), name='ikeproposal_bulk_delete'),
path('ike-proposals/<int:pk>/', include(get_model_urls('vpn', 'ikeproposal'))),
# IKE policies
path('ike-policys/', views.IKEPolicyListView.as_view(), name='ikepolicy_list'),
path('ike-policys/add/', views.IKEPolicyEditView.as_view(), name='ikepolicy_add'),
path('ike-policys/import/', views.IKEPolicyBulkImportView.as_view(), name='ikepolicy_import'),
path('ike-policys/edit/', views.IKEPolicyBulkEditView.as_view(), name='ikepolicy_bulk_edit'),
path('ike-policys/delete/', views.IKEPolicyBulkDeleteView.as_view(), name='ikepolicy_bulk_delete'),
path('ike-policys/<int:pk>/', include(get_model_urls('vpn', 'ikepolicy'))),
# IPSec proposals
path('ipsec-proposals/', views.IPSecProposalListView.as_view(), name='ipsecproposal_list'),
path('ipsec-proposals/add/', views.IPSecProposalEditView.as_view(), name='ipsecproposal_add'),
path('ipsec-proposals/import/', views.IPSecProposalBulkImportView.as_view(), name='ipsecproposal_import'),
path('ipsec-proposals/edit/', views.IPSecProposalBulkEditView.as_view(), name='ipsecproposal_bulk_edit'),
path('ipsec-proposals/delete/', views.IPSecProposalBulkDeleteView.as_view(), name='ipsecproposal_bulk_delete'),
path('ipsec-proposals/<int:pk>/', include(get_model_urls('vpn', 'ipsecproposal'))),
# IPSec policies
path('ipsec-policys/', views.IPSecPolicyListView.as_view(), name='ipsecpolicy_list'),
path('ipsec-policys/add/', views.IPSecPolicyEditView.as_view(), name='ipsecpolicy_add'),
path('ipsec-policys/import/', views.IPSecPolicyBulkImportView.as_view(), name='ipsecpolicy_import'),
path('ipsec-policys/edit/', views.IPSecPolicyBulkEditView.as_view(), name='ipsecpolicy_bulk_edit'),
path('ipsec-policys/delete/', views.IPSecPolicyBulkDeleteView.as_view(), name='ipsecpolicy_bulk_delete'),
path('ipsec-policys/<int:pk>/', include(get_model_urls('vpn', 'ipsecpolicy'))),
# IPSec profiles
path('ipsec-profiles/', views.IPSecProfileListView.as_view(), name='ipsecprofile_list'),
path('ipsec-profiles/add/', views.IPSecProfileEditView.as_view(), name='ipsecprofile_add'),
path('ipsec-profiles/import/', views.IPSecProfileBulkImportView.as_view(), name='ipsecprofile_import'),
path('ipsec-profiles/edit/', views.IPSecProfileBulkEditView.as_view(), name='ipsecprofile_bulk_edit'),
path('ipsec-profiles/delete/', views.IPSecProfileBulkDeleteView.as_view(), name='ipsecprofile_bulk_delete'),
path('ipsec-profiles/<int:pk>/', include(get_model_urls('vpn', 'ipsecprofile'))),
]

334
netbox/vpn/views.py Normal file
View File

@ -0,0 +1,334 @@
from netbox.views import generic
from utilities.utils import count_related
from utilities.views import register_model_view
from . import filtersets, forms, tables
from .models import *
#
# Tunnels
#
class TunnelListView(generic.ObjectListView):
queryset = Tunnel.objects.annotate(
count_terminations=count_related(TunnelTermination, 'tunnel')
)
filterset = filtersets.TunnelFilterSet
filterset_form = forms.TunnelFilterForm
table = tables.TunnelTable
@register_model_view(Tunnel)
class TunnelView(generic.ObjectView):
queryset = Tunnel.objects.all()
@register_model_view(Tunnel, 'edit')
class TunnelEditView(generic.ObjectEditView):
queryset = Tunnel.objects.all()
form = forms.TunnelForm
def dispatch(self, request, *args, **kwargs):
# If creating a new Tunnel, use the creation form
if 'pk' not in kwargs:
self.form = forms.TunnelCreateForm
return super().dispatch(request, *args, **kwargs)
@register_model_view(Tunnel, 'delete')
class TunnelDeleteView(generic.ObjectDeleteView):
queryset = Tunnel.objects.all()
class TunnelBulkImportView(generic.BulkImportView):
queryset = Tunnel.objects.all()
model_form = forms.TunnelImportForm
class TunnelBulkEditView(generic.BulkEditView):
queryset = Tunnel.objects.annotate(
count_terminations=count_related(TunnelTermination, 'tunnel')
)
filterset = filtersets.TunnelFilterSet
table = tables.TunnelTable
form = forms.TunnelBulkEditForm
class TunnelBulkDeleteView(generic.BulkDeleteView):
queryset = Tunnel.objects.annotate(
count_terminations=count_related(TunnelTermination, 'tunnel')
)
filterset = filtersets.TunnelFilterSet
table = tables.TunnelTable
#
# Tunnel terminations
#
class TunnelTerminationListView(generic.ObjectListView):
queryset = TunnelTermination.objects.all()
filterset = filtersets.TunnelTerminationFilterSet
filterset_form = forms.TunnelTerminationFilterForm
table = tables.TunnelTerminationTable
@register_model_view(TunnelTermination)
class TunnelTerminationView(generic.ObjectView):
queryset = TunnelTermination.objects.all()
@register_model_view(TunnelTermination, 'edit')
class TunnelTerminationEditView(generic.ObjectEditView):
queryset = TunnelTermination.objects.all()
form = forms.TunnelTerminationForm
@register_model_view(TunnelTermination, 'delete')
class TunnelTerminationDeleteView(generic.ObjectDeleteView):
queryset = TunnelTermination.objects.all()
class TunnelTerminationBulkImportView(generic.BulkImportView):
queryset = TunnelTermination.objects.all()
model_form = forms.TunnelTerminationImportForm
class TunnelTerminationBulkEditView(generic.BulkEditView):
queryset = TunnelTermination.objects.all()
filterset = filtersets.TunnelTerminationFilterSet
table = tables.TunnelTerminationTable
form = forms.TunnelTerminationBulkEditForm
class TunnelTerminationBulkDeleteView(generic.BulkDeleteView):
queryset = TunnelTermination.objects.all()
filterset = filtersets.TunnelTerminationFilterSet
table = tables.TunnelTerminationTable
#
# IKE proposals
#
class IKEProposalListView(generic.ObjectListView):
queryset = IKEProposal.objects.all()
filterset = filtersets.IKEProposalFilterSet
filterset_form = forms.IKEProposalFilterForm
table = tables.IKEProposalTable
@register_model_view(IKEProposal)
class IKEProposalView(generic.ObjectView):
queryset = IKEProposal.objects.all()
@register_model_view(IKEProposal, 'edit')
class IKEProposalEditView(generic.ObjectEditView):
queryset = IKEProposal.objects.all()
form = forms.IKEProposalForm
@register_model_view(IKEProposal, 'delete')
class IKEProposalDeleteView(generic.ObjectDeleteView):
queryset = IKEProposal.objects.all()
class IKEProposalBulkImportView(generic.BulkImportView):
queryset = IKEProposal.objects.all()
model_form = forms.IKEProposalImportForm
class IKEProposalBulkEditView(generic.BulkEditView):
queryset = IKEProposal.objects.all()
filterset = filtersets.IKEProposalFilterSet
table = tables.IKEProposalTable
form = forms.IKEProposalBulkEditForm
class IKEProposalBulkDeleteView(generic.BulkDeleteView):
queryset = IKEProposal.objects.all()
filterset = filtersets.IKEProposalFilterSet
table = tables.IKEProposalTable
#
# IKE policies
#
class IKEPolicyListView(generic.ObjectListView):
queryset = IKEPolicy.objects.all()
filterset = filtersets.IKEPolicyFilterSet
filterset_form = forms.IKEPolicyFilterForm
table = tables.IKEPolicyTable
@register_model_view(IKEPolicy)
class IKEPolicyView(generic.ObjectView):
queryset = IKEPolicy.objects.all()
@register_model_view(IKEPolicy, 'edit')
class IKEPolicyEditView(generic.ObjectEditView):
queryset = IKEPolicy.objects.all()
form = forms.IKEPolicyForm
@register_model_view(IKEPolicy, 'delete')
class IKEPolicyDeleteView(generic.ObjectDeleteView):
queryset = IKEPolicy.objects.all()
class IKEPolicyBulkImportView(generic.BulkImportView):
queryset = IKEPolicy.objects.all()
model_form = forms.IKEPolicyImportForm
class IKEPolicyBulkEditView(generic.BulkEditView):
queryset = IKEPolicy.objects.all()
filterset = filtersets.IKEPolicyFilterSet
table = tables.IKEPolicyTable
form = forms.IKEPolicyBulkEditForm
class IKEPolicyBulkDeleteView(generic.BulkDeleteView):
queryset = IKEPolicy.objects.all()
filterset = filtersets.IKEPolicyFilterSet
table = tables.IKEPolicyTable
#
# IPSec proposals
#
class IPSecProposalListView(generic.ObjectListView):
queryset = IPSecProposal.objects.all()
filterset = filtersets.IPSecProposalFilterSet
filterset_form = forms.IPSecProposalFilterForm
table = tables.IPSecProposalTable
@register_model_view(IPSecProposal)
class IPSecProposalView(generic.ObjectView):
queryset = IPSecProposal.objects.all()
@register_model_view(IPSecProposal, 'edit')
class IPSecProposalEditView(generic.ObjectEditView):
queryset = IPSecProposal.objects.all()
form = forms.IPSecProposalForm
@register_model_view(IPSecProposal, 'delete')
class IPSecProposalDeleteView(generic.ObjectDeleteView):
queryset = IPSecProposal.objects.all()
class IPSecProposalBulkImportView(generic.BulkImportView):
queryset = IPSecProposal.objects.all()
model_form = forms.IPSecProposalImportForm
class IPSecProposalBulkEditView(generic.BulkEditView):
queryset = IPSecProposal.objects.all()
filterset = filtersets.IPSecProposalFilterSet
table = tables.IPSecProposalTable
form = forms.IPSecProposalBulkEditForm
class IPSecProposalBulkDeleteView(generic.BulkDeleteView):
queryset = IPSecProposal.objects.all()
filterset = filtersets.IPSecProposalFilterSet
table = tables.IPSecProposalTable
#
# IPSec policies
#
class IPSecPolicyListView(generic.ObjectListView):
queryset = IPSecPolicy.objects.all()
filterset = filtersets.IPSecPolicyFilterSet
filterset_form = forms.IPSecPolicyFilterForm
table = tables.IPSecPolicyTable
@register_model_view(IPSecPolicy)
class IPSecPolicyView(generic.ObjectView):
queryset = IPSecPolicy.objects.all()
@register_model_view(IPSecPolicy, 'edit')
class IPSecPolicyEditView(generic.ObjectEditView):
queryset = IPSecPolicy.objects.all()
form = forms.IPSecPolicyForm
@register_model_view(IPSecPolicy, 'delete')
class IPSecPolicyDeleteView(generic.ObjectDeleteView):
queryset = IPSecPolicy.objects.all()
class IPSecPolicyBulkImportView(generic.BulkImportView):
queryset = IPSecPolicy.objects.all()
model_form = forms.IPSecPolicyImportForm
class IPSecPolicyBulkEditView(generic.BulkEditView):
queryset = IPSecPolicy.objects.all()
filterset = filtersets.IPSecPolicyFilterSet
table = tables.IPSecPolicyTable
form = forms.IPSecPolicyBulkEditForm
class IPSecPolicyBulkDeleteView(generic.BulkDeleteView):
queryset = IPSecPolicy.objects.all()
filterset = filtersets.IPSecPolicyFilterSet
table = tables.IPSecPolicyTable
#
# IPSec profiles
#
class IPSecProfileListView(generic.ObjectListView):
queryset = IPSecProfile.objects.all()
filterset = filtersets.IPSecProfileFilterSet
filterset_form = forms.IPSecProfileFilterForm
table = tables.IPSecProfileTable
@register_model_view(IPSecProfile)
class IPSecProfileView(generic.ObjectView):
queryset = IPSecProfile.objects.all()
@register_model_view(IPSecProfile, 'edit')
class IPSecProfileEditView(generic.ObjectEditView):
queryset = IPSecProfile.objects.all()
form = forms.IPSecProfileForm
@register_model_view(IPSecProfile, 'delete')
class IPSecProfileDeleteView(generic.ObjectDeleteView):
queryset = IPSecProfile.objects.all()
class IPSecProfileBulkImportView(generic.BulkImportView):
queryset = IPSecProfile.objects.all()
model_form = forms.IPSecProfileImportForm
class IPSecProfileBulkEditView(generic.BulkEditView):
queryset = IPSecProfile.objects.all()
filterset = filtersets.IPSecProfileFilterSet
table = tables.IPSecProfileTable
form = forms.IPSecProfileBulkEditForm
class IPSecProfileBulkDeleteView(generic.BulkDeleteView):
queryset = IPSecProfile.objects.all()
filterset = filtersets.IPSecProfileFilterSet
table = tables.IPSecProfileTable