-
-
Notifications
You must be signed in to change notification settings - Fork 300
[feature] Added X.509 Certificate Generator Templates #1378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: gsoc26-x509-certificate-generator-templates
Are you sure you want to change the base?
Changes from all commits
25f1a21
a795e09
b946d26
ff1e8a1
0053bcf
01221d9
5c551c7
d6fd193
cae38a5
02912a0
3434719
298572f
5c522e9
052cebf
148bfd2
badf1c1
a692c77
16703f1
212b839
37ff9a9
ef90a6c
b8bc3e0
88af90a
bca3809
99cd151
7390020
ee79c93
c92cc5e
dfa650c
88003e2
b9db466
28f9cce
228e9bc
1e9e813
717c3e1
7f8dba9
e7a6b16
406e780
25ab4c3
4711fa3
a819721
8c32282
fb54051
b52fe63
0685dc5
b486eb5
52c7544
b9a9d2a
8efc405
8b3fb40
01a556c
147c4b5
4ded312
063c292
90c7415
1c6d532
0925720
7bab6fe
d66b067
2793599
74a1e8f
73b3283
bd68792
53494cd
c12aee9
375844d
1d117a2
8c6e35a
85c865d
dda2865
0a9c7d1
98c191b
bf08a9c
c803d75
6988562
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,291 @@ | ||
| X.509 Certificate Generator Templates | ||
| ===================================== | ||
|
|
||
| .. contents:: **Table of Contents**: | ||
| :depth: 3 | ||
| :local: | ||
|
|
||
| Introduction | ||
| ------------ | ||
|
|
||
| A Certificate Template is a specific type of :doc:`Configuration Template | ||
| </controller/user/templates>` that allows OpenWISP to centrally manage and | ||
| automatically provision X.509 client certificates for devices. | ||
|
|
||
| Unlike VPN templates, which generate certificates as part of a larger | ||
| tunnel configuration (like OpenVPN or WireGuard), Certificate Templates | ||
| are standalone. They are ideal for use cases where devices need | ||
| cryptographic identities for external services, such as: | ||
|
|
||
| - Mutual TLS (mTLS) authentication against internal APIs. | ||
| - Cryptographically signed device identities for 802.1x or captive | ||
| portals. | ||
| - Secure payload signing. | ||
|
|
||
| .. _certificate_templates_setup: | ||
|
|
||
| Setting Up a Certificate Template | ||
| --------------------------------- | ||
|
|
||
| To create a Certificate Template, navigate to the Templates section in the | ||
| OpenWISP admin and set the **Type** to :guilabel:`Certificate generator` | ||
| (``cert``). This will reveal the certificate-specific configuration | ||
| fields. | ||
|
|
||
| .. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/certificate-templates/certificate-template.png | ||
| :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/certificate-templates/certificate-template.png | ||
| :alt: Certificate Template admin form | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| :guilabel:`Certificate Authority` (required) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stktyagi are you testing the build of openwisp-docs with this document? If you haven't yet, please do, as it will be one of the next things I will start testing once I tested all the other aspects.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll do this right away |
||
| The Certificate Authority that will sign the X.509 certificates | ||
| generated for each device using the template. The CA must belong to | ||
| the same organization as the template (or be shared). | ||
|
|
||
| :guilabel:`Blueprint Certificate` (optional) | ||
| An existing, unassigned, non-revoked X.509 certificate that will be | ||
| used as a *blueprint*. The extensions, key length, digest, and other | ||
| subject fields of the blueprint are copied to each newly generated | ||
| certificate. The blueprint must be signed by the selected | ||
| :guilabel:`Certificate Authority` and must not already be bound to a | ||
| device. | ||
|
|
||
| :guilabel:`Automatic certificate provisioning` (``auto_cert``) | ||
| When enabled (which is the default behavior), an X.509 certificate is | ||
| automatically created and signed by the template's CA the moment the | ||
| template is assigned to a device configuration. | ||
|
|
||
| .. _certificate_templates_validation: | ||
|
|
||
| Validation Rules | ||
| ---------------- | ||
|
|
||
| To ensure cryptographic integrity and prevent misconfigurations, | ||
| Certificate Templates enforce the following validation rules upon saving: | ||
|
|
||
| - A :guilabel:`Certificate Authority` is strictly required. | ||
| - The :guilabel:`Blueprint Certificate` **must** be signed by the selected | ||
| :guilabel:`Certificate Authority`. Saving with a mismatched pair will | ||
| fail. | ||
| - The :guilabel:`Blueprint Certificate` must be *unassigned* (it cannot be | ||
| currently bound to any device). The blueprint dropdown in the admin | ||
| panel is pre-filtered to show only unassigned, non-revoked certificates. | ||
| - Both the :guilabel:`Certificate Authority` and the :guilabel:`Blueprint | ||
| Certificate` must belong to the same organization as the template, or be | ||
| marked as shared. | ||
|
|
||
| .. _certificate_templates_lifecycle: | ||
|
|
||
| Provisioning and Revocation Lifecycle | ||
| ------------------------------------- | ||
|
|
||
| Certificate Templates are tightly coupled with the device configuration | ||
| lifecycle to ensure certificates are only valid while the device is | ||
| actively authorized to use them. | ||
|
|
||
| **When assigned to a device:** When a Certificate Template is added to a | ||
| device configuration, a ``DeviceCertificate`` relationship is established. | ||
| If ``auto_cert`` is enabled, an X.509 certificate is generated and signed | ||
| in the same database transaction: | ||
|
|
||
| - The certificate's subject and extensions are copied from the | ||
| :guilabel:`Blueprint Certificate` (or the CA defaults). | ||
| - The certificate is augmented with two custom OpenWISP OIDs that | ||
| cryptographically identify the device (see | ||
| :ref:`certificate_templates_oid_extensions`). | ||
| - The certificate's :guilabel:`Common Name` is generated using the | ||
| :ref:`OPENWISP_CONTROLLER_COMMON_NAME_FORMAT | ||
| <openwisp_controller_common_name_format>` setting, suffixed with a | ||
| unique slug to prevent collisions. | ||
|
|
||
| **When removed from a device:** When the Certificate Template is | ||
| unassigned from a device configuration, the ``DeviceCertificate`` | ||
| relationship is deleted. Crucially, the underlying X.509 certificate is | ||
| **automatically revoked** (provided it was created via | ||
| ``auto_cert=True``). This ensures that compromised or decommissioned | ||
| devices immediately lose their cryptographic access. | ||
|
|
||
| **On certificate renewal:** When an administrator triggers renewal of a | ||
| standalone certificate through the PKI endpoint (e.g., | ||
| ``/api/v1/controller/cert/{id}/renew/``), the ``Cert.renew()`` method | ||
| generates a new serial number, updates the validity dates, and regenerates | ||
| the certificate and private key. The existing ``certificate_updated`` | ||
| signal handler (registered in ``config/apps.py``) detects the | ||
| ``DeviceCertificate`` relationship and calls | ||
| :meth:`~openwisp_controller.config.base.config.Config.update_status_if_checksum_changed` | ||
| on the associated device configuration, ensuring the device pulls the | ||
| renewed certificate on its next check-in. | ||
|
|
||
| **On device property changes :** When a device's **hostname** or **MAC | ||
| address** is modified, OpenWISP automatically detects the change and | ||
| triggers an asynchronous task. The task revokes the existing certificate | ||
| and generates a new one with the updated Common Name and custom OID | ||
| values. A ``generic_message`` notification is sent to the admin informing | ||
| them of the regeneration. This behavior can be disabled by setting | ||
| :ref:`OPENWISP_CONTROLLER_REGENERATE_CERTS_ON_HARDWARE_CHANGE | ||
| <OPENWISP_CONTROLLER_REGENERATE_CERTS_ON_HARDWARE_CHANGE>` to ``False``. | ||
|
|
||
| .. _certificate_templates_active_lock: | ||
|
|
||
| Active Template Mutation Lock | ||
| ----------------------------- | ||
|
|
||
| To prevent breaking the cryptographic binding with devices that are | ||
| already using a template, certain destructive changes are blocked while | ||
| the template is assigned to *active* or *activating* device | ||
| configurations. | ||
|
|
||
| You **cannot** change the following on an actively used template: | ||
|
|
||
| - :guilabel:`Type` (only changing a ``cert`` template to a different type | ||
| is blocked) | ||
| - :guilabel:`Certificate Authority` | ||
| - :guilabel:`Blueprint Certificate` | ||
|
|
||
| If you need to update these core parameters, you must first unassign the | ||
| Certificate Template from all affected device configurations (or | ||
| deactivate the devices), apply your template changes, and then reassign | ||
| them. | ||
|
|
||
| .. _certificate_templates_oid_extensions: | ||
|
|
||
| Custom Device Identification OIDs | ||
| --------------------------------- | ||
|
|
||
| To allow external systems to uniquely and securely identify devices by | ||
| parsing their X.509 certificates, every automatically generated | ||
| certificate includes two custom ASN.1 Object Identifiers (OIDs): | ||
|
|
||
| - ``1.3.6.1.4.1.65901.1``: Contains the MAC address of the device | ||
| (``ASN1:UTF8:string:<mac_address>``). | ||
| - ``1.3.6.1.4.1.65901.2``: Contains the UUID of the device | ||
| (``ASN1:UTF8:string:<device_id>``). | ||
|
|
||
| These OIDs are appended securely to the certificate in addition to any | ||
| extensions inherited from the :guilabel:`Blueprint Certificate`. | ||
|
|
||
| .. _certificate_templates_context: | ||
|
|
||
| Using Certificates in Configuration (Context Injection) | ||
| ------------------------------------------------------- | ||
|
|
||
| To deploy the certificate to the device, administrators must reference the | ||
| automatically generated variables within the template's JSON configuration | ||
| payload. | ||
|
|
||
| To prevent variable collisions when multiple Certificate Templates are | ||
| assigned to a single device, OpenWISP namespaces the Jinja2 variables | ||
| using the template's 32-character hexadecimal UUID (the UUID with dashes | ||
| removed). | ||
|
|
||
| The following variables are dynamically injected into the configuration | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The certificate context section says "the following variables" but omits |
||
| context: | ||
|
|
||
| - ``{{ cert_<template_uuid_hex>_pem }}``: The public certificate. | ||
| - ``{{ cert_<template_uuid_hex>_key }}``: The private key. | ||
| - ``{{ cert_<template_uuid_hex>_uuid }}``: The UUID of the generated | ||
| certificate. | ||
| - ``{{ cert_<template_uuid_hex>_path }}``: The file system path where the | ||
| certificate file will be installed on the device. | ||
| - ``{{ cert_<template_uuid_hex>_key_path }}``: The file system path where | ||
| the private key file will be installed on the device. | ||
|
|
||
| **Workflow Example:** | ||
|
|
||
| 1. Create a Certificate Template and click **Save and continue editing** | ||
| to generate its UUID. | ||
| 2. Note the UUID from the URL (e.g., | ||
| ``d7d20eb8-b3c8-420c-9103-401e7960cfb3``). | ||
| 3. Strip the dashes to get the hex string | ||
| (``d7d20eb8b3c8420c9103401e7960cfb3``). | ||
| 4. Add the following JSON payload to write the certificate to the device's | ||
| filesystem: | ||
|
|
||
| .. code-block:: json | ||
|
|
||
| { | ||
| "files": [ | ||
| { | ||
| "path": "/etc/ssl/certs/device-cert.pem", | ||
| "mode": "0600", | ||
| "contents": "{{ cert_d7d20eb8b3c8420c9103401e7960cfb3_pem }}" | ||
| }, | ||
| { | ||
| "path": "/etc/ssl/private/device-key.pem", | ||
| "mode": "0600", | ||
| "contents": "{{ cert_d7d20eb8b3c8420c9103401e7960cfb3_key }}" | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| When this template is assigned to a device, OpenWISP will automatically | ||
| replace the variables with the exact, unique X.509 certificate and private | ||
| key generated for that specific device. | ||
|
|
||
| .. _certificate_templates_api: | ||
|
|
||
| Certificate Templates via the REST API | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check if the rest api docs page needs to be updated. |
||
| -------------------------------------- | ||
|
|
||
| The Certificate Template architecture is fully supported by the | ||
| :doc:`OpenWISP REST API </controller/user/rest-api>`: | ||
|
|
||
| - The ``type`` field accepts the ``cert`` enumeration. | ||
| - ``ca`` and ``blueprint_cert`` are writable fields on the | ||
| ``/api/v1/controller/template/`` endpoint. | ||
| - The ``blueprint_cert`` field is strictly filtered. Attempting to pass | ||
| the UUID of an already-assigned or revoked certificate will return a | ||
| ``400 Bad Request``. | ||
| - The Active Mutation Lock is enforced at the serializer level: attempting | ||
| to patch the ``type``, ``ca``, or ``blueprint_cert`` of an actively | ||
| deployed template will return a ``400 Bad Request``. | ||
|
|
||
| Additionally, you can trigger the automated creation and revocation | ||
| lifecycle by patching the ``config.templates`` array on the ``PATCH | ||
| /api/v1/controller/device/{id}/`` endpoint. | ||
|
|
||
| .. _certificate_templates_limitations: | ||
|
|
||
| Supported Options and Limitations | ||
| --------------------------------- | ||
|
|
||
| **Blueprint fields that are cloned** | ||
|
|
||
| When a blueprint certificate is specified, the following properties are | ||
| copied to each newly generated certificate: | ||
|
|
||
| - ``key_length`` | ||
| - ``digest`` | ||
| - ``country_code``, ``state``, ``city`` | ||
| - ``organization_name``, ``organizational_unit_name`` | ||
| - ``email`` | ||
| - Extensions (custom OIDs and named extensions like ``nsCertType``) | ||
|
|
||
| If no blueprint certificate is provided, these properties fall back to the | ||
| defaults of the selected Certificate Authority. | ||
|
|
||
| **Custom OID injection** | ||
|
|
||
| Every auto-generated certificate automatically receives two custom ASN.1 | ||
| Object Identifiers that uniquely identify the device (see | ||
| :ref:`certificate_templates_oid_extensions`). These are appended in | ||
| addition to any extensions inherited from the blueprint. | ||
|
|
||
| **Out of scope** | ||
|
|
||
| The following features are **not** managed by certificate templates: | ||
|
|
||
| - **Subject Alternative Names (SAN):** SAN extensions are not | ||
| automatically injected. If a SAN is required, it must be included in the | ||
| blueprint certificate's extensions and will be copied to generated | ||
| certificates. | ||
| - **OCSP / CRL distribution points:** These must be configured at the CA | ||
| level in ``django-x509`` and are not managed per-template. | ||
| - **Let's Encrypt / ACME integration:** Automated ACME or public CA | ||
| enrollment is not supported. Certificate Templates are designed for | ||
| private CAs managed within OpenWISP's PKI module. | ||
| - **Certificate expiration management:** Auto-renewal upon expiry is not | ||
| built in; renewal must be triggered manually through the admin or API | ||
| endpoint. | ||
| - **Non-``auto_cert`` certificates:** If ``auto_cert`` is disabled, no | ||
| certificate is automatically generated on template assignment. The | ||
| administrator is responsible for manual provisioning. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this page to the
/docs/index.rst, please also add a mention of this feature in/docs/user/intro.rst.