mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 04:32:51 -06:00
- Introduces a new `vpn` app with the following models: - Tunnel - TunnelTermination - IKEProposal - IKEPolicy - IPSecProposal - IPSecPolicy - IPSecProfile
This commit is contained in:
parent
975a647d9a
commit
6678880db5
49
docs/features/vpn-tunnels.md
Normal file
49
docs/features/vpn-tunnels.md
Normal 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/"
|
||||||
|
```
|
25
docs/models/vpn/ikepolicy.md
Normal file
25
docs/models/vpn/ikepolicy.md
Normal 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).
|
39
docs/models/vpn/ikeproposal.md
Normal file
39
docs/models/vpn/ikeproposal.md
Normal 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.
|
17
docs/models/vpn/ipsecpolicy.md
Normal file
17
docs/models/vpn/ipsecpolicy.md
Normal 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).
|
21
docs/models/vpn/ipsecprofile.md
Normal file
21
docs/models/vpn/ipsecprofile.md
Normal 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.
|
25
docs/models/vpn/ipsecproposal.md
Normal file
25
docs/models/vpn/ipsecproposal.md
Normal 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
36
docs/models/vpn/tunnel.md
Normal 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.
|
30
docs/models/vpn/tunneltermination.md
Normal file
30
docs/models/vpn/tunneltermination.md
Normal 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.
|
@ -74,6 +74,7 @@ nav:
|
|||||||
- Circuits: 'features/circuits.md'
|
- Circuits: 'features/circuits.md'
|
||||||
- Wireless: 'features/wireless.md'
|
- Wireless: 'features/wireless.md'
|
||||||
- Virtualization: 'features/virtualization.md'
|
- Virtualization: 'features/virtualization.md'
|
||||||
|
- VPN Tunnels: 'features/vpn-tunnels.md'
|
||||||
- Tenancy: 'features/tenancy.md'
|
- Tenancy: 'features/tenancy.md'
|
||||||
- Contacts: 'features/contacts.md'
|
- Contacts: 'features/contacts.md'
|
||||||
- Search: 'features/search.md'
|
- Search: 'features/search.md'
|
||||||
@ -252,6 +253,14 @@ nav:
|
|||||||
- ClusterType: 'models/virtualization/clustertype.md'
|
- ClusterType: 'models/virtualization/clustertype.md'
|
||||||
- VMInterface: 'models/virtualization/vminterface.md'
|
- VMInterface: 'models/virtualization/vminterface.md'
|
||||||
- VirtualMachine: 'models/virtualization/virtualmachine.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:
|
- Wireless:
|
||||||
- WirelessLAN: 'models/wireless/wirelesslan.md'
|
- WirelessLAN: 'models/wireless/wirelesslan.md'
|
||||||
- WirelessLANGroup: 'models/wireless/wirelesslangroup.md'
|
- WirelessLANGroup: 'models/wireless/wirelesslangroup.md'
|
||||||
|
@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.management.base import BaseCommand
|
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})
|
BANNER_TEXT = """### NetBox interactive shell ({node})
|
||||||
### Python {python} | Django {django} | NetBox {netbox}
|
### Python {python} | Django {django} | NetBox {netbox}
|
||||||
|
@ -566,6 +566,10 @@ class BaseInterface(models.Model):
|
|||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tunnel_termination(self):
|
||||||
|
return self.tunnel_terminations.first()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def count_ipaddresses(self):
|
def count_ipaddresses(self):
|
||||||
return self.ip_addresses.count()
|
return self.ip_addresses.count()
|
||||||
@ -719,6 +723,12 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
object_id_field='interface_id',
|
object_id_field='interface_id',
|
||||||
related_query_name='+'
|
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(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='ipam.L2VPNTermination',
|
||||||
content_type_field='assigned_object_type',
|
content_type_field='assigned_object_type',
|
||||||
|
@ -584,6 +584,12 @@ class BaseInterfaceTable(NetBoxTable):
|
|||||||
orderable=False,
|
orderable=False,
|
||||||
verbose_name=_('L2VPN')
|
verbose_name=_('L2VPN')
|
||||||
)
|
)
|
||||||
|
tunnel = tables.Column(
|
||||||
|
accessor=tables.A('tunnel_termination__tunnel'),
|
||||||
|
linkify=True,
|
||||||
|
orderable=False,
|
||||||
|
verbose_name=_('Tunnel')
|
||||||
|
)
|
||||||
untagged_vlan = tables.Column(
|
untagged_vlan = tables.Column(
|
||||||
verbose_name=_('Untagged VLAN'),
|
verbose_name=_('Untagged VLAN'),
|
||||||
linkify=True
|
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',
|
'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',
|
'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',
|
'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')
|
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',
|
'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',
|
'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',
|
'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',
|
'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn', 'tunnel', 'ip_addresses',
|
||||||
'untagged_vlan', 'tagged_vlans', 'actions',
|
'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',
|
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',
|
||||||
|
@ -359,6 +359,16 @@ INTERFACE_BUTTONS = """
|
|||||||
<i class="mdi mdi-wifi-off" aria-hidden="true"></i>
|
<i class="mdi mdi-wifi-off" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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 %}
|
{% 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-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>
|
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
||||||
|
@ -39,6 +39,7 @@ class APIRootView(APIView):
|
|||||||
'tenancy': reverse('tenancy-api:api-root', request=request, format=format),
|
'tenancy': reverse('tenancy-api:api-root', request=request, format=format),
|
||||||
'users': reverse('users-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),
|
'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),
|
'wireless': reverse('wireless-api:api-root', request=request, format=format),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from netbox.registry import registry
|
|||||||
from tenancy.graphql.schema import TenancyQuery
|
from tenancy.graphql.schema import TenancyQuery
|
||||||
from users.graphql.schema import UsersQuery
|
from users.graphql.schema import UsersQuery
|
||||||
from virtualization.graphql.schema import VirtualizationQuery
|
from virtualization.graphql.schema import VirtualizationQuery
|
||||||
|
from vpn.graphql.schema import VPNQuery
|
||||||
from wireless.graphql.schema import WirelessQuery
|
from wireless.graphql.schema import WirelessQuery
|
||||||
|
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ class Query(
|
|||||||
IPAMQuery,
|
IPAMQuery,
|
||||||
TenancyQuery,
|
TenancyQuery,
|
||||||
VirtualizationQuery,
|
VirtualizationQuery,
|
||||||
|
VPNQuery,
|
||||||
WirelessQuery,
|
WirelessQuery,
|
||||||
*registry['plugins']['graphql_schemas'], # Append plugin schemas
|
*registry['plugins']['graphql_schemas'], # Append plugin schemas
|
||||||
graphene.ObjectType
|
graphene.ObjectType
|
||||||
|
@ -195,17 +195,34 @@ IPAM_MENU = Menu(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
OVERLAY_MENU = Menu(
|
VPN_MENU = Menu(
|
||||||
label=_('Overlay'),
|
label=_('VPN'),
|
||||||
icon_class='mdi mdi-graph-outline',
|
icon_class='mdi mdi-graph-outline',
|
||||||
groups=(
|
groups=(
|
||||||
MenuGroup(
|
MenuGroup(
|
||||||
label='L2VPNs',
|
label=_('Tunnels'),
|
||||||
|
items=(
|
||||||
|
get_model_item('vpn', 'tunnel', _('Tunnels')),
|
||||||
|
get_model_item('vpn', 'tunneltermination', _('Tunnel Terminations')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MenuGroup(
|
||||||
|
label=_('L2VPNs'),
|
||||||
items=(
|
items=(
|
||||||
get_model_item('ipam', 'l2vpn', _('L2VPNs')),
|
get_model_item('ipam', 'l2vpn', _('L2VPNs')),
|
||||||
get_model_item('ipam', 'l2vpntermination', _('Terminations')),
|
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,
|
CONNECTIONS_MENU,
|
||||||
WIRELESS_MENU,
|
WIRELESS_MENU,
|
||||||
IPAM_MENU,
|
IPAM_MENU,
|
||||||
OVERLAY_MENU,
|
VPN_MENU,
|
||||||
VIRTUALIZATION_MENU,
|
VIRTUALIZATION_MENU,
|
||||||
CIRCUITS_MENU,
|
CIRCUITS_MENU,
|
||||||
POWER_MENU,
|
POWER_MENU,
|
||||||
|
@ -379,6 +379,7 @@ INSTALLED_APPS = [
|
|||||||
'users',
|
'users',
|
||||||
'utilities',
|
'utilities',
|
||||||
'virtualization',
|
'virtualization',
|
||||||
|
'vpn',
|
||||||
'wireless',
|
'wireless',
|
||||||
'django_rq', # Must come after extras to allow overriding management commands
|
'django_rq', # Must come after extras to allow overriding management commands
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
|
@ -33,6 +33,7 @@ _patterns = [
|
|||||||
path('tenancy/', include('tenancy.urls')),
|
path('tenancy/', include('tenancy.urls')),
|
||||||
path('users/', include('users.urls')),
|
path('users/', include('users.urls')),
|
||||||
path('virtualization/', include('virtualization.urls')),
|
path('virtualization/', include('virtualization.urls')),
|
||||||
|
path('vpn/', include('vpn.urls')),
|
||||||
path('wireless/', include('wireless.urls')),
|
path('wireless/', include('wireless.urls')),
|
||||||
|
|
||||||
# Current user views
|
# Current user views
|
||||||
@ -51,6 +52,7 @@ _patterns = [
|
|||||||
path('api/tenancy/', include('tenancy.api.urls')),
|
path('api/tenancy/', include('tenancy.api.urls')),
|
||||||
path('api/users/', include('users.api.urls')),
|
path('api/users/', include('users.api.urls')),
|
||||||
path('api/virtualization/', include('virtualization.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/wireless/', include('wireless.api.urls')),
|
||||||
path('api/status/', StatusView.as_view(), name='api-status'),
|
path('api/status/', StatusView.as_view(), name='api-status'),
|
||||||
|
|
||||||
|
67
netbox/templates/vpn/ikepolicy.html
Normal file
67
netbox/templates/vpn/ikepolicy.html
Normal 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 %}
|
63
netbox/templates/vpn/ikeproposal.html
Normal file
63
netbox/templates/vpn/ikeproposal.html
Normal 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 %}
|
55
netbox/templates/vpn/ipsecpolicy.html
Normal file
55
netbox/templates/vpn/ipsecpolicy.html
Normal 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 %}
|
112
netbox/templates/vpn/ipsecprofile.html
Normal file
112
netbox/templates/vpn/ipsecprofile.html
Normal 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 %}
|
59
netbox/templates/vpn/ipsecproposal.html
Normal file
59
netbox/templates/vpn/ipsecproposal.html
Normal 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 %}
|
85
netbox/templates/vpn/tunnel.html
Normal file
85
netbox/templates/vpn/tunnel.html
Normal 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 %}
|
62
netbox/templates/vpn/tunneltermination.html
Normal file
62
netbox/templates/vpn/tunneltermination.html
Normal 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 %}
|
@ -351,6 +351,12 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
|
|||||||
object_id_field='interface_id',
|
object_id_field='interface_id',
|
||||||
related_query_name='+'
|
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(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='ipam.L2VPNTermination',
|
||||||
content_type_field='assigned_object_type',
|
content_type_field='assigned_object_type',
|
||||||
|
@ -131,7 +131,8 @@ class VMInterfaceTable(BaseInterfaceTable):
|
|||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
'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')
|
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
|
|||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
'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')
|
default_columns = ('pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'ip_addresses')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
|
0
netbox/vpn/__init__.py
Normal file
0
netbox/vpn/__init__.py
Normal file
3
netbox/vpn/admin.py
Normal file
3
netbox/vpn/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
0
netbox/vpn/api/__init__.py
Normal file
0
netbox/vpn/api/__init__.py
Normal file
84
netbox/vpn/api/nested_serializers.py
Normal file
84
netbox/vpn/api/nested_serializers.py
Normal 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')
|
193
netbox/vpn/api/serializers.py
Normal file
193
netbox/vpn/api/serializers.py
Normal 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
15
netbox/vpn/api/urls.py
Normal 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
74
netbox/vpn/api/views.py
Normal 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
9
netbox/vpn/apps.py
Normal 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
201
netbox/vpn/choices.py
Normal 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
241
netbox/vpn/filtersets.py
Normal 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)
|
||||||
|
)
|
4
netbox/vpn/forms/__init__.py
Normal file
4
netbox/vpn/forms/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from .bulk_edit import *
|
||||||
|
from .bulk_import import *
|
||||||
|
from .filtersets import *
|
||||||
|
from .model_forms import *
|
243
netbox/vpn/forms/bulk_edit.py
Normal file
243
netbox/vpn/forms/bulk_edit.py
Normal 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',
|
||||||
|
)
|
230
netbox/vpn/forms/bulk_import.py
Normal file
230
netbox/vpn/forms/bulk_import.py
Normal 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',
|
||||||
|
)
|
182
netbox/vpn/forms/filtersets.py
Normal file
182
netbox/vpn/forms/filtersets.py
Normal 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)
|
357
netbox/vpn/forms/model_forms.py
Normal file
357
netbox/vpn/forms/model_forms.py
Normal 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',
|
||||||
|
]
|
0
netbox/vpn/graphql/__init__.py
Normal file
0
netbox/vpn/graphql/__init__.py
Normal file
51
netbox/vpn/graphql/schema.py
Normal file
51
netbox/vpn/graphql/schema.py
Normal 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)
|
69
netbox/vpn/graphql/types.py
Normal file
69
netbox/vpn/graphql/types.py
Normal 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
|
186
netbox/vpn/migrations/0001_initial.py
Normal file
186
netbox/vpn/migrations/0001_initial.py
Normal 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.'),
|
||||||
|
),
|
||||||
|
]
|
0
netbox/vpn/migrations/__init__.py
Normal file
0
netbox/vpn/migrations/__init__.py
Normal file
2
netbox/vpn/models/__init__.py
Normal file
2
netbox/vpn/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .crypto import *
|
||||||
|
from .tunnels import *
|
254
netbox/vpn/models/crypto.py
Normal file
254
netbox/vpn/models/crypto.py
Normal 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])
|
146
netbox/vpn/models/tunnels.py
Normal file
146
netbox/vpn/models/tunnels.py
Normal 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
65
netbox/vpn/search.py
Normal 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
254
netbox/vpn/tables.py
Normal 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')
|
0
netbox/vpn/tests/__init__.py
Normal file
0
netbox/vpn/tests/__init__.py
Normal file
473
netbox/vpn/tests/test_api.py
Normal file
473
netbox/vpn/tests/test_api.py
Normal 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',
|
||||||
|
}
|
592
netbox/vpn/tests/test_filtersets.py
Normal file
592
netbox/vpn/tests/test_filtersets.py
Normal 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)
|
508
netbox/vpn/tests/test_views.py
Normal file
508
netbox/vpn/tests/test_views.py
Normal 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
65
netbox/vpn/urls.py
Normal 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
334
netbox/vpn/views.py
Normal 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
|
Loading…
Reference in New Issue
Block a user