Repository: netbox-community/netbox

Issue:
Error when creating VPN tunnels with same Outside IP
### Deployment Type

Self-hosted

### NetBox Version

v3.7.0

### Python Version

3.9

### Steps to Reproduce

Setup for test device:

1. Create site MySite
2. Create manufacturer Test
3. Create device type TestModel
4. Create device role VPN
5. Create device **TestDevice** (using the attribute values above)
6. For the device, create interfaces **tunnel.[1-2]** (type = virtual)
7. For the device, create interface **Internet** (type = f.ex. 1000base-T)
8. For the Internet interface, add IP address **123.123.123.123/32**

Now the actual VPN tunnels:

9. Create VPN tunnel **Tunnel1**:
    - encap = IPsec - Tunnel
    - first termination:
        - Role = Hub
        - Type = Device
        - Device = TestDevice
        - Interface = tunnel.1
        - Outside IP = 123.123.123.123/32
10. Create another VPN tunnel **Tunnel2**:
    - encap = IPsec - Tunnel
    - first termination:
        - Role = Hub
        - Type = Device
        - Device = TestDevice
        - Interface = tunnel.2
        - Outside IP = 123.123.123.123/32


### Expected Behavior

Both tunnels and terminations are created successfully, as they will both terminate on the same Outside IP. That is the usual way of using several tunnels on the same device.

### Observed Behavior

When pressing the Create button for the second tunnel:

"
Server Error

There was a problem with your request. Please contact an administrator.
The complete exception is provided below:
<class 'django.db.utils.IntegrityError'>

duplicate key value violates unique constraint "vpn_tunneltermination_outside_ip_id_key"
DETAIL:  Key (outside_ip_id)=(14) already exists.

Python version: 3.9.2
NetBox version: 3.7.0
Plugins: None installed
"



Discussion:
By the way, the error is output differently if I just create **Tunnel2** first _without_ terminations, and then go to Tunnel Terminations to create one:

![image](https://github.com/netbox-community/netbox/assets/25136274/c86af8d9-bffc-4901-8ef7-0a09da447f9b)

= there is no Server Error but an error message "Tunnel termination with this Outside ip already exists" in the form.

Just putting this out here if someone tests the issue this way.

Resolving patch:
diff --git a/netbox/vpn/migrations/0003_ipaddress_multiple_tunnel_terminations.py b/netbox/vpn/migrations/0003_ipaddress_multiple_tunnel_terminations.py
new file mode 100644
--- /dev/null
+++ b/netbox/vpn/migrations/0003_ipaddress_multiple_tunnel_terminations.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.8 on 2024-01-05 19:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ipam', '0069_gfk_indexes'),
+        ('vpn', '0002_move_l2vpn'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='tunneltermination',
+            name='outside_ip',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnel_terminations', to='ipam.ipaddress'),
+        ),
+    ]
diff --git a/netbox/vpn/models/tunnels.py b/netbox/vpn/models/tunnels.py
--- a/netbox/vpn/models/tunnels.py
+++ b/netbox/vpn/models/tunnels.py
@@ -129,10 +129,10 @@ class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLo
         ct_field='termination_type',
         fk_field='termination_id'
     )
-    outside_ip = models.OneToOneField(
+    outside_ip = models.ForeignKey(
         to='ipam.IPAddress',
         on_delete=models.PROTECT,
-        related_name='tunnel_termination',
+        related_name='tunnel_terminations',
         blank=True,
         null=True
     )


Test patch: