diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3cb9bae4fd4e9c05ac9294f339de2c18e0a4a3c..ef15830bcb1f4ad19d8aac216a847576a8e69269 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,34 +17,37 @@ include: lint-job: stage: lint before_script: - - python3 -m venv "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/venv" - - source "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/venv/bin/activate" + - python3 -m venv "$CI_PROJECT_DIR/plugins/venv" + - source "$CI_PROJECT_DIR/plugins/venv/bin/activate" - pip install pylint script: - pylint "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/netbox_rps_plugin" "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/tests" + - pylint "$CI_PROJECT_DIR/plugins/netbox-cert-plugin/netbox_cert_plugin" "$CI_PROJECT_DIR/plugins/netbox-cert-plugin/tests" after_script: - deactivate - - rm -rf "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/venv" + - rm -rf "$CI_PROJECT_DIR/plugins/venv" build-job: # This job runs in the build stage, which runs first. stage: build script: - ansible-playbook ansible/build.yml -run-test: +run-test-job: stage: test before_script: - chmod 600 $ANSIBLE_PRIVATE_KEY_FILE - env ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_PRIVATE_KEY_FILE="$ANSIBLE_PRIVATE_KEY_FILE" ansible-playbook -i "$TESTING_HOSTS" -u debian ansible/deploy_on_test.yml - - python3 -m venv "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/venv" - - source "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/venv/bin/activate" + - python3 -m venv "$CI_PROJECT_DIR/plugins/venv" + - source "$CI_PROJECT_DIR/plugins/venv/bin/activate" - pip install -r "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/tests/requirements.e2e.txt" + - pip install -r "$CI_PROJECT_DIR/plugins/netbox-cert-plugin/tests/requirements.e2e.txt" script: - env HOST="$HOST" PORT="$PORT" API_KEY="$API_KEY" python3 -m unittest discover -b "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/tests/" + - env HOST="$HOST" PORT="$PORT" API_KEY="$API_KEY" python3 -m unittest discover -b "$CI_PROJECT_DIR/plugins/netbox-cert-plugin/tests/" after_script: - env ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_PRIVATE_KEY_FILE="$ANSIBLE_PRIVATE_KEY_FILE" ansible-playbook -i "$TESTING_HOSTS" -u debian ansible/halt_test.yml - deactivate - - rm -rf "$CI_PROJECT_DIR/plugins/netbox-rps-plugin/venv" + - rm -rf "$CI_PROJECT_DIR/plugins/venv" deliver-latest-job: stage: deliver diff --git a/ansible/deploy_on_test.yml b/ansible/deploy_on_test.yml index 4f6e4dca68a6700017eb4b5865145a07d55b2f08..31831e9c1176d7e08c78a7af544a5252b31e10e9 100644 --- a/ansible/deploy_on_test.yml +++ b/ansible/deploy_on_test.yml @@ -35,4 +35,4 @@ - name: Pause for 10 seconds ansible.builtin.pause: - seconds: 10 + seconds: 60 diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 20d07c3bc438b8dbadc4ba8fc0cf991b9169f6c4..44c7d5fd9a9ef21470b06dedc3fb975d93dc296f 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -15,6 +15,7 @@ services: syslog-format: "rfc5424" tag: "netbox" netbox-worker: + image: "code.europa.eu:4567/digit-c4/netbox-plugins:${TAG}" env_file: env/netbox.env logging: driver: "syslog" @@ -23,6 +24,7 @@ services: syslog-format: "rfc5424" tag: "netbox-worker" netbox-housekeeping: + image: "code.europa.eu:4567/digit-c4/netbox-plugins:${TAG}" env_file: env/netbox.env logging: driver: "syslog" diff --git a/netbox_configuration/plugins.py b/netbox_configuration/plugins.py index f9459047927d678e75912bbd33d96777080a0225..d7789b7787c7d2ef98154a0aae9ab526fdc2cebc 100644 --- a/netbox_configuration/plugins.py +++ b/netbox_configuration/plugins.py @@ -1 +1 @@ -PLUGINS = ['netbox_dns', 'netbox_rps_plugin', 'netbox_prometheus_sd'] +PLUGINS = ['netbox_dns', 'netbox_rps_plugin', 'netbox_cert_plugin', 'netbox_prometheus_sd'] diff --git a/plugins/netbox-cert-plugin/MANIFEST.in b/plugins/netbox-cert-plugin/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..f18e6834e699fcda14ee9ffa3d8b46e63a2d4157 --- /dev/null +++ b/plugins/netbox-cert-plugin/MANIFEST.in @@ -0,0 +1 @@ +global-include *.html diff --git a/plugins/netbox-cert-plugin/README.md b/plugins/netbox-cert-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..af05d914aac73250f3471e75376099d65af52e43 --- /dev/null +++ b/plugins/netbox-cert-plugin/README.md @@ -0,0 +1,6 @@ +# Certificate Plugin for Netbox + +A Netbox plugin for certificate management + +## Tests +see [tests directory](./tests/README.md) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/__init__.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8fe39a8929559f43f6fa39cb130761bf2edfa2dd --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/__init__.py @@ -0,0 +1,19 @@ +"""Netbox Plugin Configuration""" + +# pylint: disable=E0401 +from extras.plugins import PluginConfig + + +class NetBoxCertConfig(PluginConfig): + """Netbox Plugin Configuration class""" + + name = "netbox_cert_plugin" + verbose_name = "Netbox Certificate" + description = "A Netbox plugin to manage certificates" + version = "0.0.1" + author = "Vincent Simonin" + author_email = "vincent.simonin@ext.ec.europa.eu" + base_url = "cert" + +# pylint: disable=C0103 +config = NetBoxCertConfig diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/api/__init__.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/api/serializers.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..a0d416328bf0dd3ca4f4a4c4f374dfab10fc74c9 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/serializers.py @@ -0,0 +1,30 @@ +"""API Serializer definitions""" + +from rest_framework import serializers +from netbox.api.serializers import NetBoxModelSerializer +from ..models import Certificate + + +class CertificateSerializer(NetBoxModelSerializer): + """Certificate Serializer class""" + + url = serializers.HyperlinkedIdentityField( + view_name="plugins-api:netbox_cert_plugin-api:certificate-detail" + ) + + class Meta: + model = Certificate + fields = ( + "id", + "url", + "cn", + "alt_name", + "ca", + "expiration_time", + "cert_created_at", + "cert_expired_at", + "custom_fields", + "created", + "last_updated", + "tags" + ) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/api/urls.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..ac9953f0400cab773f1ccdad4d175e69886a955d --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/urls.py @@ -0,0 +1,11 @@ +"""API URLs definition""" + +from netbox.api.routers import NetBoxRouter +from . import views + +APP_NAME = 'netbox_cert_plugin' + +router = NetBoxRouter() +router.register('certificate', views.CertificateViewSet) + +urlpatterns = router.urls diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/api/views.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/views.py new file mode 100644 index 0000000000000000000000000000000000000000..9d494a10ec1c9551de8aa2c03c37051827a6aba6 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/api/views.py @@ -0,0 +1,14 @@ +"""API views definitions""" + +from netbox.api.viewsets import NetBoxModelViewSet +from .. import filtersets, models +from .serializers import CertificateSerializer + + +class CertificateViewSet(NetBoxModelViewSet): + """Mapping view set class""" + + queryset = models.Certificate.objects.all() + serializer_class = CertificateSerializer + filterset_class = filtersets.CertificateFilterSet + http_method_names = ["get", "post", "patch", "delete"] diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/filtersets.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/filtersets.py new file mode 100644 index 0000000000000000000000000000000000000000..51bf487db48f7f0b2b6496a8952b793dd3756f2e --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/filtersets.py @@ -0,0 +1,31 @@ +"""Filtersets definitions""" + +from netbox.filtersets import NetBoxModelFilterSet +from django.db.models import Q +from .models import Certificate + + +class CertificateFilterSet(NetBoxModelFilterSet): + """Certificate filterset definition class""" + + class Meta: + model = Certificate + fields = ( + "id", + "cn", + "alt_name", + "ca", + "expiration_time", + "cert_created_at", + "cert_expired_at", + ) + + # pylint: disable=W0613 + def search(self, queryset, name, value): + """override""" + if not value.strip(): + return queryset + return queryset.filter( + Q(cn__icontains=value) + | Q(alt_name__icontains=value) + ) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/forms.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..736821f48d4068fe9d91684558ea8c7447191b24 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/forms.py @@ -0,0 +1,86 @@ +"""Forms definitions""" + +from django import forms +from netbox.forms import ( + NetBoxModelForm, + NetBoxModelImportForm, + NetBoxModelFilterSetForm, +) +from utilities.forms.widgets import DatePicker +from utilities.forms.fields import TagFilterField +from .models import Certificate, ExpirationTimeChoices, CaChoices + + +class CertificateForm(NetBoxModelForm): + """Certificate form definition class""" + + class Meta: + model = Certificate + fields = ( + "cn", + "alt_name", + "ca", + "expiration_time", + "cert_created_at", + "cert_expired_at", + "tags", + ) + help_texts = {"CN": "Unique Common Name", "CA": "Valid Certificate Authority"} + labels = { + "cn": "CN", + "alt_name": "Alt name", + "ca": "CA", + "expiration_time": "Expiration time", + "cert_created_at": "Effective certificate's creation date", + "cert_expired_at": "Effective certificate's expiration date", + } + widgets = { + "cert_created_at": DatePicker(), + "cert_expired_at": DatePicker(), + } + + +class CertificateFilterForm(NetBoxModelFilterSetForm): + """Certificate filter form definition class""" + + model = Certificate + cn = forms.CharField( + label="Common Name", max_length=256, min_length=1, required=False + ) + alt_name = forms.CharField( + label="Alt name", max_length=256, min_length=1, required=False + ) + ca = forms.MultipleChoiceField( + label="Certificate Authority", choices=CaChoices, required=False + ) + expiration_time = forms.MultipleChoiceField( + label="Expiration time", choices=ExpirationTimeChoices, required=False + ) + cert_created_at = forms.DateField( + label="Effective certificate's creation date", required=False, widget=DatePicker + ) + cert_expired_at = forms.DateField( + label="Effective certificate's expirations date", + required=False, + widget=DatePicker, + ) + tag = TagFilterField(model) + + +class CertificateImportForm(NetBoxModelImportForm): + """Certificate importation form definition class""" + + class Meta: + model = Certificate + fields = ( + "cn", + "alt_name", + "ca", + "expiration_time", + "cert_created_at", + "cert_expired_at", + ) + labels = { + "ca": "Certificate Authority. Can be 'letsencrypt', 'comisign', 'globalsign'", + "expiration_time": "Expiration time needed. Can be '1m', '3m', '6m', '1y', '3y'", + } diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/migrations/0001_initial.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..9379126d089ea81fb99f689620ea27b58f991719 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/migrations/0001_initial.py @@ -0,0 +1,53 @@ +"""Migration File""" +# pylint: disable=C0103 + +from django.db import migrations, models +import taggit.managers +import utilities.json + + +class Migration(migrations.Migration): + """Migration Class""" + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Certificate", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ("created", models.DateTimeField(auto_now_add=True, null=True)), + ("last_updated", models.DateTimeField(auto_now=True, null=True)), + ( + "custom_field_data", + models.JSONField( + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, + ), + ), + ("cn", models.CharField(blank=False, unique=True, max_length=256)), + ("alt_name", models.CharField(blank=True, null=True, max_length=256)), + ("ca", models.CharField(blank=False, max_length=32)), + ("expiration_time", models.CharField(blank=False, max_length=2)), + ("cert_created_at", models.DateField(blank=True, null=True)), + ("cert_expired_at", models.DateField(blank=True, null=True)), + ( + "tags", + taggit.managers.TaggableManager( + through="extras.TaggedItem", to="extras.Tag" + ), + ), + ], + options={ + "ordering": ("source"), + }, + ), + ] diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/migrations/__init__.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/models.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/models.py new file mode 100644 index 0000000000000000000000000000000000000000..78128024fe0c638214d27e4d302f4b529b008e98 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/models.py @@ -0,0 +1,86 @@ +"""Models definitions""" + +from django.db import models +from django.urls import reverse +from netbox.models import NetBoxModel +from utilities.choices import ChoiceSet + + +class CaChoices(ChoiceSet): + """CA choices definition class""" + + key = "Certificate.ca" + + DEFAULT_VALUE = "letsencrypt" + + CHOICES = [ + ("letsencrypt", "Let's Encrypt", "yellow"), + ("comisign", "Comisign", "red"), + ("globalsign", "GlobalSign", "blue"), + ] + +class ExpirationTimeChoices(ChoiceSet): + """Expiration time choices definition class""" + + key = "Certificate.expiration_time" + + DEFAULT_VALUE = "1m" + + CHOICES = [ + ("1m", "1 month", "blue"), + ("3m", "3 months", "blue"), + ("6m", "6 months", "blue"), + ("1y", "1 year", "blue"), + ("3y", "3 year", "blue"), + ] + + +class Certificate(NetBoxModel): + """Certificate definition class""" + + cn = models.CharField( + max_length=256, + blank=False, + verbose_name="Common Name", + unique=True, + ) + alt_name = models.CharField( + max_length=256, + blank=True, + null=True, + verbose_name="Alt Name", + ) + ca = models.CharField( + max_length=32, + choices=CaChoices, + default=CaChoices.DEFAULT_VALUE, + blank=False, + verbose_name="Certificate Authority", + ) + expiration_time = models.CharField( + max_length=2, + choices=ExpirationTimeChoices, + default=CaChoices.DEFAULT_VALUE, + blank=False, + verbose_name="Expiration time needed", + ) + cert_created_at = models.DateField( + blank=True, + null=True, + verbose_name="Effective certificate's creation date" + ) + cert_expired_at = models.DateField( + blank=True, + null=True, + verbose_name="Effective certificate's expiration date" + ) + + class Meta: + ordering = ["cn"] + + def __str__(self): + return f"{self.cn}" + + def get_absolute_url(self): + """override""" + return reverse("plugins:netbox_cert_plugin:certificate", args=[self.pk]) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/navigation.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/navigation.py new file mode 100644 index 0000000000000000000000000000000000000000..a6cf54ea46fae51aa5f2be9bd48a79cacc9215dc --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/navigation.py @@ -0,0 +1,34 @@ +"""Navigation Menu definitions""" + +from extras.plugins import PluginMenuButton, PluginMenuItem, PluginMenu +from utilities.choices import ButtonColorChoices + +cert_buttons = [ + PluginMenuButton( + link="plugins:netbox_cert_plugin:certificate_add", + title="Add", + icon_class="mdi mdi-plus-thick", + color=ButtonColorChoices.GREEN, + ), + PluginMenuButton( + link="plugins:netbox_cert_plugin:certificate_import", + title="Import", + icon_class="mdi mdi-upload", + color=ButtonColorChoices.CYAN, + ), +] + +certItem = [ + PluginMenuItem( + link="plugins:netbox_cert_plugin:certificate_list", + link_text="Certificates", + buttons=cert_buttons, + permissions=["netbox_cert_plugin.view_certificate"], + ), +] + +menu = PluginMenu( + label="Certificates", + groups=(("CERTIFICATE", certItem),), + icon_class="mdi mdi-certificate", +) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/search.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/search.py new file mode 100644 index 0000000000000000000000000000000000000000..603713e87bfcf07ff0cd8e15aaf68eb19cbf7a50 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/search.py @@ -0,0 +1,16 @@ +"""Search definitions""" + +from netbox.search import SearchIndex, register_search +from .models import Certificate + + +@register_search +class CertificateIndex(SearchIndex): + """Certificate search definition class""" + + model = Certificate + fields = ( + ('cn', 256), + ('alt_name', 256), + ('ca', 32), + ) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/tables.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/tables.py new file mode 100644 index 0000000000000000000000000000000000000000..fc7d98b82cac217c42f086c7e9d71b2b717e9640 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/tables.py @@ -0,0 +1,34 @@ +"""Tables definitions""" + +import django_tables2 as tables +from netbox.tables import NetBoxTable, columns +from .models import Certificate + + +class CertificateTable(NetBoxTable): + """Certificate Table definition class""" + + cn = tables.Column(linkify=True) + tags = columns.TagColumn() + + class Meta(NetBoxTable.Meta): + model = Certificate + fields = ( + "pk", + "id", + "cn", + "alt_name", + "ca", + "expiration_time", + "cert_created_at", + "cert_expired_at", + "tags" + ) + default_columns = ( + "cn", + "alt_name", + "ca", + "expiration_time", + "cert_created_at", + "cert_expired_at", + ) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/templates/netbox_cert_plugin/certificate.html b/plugins/netbox-cert-plugin/netbox_cert_plugin/templates/netbox_cert_plugin/certificate.html new file mode 100644 index 0000000000000000000000000000000000000000..7334a0b34a60a08195e6175a3a07c1d7f617a945 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/templates/netbox_cert_plugin/certificate.html @@ -0,0 +1,50 @@ +{% extends 'generic/object.html' %} + +{% block content %} +<div class="row mb-3"> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">CERTIFICATE</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Common Name</th> + <td>{{ object.cn }}</td> + </tr> + <tr> + <th scope="row">Certificate Authority</th> + <td>{{ object.get_ca_display }}</td> + </tr> + <tr> + <th scope="row">Effective certificate's expiration date</th> + <td>{{ object.cert_expired_at|placeholder }}</td> + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + </div> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">Details</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Alt name</th> + <td>{{ object.alt_name|placeholder }}</td> + </tr> + <tr> + <th scope="row">Expiration time</th> + <td>{{ object.get_expiration_time_display|placeholder }}</td> + </tr> + <tr> + <th scope="row">Effective certificate's creation date</th> + <td>{{ object.cert_created_at|placeholder }}</td> + </tr> + </table> + </div> + </div> + </div> +</div> +{% endblock content %} diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/urls.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..c19c44851609d5e87304909a9b08e50b1d2f093a --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/urls.py @@ -0,0 +1,24 @@ +"""URL definitions""" + +from django.urls import path +from netbox_cert_plugin import views, models +from netbox.views.generic import ObjectChangeLogView, ObjectJournalView + + +urlpatterns = ( + + # Cert + path('certificates/', views.CertificateListView.as_view(), name='certificate_list'), + path('certificates/add/', views.CertificateEditView.as_view(), name='certificate_add'), + path('certificates/import/', views.CertificateBulkImportView.as_view(), name='certificate_import'), + path('certificates/delete/', views.CertificateBulkDeleteView.as_view(), name='certificate_bulk_delete'), + path('certificates/<int:pk>/', views.CertificateView.as_view(), name='certificate'), + path('certificates/<int:pk>/edit/', views.CertificateEditView.as_view(), name='certificate_edit'), + path('certificates/<int:pk>/delete/', views.CertificateDeleteView.as_view(), name='certificate_delete'), + path('certificates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='certificate_changelog', kwargs={ + 'model': models.Certificate + }), + path('certificates/<int:pk>/journal/', ObjectJournalView.as_view(), name='certificate_journal', kwargs={ + 'model': models.Certificate + }), +) diff --git a/plugins/netbox-cert-plugin/netbox_cert_plugin/views.py b/plugins/netbox-cert-plugin/netbox_cert_plugin/views.py new file mode 100644 index 0000000000000000000000000000000000000000..b33fa5999070a8fbcca045190375cc470c1d7b91 --- /dev/null +++ b/plugins/netbox-cert-plugin/netbox_cert_plugin/views.py @@ -0,0 +1,48 @@ +"""Model views definitions""" + +from netbox.views import generic +from netbox_cert_plugin import models, tables, filtersets, forms +from django.utils.translation import gettext as _ + + +class CertificateView(generic.ObjectView): + """Certificate view definition""" + + queryset = models.Certificate.objects.all() + + +class CertificateListView(generic.ObjectListView): + """Certificate list view definition""" + + queryset = models.Certificate.objects.all() + table = tables.CertificateTable + filterset = filtersets.CertificateFilterSet + filterset_form = forms.CertificateFilterForm + + +class CertificateEditView(generic.ObjectEditView): + """Certificate edition view definition""" + + queryset = models.Certificate.objects.all() + form = forms.CertificateForm + + +class CertificateBulkImportView(generic.BulkImportView): + """Certificate bulk import view definition""" + + queryset = models.Certificate.objects.all() + model_form = forms.CertificateImportForm + + +class CertificateDeleteView(generic.ObjectDeleteView): + """Certificate delete view definition""" + + queryset = models.Certificate.objects.all() + + +class CertificateBulkDeleteView(generic.BulkDeleteView): + """Certificate bulk delete view definition""" + + queryset = models.Certificate.objects.all() + filterset = filtersets.CertificateFilterSet + table = tables.CertificateTable diff --git a/plugins/netbox-cert-plugin/setup.py b/plugins/netbox-cert-plugin/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..445c332684f1d123347f454312498cc8e8f760ca --- /dev/null +++ b/plugins/netbox-cert-plugin/setup.py @@ -0,0 +1,11 @@ +from setuptools import find_packages, setup + +setup( + name='netbox_cert_plugin', + version='0.0.1', + description='A Netbox plugin to manage certificates', + install_requires=[], + packages=find_packages(), + include_package_data=True, + zip_safe=False +) diff --git a/plugins/netbox-cert-plugin/tests/README.md b/plugins/netbox-cert-plugin/tests/README.md new file mode 100644 index 0000000000000000000000000000000000000000..31370e48b1f9ed7b354212e5bfc110de507fd007 --- /dev/null +++ b/plugins/netbox-cert-plugin/tests/README.md @@ -0,0 +1,12 @@ +# Testing the plugin + +## End to end + +Prepare a python environment to execute the E2E tests suite + +```shell +python3 -m venv venv +source venv/bin/activate +pip install -r tests/requirements.e2e.txt +python -m unittest discover tests/e2e +``` diff --git a/plugins/netbox-cert-plugin/tests/__init__.py b/plugins/netbox-cert-plugin/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plugins/netbox-cert-plugin/tests/e2e/__init__.py b/plugins/netbox-cert-plugin/tests/e2e/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plugins/netbox-cert-plugin/tests/e2e/test_certificate_creation.py b/plugins/netbox-cert-plugin/tests/e2e/test_certificate_creation.py new file mode 100644 index 0000000000000000000000000000000000000000..9378354a9ea9ea902cadb31f2284914a3233af5c --- /dev/null +++ b/plugins/netbox-cert-plugin/tests/e2e/test_certificate_creation.py @@ -0,0 +1,115 @@ +"""Test case for Mapping creation""" + +import unittest +import json +import os +import requests + + +HOST = os.getenv("HOST", default="localhost") +PORT = os.getenv("PORT", default="8080") +API_KEY = os.getenv("API_KEY", "only4testingpurpose") + + +class TestCertificateCreation(unittest.TestCase): + """Test case for Mapping creation class""" + + mapping_id = None + + def test_that_certificate_is_created(self) -> None: + """Test that certificate is created""" + + response = requests.post( + url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", + json={ + "cn": "truc00.com", + "ca": "letsencrypt", + "expiration_time": "1m" + }, + headers={"Authorization": f"Token {API_KEY}"}, + timeout=5, + ) + + self.assertEqual(response.status_code, 201) + + self.mapping_id = json.loads(response.content)["id"] + + def test_that_cn_is_unique(self) -> None: + """Test that CN is unique""" + + response = requests.post( + url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", + json={ + "cn": "truc00.com", + "ca": "letsencrypt", + "expiration_time": "1m" + }, + headers={"Authorization": f"Token {API_KEY}"}, + timeout=5, + ) + + self.assertEqual(response.status_code, 201) + + self.mapping_id = json.loads(response.content)["id"] + + response = requests.post( + url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", + json={ + "cn": "truc00.com", + "ca": "comisign", + "expiration_time": "3m" + }, + headers={"Authorization": f"Token {API_KEY}"}, + timeout=5, + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(response.content, b'{"cn":["certificate with this Common Name already exists."]}') + + def test_that_ca_is_valid(self) -> None: + """Test that CA is valid""" + + response = requests.post( + url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", + json={ + "cn": "truc00.com", + "ca": "randomca", + "expiration_time": "3m" + }, + headers={"Authorization": f"Token {API_KEY}"}, + timeout=5, + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(response.content, b'{"ca":["\\"randomca\\" is not a valid choice."]}') + + def test_that_expiration_time_is_valid(self) -> None: + """Test that expiration time is valid""" + + response = requests.post( + url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", + json={ + "cn": "truc00.com", + "ca": "comisign", + "expiration_time": "10m" + }, + headers={"Authorization": f"Token {API_KEY}"}, + timeout=5, + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(response.content, b'{"expiration_time":["\\"10m\\" is not a valid choice."]}') + + def tearDown(self) -> None: + """Teardown function""" + + requests.delete( + url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", + json=[{"id": self.mapping_id}], + headers={"Authorization": f"Token {API_KEY}"}, + timeout=5, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/netbox-cert-plugin/tests/requirements.e2e.txt b/plugins/netbox-cert-plugin/tests/requirements.e2e.txt new file mode 100644 index 0000000000000000000000000000000000000000..becc27ff2e02f26b19133cd3224759d1e8578b79 --- /dev/null +++ b/plugins/netbox-cert-plugin/tests/requirements.e2e.txt @@ -0,0 +1 @@ +requests==2.30.0 diff --git a/requirements.txt b/requirements.txt index 58ca9b98b820d9b3020a3ac3fdd3f9b2717fde57..07a41df9fff15ccaa2c0cf8cb93dd6d3005ece1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ /opt/netbox/plugins/netbox-rps-plugin +/opt/netbox/plugins/netbox-cert-plugin netbox-plugin-dns netbox-plugin-prometheus-sd