From e0b02437e126ce9c7e92898b11efe38a0f53b46b Mon Sep 17 00:00:00 2001 From: Marco Marino <1167190+marino-mrc@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:17:37 +0200 Subject: [PATCH 1/4] Added Subnet and IP in the Vpn list and Vpn detail APIs Added 'subnet' and 'ip' fields in the /controller/vpn/ and /controller/vpn/${id} get requests --- openwisp_controller/config/api/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openwisp_controller/config/api/serializers.py b/openwisp_controller/config/api/serializers.py index 50033f2b2..ac0bed4e8 100644 --- a/openwisp_controller/config/api/serializers.py +++ b/openwisp_controller/config/api/serializers.py @@ -77,6 +77,7 @@ def validate_config(self, value): class VpnSerializer(BaseSerializer): config = serializers.JSONField(initial={}) include_shared = True + ip = serializers.PrimaryKeyRelatedField(read_only=True) class Meta(BaseMeta): model = Vpn @@ -85,6 +86,8 @@ class Meta(BaseMeta): 'name', 'host', 'organization', + 'subnet', + 'ip', 'key', 'ca', 'cert', From dd731208ac09d82d16ddcb89825d8da976b77a0f Mon Sep 17 00:00:00 2001 From: Marco Marino Date: Thu, 17 Apr 2025 21:22:47 +0200 Subject: [PATCH 2/4] Added vpnclient config in device details Added the "vpnclient_config" object inside the "config" object when the "controller_device_read" api is called. It's useful to see the ip addresses assigned to a device when connected to a wireguard vpn --- openwisp_controller/config/api/serializers.py | 15 +++++++++++++++ openwisp_controller/config/api/views.py | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openwisp_controller/config/api/serializers.py b/openwisp_controller/config/api/serializers.py index ac0bed4e8..7887c4538 100644 --- a/openwisp_controller/config/api/serializers.py +++ b/openwisp_controller/config/api/serializers.py @@ -14,6 +14,7 @@ Template = load_model('config', 'Template') Vpn = load_model('config', 'Vpn') +VpnClient = load_model('config', 'VpnClient') Device = load_model('config', 'Device') DeviceGroup = load_model('config', 'DeviceGroup') Config = load_model('config', 'Config') @@ -99,6 +100,16 @@ class Meta(BaseMeta): 'modified', ] +class VpnClientSerializer(serializers.ModelSerializer): + vpn = serializers.PrimaryKeyRelatedField(read_only=True) + ip_address = serializers.SerializerMethodField() + class Meta: + model = VpnClient + fields = ['id', 'vpn', 'ip_address'] + + def get_ip_address(self, obj): + return obj.ip.ip_address if obj.ip else None + class FilterTemplatesByOrganization(serializers.PrimaryKeyRelatedField): def get_queryset(self): @@ -248,6 +259,10 @@ class DeviceDetailConfigSerializer(BaseConfigSerializer): initial={}, help_text=_('Configuration variables in JSON format') ) templates = FilterTemplatesByOrganization(many=True) + vpnclient_config = VpnClientSerializer(many=True, read_only=True, source='vpnclient_set') + + class Meta(BaseConfigSerializer.Meta): + fields = BaseConfigSerializer.Meta.fields + ['vpnclient_config'] class DeviceDetailSerializer(DeviceConfigMixin, BaseSerializer): diff --git a/openwisp_controller/config/api/views.py b/openwisp_controller/config/api/views.py index 5f6f205c0..d3d3b4031 100644 --- a/openwisp_controller/config/api/views.py +++ b/openwisp_controller/config/api/views.py @@ -104,7 +104,10 @@ class DeviceDetailView(ProtectedAPIMixin, RetrieveUpdateDestroyAPIView): """ serializer_class = DeviceDetailSerializer - queryset = Device.objects.select_related('config', 'group', 'organization') + queryset = Device.objects.select_related('config', 'group', 'organization').prefetch_related( + 'config__vpnclient_set' + ) + permission_classes = ProtectedAPIMixin.permission_classes + (DevicePermission,) def perform_destroy(self, instance): From b26fd6e785755ac0664b09109d64022a46a758cc Mon Sep 17 00:00:00 2001 From: Marco Marino <1167190+marino-mrc@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:47:22 +0200 Subject: [PATCH 3/4] Update serializers.py Fixed the problem when a vpn_client template is removed from the configuration of a specific device --- openwisp_controller/config/api/serializers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openwisp_controller/config/api/serializers.py b/openwisp_controller/config/api/serializers.py index 7887c4538..7a6109c7d 100644 --- a/openwisp_controller/config/api/serializers.py +++ b/openwisp_controller/config/api/serializers.py @@ -9,6 +9,7 @@ from openwisp_users.api.mixins import FilterSerializerByOrgManaged from openwisp_utils.api.serializers import ValidatedModelSerializer +from openwisp_ipam.models import IpAddress from .. import settings as app_settings @@ -108,8 +109,10 @@ class Meta: fields = ['id', 'vpn', 'ip_address'] def get_ip_address(self, obj): - return obj.ip.ip_address if obj.ip else None - + try: + return obj.ip.ip_address if obj.ip else None + except IpAddress.DoesNotExist: + return None class FilterTemplatesByOrganization(serializers.PrimaryKeyRelatedField): def get_queryset(self): From c367f634ebf9e6ecf718a816d36ddcb0311790e9 Mon Sep 17 00:00:00 2001 From: Marco Marino Date: Fri, 6 Jun 2025 17:44:43 +0200 Subject: [PATCH 4/4] webhook_endpoint and auth_token in APIs Added the possibility to set webhook_endpoint and auth_token when calling a POST on /controller/vpn (the type must be wireguard) --- openwisp_controller/config/api/serializers.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openwisp_controller/config/api/serializers.py b/openwisp_controller/config/api/serializers.py index 7887c4538..39f292d72 100644 --- a/openwisp_controller/config/api/serializers.py +++ b/openwisp_controller/config/api/serializers.py @@ -79,6 +79,8 @@ class VpnSerializer(BaseSerializer): config = serializers.JSONField(initial={}) include_shared = True ip = serializers.PrimaryKeyRelatedField(read_only=True) + webhook_endpoint = serializers.URLField(required=False, allow_blank=True) + auth_token = serializers.CharField(required=False, allow_blank=True) class Meta(BaseMeta): model = Vpn @@ -96,10 +98,30 @@ class Meta(BaseMeta): 'notes', 'dh', 'config', + 'webhook_endpoint', # added here + 'auth_token', # added here 'created', 'modified', ] + def validate(self, value): + if self.initial_data.get('backend') == 'openwisp_controller.vpn_backends.Wireguard': #and value == {}: + if not value.get('webhook_endpoint'): + raise serializers.ValidationError({ + 'webhook_endpoint': 'This field is required for WireGuard backend.' + }) + if not value.get('auth_token'): + raise serializers.ValidationError({ + 'auth_token': 'This field is required for WireGuard backend.' + }) + else: + # Remove if not needed for other backends + value.pop('webhook_endpoint', None) + value.pop('auth_token', None) + + return value + + class VpnClientSerializer(serializers.ModelSerializer): vpn = serializers.PrimaryKeyRelatedField(read_only=True) ip_address = serializers.SerializerMethodField()