Repository: netbox-community/netbox

Issue:
IKE Proposal configuration requires authentication even with AES GCM
### Deployment Type

Self-hosted

### NetBox Version

v3.7-beta1

### Python Version

3.11

### Steps to Reproduce

1. Go to VPN - IKE Proposals, Add
2. Select Encryption algorithm: 256-bit AES (GCM)

### Expected Behavior

It should be possible to select "None" in Authentication algorithm.

Example of PAN-OS configuration:

``` 
set network ike crypto-profiles ike-crypto-profiles IKE-SHA384-AES256-DH20-86400 encryption aes-256-gcm
set network ike crypto-profiles ike-crypto-profiles IKE-SHA384-AES256-DH20-86400 hash non-auth
set network ike crypto-profiles ike-crypto-profiles IKE-SHA384-AES256-DH20-86400 dh-group group20
set network ike crypto-profiles ike-crypto-profiles IKE-SHA384-AES256-DH20-86400 lifetime hours 24
```

### Observed Behavior

Authentication algorithm must be selected from the dropdown, there is no "None" option, not possible to match NetBox with the actual device configuration

![image](https://github.com/netbox-community/netbox/assets/25136274/c5db1a95-9728-4335-b5cd-a956768c2bfb)



Discussion:
Is there a difference in the PAN-OS config between explicitly setting `hash non-auth` and not setting the parameter at all? Or is this effectively the same? Just trying to fully understand the context.
```
# show
set network ike crypto-profiles ike-crypto-profiles TEST encryption aes-256-gcm
set network ike crypto-profiles ike-crypto-profiles TEST dh-group group20
set network ike crypto-profiles ike-crypto-profiles TEST lifetime hours 24

# commit
Validation Error:
 network -> ike -> crypto-profiles -> ike-crypto-profiles -> TEST  is missing 'hash'
 network -> ike -> crypto-profiles -> ike-crypto-profiles is invalid

# set hash ?
  [          Start a list of values.
  md5        below 80-bit strength
  non-auth   Integrity check is unnecessary when AESGCM is chosen
  sha1       NIST rating 128-bit strength
  sha256     NIST rating 256-bit strength
  sha384     NIST rating over 256-bit strength
  sha512     NIST rating over 256-bit strength

# set hash non-auth

# commit
Configuration committed successfully

```

PAN-OS 10.2.7

Ok, so the command itself is required and `no-hash` effectively represents "none." Thanks.
Just want to say thank you for giving the beta a test run and helping us tweak it further.
We should also think about IPSec policies (phase two): It should be possible to set only encryption _or_ authentication (or both), right?

As for IKE policies (phase one), IIRC encryption is always required.
> We should also think about IPSec policies (phase two): It should be possible to set only encryption or authentication (or both), right?

I'd say yes:

```
# edit ipsec-crypto-profiles IPSEC_TEST
# set ?
+ dh-group   phase-2 DH group (PFS DH group)
> ah         AH only
> esp        ESP options
> lifesize   IPSec SA lifesize
> lifetime   IPSec SA lifetime

# set ah ?
> authentication   Authentication algorithm

# set ah authentication ?
  [        Start a list of values.
  md5      below 80-bit strength
  sha1     NIST rating 128-bit strength
  sha256   NIST rating 256-bit strength
  sha384   NIST rating over 256-bit strength
  sha512   NIST rating over 256-bit strength

# set ah authentication sha512
# set lifetime seconds 3600
# show
set network ike crypto-profiles ipsec-crypto-profiles IPSEC_TEST ah authentication sha512
set network ike crypto-profiles ipsec-crypto-profiles IPSEC_TEST lifetime seconds 3600

# commit
Configuration committed successfully
```

= no ESP

Resolving patch:
diff --git a/netbox/vpn/migrations/0001_initial.py b/netbox/vpn/migrations/0001_initial.py
--- 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
--- 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(


Test patch: