mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-18 23:20:02 +02:00
- Introduces a new `vpn` app with the following models:
- Tunnel
- TunnelTermination
- IKEProposal
- IKEPolicy
- IPSecProposal
- IPSecPolicy
- IPSecProfile
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user