authentik.crypto.migrations.0006_certificatekeypair_cert_expiry_and_more

  1# Generated by Django 5.2.9 on 2025-12-09 06:22
  2
  3from hashlib import md5
  4
  5from cryptography.hazmat.backends import default_backend
  6from cryptography.x509 import load_pem_x509_certificate
  7from django.db import migrations, models
  8
  9from authentik.crypto.signals import extract_certificate_metadata
 10from authentik.lib.migrations import progress_bar
 11
 12
 13def backfill_certificate_metadata(apps, schema_editor):  # noqa: ARG001
 14    """Backfill certificate metadata and kid for existing records."""
 15
 16    db_alias = schema_editor.connection.alias
 17    CertificateKeyPair = apps.get_model("authentik_crypto", "CertificateKeyPair")
 18
 19    print("\nStoring extra data about certificates, this might take a couple of minutes...")
 20    for cert in progress_bar(CertificateKeyPair.objects.using(db_alias).all()):
 21        updated_fields = []
 22
 23        if cert.certificate_data:
 24            try:
 25                certificate = load_pem_x509_certificate(
 26                    cert.certificate_data.encode("utf-8"), default_backend()
 27                )
 28                metadata = extract_certificate_metadata(certificate)
 29
 30                cert.key_type = metadata["key_type"]
 31                cert.cert_expiry = metadata["cert_expiry"]
 32                cert.cert_subject = metadata["cert_subject"]
 33                cert.fingerprint_sha256 = metadata["fingerprint_sha256"]
 34                cert.fingerprint_sha1 = metadata["fingerprint_sha1"]
 35                updated_fields.extend(
 36                    [
 37                        "key_type",
 38                        "cert_expiry",
 39                        "cert_subject",
 40                        "fingerprint_sha256",
 41                        "fingerprint_sha1",
 42                    ]
 43                )
 44            except ValueError, TypeError, AttributeError:
 45                pass
 46
 47        # Backfill kid with MD5 for backwards compatibility
 48        if cert.key_data:
 49            cert.kid = md5(cert.key_data.encode("utf-8"), usedforsecurity=False).hexdigest()
 50            updated_fields.append("kid")
 51
 52        if updated_fields:
 53            cert.save(update_fields=updated_fields, using=db_alias)
 54
 55
 56class Migration(migrations.Migration):
 57
 58    dependencies = [
 59        ("authentik_crypto", "0005_alter_certificatekeypair_options"),
 60    ]
 61
 62    operations = [
 63        migrations.AddField(
 64            model_name="certificatekeypair",
 65            name="cert_expiry",
 66            field=models.DateTimeField(blank=True, help_text="Certificate expiry date", null=True),
 67        ),
 68        migrations.AddField(
 69            model_name="certificatekeypair",
 70            name="cert_subject",
 71            field=models.TextField(
 72                blank=True, help_text="Certificate subject as RFC4514 string", null=True
 73            ),
 74        ),
 75        migrations.AddField(
 76            model_name="certificatekeypair",
 77            name="fingerprint_sha1",
 78            field=models.CharField(
 79                blank=True,
 80                help_text="SHA1 fingerprint of the certificate",
 81                max_length=59,
 82                null=True,
 83            ),
 84        ),
 85        migrations.AddField(
 86            model_name="certificatekeypair",
 87            name="fingerprint_sha256",
 88            field=models.CharField(
 89                blank=True,
 90                help_text="SHA256 fingerprint of the certificate",
 91                max_length=95,
 92                null=True,
 93            ),
 94        ),
 95        migrations.AddField(
 96            model_name="certificatekeypair",
 97            name="key_type",
 98            field=models.CharField(
 99                blank=True,
100                choices=[
101                    ("rsa", "RSA"),
102                    ("ec", "Elliptic Curve"),
103                    ("dsa", "DSA"),
104                    ("ed25519", "Ed25519"),
105                    ("ed448", "Ed448"),
106                ],
107                help_text="Key algorithm type detected from the certificate's public key",
108                max_length=16,
109                null=True,
110            ),
111        ),
112        migrations.AddField(
113            model_name="certificatekeypair",
114            name="kid",
115            field=models.CharField(
116                blank=True, help_text="Key ID generated from private key", max_length=128, null=True
117            ),
118        ),
119        migrations.RunPython(backfill_certificate_metadata, migrations.RunPython.noop),
120    ]
def backfill_certificate_metadata(apps, schema_editor):
14def backfill_certificate_metadata(apps, schema_editor):  # noqa: ARG001
15    """Backfill certificate metadata and kid for existing records."""
16
17    db_alias = schema_editor.connection.alias
18    CertificateKeyPair = apps.get_model("authentik_crypto", "CertificateKeyPair")
19
20    print("\nStoring extra data about certificates, this might take a couple of minutes...")
21    for cert in progress_bar(CertificateKeyPair.objects.using(db_alias).all()):
22        updated_fields = []
23
24        if cert.certificate_data:
25            try:
26                certificate = load_pem_x509_certificate(
27                    cert.certificate_data.encode("utf-8"), default_backend()
28                )
29                metadata = extract_certificate_metadata(certificate)
30
31                cert.key_type = metadata["key_type"]
32                cert.cert_expiry = metadata["cert_expiry"]
33                cert.cert_subject = metadata["cert_subject"]
34                cert.fingerprint_sha256 = metadata["fingerprint_sha256"]
35                cert.fingerprint_sha1 = metadata["fingerprint_sha1"]
36                updated_fields.extend(
37                    [
38                        "key_type",
39                        "cert_expiry",
40                        "cert_subject",
41                        "fingerprint_sha256",
42                        "fingerprint_sha1",
43                    ]
44                )
45            except ValueError, TypeError, AttributeError:
46                pass
47
48        # Backfill kid with MD5 for backwards compatibility
49        if cert.key_data:
50            cert.kid = md5(cert.key_data.encode("utf-8"), usedforsecurity=False).hexdigest()
51            updated_fields.append("kid")
52
53        if updated_fields:
54            cert.save(update_fields=updated_fields, using=db_alias)

Backfill certificate metadata and kid for existing records.

class Migration(django.db.migrations.migration.Migration):
 57class Migration(migrations.Migration):
 58
 59    dependencies = [
 60        ("authentik_crypto", "0005_alter_certificatekeypair_options"),
 61    ]
 62
 63    operations = [
 64        migrations.AddField(
 65            model_name="certificatekeypair",
 66            name="cert_expiry",
 67            field=models.DateTimeField(blank=True, help_text="Certificate expiry date", null=True),
 68        ),
 69        migrations.AddField(
 70            model_name="certificatekeypair",
 71            name="cert_subject",
 72            field=models.TextField(
 73                blank=True, help_text="Certificate subject as RFC4514 string", null=True
 74            ),
 75        ),
 76        migrations.AddField(
 77            model_name="certificatekeypair",
 78            name="fingerprint_sha1",
 79            field=models.CharField(
 80                blank=True,
 81                help_text="SHA1 fingerprint of the certificate",
 82                max_length=59,
 83                null=True,
 84            ),
 85        ),
 86        migrations.AddField(
 87            model_name="certificatekeypair",
 88            name="fingerprint_sha256",
 89            field=models.CharField(
 90                blank=True,
 91                help_text="SHA256 fingerprint of the certificate",
 92                max_length=95,
 93                null=True,
 94            ),
 95        ),
 96        migrations.AddField(
 97            model_name="certificatekeypair",
 98            name="key_type",
 99            field=models.CharField(
100                blank=True,
101                choices=[
102                    ("rsa", "RSA"),
103                    ("ec", "Elliptic Curve"),
104                    ("dsa", "DSA"),
105                    ("ed25519", "Ed25519"),
106                    ("ed448", "Ed448"),
107                ],
108                help_text="Key algorithm type detected from the certificate's public key",
109                max_length=16,
110                null=True,
111            ),
112        ),
113        migrations.AddField(
114            model_name="certificatekeypair",
115            name="kid",
116            field=models.CharField(
117                blank=True, help_text="Key ID generated from private key", max_length=128, null=True
118            ),
119        ),
120        migrations.RunPython(backfill_certificate_metadata, migrations.RunPython.noop),
121    ]

The base class for all migrations.

Migration files will import this from django.db.migrations.Migration and subclass it as a class called Migration. It will have one or more of the following attributes:

  • operations: A list of Operation instances, probably from django.db.migrations.operations
  • dependencies: A list of tuples of (app_path, migration_name)
  • run_before: A list of tuples of (app_path, migration_name)
  • replaces: A list of migration_names

Note that all migrations come out of migrations and into the Loader or Graph as instances, having been initialized with their app label and name.

dependencies = [('authentik_crypto', '0005_alter_certificatekeypair_options')]
operations = [<AddField model_name='certificatekeypair', name='cert_expiry', field=<django.db.models.fields.DateTimeField>>, <AddField model_name='certificatekeypair', name='cert_subject', field=<django.db.models.fields.TextField>>, <AddField model_name='certificatekeypair', name='fingerprint_sha1', field=<django.db.models.fields.CharField>>, <AddField model_name='certificatekeypair', name='fingerprint_sha256', field=<django.db.models.fields.CharField>>, <AddField model_name='certificatekeypair', name='key_type', field=<django.db.models.fields.CharField>>, <AddField model_name='certificatekeypair', name='kid', field=<django.db.models.fields.CharField>>, <RunPython <function backfill_certificate_metadata>, <function RunPython.noop>>]