crypto: separate permissions for certificate and private keydownload (#18588)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2025-12-04 16:31:52 +01:00
committed by GitHub
parent 3c2f39559f
commit 334c0175f9
5 changed files with 69 additions and 17 deletions
+6 -3
View File
@@ -27,6 +27,7 @@ from rest_framework.fields import (
SerializerMethodField,
)
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.validators import UniqueValidator
@@ -42,7 +43,7 @@ from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
from authentik.crypto.models import CertificateKeyPair, KeyType
from authentik.events.models import Event, EventAction
from authentik.rbac.decorators import permission_required
from authentik.rbac.filters import ObjectFilter, SecretKeyFilter
from authentik.rbac.filters import SecretKeyFilter
LOGGER = get_logger()
@@ -292,6 +293,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
serializer = self.get_serializer(instance)
return Response(serializer.data)
@permission_required("view_certificatekeypair_certificate")
@extend_schema(
parameters=[
OpenApiParameter(
@@ -302,7 +304,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
],
responses={200: CertificateDataSerializer(many=False)},
)
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, permission_classes=[IsAuthenticated])
def view_certificate(self, request: Request, pk: str) -> Response:
"""Return certificate-key pairs certificate and log access"""
certificate: CertificateKeyPair = self.get_object()
@@ -323,6 +325,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
return response
return Response(CertificateDataSerializer({"data": certificate.certificate_data}).data)
@permission_required("view_certificatekeypair_key")
@extend_schema(
parameters=[
OpenApiParameter(
@@ -333,7 +336,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
],
responses={200: CertificateDataSerializer(many=False)},
)
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, permission_classes=[IsAuthenticated])
def view_private_key(self, request: Request, pk: str) -> Response:
"""Return certificate-key pairs private key and log access"""
certificate: CertificateKeyPair = self.get_object()
@@ -0,0 +1,27 @@
# Generated by Django 5.2.8 on 2025-11-20 14:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0004_alter_certificatekeypair_name"),
]
operations = [
migrations.AlterModelOptions(
name="certificatekeypair",
options={
"permissions": [
(
"view_certificatekeypair_certificate",
"View Certificate-Key pair's certificate",
),
("view_certificatekeypair_key", "View Certificate-Key pair's private key"),
],
"verbose_name": "Certificate-Key Pair",
"verbose_name_plural": "Certificate-Key Pairs",
},
),
]
+4
View File
@@ -140,3 +140,7 @@ class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
class Meta:
verbose_name = _("Certificate-Key Pair")
verbose_name_plural = _("Certificate-Key Pairs")
permissions = [
("view_certificatekeypair_certificate", _("View Certificate-Key pair's certificate")),
("view_certificatekeypair_key", _("View Certificate-Key pair's private key")),
]
+25 -13
View File
@@ -9,10 +9,16 @@ from cryptography.x509.extensions import SubjectAlternativeName
from cryptography.x509.general_name import DNSName
from django.urls import reverse
from django.utils.timezone import now
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.api.used_by import DeleteAction
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.core.tests.utils import (
create_test_admin_user,
create_test_cert,
create_test_flow,
create_test_user,
)
from authentik.crypto.api import CertificateKeyPairSerializer
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
@@ -144,7 +150,7 @@ class TestCrypto(APITestCase):
),
data={"name": cert.name},
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
@@ -162,7 +168,7 @@ class TestCrypto(APITestCase):
),
data={"name": cert.name, "has_key": False},
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
@@ -178,7 +184,7 @@ class TestCrypto(APITestCase):
),
data={"name": cert.name, "include_details": False},
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
self.assertEqual(api_cert["fingerprint_sha1"], None)
@@ -186,15 +192,18 @@ class TestCrypto(APITestCase):
def test_certificate_download(self):
"""Test certificate export (download)"""
self.client.force_login(create_test_admin_user())
keypair = create_test_cert()
user = create_test_user()
assign_perm("view_certificatekeypair", user, keypair)
assign_perm("view_certificatekeypair_certificate", user, keypair)
self.client.force_login(user)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-certificate",
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-certificate",
@@ -202,20 +211,23 @@ class TestCrypto(APITestCase):
),
data={"download": True},
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
self.assertIn("Content-Disposition", response)
def test_private_key_download(self):
"""Test private_key export (download)"""
self.client.force_login(create_test_admin_user())
keypair = create_test_cert()
user = create_test_user()
assign_perm("view_certificatekeypair", user, keypair)
assign_perm("view_certificatekeypair_key", user, keypair)
self.client.force_login(user)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-private-key",
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-private-key",
@@ -223,12 +235,12 @@ class TestCrypto(APITestCase):
),
data={"download": True},
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
self.assertIn("Content-Disposition", response)
def test_certificate_download_denied(self):
"""Test certificate export (download)"""
self.client.logout()
self.client.force_login(create_test_user())
keypair = create_test_cert()
response = self.client.get(
reverse(
@@ -248,7 +260,7 @@ class TestCrypto(APITestCase):
def test_private_key_download_denied(self):
"""Test private_key export (download)"""
self.client.logout()
self.client.force_login(create_test_user())
keypair = create_test_cert()
response = self.client.get(
reverse(
@@ -284,7 +296,7 @@ class TestCrypto(APITestCase):
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(200, response.status_code)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
[
+7 -1
View File
@@ -5280,6 +5280,8 @@
"authentik_crypto.change_certificatekeypair",
"authentik_crypto.delete_certificatekeypair",
"authentik_crypto.view_certificatekeypair",
"authentik_crypto.view_certificatekeypair_certificate",
"authentik_crypto.view_certificatekeypair_key",
"authentik_endpoints.add_connector",
"authentik_endpoints.add_device",
"authentik_endpoints.add_deviceaccessgroup",
@@ -5953,7 +5955,9 @@
"add_certificatekeypair",
"change_certificatekeypair",
"delete_certificatekeypair",
"view_certificatekeypair"
"view_certificatekeypair",
"view_certificatekeypair_certificate",
"view_certificatekeypair_key"
]
},
"user": {
@@ -10604,6 +10608,8 @@
"authentik_crypto.change_certificatekeypair",
"authentik_crypto.delete_certificatekeypair",
"authentik_crypto.view_certificatekeypair",
"authentik_crypto.view_certificatekeypair_certificate",
"authentik_crypto.view_certificatekeypair_key",
"authentik_endpoints.add_connector",
"authentik_endpoints.add_device",
"authentik_endpoints.add_deviceaccessgroup",