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