Skip to content

Commit

Permalink
also extensively test migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasertl committed Oct 19, 2024
1 parent 60d096f commit 0365e7d
Show file tree
Hide file tree
Showing 16 changed files with 461 additions and 77 deletions.
31 changes: 15 additions & 16 deletions ca/django_ca/migrations/0003_auto_20170304_1434.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,30 @@
from django.db import migrations


def migrate_revocation_reasons(apps, schema_editor):
Certificate = apps.get_model('django_ca', 'Certificate')
def migrate_revocation_reasons(apps, schema_editor): # pragma: no cover
Certificate = apps.get_model("django_ca", "Certificate")
certs = Certificate.objects.exclude(revoked_reason__isnull=True)

for cert in certs.exclude(revoked_reason__in=['', 'unspecified', 'superseded']):
if cert.revoked_reason == 'keyCompromise':
cert.revoked_reason = 'key_compromise'
elif cert.revoked_reason == 'caCompromise':
cert.revoked_reason = 'ca_compromise'
elif cert.revoked_reason == 'affiliationChanged':
cert.revoked_reason = 'affiliation_changed'
elif cert.revoked_reason == 'cessationOfOperation':
cert.revoked_reason = 'cessation_of_operation'
elif cert.revoked_reason == 'certificateHold':
cert.revoked_reason = 'certificate_hold'
for cert in certs.exclude(revoked_reason__in=["", "unspecified", "superseded"]):
if cert.revoked_reason == "keyCompromise":
cert.revoked_reason = "key_compromise"
elif cert.revoked_reason == "caCompromise":
cert.revoked_reason = "ca_compromise"
elif cert.revoked_reason == "affiliationChanged":
cert.revoked_reason = "affiliation_changed"
elif cert.revoked_reason == "cessationOfOperation":
cert.revoked_reason = "cessation_of_operation"
elif cert.revoked_reason == "certificateHold":
cert.revoked_reason = "certificate_hold"
else:
raise RuntimeError('Unknown revocation reason encountered: %s' % cert.revoked_reason)
raise RuntimeError("Unknown revocation reason encountered: %s" % cert.revoked_reason)

cert.save()


class Migration(migrations.Migration):

dependencies = [
('django_ca', '0002_auto_20170304_1434'),
("django_ca", "0002_auto_20170304_1434"),
]

operations = [
Expand Down
3 changes: 1 addition & 2 deletions ca/django_ca/migrations/0010_auto_20181128_2054.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.utils import timezone


def add_valid_from(apps, schema_editor):
def add_valid_from(apps, schema_editor): # pragma: no cover
Certificate = apps.get_model("django_ca", "Certificate")
for cert in Certificate.objects.all():
pem = x509.load_pem_x509_certificate(cert.pub.encode("ascii"))
Expand All @@ -32,7 +32,6 @@ def add_valid_from(apps, schema_editor):


class Migration(migrations.Migration):

dependencies = [
("django_ca", "0009_auto_20181128_2050"),
]
Expand Down
17 changes: 6 additions & 11 deletions ca/django_ca/migrations/0014_auto_20190518_1046.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,17 @@


def remove_empty(apps, schema_editor):
Certificate = apps.get_model('django_ca', 'Certificate')
Certificate.objects.filter(revoked_reason='').update(revoked_reason='unspecified')
CertificateAuthority = apps.get_model('django_ca', 'CertificateAuthority')
CertificateAuthority.objects.filter(revoked_reason='').update(revoked_reason='unspecified')


def noop():
pass
Certificate = apps.get_model("django_ca", "Certificate")
Certificate.objects.filter(revoked_reason="").update(revoked_reason="unspecified")
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")
CertificateAuthority.objects.filter(revoked_reason="").update(revoked_reason="unspecified")


class Migration(migrations.Migration):

dependencies = [
('django_ca', '0013_certificateauthority_crl_number'),
("django_ca", "0013_certificateauthority_crl_number"),
]

operations = [
migrations.RunPython(remove_empty, noop),
migrations.RunPython(remove_empty, migrations.RunPython.noop),
]
25 changes: 11 additions & 14 deletions ca/django_ca/migrations/0016_auto_20190706_1548.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
from django.db import migrations


def rm_colons(apps, schema_editor):
Certificate = apps.get_model('django_ca', 'Certificate')
CertificateAuthority = apps.get_model('django_ca', 'CertificateAuthority')
def rm_colons(apps, schema_editor): # pragma: no cover
Certificate = apps.get_model("django_ca", "Certificate")
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")

for ca in CertificateAuthority.objects.all():
ca.serial = ca.serial.replace(':', '')
ca.serial = ca.serial.replace(":", "")
ca.save()
for cert in Certificate.objects.all():
cert.serial = cert.serial.replace(':', '')
cert.serial = cert.serial.replace(":", "")
cert.save()


def add_colons(apps, schema_editor):
Certificate = apps.get_model('django_ca', 'Certificate')
CertificateAuthority = apps.get_model('django_ca', 'CertificateAuthority')
def add_colons(apps, schema_editor): # pragma: no cover
Certificate = apps.get_model("django_ca", "Certificate")
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")

add_c = lambda s: ':'.join([s[i:i + 2] for i in range(0, len(s), 2)]) # NOQA
add_c = lambda s: ":".join([s[i : i + 2] for i in range(0, len(s), 2)]) # NOQA

for ca in CertificateAuthority.objects.all():
ca.serial = add_c(ca.serial)
Expand All @@ -30,11 +30,8 @@ def add_colons(apps, schema_editor):


class Migration(migrations.Migration):

dependencies = [
('django_ca', '0015_auto_20190518_1050'),
("django_ca", "0015_auto_20190518_1050"),
]

operations = [
migrations.RunPython(rm_colons, add_colons)
]
operations = [migrations.RunPython(rm_colons, add_colons)]
10 changes: 2 additions & 8 deletions ca/django_ca/migrations/0023_auto_20210429_0000.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.db import migrations


def migrate(apps, schema_editor):
def migrate(apps, schema_editor): # pragma: no cover
Certificate = apps.get_model("django_ca", "Certificate")
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")

Expand Down Expand Up @@ -34,17 +34,11 @@ def migrate(apps, schema_editor):
ca.save()


def noop(apps, schema_editor):
"""no need to do anything in backwards data migration."""
pass


class Migration(migrations.Migration):

dependencies = [
("django_ca", "0022_auto_20210430_1124"),
]

operations = [
migrations.RunPython(migrate, noop),
migrations.RunPython(migrate, migrations.RunPython.noop),
]
2 changes: 1 addition & 1 deletion ca/django_ca/migrations/0038_auto_20231228_1932.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.db import migrations


def update_sign_certificates_schema(apps, schema_editor) -> None:
def update_sign_certificates_schema(apps, schema_editor) -> None: # pragma: no cover
"""Migrate stored data to new Pydantic-based serialization."""
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")
for ca in CertificateAuthority.objects.exclude(sign_certificate_policies=None):
Expand Down
7 changes: 3 additions & 4 deletions ca/django_ca/migrations/0040_auto_20240120_0931.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
* issuer_url and ocsp_url -> sign_authority_information_access
"""


import typing

from django.db import migrations
Expand Down Expand Up @@ -54,11 +53,11 @@ def reverse_extension_fields(apps: "StateApps", schema_editor: "BaseDatabaseSche
ca.save()


class Migration(migrations.Migration): # noqa: D101
dependencies = [ # noqa: RUF012
class Migration(migrations.Migration):
dependencies = [
("django_ca", "0039_certificateauthority_sign_authority_information_access_and_more"),
]

operations = [ # noqa: RUF012
operations = [
migrations.RunPython(populate_extension_fields, reverse_extension_fields),
]
20 changes: 18 additions & 2 deletions ca/django_ca/migrations/0043_auto_20240221_2153.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
# This file is part of django-ca (https://github.com/mathiasertl/django-ca).
#
# django-ca is free software: you can redistribute it and/or modify it under the terms of the GNU General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# django-ca is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along with django-ca. If not, see
# <http://www.gnu.org/licenses/>.
#
# Generated by Django 5.0.2 on 2024-02-21 20:53

from django.db import migrations


def migrate_path(apps, schema_editor):
"""Forward migration."""
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")
for ca in CertificateAuthority.objects.all():
ca.key_backend_alias = "default"
ca.key_backend_options = {"path": ca.private_key_path}
ca.save()


def reverse_path(apps, schema_editor):
"""Backward migration."""
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")
for ca in CertificateAuthority.objects.all():
if ca.key_backend_path == "default" and "path" in ca.key_backend_options:
if ca.key_backend_alias == "default" and "path" in ca.key_backend_options:
ca.private_key_path = ca.key_backend_options["path"]
else:
else: # pragma: no cover # django-test-migrations does not properly roll back in this case.
raise ValueError(f"{ca.name}: CA does not use StoragesBackend, cannot revert this migration.")
ca.save()

Expand Down
48 changes: 30 additions & 18 deletions ca/django_ca/migrations/0048_auto_20241017_2104.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
# This file is part of django-ca (https://github.com/mathiasertl/django-ca).
#
# django-ca is free software: you can redistribute it and/or modify it under the terms of the GNU General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# django-ca is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along with django-ca. If not, see
# <http://www.gnu.org/licenses/>.
#
# Generated by Django 5.1.2 on 2024-10-17 19:04

"""Migration for moving CertificateAuthority.crl_number to a new CertificateRevocationList instance."""

import json
from datetime import timedelta

from cryptography import x509
from cryptography.hazmat.primitives.serialization import Encoding

from django.conf import settings
from django.core.cache import cache
from django.db import migrations
from django.utils import timezone
from django.utils.timezone import make_naive

from django_ca.utils import get_crl_cache_key


def migrate_crl_number(apps, schema_editor):
"""Forward operation: CertificateAuthority.crl_number -> CertificateRevocationList."""
CertificateAuthority = apps.get_model("django_ca", "CertificateAuthority")
CertificateRevocationList = apps.get_model("django_ca", "CertificateRevocationList")

# Only migrate enabled certificate authorities
for ca in CertificateAuthority.objects.filter(enabled=True):
crl_number_data = json.loads(ca.crl_number)
for scope, crl_number in crl_number_data.get("scope", {}).items():
Expand All @@ -31,24 +46,19 @@ def migrate_crl_number(apps, schema_editor):
elif scope == "attribute":
only_contains_attribute_certs = True

cache_key = get_crl_cache_key(
ca.serial,
Encoding.DER,
only_contains_ca_certs=only_contains_ca_certs,
only_contains_user_certs=only_contains_user_certs,
only_contains_attribute_certs=only_contains_attribute_certs,
only_some_reasons=None, # not supported in old format
)
# This is how cache keys where computed before 2.1.0:
cache_key = f"crl_{ca.serial}_DER_{scope}"

# Retrieve data from cache, if possible.
try:
crl_data = cache.get(cache_key)
crl = x509.load_der_x509_crl(crl_data)
except Exception:
crl_data = None
crl = None

# If CRL was in the cache, set data, next_update and last_update.
if crl_data is not None:
crl = x509.load_der_x509_crl(crl_data)
if crl is not None:
next_update = crl.next_update_utc
last_update = crl.last_update_utc

Expand All @@ -62,21 +72,23 @@ def migrate_crl_number(apps, schema_editor):
next_update = timezone.now() - timedelta(days=1)

# Create CRL object
CertificateRevocationList.objects.create(
CertificateRevocationList.objects.filter(only_some_reasons__isnull=True).get_or_create(
ca=ca,
number=crl_number,
last_update=last_update,
next_update=next_update,
only_contains_ca_certs=only_contains_ca_certs,
only_contains_user_certs=only_contains_user_certs,
only_contains_attribute_certs=only_contains_attribute_certs,
data=crl_data,
defaults={
"only_some_reasons": None,
"last_update": last_update,
"next_update": next_update,
"data": crl_data,
},
)


class Migration(migrations.Migration):
dependencies = [
("django_ca", "0047_certificaterevocationlist"),
]

operations = [migrations.RunPython(migrate_crl_number, migrations.RunPython.noop)]
17 changes: 17 additions & 0 deletions ca/django_ca/migrations/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[tool.ruff]
extend = "../../../pyproject.toml"

[tool.ruff.lint]
extend-ignore = [
# D100: Missing docstring in public module - examples don't need docs
# auto-generated migration modules don't need a docstring.
"D100",

# D101 Missing docstring in public class
# default migration classes look like this.
"D101",

# RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
# default migration classes work like this.
"RUF012",
]
Empty file.
Loading

0 comments on commit 0365e7d

Please sign in to comment.