diff --git a/docs/models/vpn/ikeproposal.md b/docs/models/vpn/ikeproposal.md index dd8d753306..312ec1f6c8 100644 --- a/docs/models/vpn/ikeproposal.md +++ b/docs/models/vpn/ikeproposal.md @@ -28,7 +28,7 @@ The protocol employed for data encryption. Options include DES, 3DES, and variou ### Authentication Algorithm -The mechanism employed to ensure data integrity. Options include MD5 and SHA HMAC implementations. +The mechanism employed to ensure data integrity. Options include MD5 and SHA HMAC implementations. Specifying an authentication algorithm is optional, as some encryption algorithms (e.g. AES-GCM) provide authentication natively. ### Group diff --git a/docs/models/vpn/ipsecproposal.md b/docs/models/vpn/ipsecproposal.md index d061b15354..ad3279d7ab 100644 --- a/docs/models/vpn/ipsecproposal.md +++ b/docs/models/vpn/ipsecproposal.md @@ -12,10 +12,16 @@ The unique user-assigned name for the proposal. The protocol employed for data encryption. Options include DES, 3DES, and various flavors of AES. +!!! note + If an encryption algorithm is not specified, an authentication algorithm must be specified. + ### Authentication Algorithm The mechanism employed to ensure data integrity. Options include MD5 and SHA HMAC implementations. +!!! note + If an authentication algorithm is not specified, an encryption algorithm must be specified. + ### SA Lifetime (Seconds) The maximum amount of time for which the security association (SA) may be active, in seconds. diff --git a/netbox/vpn/migrations/0001_initial.py b/netbox/vpn/migrations/0001_initial.py index 20cedfe0d9..6814748377 100644 --- a/netbox/vpn/migrations/0001_initial.py +++ b/netbox/vpn/migrations/0001_initial.py @@ -29,7 +29,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=100, unique=True)), ('authentication_method', models.CharField()), ('encryption_algorithm', models.CharField()), - ('authentication_algorithm', models.CharField()), + ('authentication_algorithm', models.CharField(blank=True)), ('group', models.PositiveSmallIntegerField()), ('sa_lifetime', models.PositiveIntegerField(blank=True, null=True)), ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), @@ -82,8 +82,8 @@ class Migration(migrations.Migration): ('description', models.CharField(blank=True, max_length=200)), ('comments', models.TextField(blank=True)), ('name', models.CharField(max_length=100, unique=True)), - ('encryption_algorithm', models.CharField()), - ('authentication_algorithm', models.CharField()), + ('encryption_algorithm', models.CharField(blank=True)), + ('authentication_algorithm', models.CharField(blank=True)), ('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')), diff --git a/netbox/vpn/models/crypto.py b/netbox/vpn/models/crypto.py index 260f779409..f89c555e4f 100644 --- a/netbox/vpn/models/crypto.py +++ b/netbox/vpn/models/crypto.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -34,7 +35,8 @@ class IKEProposal(PrimaryModel): ) authentication_algorithm = models.CharField( verbose_name=_('authentication algorithm'), - choices=AuthenticationAlgorithmChoices + choices=AuthenticationAlgorithmChoices, + blank=True ) group = models.PositiveSmallIntegerField( verbose_name=_('group'), @@ -120,11 +122,13 @@ class IPSecProposal(PrimaryModel): ) encryption_algorithm = models.CharField( verbose_name=_('encryption'), - choices=EncryptionAlgorithmChoices + choices=EncryptionAlgorithmChoices, + blank=True ) authentication_algorithm = models.CharField( verbose_name=_('authentication'), - choices=AuthenticationAlgorithmChoices + choices=AuthenticationAlgorithmChoices, + blank=True ) sa_lifetime_seconds = models.PositiveIntegerField( verbose_name=_('SA lifetime (seconds)'), @@ -154,6 +158,13 @@ def __str__(self): def get_absolute_url(self): return reverse('vpn:ipsecproposal', args=[self.pk]) + def clean(self): + super().clean() + + # Encryption and/or authentication algorithm must be defined + if not self.encryption_algorithm and not self.authentication_algorithm: + raise ValidationError(_("Encryption and/or authentication algorithm must be defined")) + class IPSecPolicy(PrimaryModel): name = models.CharField(