Offload certificate key generation to worker threads

This commit is contained in:
2026-10-25 21:26:34 +00:00
parent 4c1bfa4e4d
commit 718d11b4c0
4 changed files with 117 additions and 24 deletions
+6 -11
View File
@@ -1,7 +1,6 @@
"""Key generation, detail, rename/delete and export."""
from __future__ import annotations
import asyncio
import base64
from typing import TYPE_CHECKING, Optional
@@ -18,6 +17,7 @@ from ..models import Certificate, Key, Request
from ..openapi import error_response, json_response, schema_ref
from ..service import (
ValidationError,
generate_key as generate_private_key,
parse_key_generation,
parse_uuid,
save_key,
@@ -30,9 +30,6 @@ from ..service import (
if TYPE_CHECKING:
from ..mixin import Kca
# Key generation is CPU-bound; this bounds concurrent RSA/DSA jobs.
_keygen_semaphore = asyncio.Semaphore(2)
async def _vault(kca: "Kca", ctx: HttpContext, vault_id: str):
user = await authenticated_user(kca.oidc, ctx)
@@ -85,13 +82,11 @@ def register(app: KayaApp, kca: "Kca") -> None:
await send_error(ctx, 400, str(exc))
return
try:
async with _keygen_semaphore:
generated = await asyncio.to_thread(
key_core.generate_key,
params["key_type"],
params["size"],
params["curve"],
)
generated = await generate_private_key(
params["key_type"],
params["size"],
params["curve"],
)
key = await save_key(
vault,
generated,
@@ -1,7 +1,6 @@
"""Certificate signing requests: list, create, detail, export and transforms."""
from __future__ import annotations
import asyncio
import base64
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Optional
@@ -21,6 +20,7 @@ from ..models import Key, Request, Template
from ..openapi import error_response, json_response, schema_ref
from ..service import (
ValidationError,
generate_key,
parse_extensions,
parse_subject,
parse_uuid,
@@ -36,8 +36,6 @@ from ..service import (
if TYPE_CHECKING:
from ..mixin import Kca
_keygen_semaphore = asyncio.Semaphore(2)
async def _vault(kca: "Kca", ctx: HttpContext, vault_id: str):
user = await authenticated_user(kca.oidc, ctx)
@@ -98,13 +96,11 @@ def register(app: KayaApp, kca: "Kca") -> None:
key_obj = await load_private_key(key_model)
elif isinstance(body.get("new_key"), dict):
new_key = body["new_key"]
async with _keygen_semaphore:
generated = await asyncio.to_thread(
key_core.generate_key,
str(new_key.get("key_type") or "rsa"),
int(new_key.get("size") or key_core.DEFAULT_KEY_SIZE),
new_key.get("curve"),
)
generated = await generate_key(
str(new_key.get("key_type") or "rsa"),
int(new_key.get("size") or key_core.DEFAULT_KEY_SIZE),
new_key.get("curve"),
)
key_model = await save_key(
vault,
generated,
+19 -3
View File
@@ -1,6 +1,7 @@
"""Business logic: validation, persistence, serialization and CA operations."""
from __future__ import annotations
import asyncio
import hashlib
import secrets
from datetime import datetime, timedelta, timezone
@@ -471,6 +472,20 @@ async def serialize_vault_detail(vault: Vault) -> Dict[str, Any]:
# ---------------------------------------------------------------------------
# Key generation is CPU-bound; bound concurrent jobs across all routes.
_keygen_semaphore = asyncio.Semaphore(2)
async def generate_key(
key_type: str,
size: int = key_core.DEFAULT_KEY_SIZE,
curve: Optional[str] = None,
) -> key_core.PrivateKey:
"""Generate a private key without blocking the event loop."""
async with _keygen_semaphore:
return await asyncio.to_thread(key_core.generate_key, key_type, size, curve)
def parse_key_generation(payload: Mapping[str, Any]) -> Dict[str, Any]:
key_type = str(payload.get("key_type") or payload.get("type") or "rsa").lower()
if key_type not in key_core.KEY_TYPES:
@@ -708,7 +723,7 @@ async def issue_certificate(
key_obj = await load_private_key(key_model)
elif new_key_payload:
try:
generated = key_core.generate_key(
generated = await generate_key(
str(new_key_payload.get("key_type") or "rsa"),
int(new_key_payload.get("size") or key_core.DEFAULT_KEY_SIZE),
new_key_payload.get("curve"),
@@ -873,13 +888,13 @@ async def preview_certificate(vault: Vault, payload: Mapping[str, Any]) -> Dict[
else:
new_key = payload.get("new_key")
if isinstance(new_key, Mapping):
generated = key_core.generate_key(
generated = await generate_key(
str(new_key.get("key_type") or "ec"),
int(new_key.get("size") or 2048),
new_key.get("curve") or "secp256r1",
)
else:
generated = key_core.generate_key("ec", curve="secp256r1")
generated = await generate_key("ec", curve="secp256r1")
public_key = generated.public_key()
extensions_spec = parse_extensions(payload.get("extensions"))
@@ -1446,6 +1461,7 @@ __all__ = [
"find_key",
"find_request",
"generate_crl",
"generate_key",
"import_parsed_items",
"iso",
"issue_certificate",