Skip to content

Commit bb4d936

Browse files
committed
Prevent certificate tree corruption
1 parent 965329e commit bb4d936

File tree

2 files changed

+40
-31
lines changed

2 files changed

+40
-31
lines changed

morango/api/viewsets.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uuid
55

66
from django.core.exceptions import ValidationError
7+
from django.db import transaction
78
from django.utils import timezone
89
from ipware import get_client_ip
910
from rest_framework import mixins
@@ -145,36 +146,41 @@ def create(self, request):
145146

146147
if serialized_cert.is_valid():
147148

148-
# inflate the provided data into an actual in-memory certificate
149-
certificate = Certificate(**serialized_cert.validated_data)
150-
151-
# add a salt, ID and signature to the certificate
152-
certificate.salt = uuid.uuid4().hex
153-
certificate.id = certificate.calculate_uuid()
154-
certificate.parent.sign_certificate(certificate)
155-
156-
# ensure that the certificate model fields validate
157-
try:
158-
certificate.full_clean()
159-
except ValidationError as e:
160-
return response.Response(e, status=status.HTTP_400_BAD_REQUEST)
161-
162-
# verify the certificate (scope is a subset, profiles match, etc)
163-
try:
164-
certificate.check_certificate()
165-
except errors.MorangoCertificateError as e:
166-
return response.Response(
167-
{
168-
"error_class": e.__class__.__name__,
169-
"error_message": getattr(
170-
e, "message", (getattr(e, "args") or ("",))[0]
171-
),
172-
},
173-
status=status.HTTP_400_BAD_REQUEST,
174-
)
175-
176-
# we got this far, and everything looks good, so we can save the certificate
177-
certificate.save()
149+
# Use atomic transaction to prevent MPTT tree corruption during concurrent certificate creation
150+
with transaction.atomic():
151+
# lock the parent certificate row for update
152+
parent = Certificate.objects.select_for_update().get(pk=serialized_cert.validated_data['parent'].pk)
153+
serialized_cert.validated_data['parent'] = parent
154+
# inflate the provided data into an actual in-memory certificate
155+
certificate = Certificate(**serialized_cert.validated_data)
156+
157+
# add a salt, ID and signature to the certificate
158+
certificate.salt = uuid.uuid4().hex
159+
certificate.id = certificate.calculate_uuid()
160+
certificate.parent.sign_certificate(certificate)
161+
162+
# ensure that the certificate model fields validate
163+
try:
164+
certificate.full_clean()
165+
except ValidationError as e:
166+
return response.Response(e, status=status.HTTP_400_BAD_REQUEST)
167+
168+
# verify the certificate (scope is a subset, profiles match, etc)
169+
try:
170+
certificate.check_certificate()
171+
except errors.MorangoCertificateError as e:
172+
return response.Response(
173+
{
174+
"error_class": e.__class__.__name__,
175+
"error_message": getattr(
176+
e, "message", (getattr(e, "args") or ("",))[0]
177+
),
178+
},
179+
status=status.HTTP_400_BAD_REQUEST,
180+
)
181+
182+
# we got this far, and everything looks good, so we can save the certificate
183+
certificate.save()
178184

179185
# return a serialized copy of the signed certificate to the client
180186
return response.Response(

morango/sync/syncsession.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from requests.adapters import HTTPAdapter
1515
from requests.exceptions import HTTPError
1616
from requests.packages.urllib3.util.retry import Retry
17+
from django.db import transaction
1718

1819
from .session import SessionWrapper
1920
from morango.api.serializers import CertificateSerializer
@@ -376,7 +377,9 @@ def certificate_signing_request(
376377
)
377378
csr_cert.private_key = csr_key
378379
csr_cert.check_certificate()
379-
csr_cert.save()
380+
# Use atomic transaction to prevent MPTT tree corruption during concurrent certificate creation
381+
with transaction.atomic():
382+
csr_cert.save()
380383
return csr_cert
381384

382385
def push_signed_client_certificate_chain(

0 commit comments

Comments
 (0)