Skip to content

Commit

Permalink
[feature] Added REST API for PKI app (certs and CAs) openwisp#462
Browse files Browse the repository at this point in the history
Implements and closes openwisp#462
  • Loading branch information
ManishShah120 committed Jul 25, 2021
1 parent c0f0512 commit 3f8e4c6
Show file tree
Hide file tree
Showing 7 changed files with 797 additions and 3 deletions.
143 changes: 141 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,8 @@ Download template configuration
GET /api/v1/controller/template/{id}/configuration/
The above endpoint triggers the download of a ``tar.gz`` file containing the generated configuration for that specific template.
The above endpoint triggers the download of a ``tar.gz`` file
containing the generated configuration for that specific template.

Change details of template
^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -1003,7 +1004,8 @@ Download VPN configuration
GET /api/v1/controller/vpn/{id}/configuration/
The above endpoint triggers the download of a ``tar.gz`` file containing the generated configuration for that specific VPN.
The above endpoint triggers the download of a ``tar.gz`` file
containing the generated configuration for that specific VPN.

Change details of VPN
^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -1026,6 +1028,143 @@ Delete VPN
DELETE /api/v1/controller/vpn/{id}/
List CA
^^^^^^^

.. code-block:: text
GET /api/v1/controller/ca/
Create new CA
^^^^^^^^^^^^^

.. code-block:: text
POST /api/v1/controller/ca/
Import existing CA
^^^^^^^^^^^^^^^^^^

.. code-block:: text
POST /api/v1/controller/ca/
**Note**: To import an existing CA, only ``name``, ``certificate``
and ``private_key`` fields have to be filled in the ``HTML`` form or
included in the ``JSON`` format.

Get CA Detail
^^^^^^^^^^^^^

.. code-block:: text
GET /api/v1/controller/ca/{id}/
Change details of CA
^^^^^^^^^^^^^^^^^^^^

.. code-block:: text
PUT /api/v1/controller/ca/{id}/
Patch details of CA
^^^^^^^^^^^^^^^^^^^

.. code-block:: text
PATCH /api/v1/controller/ca/{id}/
Download CA(crl)
^^^^^^^^^^^^^^^^

.. code-block:: text
GET /api/v1/controller/ca/{id}/crl/
The above endpoint triggers the download of ``{id}.crl`` file containing
up to date CRL of that specific CA.

Delete CA
^^^^^^^^^

.. code-block:: text
DELETE /api/v1/controller/ca/{id}/
Renew CA
^^^^^^^^

.. code-block:: text
POST /api/v1/controller/ca/{id}/renew/
List Cert
^^^^^^^^^

.. code-block:: text
GET /api/v1/controller/cert/
Create new Cert
^^^^^^^^^^^^^^^

.. code-block:: text
POST /api/v1/controller/cert/
Import existing Cert
^^^^^^^^^^^^^^^^^^^^

.. code-block:: text
POST /api/v1/controller/cert/
**Note**: To import an existing Cert, only ``name``, ``ca``,
``certificate`` and ``private_key`` fields have to be filled
in the ``HTML`` form or included in the ``JSON`` format.

Get Cert Detail
^^^^^^^^^^^^^^^

.. code-block:: text
GET /api/v1/controller/cert/{id}/
Change details of Cert
^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: text
PUT /api/v1/controller/cert/{id}/
Patch details of Cert
^^^^^^^^^^^^^^^^^^^^^

.. code-block:: text
PATCH /api/v1/controller/cert/{id}/
Delete Cert
^^^^^^^^^^^

.. code-block:: text
DELETE /api/v1/controller/cert/{id}/
Renew Cert
^^^^^^^^^^

.. code-block:: text
POST /api/v1/controller/cert/{id}/renew/
Revoke Cert
^^^^^^^^^^^

.. code-block:: text
POST /api/v1/controller/cert/{id}/revoke/
Default Alerts / Notifications
------------------------------

Expand Down
191 changes: 191 additions & 0 deletions openwisp_controller/pki/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
from django.utils.translation import ugettext_lazy as _
from django_x509.base.models import (
default_ca_validity_end,
default_cert_validity_end,
default_validity_start,
)
from rest_framework import serializers
from swapper import load_model

from openwisp_users.api.mixins import FilterSerializerByOrgManaged
from openwisp_utils.api.serializers import ValidatedModelSerializer

Ca = load_model('django_x509', 'Ca')
Cert = load_model('django_x509', 'Cert')


class BaseSerializer(FilterSerializerByOrgManaged, ValidatedModelSerializer):
pass


class BaseListSerializer(BaseSerializer):
extensions = serializers.JSONField(
initial=[],
help_text=_('additional x509 certificate extensions'),
required=False,
)

def get_import_data_hook(self, instance):
return get_import_data(instance)

def validate(self, data):
instance = self.instance or self.Meta.model(**data)
instance.full_clean()
if data.get('certificate') and data.get('private_key'):
data = self.get_import_data_hook(instance)
return data

def validate_validity_start(self, value):
if value is None:
value = default_validity_start()
return value

def default_validity_end_hook(self):
return default_ca_validity_end()

def validate_validity_end(self, value):
if value is None:
value = self.default_validity_end_hook()
return value


class CaListSerializer(BaseListSerializer):
class Meta:
model = Ca
fields = [
'id',
'name',
'organization',
'notes',
'key_length',
'digest',
'validity_start',
'validity_end',
'country_code',
'state',
'city',
'organization_name',
'organizational_unit_name',
'email',
'common_name',
'extensions',
'serial_number',
'certificate',
'private_key',
'passphrase',
'created',
'modified',
]
read_only_fields = ['created', 'modified']
extra_kwargs = {
'organization': {'required': True},
'key_length': {'initial': '2048'},
'digest': {'initial': 'sha256'},
'passphrase': {'write_only': True},
'validity_start': {'default': default_validity_start()},
'validity_end': {'default': default_ca_validity_end()},
}


def get_ca_detail_fields(fields):
"""
Returns the fields for the `CADetailSerializer`.
"""
fields.remove('passphrase')
return fields


class CaDetailSerializer(BaseSerializer):
extensions = serializers.JSONField(read_only=True)

class Meta:
model = Ca
fields = get_ca_detail_fields(CaListSerializer.Meta.fields[:])
read_only_fields = fields[4:]


class CaRenewSerializer(CaDetailSerializer):
class Meta(CaDetailSerializer.Meta):
read_only_fields = CaDetailSerializer.Meta.fields


def get_import_data(instance):
data = {
'name': instance.name,
'organization': instance.organization,
'key_length': instance.key_length,
'digest': instance.digest,
'validity_start': instance.validity_start,
'validity_end': instance.validity_end,
'country_code': instance.country_code,
'state': instance.state,
'city': instance.city,
'organization_name': instance.organization_name,
'organizational_unit_name': instance.organizational_unit_name,
'email': instance.email,
'common_name': instance.common_name,
'extensions': instance.extensions,
'serial_number': instance.serial_number,
'certificate': instance.certificate,
'private_key': instance.private_key,
'passphrase': instance.passphrase,
}
return data


def get_cert_list_fields(fields):
"""
Returns the fields for the `CertListSerializer`.
"""
fields.insert(3, 'ca')
fields.insert(5, 'revoked')
fields.insert(6, 'revoked_at')
return fields


class CertListSerializer(BaseListSerializer):
include_shared = True

class Meta:
model = Cert
fields = get_cert_list_fields(CaListSerializer.Meta.fields[:])
read_only_fields = ['created', 'modified']
extra_kwargs = {
'revoked': {'read_only': True},
'revoked_at': {'read_only': True},
'key_length': {'initial': '2048'},
'digest': {'initial': 'sha256'},
'passphrase': {'write_only': True},
'validity_start': {'default': default_validity_start()},
'validity_end': {'default': default_cert_validity_end()},
}

def get_import_data_hook(self, instance):
data = super().get_import_data_hook(instance)
data.update({'ca': instance.ca})
return data

def default_validity_end_hook(self):
return default_cert_validity_end()


def get_cert_detail_fields(fields):
"""
Returns the fields for the `CertDetailSerializer`.
"""
fields.remove('passphrase')
return fields


class CertDetailSerializer(BaseSerializer):
extensions = serializers.JSONField(read_only=True)

class Meta:
model = Cert
fields = get_cert_detail_fields(CertListSerializer.Meta.fields[:])
read_only_fields = ['ca'] + fields[5:]


class CertRevokeRenewSerializer(CertDetailSerializer):
class Meta(CertDetailSerializer.Meta):
read_only_fields = CertDetailSerializer.Meta.fields
42 changes: 42 additions & 0 deletions openwisp_controller/pki/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.conf import settings
from django.urls import path

from . import views as api_views

app_name = 'openwisp_controller'


def get_pki_api_urls(api_views):
"""
returns:: all the API urls of the PKI app
"""
if getattr(settings, 'OPENWISP_CONTROLLER_PKI_API', True):
return [
path('controller/ca/', api_views.ca_list, name='ca_list'),
path('controller/ca/<str:pk>/', api_views.ca_detail, name='ca_detail'),
path('controller/ca/<str:pk>/renew/', api_views.ca_renew, name='ca_renew'),
path(
'controller/ca/<str:pk>/crl',
api_views.crl_download,
name='crl_download',
),
path('controller/cert/', api_views.cert_list, name='cert_list'),
path(
'controller/cert/<str:pk>/', api_views.cert_detail, name='cert_detail'
),
path(
'controller/cert/<str:pk>/revoke/',
api_views.cert_revoke,
name='cert_revoke',
),
path(
'controller/cert/<str:pk>/renew/',
api_views.cert_renew,
name='cert_renew',
),
]
else:
return []


urlpatterns = get_pki_api_urls(api_views)
Loading

0 comments on commit 3f8e4c6

Please sign in to comment.