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.
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>>]